@hybridlabor-api/bdb-hardware-pcb 0.1.0

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.
Files changed (192) hide show
  1. package/README.md +116 -0
  2. package/config/mcp/antigravity.json +22 -0
  3. package/config/mcp/claude.json +22 -0
  4. package/config/mcp/codex.toml +25 -0
  5. package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
  6. package/docs/review_documentation.md +121 -0
  7. package/installer.js +358 -0
  8. package/mcp_servers/kicad-mcp-server/.env.example +22 -0
  9. package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
  10. package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
  11. package/mcp_servers/kicad-mcp-server/README.md +316 -0
  12. package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
  13. package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
  14. package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
  15. package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
  16. package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
  17. package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
  18. package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
  19. package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
  20. package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
  21. package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
  22. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
  23. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
  24. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
  25. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
  26. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
  27. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
  28. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
  29. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
  30. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
  31. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
  32. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
  33. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
  34. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
  35. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
  36. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
  37. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
  38. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
  39. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
  40. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
  41. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
  42. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
  43. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
  44. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
  45. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
  46. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
  47. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
  48. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
  49. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
  50. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
  51. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
  52. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
  53. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
  54. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
  55. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
  56. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
  57. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
  58. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
  59. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
  60. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
  61. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
  62. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
  63. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
  64. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
  65. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
  66. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
  67. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
  68. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
  69. package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
  70. package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
  71. package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
  72. package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
  73. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
  74. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
  75. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
  76. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
  77. package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
  78. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
  79. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
  80. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
  81. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
  82. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
  83. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
  84. package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
  85. package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
  86. package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
  87. package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
  88. package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
  89. package/mcp_servers/openscad-mcp-server/README.md +154 -0
  90. package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
  91. package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
  92. package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
  93. package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
  94. package/mcp_servers/openscad-mcp-server/glama.json +4 -0
  95. package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
  96. package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
  97. package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
  98. package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
  99. package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
  100. package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
  101. package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
  102. package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
  103. package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
  104. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
  105. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
  106. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
  107. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
  108. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
  109. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
  110. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
  111. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
  112. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
  113. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
  114. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
  115. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
  116. package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
  117. package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
  118. package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
  119. package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
  120. package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
  121. package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
  122. package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
  123. package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
  124. package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
  125. package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
  126. package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
  127. package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
  128. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
  129. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
  130. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
  131. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
  132. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
  133. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
  134. package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
  135. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
  136. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
  137. package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
  138. package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
  139. package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
  140. package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
  141. package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
  142. package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
  143. package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
  144. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
  145. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
  146. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
  147. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
  148. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
  149. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
  150. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
  151. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
  152. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
  153. package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
  154. package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
  155. package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
  156. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
  157. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
  158. package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
  159. package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
  160. package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
  161. package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
  162. package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
  163. package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
  164. package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
  165. package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
  166. package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
  167. package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
  168. package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
  169. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
  170. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
  171. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
  172. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
  173. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
  174. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
  175. package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
  176. package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
  177. package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
  178. package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
  179. package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
  180. package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
  181. package/package.json +44 -0
  182. package/scripts/install_mcps.sh +86 -0
  183. package/scripts/openscad_wrapper.sh +62 -0
  184. package/scripts/run_kicad_mcp.sh +25 -0
  185. package/scripts/run_openscad_mcp.sh +41 -0
  186. package/scripts/test_mcp_connection.py +626 -0
  187. package/scripts/test_mcp_connection.sh +168 -0
  188. package/skills/code-first-hardware-design/SKILL.md +260 -0
  189. package/skills/pcb-constraint-definition/SKILL.md +236 -0
  190. package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
  191. package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
  192. package/skills/schematic-datasheet-analysis/SKILL.md +202 -0
