two venvs
This commit is contained in:
-147
@@ -3,9 +3,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
print("[geompack] loading...", file=sys.stderr, flush=True)
|
||||
from comfy_env import register_nodes
|
||||
@@ -36,150 +34,5 @@ def _generate_widget_mappings():
|
||||
|
||||
_generate_widget_mappings()
|
||||
|
||||
# Server routes
|
||||
try:
|
||||
from aiohttp import web
|
||||
from server import PromptServer
|
||||
import folder_paths
|
||||
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
@routes.post("/geometrypack/save_preview")
|
||||
async def save_preview_mesh(request):
|
||||
try:
|
||||
json_data = await request.json()
|
||||
temp_filename = json_data.get("temp_filename")
|
||||
if not temp_filename:
|
||||
return web.json_response({"success": False, "error": "No temp_filename"}, status=400)
|
||||
|
||||
output_dir = folder_paths.get_output_directory()
|
||||
temp_filepath = os.path.join(output_dir, temp_filename)
|
||||
if not os.path.exists(temp_filepath):
|
||||
return web.json_response({"success": False, "error": "File not found"}, status=404)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
file_ext = os.path.splitext(temp_filename)[1]
|
||||
saved_filename = f"mesh_{timestamp}{file_ext}"
|
||||
shutil.copy2(temp_filepath, os.path.join(output_dir, saved_filename))
|
||||
|
||||
return web.json_response({"success": True, "saved_filename": saved_filename})
|
||||
except Exception as e:
|
||||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/geompack/analyze")
|
||||
async def analyze_mesh(request):
|
||||
try:
|
||||
from .nodes.visualization.preview_mesh_analysis import (
|
||||
get_cached_mesh, add_field_to_cached_mesh,
|
||||
compute_boundary_vertices, compute_connected_components,
|
||||
compute_self_intersections
|
||||
)
|
||||
from .nodes.visualization._vtp_export import export_mesh_with_scalars_vtp
|
||||
|
||||
json_data = await request.json()
|
||||
mesh_id = json_data.get("mesh_id")
|
||||
analysis_type = json_data.get("analysis_type")
|
||||
|
||||
if not mesh_id or not analysis_type:
|
||||
return web.json_response({"success": False, "error": "Missing params"}, status=400)
|
||||
|
||||
cache_entry = get_cached_mesh(mesh_id)
|
||||
if not cache_entry:
|
||||
return web.json_response({"success": False, "error": "Mesh not found"}, status=404)
|
||||
|
||||
mesh = cache_entry['mesh']
|
||||
filename = cache_entry['filename']
|
||||
|
||||
if analysis_type == "open_edges":
|
||||
mesh, count = compute_boundary_vertices(mesh)
|
||||
field_name = "boundary_vertex"
|
||||
elif analysis_type == "components":
|
||||
mesh, count = compute_connected_components(mesh)
|
||||
field_name = "face.part_id"
|
||||
elif analysis_type == "self_intersect":
|
||||
mesh, count = compute_self_intersections(mesh)
|
||||
field_name = "face.self_intersect"
|
||||
else:
|
||||
return web.json_response({"success": False, "error": f"Unknown: {analysis_type}"}, status=400)
|
||||
|
||||
add_field_to_cached_mesh(mesh_id, field_name)
|
||||
filepath = os.path.join(folder_paths.get_output_directory(), filename)
|
||||
export_mesh_with_scalars_vtp(mesh, filepath)
|
||||
|
||||
return web.json_response({"success": True, "filename": filename, "field_name": field_name, "count": count})
|
||||
except Exception as e:
|
||||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||||
|
||||
@routes.post("/geompack/find_location")
|
||||
async def find_location(request):
|
||||
try:
|
||||
from .nodes.visualization.preview_mesh_analysis import get_cached_mesh
|
||||
import re
|
||||
|
||||
json_data = await request.json()
|
||||
mesh_id = json_data.get("mesh_id")
|
||||
query = json_data.get("query", "").strip()
|
||||
|
||||
if not mesh_id or not query:
|
||||
return web.json_response({"success": False, "error": "Missing params"}, status=400)
|
||||
|
||||
cache_entry = get_cached_mesh(mesh_id)
|
||||
if not cache_entry:
|
||||
return web.json_response({"success": False, "error": "Mesh not found"}, status=404)
|
||||
|
||||
mesh = cache_entry['mesh']
|
||||
point, query_type, element_id = None, None, None
|
||||
|
||||
# Face ID: f123, face123
|
||||
face_match = re.match(r'^[fF](?:ace)?\s*(\d+)$', query)
|
||||
if face_match:
|
||||
face_id = int(face_match.group(1))
|
||||
if 0 <= face_id < len(mesh.faces):
|
||||
point = mesh.vertices[mesh.faces[face_id]].mean(axis=0).tolist()
|
||||
query_type, element_id = "face", face_id
|
||||
|
||||
# Vertex ID: v123, vertex123
|
||||
if point is None:
|
||||
vertex_match = re.match(r'^[vVpP](?:ertex|oint)?\s*(\d+)$', query)
|
||||
if vertex_match:
|
||||
vertex_id = int(vertex_match.group(1))
|
||||
if 0 <= vertex_id < len(mesh.vertices):
|
||||
point = mesh.vertices[vertex_id].tolist()
|
||||
query_type, element_id = "vertex", vertex_id
|
||||
|
||||
# XYZ coordinates
|
||||
if point is None:
|
||||
coord_str = query.strip('()[]')
|
||||
parts = re.split(r'[,\s]+', coord_str)
|
||||
if len(parts) == 3:
|
||||
try:
|
||||
point = [float(parts[0]), float(parts[1]), float(parts[2])]
|
||||
query_type = "coordinate"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Plain number = face ID
|
||||
if point is None:
|
||||
try:
|
||||
face_id = int(query)
|
||||
if 0 <= face_id < len(mesh.faces):
|
||||
point = mesh.vertices[mesh.faces[face_id]].mean(axis=0).tolist()
|
||||
query_type, element_id = "face", face_id
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if point is None:
|
||||
return web.json_response({"success": False, "error": f"Could not parse: {query}"}, status=400)
|
||||
|
||||
response = {"success": True, "point": point, "type": query_type}
|
||||
if element_id is not None:
|
||||
response["id"] = element_id
|
||||
return web.json_response(response)
|
||||
except Exception as e:
|
||||
return web.json_response({"success": False, "error": str(e)}, status=500)
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS', 'WEB_DIRECTORY']
|
||||
|
||||
+1
-9
@@ -6,12 +6,4 @@ OMP_NUM_THREADS = 1
|
||||
# Include both old and new package names for compatibility across Ubuntu versions
|
||||
# (libgl1-mesa-glx was renamed to libgl1 in Ubuntu 22.04+)
|
||||
# libopengl0 provides libOpenGL.so.0 (required by pymeshlab)
|
||||
packages = ["libgl1-mesa-glx", "libgl1", "libopengl0", "libglu1-mesa"]
|
||||
|
||||
# Linux-specific pypi packages
|
||||
[target.linux-64.pypi-dependencies]
|
||||
embreex = "*"
|
||||
|
||||
# Windows-specific packages
|
||||
[target.win-64.pypi-dependencies]
|
||||
embreex = "*"
|
||||
packages = ["libgl1-mesa-glx", "libgl1", "libopengl0", "libglu1-mesa"]
|
||||
@@ -1,29 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# Copyright (C) 2025 ComfyUI-GeometryPack Contributors
|
||||
|
||||
"""
|
||||
CGAL-dependent nodes - requires igl.copyleft.cgal or CGAL Python bindings.
|
||||
These nodes will only load if CGAL dependencies are available.
|
||||
"""
|
||||
|
||||
# Import submodules
|
||||
from . import repair
|
||||
from . import boolean
|
||||
from . import remeshing
|
||||
from . import reconstruction
|
||||
|
||||
# Collect all node class mappings
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(repair.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(boolean.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(remeshing.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(reconstruction.NODE_CLASS_MAPPINGS)
|
||||
|
||||
# Collect all display name mappings
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(repair.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(boolean.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(remeshing.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(reconstruction.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -1,19 +0,0 @@
|
||||
[apt]
|
||||
packages = ["libgl1-mesa-glx", "libgl1", "libopengl0", "libglu1-mesa"]
|
||||
|
||||
[dependencies]
|
||||
cgal = "*"
|
||||
|
||||
[pypi-dependencies]
|
||||
numpy = "*"
|
||||
trimesh = { version = "*", extras = ["easy"] }
|
||||
pymeshlab = ">=2025.7"
|
||||
libigl = "*"
|
||||
|
||||
# Windows needs Visual C++ runtime for CGAL DLLs
|
||||
[target.win-64.pypi-dependencies]
|
||||
msvc-runtime = "*"
|
||||
|
||||
[env_vars]
|
||||
KMP_DUPLICATE_LIB_OK = "TRUE"
|
||||
OMP_NUM_THREADS = 1
|
||||
@@ -1,12 +0,0 @@
|
||||
# Import submodules
|
||||
from . import remesh
|
||||
|
||||
# Collect all node class mappings
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(remesh.NODE_CLASS_MAPPINGS)
|
||||
|
||||
# Collect all display name mappings
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(remesh.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -1,7 +0,0 @@
|
||||
[cuda]
|
||||
packages = ["cumesh", "torch", "torchvision"]
|
||||
|
||||
[pypi-dependencies]
|
||||
numpy = "*"
|
||||
trimesh = { version = "*", extras = ["easy"] }
|
||||
tqdm = "*"
|
||||
+21
-2
@@ -2,8 +2,8 @@
|
||||
# Copyright (C) 2025 ComfyUI-GeometryPack Contributors
|
||||
|
||||
"""
|
||||
Main nodes - Basic dependencies (trimesh, numpy, pymeshlab, etc.)
|
||||
These nodes will always load regardless of CGAL or Blender availability.
|
||||
Main nodes - All non-Blender geometry processing nodes.
|
||||
Runs in an isolation env (pixi/conda) to avoid DLL conflicts with ComfyUI's torch.
|
||||
"""
|
||||
|
||||
from . import io
|
||||
@@ -21,6 +21,15 @@ from . import remeshing
|
||||
from . import reconstruction
|
||||
from . import texture_remeshing
|
||||
|
||||
# CGAL nodes (moved from nodes/cgal/)
|
||||
from . import boolean
|
||||
from . import reconstruction_cgal
|
||||
from . import remeshing_cgal
|
||||
from . import repair_cgal
|
||||
|
||||
# GPU nodes (moved from nodes/gpu/)
|
||||
from . import remeshing_gpu
|
||||
|
||||
# Collect all node class mappings
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_CLASS_MAPPINGS.update(io.NODE_CLASS_MAPPINGS)
|
||||
@@ -37,6 +46,11 @@ NODE_CLASS_MAPPINGS.update(repair.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(remeshing.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(reconstruction.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(texture_remeshing.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(boolean.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(reconstruction_cgal.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(remeshing_cgal.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(repair_cgal.NODE_CLASS_MAPPINGS)
|
||||
NODE_CLASS_MAPPINGS.update(remeshing_gpu.NODE_CLASS_MAPPINGS)
|
||||
|
||||
# Collect all display name mappings
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
@@ -54,5 +68,10 @@ NODE_DISPLAY_NAME_MAPPINGS.update(repair.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(remeshing.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(reconstruction.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(texture_remeshing.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(boolean.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(reconstruction_cgal.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(remeshing_cgal.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(repair_cgal.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(remeshing_gpu.NODE_DISPLAY_NAME_MAPPINGS)
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
[apt]
|
||||
packages = ["libgl1-mesa-glx", "libgl1", "libopengl0", "libglu1-mesa"]
|
||||
|
||||
[dependencies]
|
||||
cgal = "*"
|
||||
|
||||
[cuda]
|
||||
packages = ["cumesh", "torch", "torchvision"]
|
||||
|
||||
[pypi-dependencies]
|
||||
numpy = "*"
|
||||
scipy = ">=1.7.0"
|
||||
tqdm = ">=4.60.0"
|
||||
requests = ">=2.25.0"
|
||||
trimesh = { version = ">=4.0.0", extras = ["easy"] }
|
||||
libigl = ">=2.6.1"
|
||||
pymeshlab = ">=2025.7"
|
||||
rtree = "*"
|
||||
point-cloud-utils = ">=0.30.0"
|
||||
mesh-to-sdf = ">=0.0.14"
|
||||
fast-simplification = ">=0.1.5"
|
||||
pymeshfix = ">=0.17.0"
|
||||
xatlas = ">=0.0.11"
|
||||
skeletor = ">=1.2.0"
|
||||
pynanoinstantmeshes = "*"
|
||||
matplotlib = ">=3.5.0"
|
||||
pillow = ">=9.0.0"
|
||||
pyvista = ">=0.43.0"
|
||||
|
||||
[target.win-64.pypi-dependencies]
|
||||
embreex = "*"
|
||||
msvc-runtime = "*"
|
||||
|
||||
[target.linux-64.pypi-dependencies]
|
||||
embreex = "*"
|
||||
|
||||
[env_vars]
|
||||
KMP_DUPLICATE_LIB_OK = "TRUE"
|
||||
OMP_NUM_THREADS = 1
|
||||
@@ -0,0 +1,10 @@
|
||||
# Import submodules
|
||||
from .remesh import NODE_CLASS_MAPPINGS as REMESH_MAPS, NODE_DISPLAY_NAME_MAPPINGS as REMESH_DISP
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
NODE_CLASS_MAPPINGS.update(REMESH_MAPS)
|
||||
NODE_DISPLAY_NAME_MAPPINGS.update(REMESH_DISP)
|
||||
|
||||
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAME_MAPPINGS']
|
||||
@@ -9,42 +9,6 @@ from comfy_3d_viewers import copy_viewer
|
||||
|
||||
setup_env()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Crash diagnostics: install hooks that persist AFTER prestartup completes.
|
||||
# These help identify which ComfyUI import causes the process to crash.
|
||||
# Only active when COMFY_ENV_DEBUG=1 (set in CI).
|
||||
# ---------------------------------------------------------------------------
|
||||
if os.environ.get("COMFY_ENV_DEBUG", "").lower() in ("1", "true", "yes"):
|
||||
import atexit
|
||||
|
||||
def _exit_hook():
|
||||
print("[geompack] atexit handler called — clean Python shutdown",
|
||||
file=sys.stderr, flush=True)
|
||||
atexit.register(_exit_hook)
|
||||
|
||||
_orig_excepthook = sys.excepthook
|
||||
def _crash_excepthook(exc_type, exc_value, exc_tb):
|
||||
print(f"[geompack] UNHANDLED: {exc_type.__name__}: {exc_value}",
|
||||
file=sys.stderr, flush=True)
|
||||
_orig_excepthook(exc_type, exc_value, exc_tb)
|
||||
sys.excepthook = _crash_excepthook
|
||||
|
||||
class _ImportTracer:
|
||||
"""Log first-time imports of key modules to identify crash location."""
|
||||
_PREFIXES = ('comfy', 'execution', 'server', 'protocol', 'nodes',
|
||||
'torch', 'comfyui_version', 'hook_breaker', 'comfy_aimdo',
|
||||
'app', 'cuda_malloc')
|
||||
|
||||
def find_spec(self, fullname, path=None, target=None):
|
||||
if fullname not in sys.modules:
|
||||
parts = fullname.split('.')
|
||||
if parts[0] in self._PREFIXES and len(parts) <= 2:
|
||||
print(f"[import-trace] {fullname}",
|
||||
file=sys.stderr, flush=True)
|
||||
return None # never handle — let real finders do the work
|
||||
|
||||
sys.meta_path.insert(0, _ImportTracer())
|
||||
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
COMFYUI_DIR = SCRIPT_DIR.parent.parent
|
||||
|
||||
|
||||
+5
-28
@@ -1,30 +1,7 @@
|
||||
# Core
|
||||
comfy-env==0.1.63
|
||||
scipy>=1.7.0
|
||||
tqdm>=4.60.0
|
||||
requests>=2.25.0
|
||||
|
||||
# Mesh processing
|
||||
trimesh>=4.0.0
|
||||
libigl>=2.6.1
|
||||
pymeshlab>=2025.7
|
||||
rtree
|
||||
point-cloud-utils>=0.30.0
|
||||
mesh-to-sdf>=0.0.14
|
||||
fast-simplification>=0.1.5
|
||||
pymeshfix>=0.17.0
|
||||
xatlas>=0.0.11
|
||||
skeletor>=1.2.0
|
||||
PyNanoInstantMeshes
|
||||
|
||||
# Visualization
|
||||
matplotlib>=3.5.0
|
||||
Pillow>=9.0.0
|
||||
pyvista>=0.43.0
|
||||
|
||||
# ComfyUI integration
|
||||
# Main process deps (prestartup + lightweight shared libs)
|
||||
comfy-env==0.1.64
|
||||
comfy-dynamic-widgets==0.1.1
|
||||
comfy-3d-viewers==0.2.2
|
||||
|
||||
# Platform-specific
|
||||
embreex; sys_platform != "darwin"
|
||||
numpy
|
||||
trimesh>=4.0.0
|
||||
scipy>=1.7.0
|
||||
|
||||
Reference in New Issue
Block a user