comfy-env 0.1.10__py3-none-any.whl → 0.1.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -211,6 +211,34 @@ def _find_env_dir(node_dir: Path) -> Optional[Path]:
211
211
  return None
212
212
 
213
213
 
214
+ def _find_custom_node_root(nodes_dir: Path) -> Optional[Path]:
215
+ """
216
+ Find the custom node root (direct child of custom_nodes/).
217
+
218
+ Uses folder_paths to find custom_nodes directories, then finds
219
+ which one is an ancestor of nodes_dir.
220
+
221
+ Example: /path/custom_nodes/ComfyUI-UniRig/nodes/nodes_gpu
222
+ -> returns /path/custom_nodes/ComfyUI-UniRig
223
+ """
224
+ try:
225
+ import folder_paths
226
+ custom_nodes_dirs = folder_paths.get_folder_paths("custom_nodes")
227
+ except (ImportError, KeyError):
228
+ return None
229
+
230
+ for cn_dir in custom_nodes_dirs:
231
+ cn_path = Path(cn_dir)
232
+ try:
233
+ rel = nodes_dir.relative_to(cn_path)
234
+ if rel.parts:
235
+ return cn_path / rel.parts[0]
236
+ except ValueError:
237
+ continue
238
+
239
+ return None
240
+
241
+
214
242
  def _wrap_node_class(
215
243
  cls: type,
216
244
  env_dir: Path,
@@ -388,7 +416,9 @@ def wrap_isolated_nodes(
388
416
  print(f"[comfy-env] Run 'comfy-env install' in {nodes_dir}")
389
417
  return node_class_mappings
390
418
 
391
- # Build sys.path for the worker - site-packages first, then node dir
419
+ # Build sys.path - site-packages first, then nodes_dir
420
+ # Note: isolated modules should use absolute imports (their dir is in sys.path)
421
+ # Relative imports would require importing parent package which may have host-only deps
392
422
  sys_path = [str(site_packages), str(nodes_dir)]
393
423
 
394
424
  # lib_dir for LD_LIBRARY_PATH (conda libraries)
comfy_env/nodes.py CHANGED
@@ -10,6 +10,7 @@ Example configuration:
10
10
  ComfyUI-DepthAnythingV2 = "kijai/ComfyUI-DepthAnythingV2"
11
11
  """
12
12
 
13
+ import shutil
13
14
  import subprocess
14
15
  import sys
15
16
  from pathlib import Path
@@ -84,16 +85,20 @@ def install_requirements(
84
85
  """
85
86
  requirements_file = node_dir / "requirements.txt"
86
87
 
87
- if requirements_file.exists():
88
- log(f" Installing requirements for {node_dir.name}...")
89
- result = subprocess.run(
90
- ["uv", "pip", "install", "-r", str(requirements_file), "--python", sys.executable],
91
- cwd=node_dir,
92
- capture_output=True,
93
- text=True,
94
- )
95
- if result.returncode != 0:
96
- log(f" Warning: requirements.txt install failed for {node_dir.name}: {result.stderr.strip()[:200]}")
88
+ if not requirements_file.exists():
89
+ return
90
+
91
+ log(f" Installing requirements for {node_dir.name}...")
92
+
93
+ # Try uv first, fall back to pip if uv not in PATH
94
+ if shutil.which("uv"):
95
+ cmd = ["uv", "pip", "install", "-r", str(requirements_file), "--python", sys.executable]
96
+ else:
97
+ cmd = [sys.executable, "-m", "pip", "install", "-r", str(requirements_file)]
98
+
99
+ result = subprocess.run(cmd, cwd=node_dir, capture_output=True, text=True)
100
+ if result.returncode != 0:
101
+ log(f" Warning: requirements.txt install failed for {node_dir.name}: {result.stderr.strip()[:200]}")
97
102
 
98
103
 
99
104
  def run_install_script(
comfy_env/pixi/core.py CHANGED
@@ -457,11 +457,14 @@ def pixi_install(
457
457
  # Build pypi-dependencies section (CUDA packages excluded - installed separately)
458
458
  pypi_deps = pixi_data.get("pypi-dependencies", {})
459
459
 
460
- # Add torch if we have CUDA packages
460
+ # Enforce torch version if we have CUDA packages (must match cuda_packages wheels)
461
461
  if cfg.has_cuda and torch_version:
462
462
  torch_major = torch_version.split(".")[0]
463
463
  torch_minor = int(torch_version.split(".")[1])
464
- pypi_deps.setdefault("torch", f">={torch_version},<{torch_major}.{torch_minor + 1}")
464
+ required_torch = f">={torch_version},<{torch_major}.{torch_minor + 1}"
465
+ if "torch" in pypi_deps and pypi_deps["torch"] != required_torch:
466
+ log(f"Overriding torch={pypi_deps['torch']} with {required_torch} (required for cuda_packages)")
467
+ pypi_deps["torch"] = required_torch
465
468
 
466
469
  # NOTE: CUDA packages are NOT added here - they're installed with --no-deps after pixi
467
470
 
@@ -494,8 +497,16 @@ def pixi_install(
494
497
  if not python_path:
495
498
  raise RuntimeError("Could not find Python in pixi environment")
496
499
 
497
- # Get Python version from the pixi environment
498
- py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
500
+ # Get Python version from the pixi environment (not host Python)
501
+ result = subprocess.run(
502
+ [str(python_path), "-c", "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"],
503
+ capture_output=True, text=True
504
+ )
505
+ if result.returncode == 0:
506
+ py_version = result.stdout.strip()
507
+ else:
508
+ py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
509
+ log(f"Warning: Could not detect pixi Python version, using host: {py_version}")
499
510
 
500
511
  for package in cfg.cuda_packages:
501
512
  # Find direct wheel URL (bypasses metadata validation)
@@ -228,23 +228,19 @@ def _to_shm(obj, registry, visited=None):
228
228
  result["__was_tensor__"] = True
229
229
  return result
230
230
 
231
- # trimesh.Trimesh → vertices + faces arrays shared memory
231
+ # trimesh.Trimesh → pickle shared memory (preserves visual, metadata, normals)
232
232
  if t == 'Trimesh':
233
- verts = np.ascontiguousarray(obj.vertices, dtype=np.float64)
234
- faces = np.ascontiguousarray(obj.faces, dtype=np.int64)
233
+ import pickle
234
+ mesh_bytes = pickle.dumps(obj)
235
235
 
236
- v_block = shm.SharedMemory(create=True, size=verts.nbytes)
237
- np.ndarray(verts.shape, verts.dtype, buffer=v_block.buf)[:] = verts
238
- registry.append(v_block)
239
-
240
- f_block = shm.SharedMemory(create=True, size=faces.nbytes)
241
- np.ndarray(faces.shape, faces.dtype, buffer=f_block.buf)[:] = faces
242
- registry.append(f_block)
236
+ block = shm.SharedMemory(create=True, size=len(mesh_bytes))
237
+ block.buf[:len(mesh_bytes)] = mesh_bytes
238
+ registry.append(block)
243
239
 
244
240
  result = {
245
241
  "__shm_trimesh__": True,
246
- "v_name": v_block.name, "v_shape": list(verts.shape),
247
- "f_name": f_block.name, "f_shape": list(faces.shape),
242
+ "name": block.name,
243
+ "size": len(mesh_bytes),
248
244
  }
249
245
  visited[obj_id] = result
250
246
  return result
@@ -294,22 +290,15 @@ def _from_shm(obj, unlink=True):
294
290
  return torch.from_numpy(arr)
295
291
  return arr
296
292
 
297
- # trimesh
293
+ # trimesh (pickled to preserve visual, metadata, normals)
298
294
  if "__shm_trimesh__" in obj:
299
- import trimesh
300
- v_block = shm.SharedMemory(name=obj["v_name"])
301
- verts = np.ndarray(tuple(obj["v_shape"]), dtype=np.float64, buffer=v_block.buf).copy()
302
- v_block.close()
303
- if unlink:
304
- v_block.unlink()
305
-
306
- f_block = shm.SharedMemory(name=obj["f_name"])
307
- faces = np.ndarray(tuple(obj["f_shape"]), dtype=np.int64, buffer=f_block.buf).copy()
308
- f_block.close()
295
+ import pickle
296
+ block = shm.SharedMemory(name=obj["name"])
297
+ mesh_bytes = bytes(block.buf[:obj["size"]])
298
+ block.close()
309
299
  if unlink:
310
- f_block.unlink()
311
-
312
- return trimesh.Trimesh(vertices=verts, faces=faces, process=False)
300
+ block.unlink()
301
+ return pickle.loads(mesh_bytes)
313
302
 
314
303
  # regular dict - recurse
315
304
  return {k: _from_shm(v, unlink) for k, v in obj.items()}
@@ -426,42 +415,29 @@ faulthandler.enable(file=sys.stderr, all_threads=True)
426
415
  # Debug logging (set COMFY_ENV_DEBUG=1 to enable)
427
416
  _DEBUG = os.environ.get("COMFY_ENV_DEBUG", "").lower() in ("1", "true", "yes")
428
417
 
429
- # Pre-import bpy FIRST to avoid DLL conflicts with numpy/torch/MKL
430
- # bpy's DLLs must be loaded before other packages load conflicting versions
431
- try:
432
- import bpy
433
- if _DEBUG:
434
- print("[worker] Pre-imported bpy successfully", file=sys.stderr, flush=True)
435
- except ImportError as e:
436
- # bpy not available in this environment - that's fine
437
- pass
438
- except Exception as e:
439
- if _DEBUG:
440
- print(f"[worker] bpy pre-import warning: {e}", file=sys.stderr, flush=True)
441
-
442
418
  # Watchdog: dump all thread stacks every 60 seconds to catch hangs
443
419
  import threading
444
420
  import tempfile as _tempfile
445
421
  _watchdog_log = os.path.join(_tempfile.gettempdir(), "comfy_worker_watchdog.log")
446
422
  def _watchdog():
447
423
  import time
448
- import io
449
424
  tick = 0
450
425
  while True:
451
426
  time.sleep(60)
452
427
  tick += 1
453
- # Capture stack dump to string
454
- buf = io.StringIO()
455
- faulthandler.dump_traceback(file=buf, all_threads=True)
456
- dump = buf.getvalue()
457
-
458
- # Write to file
428
+ # Dump to temp file first (faulthandler needs real file descriptor)
429
+ tmp_path = _watchdog_log + ".tmp"
430
+ with open(tmp_path, "w", encoding="utf-8") as tmp:
431
+ faulthandler.dump_traceback(file=tmp, all_threads=True)
432
+ with open(tmp_path, "r", encoding="utf-8") as tmp:
433
+ dump = tmp.read()
434
+
435
+ # Write to persistent log
459
436
  with open(_watchdog_log, "a", encoding="utf-8") as f:
460
437
  f.write(f"\\n=== WATCHDOG TICK {tick} ({time.strftime('%H:%M:%S')}) ===\\n")
461
438
  f.write(dump)
462
439
  f.write("=== END ===\\n")
463
440
  f.flush()
464
- os.fsync(f.fileno())
465
441
 
466
442
  # Also print
467
443
  print(f"\\n=== WATCHDOG TICK {tick} ===", flush=True)
@@ -554,22 +530,19 @@ def _to_shm(obj, registry, visited=None):
554
530
  result["__was_tensor__"] = True
555
531
  return result
556
532
 
533
+ # trimesh.Trimesh → pickle → shared memory (preserves visual, metadata, normals)
557
534
  if t == 'Trimesh':
558
- verts = np.ascontiguousarray(obj.vertices, dtype=np.float64)
559
- faces = np.ascontiguousarray(obj.faces, dtype=np.int64)
535
+ import pickle
536
+ mesh_bytes = pickle.dumps(obj)
560
537
 
561
- v_block = shm.SharedMemory(create=True, size=verts.nbytes)
562
- np.ndarray(verts.shape, verts.dtype, buffer=v_block.buf)[:] = verts
563
- registry.append(v_block)
564
-
565
- f_block = shm.SharedMemory(create=True, size=faces.nbytes)
566
- np.ndarray(faces.shape, faces.dtype, buffer=f_block.buf)[:] = faces
567
- registry.append(f_block)
538
+ block = shm.SharedMemory(create=True, size=len(mesh_bytes))
539
+ block.buf[:len(mesh_bytes)] = mesh_bytes
540
+ registry.append(block)
568
541
 
569
542
  result = {
570
543
  "__shm_trimesh__": True,
571
- "v_name": v_block.name, "v_shape": list(verts.shape),
572
- "f_name": f_block.name, "f_shape": list(faces.shape),
544
+ "name": block.name,
545
+ "size": len(mesh_bytes),
573
546
  }
574
547
  visited[obj_id] = result
575
548
  return result
@@ -600,17 +573,13 @@ def _from_shm(obj):
600
573
  import torch
601
574
  return torch.from_numpy(arr)
602
575
  return arr
576
+ # trimesh (pickled to preserve visual, metadata, normals)
603
577
  if "__shm_trimesh__" in obj:
604
- import trimesh
605
- v_block = shm.SharedMemory(name=obj["v_name"])
606
- verts = np.ndarray(tuple(obj["v_shape"]), dtype=np.float64, buffer=v_block.buf).copy()
607
- v_block.close()
608
-
609
- f_block = shm.SharedMemory(name=obj["f_name"])
610
- faces = np.ndarray(tuple(obj["f_shape"]), dtype=np.int64, buffer=f_block.buf).copy()
611
- f_block.close()
612
-
613
- return trimesh.Trimesh(vertices=verts, faces=faces, process=False)
578
+ import pickle
579
+ block = shm.SharedMemory(name=obj["name"])
580
+ mesh_bytes = bytes(block.buf[:obj["size"]])
581
+ block.close()
582
+ return pickle.loads(mesh_bytes)
614
583
  return {k: _from_shm(v) for k, v in obj.items()}
615
584
 
616
585
  def _cleanup_shm(registry):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.1.10
3
+ Version: 0.1.12
4
4
  Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
5
5
  Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
6
6
  Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
@@ -2,15 +2,15 @@ comfy_env/__init__.py,sha256=s0RkyKsBlDiSI4ZSwivtWLvYhc8DS0CUEWgFLVJbtLA,2176
2
2
  comfy_env/cli.py,sha256=ty4HYlzollCUCS0o6Sha6eczPAsW_gHRVgvck3IfA2w,12723
3
3
  comfy_env/errors.py,sha256=q-C3vyrPa_kk_Ao8l17mIGfJiG2IR0hCFV0GFcNLmcI,9924
4
4
  comfy_env/install.py,sha256=N7eBj8wB2DrGepVYk-Hks2mSf6UuGzj34pfVLNYJgQ4,10357
5
- comfy_env/nodes.py,sha256=nR-LctovXYTqk-seHovq9W_Ll0hlLeGMw-SQsa6IkBM,5235
5
+ comfy_env/nodes.py,sha256=tUbsUdjnJCUUoxM7NpsdUuawuIz1exfOmWdsLGILXiY,5391
6
6
  comfy_env/prestartup.py,sha256=aKuW07R0CmoeIXukENJZnGMPzCv7kU7azvHgIOt2usk,5813
7
7
  comfy_env/config/__init__.py,sha256=4Guylkb-FV8QxhFwschzpzbr2eu8y-KNgNT3_JOm9jc,403
8
8
  comfy_env/config/parser.py,sha256=dA1lX5ExBEfCqUJwe4V5i_jn2NJ69bMq3c3ji3lMSV8,4295
9
9
  comfy_env/config/types.py,sha256=Sb8HO34xsSZu5YAc2K4M7Gb3QNevJlngf12hHiwuU0w,2140
10
10
  comfy_env/isolation/__init__.py,sha256=vw9a4mpJ2CFjy-PLe_A3zQ6umBQklgqWNxwn9beNw3g,175
11
- comfy_env/isolation/wrap.py,sha256=9pxlrO2qUP-4Aov9LUXhgqkDw8V4FT-M-YPeDRXUjuE,13664
11
+ comfy_env/isolation/wrap.py,sha256=cTRzbdHJncmg4yUAyH5n9TPBu88grv6D1XezZwXKpok,14638
12
12
  comfy_env/pixi/__init__.py,sha256=BUrq7AQf3WDm0cHWh72B2xZbURNnDu2dCuELWiQCUiM,997
13
- comfy_env/pixi/core.py,sha256=jy1CAaryPlldCm9bGq6i2UnPckYqyqzQELSDhQsB8ho,19958
13
+ comfy_env/pixi/core.py,sha256=fqozZZEoub8dAQaE6v6gyqw1mMiAYiCcdzH0gjCANBo,20637
14
14
  comfy_env/pixi/cuda_detection.py,sha256=sqB3LjvGNdV4eFqiARQGfyecBM3ZiUmeh6nG0YCRYQw,9751
15
15
  comfy_env/pixi/resolver.py,sha256=U_A8rBDxCj4gUlJt2YJQniP4cCKqxJEiVFgXOoH7vM8,6339
16
16
  comfy_env/pixi/platform/__init__.py,sha256=Nb5MPZIEeanSMEWwqU4p4bnEKTJn1tWcwobnhq9x9IY,614
@@ -23,10 +23,10 @@ comfy_env/templates/comfy-env.toml,sha256=ROIqi4BlPL1MEdL1VgebfTHpdwPNYGHwWeigI9
23
23
  comfy_env/workers/__init__.py,sha256=TMVG55d2XLP1mJ3x1d16H0SBDJZtk2kMC5P4HLk9TrA,1073
24
24
  comfy_env/workers/base.py,sha256=4ZYTaQ4J0kBHCoO_OfZnsowm4rJCoqinZUaOtgkOPbw,2307
25
25
  comfy_env/workers/mp.py,sha256=vsxDGWepmSNgfqBhZPW7h8yOiKEyQcDFYP09masLTV4,32337
26
- comfy_env/workers/subprocess.py,sha256=neggSmCqJxfiIOCUQwn_TrDONFJoT5veBZuMjDYf_LA,58537
26
+ comfy_env/workers/subprocess.py,sha256=bNwHpgz_EIIwZVRlgsx_ZEowYRrWbp-8uUIboacc-7M,57136
27
27
  comfy_env/workers/tensor_utils.py,sha256=TCuOAjJymrSbkgfyvcKtQ_KbVWTqSwP9VH_bCaFLLq8,6409
28
- comfy_env-0.1.10.dist-info/METADATA,sha256=tfO_Jhe-jz6XY_AHhfnHroJAVuAzDAe4evf9FYY3nw0,6971
29
- comfy_env-0.1.10.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
- comfy_env-0.1.10.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
- comfy_env-0.1.10.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
- comfy_env-0.1.10.dist-info/RECORD,,
28
+ comfy_env-0.1.12.dist-info/METADATA,sha256=J41pwCOTdj_2zckDDKjse-HfGWErDaDX3Hire4lKwgM,6971
29
+ comfy_env-0.1.12.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
30
+ comfy_env-0.1.12.dist-info/entry_points.txt,sha256=J4fXeqgxU_YenuW_Zxn_pEL7J-3R0--b6MS5t0QmAr0,49
31
+ comfy_env-0.1.12.dist-info/licenses/LICENSE,sha256=E68QZMMpW4P2YKstTZ3QU54HRQO8ecew09XZ4_Vn870,1093
32
+ comfy_env-0.1.12.dist-info/RECORD,,