@@ -0,0 +1,209 @@
1
+ """
2
+ SAM2 (Segment Anything Model 2) integration for object segmentation.
3
+ """
4
+
5
+ import os
6
+ import cv2
7
+ import numpy as np
8
+ import logging
9
+ from typing import Dict, Any, List, Tuple, Optional
10
+ from pathlib import Path
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class SAMSegmenter:
15
+ """
16
+ Wrapper for Segment Anything Model 2 (SAM2) for object segmentation.
17
+ """
18
+
19
+ def __init__(self, model_type: str = "vit_h", checkpoint_path: Optional[str] = None,
20
+ use_gpu: bool = True, output_dir: str = "output/masks"):
21
+ """
22
+ Initialize the SAM2 segmenter.
23
+
24
+ Args:
25
+ model_type: SAM2 model type ("vit_h", "vit_l", "vit_b")
26
+ checkpoint_path: Path to model checkpoint
27
+ use_gpu: Whether to use GPU for inference
28
+ output_dir: Directory to store segmentation results
29
+ """
30
+ self.model_type = model_type
31
+ self.checkpoint_path = checkpoint_path
32
+ self.use_gpu = use_gpu
33
+ self.output_dir = output_dir
34
+
35
+ # Create output directory if it doesn't exist
36
+ os.makedirs(output_dir, exist_ok=True)
37
+
38
+ # Model will be initialized on first use to avoid loading it unnecessarily
39
+ self.model = None
40
+ self.predictor = None
41
+
42
+ def _initialize_model(self) -> None:
43
+ """
44
+ Initialize the SAM2 model.
45
+
46
+ Note: This requires PyTorch and the segment-anything-2 package to be installed.
47
+ """
48
+ try:
49
+ # Import here to avoid dependency issues if SAM2 is not installed
50
+ import torch
51
+ from segment_anything_2 import sam_model_registry, SamPredictor
52
+
53
+ if not self.checkpoint_path:
54
+ raise ValueError("SAM2 checkpoint path is required")
55
+
56
+ # Check if checkpoint exists
57
+ if not os.path.exists(self.checkpoint_path):
58
+ raise FileNotFoundError(f"SAM2 checkpoint not found at {self.checkpoint_path}")
59
+
60
+ # Determine device
61
+ device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu"
62
+
63
+ # Load SAM2 model
64
+ self.model = sam_model_registry[self.model_type](checkpoint=self.checkpoint_path)
65
+ self.model.to(device=device)
66
+ self.predictor = SamPredictor(self.model)
67
+
68
+ logger.info(f"Initialized SAM2 model ({self.model_type}) on {device}")
69
+ except ImportError as e:
70
+ logger.error(f"Required packages not installed: {str(e)}")
71
+ raise
72
+ except Exception as e:
73
+ logger.error(f"Error initializing SAM2 model: {str(e)}")
74
+ raise
75
+
76
+ def segment_image(self, image_path: str, points: Optional[List[Tuple[int, int]]] = None,
77
+ output_dir: Optional[str] = None) -> Dict[str, Any]:
78
+ """
79
+ Segment objects in an image using SAM2.
80
+
81
+ Args:
82
+ image_path: Path to input image
83
+ points: Optional list of (x, y) points to guide segmentation
84
+ output_dir: Optional directory to save segmentation results
85
+
86
+ Returns:
87
+ Dictionary containing segmentation masks and metadata
88
+ """
89
+ # Initialize model if not already initialized
90
+ if self.model is None:
91
+ self._initialize_model()
92
+
93
+ try:
94
+ # Load image
95
+ image = cv2.imread(image_path)
96
+ if image is None:
97
+ raise ValueError(f"Could not load image from {image_path}")
98
+
99
+ image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
100
+
101
+ # Set image in predictor
102
+ self.predictor.set_image(image)
103
+
104
+ # Generate masks
105
+ if points:
106
+ # Convert points to numpy arrays
107
+ import numpy as np
108
+ point_coords = np.array(points)
109
+ point_labels = np.ones(len(points))
110
+
111
+ # Generate masks from points
112
+ masks, scores, logits = self.predictor.predict(
113
+ point_coords=point_coords,
114
+ point_labels=point_labels,
115
+ multimask_output=True
116
+ )
117
+ else:
118
+ # Automatic segmentation (using center point)
119
+ h, w = image.shape[:2]
120
+ center_point = np.array([[w//2, h//2]])
121
+ center_label = np.array([1])
122
+
123
+ masks, scores, logits = self.predictor.predict(
124
+ point_coords=center_point,
125
+ point_labels=center_label,
126
+ multimask_output=True
127
+ )
128
+
129
+ # Use provided output directory or default
130
+ output_dir = output_dir or os.path.join(self.output_dir, Path(image_path).stem)
131
+ os.makedirs(output_dir, exist_ok=True)
132
+
133
+ # Process results
134
+ masked_images = []
135
+ for i, mask in enumerate(masks):
136
+ # Apply mask to image
137
+ masked_image = self._apply_mask_to_image(image, mask)
138
+
139
+ # Save masked image
140
+ output_path = os.path.join(output_dir, f"mask_{i}.png")
141
+ cv2.imwrite(output_path, cv2.cvtColor(masked_image, cv2.COLOR_RGB2BGR))
142
+
143
+ masked_images.append(output_path)
144
+
145
+ # Convert numpy arrays to lists for JSON serialization
146
+ result = {
147
+ "image_path": image_path,
148
+ "masked_images": masked_images,
149
+ "scores": scores.tolist(),
150
+ "mask_count": len(masks)
151
+ }
152
+
153
+ return result
154
+ except Exception as e:
155
+ logger.error(f"Error segmenting image: {str(e)}")
156
+ raise
157
+
158
+ def _apply_mask_to_image(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray:
159
+ """
160
+ Apply mask to image, keeping only the masked region.
161
+
162
+ Args:
163
+ image: Input image as numpy array
164
+ mask: Binary mask as numpy array
165
+
166
+ Returns:
167
+ Masked image as numpy array
168
+ """
169
+ # Create a copy of the image
170
+ masked_image = image.copy()
171
+
172
+ # Apply mask
173
+ masked_image[~mask] = [0, 0, 0] # Set background to black
174
+
175
+ return masked_image
176
+
177
+ def segment_with_auto_points(self, image_path: str, num_points: int = 5,
178
+ output_dir: Optional[str] = None) -> Dict[str, Any]:
179
+ """
180
+ Segment image using automatically generated points with SAM2.
181
+
182
+ Args:
183
+ image_path: Path to input image
184
+ num_points: Number of points to generate
185
+ output_dir: Optional directory to save segmentation results
186
+
187
+ Returns:
188
+ Dictionary containing segmentation masks and metadata
189
+ """
190
+ # Load image
191
+ image = cv2.imread(image_path)
192
+ if image is None:
193
+ raise ValueError(f"Could not load image from {image_path}")
194
+
195
+ h, w = image.shape[:2]
196
+
197
+ # Generate points in a grid pattern
198
+ points = []
199
+ rows = int(np.sqrt(num_points))
200
+ cols = num_points // rows
201
+
202
+ for i in range(rows):
203
+ for j in range(cols):
204
+ x = int(w * (j + 0.5) / cols)
205
+ y = int(h * (i + 0.5) / rows)
206
+ points.append((x, y))
207
+
208
+ # Segment with generated points
209
+ return self.segment_image(image_path, points, output_dir)
@@ -0,0 +1,231 @@
1
+ """
2
+ threestudio integration for 3D model generation from images.
3
+ """
4
+
5
+ import os
6
+ import subprocess
7
+ import logging
8
+ import json
9
+ import tempfile
10
+ from typing import Dict, Any, List, Optional
11
+ from pathlib import Path
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class ThreeStudioGenerator:
16
+ """
17
+ Wrapper for threestudio for 3D model generation from images.
18
+ """
19
+
20
+ def __init__(self, threestudio_path: str, output_dir: str = "output/models"):
21
+ """
22
+ Initialize the threestudio generator.
23
+
24
+ Args:
25
+ threestudio_path: Path to threestudio installation
26
+ output_dir: Directory to store output files
27
+ """
28
+ self.threestudio_path = threestudio_path
29
+ self.output_dir = output_dir
30
+
31
+ # Create output directory if it doesn't exist
32
+ os.makedirs(output_dir, exist_ok=True)
33
+
34
+ # Validate threestudio installation
35
+ self._validate_installation()
36
+
37
+ def _validate_installation(self) -> None:
38
+ """
39
+ Validate threestudio installation.
40
+
41
+ Raises:
42
+ FileNotFoundError: If threestudio installation is not found
43
+ """
44
+ if not os.path.exists(self.threestudio_path):
45
+ raise FileNotFoundError(f"threestudio not found at {self.threestudio_path}")
46
+
47
+ # Check for required files
48
+ required_files = ["launch.py", "README.md"]
49
+ for file in required_files:
50
+ if not os.path.exists(os.path.join(self.threestudio_path, file)):
51
+ raise FileNotFoundError(f"Required file {file} not found in threestudio directory")
52
+
53
+ def generate_model_from_image(self, image_path: str, method: str = "zero123",
54
+ num_iterations: int = 5000, export_format: str = "obj",
55
+ config_overrides: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
56
+ """
57
+ Generate a 3D model from an image using threestudio.
58
+
59
+ Args:
60
+ image_path: Path to input image
61
+ method: Method to use ("zero123", "sjc", "magic3d", etc.)
62
+ num_iterations: Number of training iterations
63
+ export_format: Format to export ("obj", "glb", "ply")
64
+ config_overrides: Optional configuration overrides
65
+
66
+ Returns:
67
+ Dictionary containing paths to generated model files
68
+ """
69
+ try:
70
+ # Create a unique ID for this generation
71
+ model_id = Path(image_path).stem
72
+
73
+ # Create a temporary config file
74
+ config_file = self._create_config_file(image_path, method, num_iterations, config_overrides)
75
+
76
+ # Run threestudio
77
+ output_dir = os.path.join(self.output_dir, model_id)
78
+ os.makedirs(output_dir, exist_ok=True)
79
+
80
+ cmd = [
81
+ "python", "launch.py",
82
+ "--config", config_file,
83
+ "--train",
84
+ "--gpu", "0",
85
+ "--output_dir", output_dir
86
+ ]
87
+
88
+ logger.info(f"Running threestudio with command: {' '.join(cmd)}")
89
+
90
+ # Execute in threestudio directory
91
+ process = subprocess.Popen(
92
+ cmd,
93
+ cwd=self.threestudio_path,
94
+ stdout=subprocess.PIPE,
95
+ stderr=subprocess.PIPE,
96
+ text=True
97
+ )
98
+
99
+ # Wait for process to complete
100
+ stdout, stderr = process.communicate()
101
+
102
+ if process.returncode != 0:
103
+ logger.error(f"Error running threestudio: {stderr}")
104
+ raise RuntimeError(f"threestudio failed with exit code {process.returncode}")
105
+
106
+ # Export model
107
+ exported_files = self._export_model(output_dir, export_format)
108
+
109
+ return {
110
+ "model_id": model_id,
111
+ "output_dir": output_dir,
112
+ "exported_files": exported_files,
113
+ "preview_images": self._get_preview_images(output_dir)
114
+ }
115
+ except Exception as e:
116
+ logger.error(f"Error generating 3D model with threestudio: {str(e)}")
117
+ raise
118
+
119
+ def _create_config_file(self, image_path: str, method: str, num_iterations: int,
120
+ config_overrides: Optional[Dict[str, Any]] = None) -> str:
121
+ """
122
+ Create a configuration file for threestudio.
123
+
124
+ Args:
125
+ image_path: Path to input image
126
+ method: Method to use
127
+ num_iterations: Number of training iterations
128
+ config_overrides: Optional configuration overrides
129
+
130
+ Returns:
131
+ Path to the created configuration file
132
+ """
133
+ # Base configuration
134
+ config = {
135
+ "method": method,
136
+ "image_path": os.path.abspath(image_path),
137
+ "num_iterations": num_iterations,
138
+ "save_interval": 1000,
139
+ "export_interval": 1000
140
+ }
141
+
142
+ # Apply overrides
143
+ if config_overrides:
144
+ config.update(config_overrides)
145
+
146
+ # Write to temporary file
147
+ fd, config_file = tempfile.mkstemp(suffix=".json")
148
+ with os.fdopen(fd, 'w') as f:
149
+ json.dump(config, f, indent=2)
150
+
151
+ return config_file
152
+
153
+ def _export_model(self, output_dir: str, export_format: str) -> List[str]:
154
+ """
155
+ Export the model in the specified format.
156
+
157
+ Args:
158
+ output_dir: Directory containing the model
159
+ export_format: Format to export
160
+
161
+ Returns:
162
+ List of paths to exported files
163
+ """
164
+ # Find the latest checkpoint
165
+ checkpoints_dir = os.path.join(output_dir, "checkpoints")
166
+ if not os.path.exists(checkpoints_dir):
167
+ raise FileNotFoundError(f"Checkpoints directory not found: {checkpoints_dir}")
168
+
169
+ # Get the latest checkpoint
170
+ checkpoints = sorted([f for f in os.listdir(checkpoints_dir) if f.endswith(".ckpt")])
171
+ if not checkpoints:
172
+ raise FileNotFoundError("No checkpoints found")
173
+
174
+ latest_checkpoint = os.path.join(checkpoints_dir, checkpoints[-1])
175
+
176
+ # Export command
177
+ cmd = [
178
+ "python", "launch.py",
179
+ "--config", os.path.join(output_dir, "config.yaml"),
180
+ "--export",
181
+ "--gpu", "0",
182
+ "--checkpoint", latest_checkpoint,
183
+ "--export_format", export_format
184
+ ]
185
+
186
+ logger.info(f"Exporting model with command: {' '.join(cmd)}")
187
+
188
+ # Execute in threestudio directory
189
+ process = subprocess.Popen(
190
+ cmd,
191
+ cwd=self.threestudio_path,
192
+ stdout=subprocess.PIPE,
193
+ stderr=subprocess.PIPE,
194
+ text=True
195
+ )
196
+
197
+ # Wait for process to complete
198
+ stdout, stderr = process.communicate()
199
+
200
+ if process.returncode != 0:
201
+ logger.error(f"Error exporting model: {stderr}")
202
+ raise RuntimeError(f"Model export failed with exit code {process.returncode}")
203
+
204
+ # Find exported files
205
+ exports_dir = os.path.join(output_dir, "exports")
206
+ if not os.path.exists(exports_dir):
207
+ raise FileNotFoundError(f"Exports directory not found: {exports_dir}")
208
+
209
+ exported_files = [os.path.join(exports_dir, f) for f in os.listdir(exports_dir)]
210
+
211
+ return exported_files
212
+
213
+ def _get_preview_images(self, output_dir: str) -> List[str]:
214
+ """
215
+ Get paths to preview images.
216
+
217
+ Args:
218
+ output_dir: Directory containing the model
219
+
220
+ Returns:
221
+ List of paths to preview images
222
+ """
223
+ # Find preview images
224
+ previews_dir = os.path.join(output_dir, "images")
225
+ if not os.path.exists(previews_dir):
226
+ return []
227
+
228
+ preview_images = [os.path.join(previews_dir, f) for f in os.listdir(previews_dir)
229
+ if f.endswith(".png") or f.endswith(".jpg")]
230
+
231
+ return sorted(preview_images)
@@ -0,0 +1,260 @@
1
+ """
2
+ Workflow orchestration for the image-to-model pipeline.
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ import uuid
8
+ from typing import Dict, Any, List, Optional, Tuple
9
+ from pathlib import Path
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class ImageToModelPipeline:
14
+ """
15
+ Orchestrates the workflow from text prompt to 3D model:
16
+ 1. Generate image with Venice.ai
17
+ 2. Segment object with SAM2
18
+ 3. Create 3D model with threestudio
19
+ 4. Convert to OpenSCAD for parametric editing
20
+ """
21
+
22
+ def __init__(self,
23
+ venice_generator,
24
+ sam_segmenter,
25
+ threestudio_generator,
26
+ openscad_wrapper,
27
+ output_dir: str = "output/pipeline"):
28
+ """
29
+ Initialize the pipeline.
30
+
31
+ Args:
32
+ venice_generator: Instance of VeniceImageGenerator
33
+ sam_segmenter: Instance of SAMSegmenter
34
+ threestudio_generator: Instance of ThreeStudioGenerator
35
+ openscad_wrapper: Instance of OpenSCADWrapper
36
+ output_dir: Directory to store output files
37
+ """
38
+ self.venice_generator = venice_generator
39
+ self.sam_segmenter = sam_segmenter
40
+ self.threestudio_generator = threestudio_generator
41
+ self.openscad_wrapper = openscad_wrapper
42
+ self.output_dir = output_dir
43
+
44
+ # Create output directories
45
+ os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
46
+ os.makedirs(os.path.join(output_dir, "masks"), exist_ok=True)
47
+ os.makedirs(os.path.join(output_dir, "models"), exist_ok=True)
48
+ os.makedirs(os.path.join(output_dir, "scad"), exist_ok=True)
49
+
50
+ def generate_model_from_text(self, prompt: str,
51
+ venice_params: Optional[Dict[str, Any]] = None,
52
+ sam_params: Optional[Dict[str, Any]] = None,
53
+ threestudio_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
54
+ """
55
+ Generate a 3D model from a text prompt.
56
+
57
+ Args:
58
+ prompt: Text description for image generation
59
+ venice_params: Optional parameters for Venice.ai
60
+ sam_params: Optional parameters for SAM2
61
+ threestudio_params: Optional parameters for threestudio
62
+
63
+ Returns:
64
+ Dictionary containing paths to generated files and metadata
65
+ """
66
+ try:
67
+ # Generate a unique ID for this pipeline run
68
+ pipeline_id = str(uuid.uuid4())
69
+ logger.info(f"Starting pipeline {pipeline_id} for prompt: {prompt}")
70
+
71
+ # Step 1: Generate image with Venice.ai
72
+ image_path = os.path.join(self.output_dir, "images", f"{pipeline_id}.png")
73
+ venice_result = self._generate_image(prompt, image_path, venice_params)
74
+
75
+ # Step 2: Segment object with SAM2
76
+ masks_dir = os.path.join(self.output_dir, "masks", pipeline_id)
77
+ sam_result = self._segment_image(image_path, masks_dir, sam_params)
78
+
79
+ # Get the best mask (highest score or first mask if no scores)
80
+ if "scores" in sam_result and sam_result["scores"]:
81
+ best_mask_idx = sam_result["scores"].index(max(sam_result["scores"]))
82
+ best_mask_path = sam_result["mask_paths"][best_mask_idx]
83
+ else:
84
+ # If no scores available, use the first mask
85
+ best_mask_path = sam_result["mask_paths"][0] if sam_result.get("mask_paths") else None
86
+
87
+ if not best_mask_path:
88
+ raise ValueError("No valid mask generated from segmentation")
89
+
90
+ # Step 3: Create 3D model with threestudio
91
+ threestudio_result = self._generate_3d_model(best_mask_path, threestudio_params)
92
+
93
+ # Step 4: Convert to OpenSCAD for parametric editing
94
+ scad_result = self._convert_to_openscad(threestudio_result["exported_files"][0], pipeline_id)
95
+
96
+ # Compile results
97
+ result = {
98
+ "pipeline_id": pipeline_id,
99
+ "prompt": prompt,
100
+ "image": venice_result,
101
+ "segmentation": sam_result,
102
+ "model_3d": threestudio_result,
103
+ "openscad": scad_result
104
+ }
105
+
106
+ logger.info(f"Pipeline {pipeline_id} completed successfully")
107
+ return result
108
+ except Exception as e:
109
+ logger.error(f"Error in pipeline: {str(e)}")
110
+ raise
111
+
112
+ def _generate_image(self, prompt: str, output_path: str,
113
+ params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
114
+ """
115
+ Generate image with Venice.ai.
116
+
117
+ Args:
118
+ prompt: Text description for image generation
119
+ output_path: Path to save the generated image
120
+ params: Optional parameters for Venice.ai
121
+
122
+ Returns:
123
+ Dictionary containing image data and metadata
124
+ """
125
+ logger.info(f"Generating image for prompt: {prompt}")
126
+
127
+ # Default parameters
128
+ default_params = {
129
+ "model": "fluently-xl", # Default to fastest model
130
+ "width": 1024,
131
+ "height": 1024
132
+ }
133
+
134
+ # Merge with provided parameters
135
+ if params:
136
+ default_params.update(params)
137
+
138
+ # Generate image
139
+ result = self.venice_generator.generate_image(
140
+ prompt=prompt,
141
+ output_path=output_path,
142
+ **default_params
143
+ )
144
+
145
+ logger.info(f"Image generated: {output_path}")
146
+ return result
147
+
148
+ def _segment_image(self, image_path: str, output_dir: str,
149
+ params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
150
+ """
151
+ Segment object with SAM2.
152
+
153
+ Args:
154
+ image_path: Path to input image
155
+ output_dir: Directory to save segmentation results
156
+ params: Optional parameters for SAM2
157
+
158
+ Returns:
159
+ Dictionary containing segmentation masks and metadata
160
+ """
161
+ logger.info(f"Segmenting image: {image_path}")
162
+
163
+ # Segment image with SAM2
164
+ # Check if points are provided in params
165
+ points = params.get("points") if params else None
166
+
167
+ if points:
168
+ result = self.sam_segmenter.segment_image(
169
+ image_path=image_path,
170
+ points=points,
171
+ output_dir=output_dir
172
+ )
173
+ else:
174
+ # Use automatic point generation
175
+ result = self.sam_segmenter.segment_with_auto_points(
176
+ image_path=image_path,
177
+ output_dir=output_dir
178
+ )
179
+
180
+ logger.info(f"Image segmented, {result.get('num_masks', 0)} masks generated")
181
+ return result
182
+
183
+ def _generate_3d_model(self, image_path: str,
184
+ params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
185
+ """
186
+ Generate 3D model with threestudio.
187
+
188
+ Args:
189
+ image_path: Path to input image
190
+ params: Optional parameters for threestudio
191
+
192
+ Returns:
193
+ Dictionary containing paths to generated model files
194
+ """
195
+ logger.info(f"Generating 3D model from image: {image_path}")
196
+
197
+ # Default parameters
198
+ default_params = {
199
+ "method": "zero123",
200
+ "num_iterations": 5000,
201
+ "export_format": "obj"
202
+ }
203
+
204
+ # Merge with provided parameters
205
+ if params:
206
+ default_params.update(params)
207
+
208
+ # Generate 3D model
209
+ result = self.threestudio_generator.generate_model_from_image(
210
+ image_path=image_path,
211
+ **default_params
212
+ )
213
+
214
+ logger.info(f"3D model generated: {result['exported_files']}")
215
+ return result
216
+
217
+ def _convert_to_openscad(self, model_path: str, model_id: str) -> Dict[str, Any]:
218
+ """
219
+ Convert 3D model to OpenSCAD format.
220
+
221
+ Args:
222
+ model_path: Path to input model
223
+ model_id: Unique identifier for the model
224
+
225
+ Returns:
226
+ Dictionary containing paths to generated files
227
+ """
228
+ logger.info(f"Converting model to OpenSCAD: {model_path}")
229
+
230
+ # Generate OpenSCAD code for importing the model
231
+ scad_code = f"""// Generated OpenSCAD code for model {model_id}
232
+ // Imported from {os.path.basename(model_path)}
233
+
234
+ // Parameters
235
+ scale_factor = 1.0;
236
+ position_x = 0;
237
+ position_y = 0;
238
+ position_z = 0;
239
+ rotation_x = 0;
240
+ rotation_y = 0;
241
+ rotation_z = 0;
242
+
243
+ // Import and transform the model
244
+ translate([position_x, position_y, position_z])
245
+ rotate([rotation_x, rotation_y, rotation_z])
246
+ scale(scale_factor)
247
+ import("{model_path}");
248
+ """
249
+
250
+ # Save SCAD code to file
251
+ scad_file = self.openscad_wrapper.generate_scad(scad_code, model_id)
252
+
253
+ # Generate previews
254
+ previews = self.openscad_wrapper.generate_multi_angle_previews(scad_file)
255
+
256
+ return {
257
+ "scad_file": scad_file,
258
+ "previews": previews,
259
+ "model_path": model_path
260
+ }