@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,374 @@
1
+ """
2
+ Test script for the complete workflow from text to 3D model.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import logging
8
+ import unittest
9
+ from unittest.mock import patch, MagicMock
10
+ from pathlib import Path
11
+
12
+ # Add the src directory to the path
13
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from src.ai.gemini_api import GeminiImageGenerator
16
+ from src.workflow.image_approval import ImageApprovalManager
17
+ from src.models.cuda_mvs import CUDAMultiViewStereo
18
+ from src.workflow.multi_view_to_model_pipeline import MultiViewToModelPipeline
19
+ from src.config import MULTI_VIEW_PIPELINE, IMAGE_APPROVAL, REMOTE_CUDA_MVS
20
+
21
+ # Configure logging
22
+ logging.basicConfig(level=logging.INFO)
23
+ logger = logging.getLogger(__name__)
24
+
25
+ class TestCompleteWorkflow(unittest.TestCase):
26
+ """
27
+ Test cases for the complete workflow from text to 3D model.
28
+ """
29
+
30
+ def setUp(self):
31
+ """
32
+ Set up test environment.
33
+ """
34
+ # Create test output directories
35
+ self.test_output_dir = "output/test_complete_workflow"
36
+ self.test_images_dir = os.path.join(self.test_output_dir, "images")
37
+ self.test_multi_view_dir = os.path.join(self.test_output_dir, "multi_view")
38
+ self.test_approved_dir = os.path.join(self.test_output_dir, "approved")
39
+ self.test_models_dir = os.path.join(self.test_output_dir, "models")
40
+
41
+ os.makedirs(self.test_output_dir, exist_ok=True)
42
+ os.makedirs(self.test_images_dir, exist_ok=True)
43
+ os.makedirs(self.test_multi_view_dir, exist_ok=True)
44
+ os.makedirs(self.test_approved_dir, exist_ok=True)
45
+ os.makedirs(self.test_models_dir, exist_ok=True)
46
+
47
+ # Mock API key
48
+ self.api_key = "test_api_key"
49
+
50
+ # Create the components
51
+ self.image_generator = GeminiImageGenerator(
52
+ api_key=self.api_key,
53
+ output_dir=self.test_images_dir
54
+ )
55
+
56
+ self.approval_manager = ImageApprovalManager(
57
+ output_dir=self.test_multi_view_dir,
58
+ approved_dir=self.test_approved_dir,
59
+ min_approved_images=3,
60
+ auto_approve=False
61
+ )
62
+
63
+ self.cuda_mvs = CUDAMultiViewStereo(
64
+ output_dir=self.test_models_dir,
65
+ use_gpu=False
66
+ )
67
+
68
+ # Create the pipeline
69
+ self.pipeline = MultiViewToModelPipeline(
70
+ image_generator=self.image_generator,
71
+ approval_manager=self.approval_manager,
72
+ model_generator=self.cuda_mvs,
73
+ output_dir=self.test_output_dir,
74
+ config=MULTI_VIEW_PIPELINE
75
+ )
76
+
77
+ @patch('src.ai.gemini_api.requests.post')
78
+ def test_generate_images_from_text(self, mock_post):
79
+ """
80
+ Test generating images from text description.
81
+ """
82
+ # Mock response
83
+ mock_response = MagicMock()
84
+ mock_response.status_code = 200
85
+ mock_response.json.return_value = {
86
+ "candidates": [
87
+ {
88
+ "content": {
89
+ "parts": [
90
+ {
91
+ "text": "Generated image description"
92
+ },
93
+ {
94
+ "inlineData": {
95
+ "mimeType": "image/png",
96
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
97
+ }
98
+ }
99
+ ]
100
+ }
101
+ }
102
+ ]
103
+ }
104
+ mock_post.return_value = mock_response
105
+
106
+ # Test parameters
107
+ prompt = "A low-poly rabbit"
108
+ num_views = 4
109
+
110
+ # Call the method
111
+ results = self.pipeline.generate_images_from_text(prompt, num_views)
112
+
113
+ # Verify the results
114
+ self.assertEqual(len(results), num_views)
115
+ for i, result in enumerate(results):
116
+ self.assertTrue("view_direction" in result)
117
+ self.assertEqual(result["view_index"], i + 1)
118
+ self.assertTrue("local_path" in result)
119
+ self.assertTrue(os.path.exists(result["local_path"]))
120
+
121
+ # Verify the API calls
122
+ self.assertEqual(mock_post.call_count, num_views)
123
+
124
+ def test_approve_images(self):
125
+ """
126
+ Test approving images in the workflow.
127
+ """
128
+ # Create test images
129
+ test_images = []
130
+ for i in range(4):
131
+ image_path = os.path.join(self.test_multi_view_dir, f"test_image_{i}.png")
132
+ with open(image_path, "w") as f:
133
+ f.write(f"test image data {i}")
134
+ test_images.append({
135
+ "id": f"image_{i}",
136
+ "local_path": image_path,
137
+ "view_index": i + 1,
138
+ "view_direction": f"view_{i}"
139
+ })
140
+
141
+ # Add images to the pipeline
142
+ self.pipeline.add_images_for_approval(test_images)
143
+
144
+ # Approve some images
145
+ self.pipeline.approve_image("image_0")
146
+ self.pipeline.approve_image("image_1")
147
+ self.pipeline.approve_image("image_2")
148
+
149
+ # Reject an image
150
+ self.pipeline.reject_image("image_3")
151
+
152
+ # Get the status
153
+ status = self.pipeline.get_approval_status()
154
+
155
+ # Verify the status
156
+ self.assertEqual(status["total_images"], 4)
157
+ self.assertEqual(status["pending_count"], 0)
158
+ self.assertEqual(status["approved_count"], 3)
159
+ self.assertEqual(status["rejected_count"], 1)
160
+ self.assertTrue(status["has_minimum_approved"])
161
+
162
+ @patch('src.models.cuda_mvs.subprocess.run')
163
+ def test_create_model_from_approved_images(self, mock_run):
164
+ """
165
+ Test creating a 3D model from approved images.
166
+ """
167
+ # Mock subprocess.run
168
+ mock_process = MagicMock()
169
+ mock_process.returncode = 0
170
+ mock_run.return_value = mock_process
171
+
172
+ # Create test images
173
+ test_images = []
174
+ for i in range(4):
175
+ image_path = os.path.join(self.test_approved_dir, f"image_{i}.png")
176
+ with open(image_path, "w") as f:
177
+ f.write(f"test image data {i}")
178
+ test_images.append({
179
+ "id": f"image_{i}",
180
+ "local_path": image_path,
181
+ "view_index": i + 1,
182
+ "view_direction": f"view_{i}"
183
+ })
184
+
185
+ # Create a mock model file
186
+ model_path = os.path.join(self.test_models_dir, "test_model.obj")
187
+ with open(model_path, "w") as f:
188
+ f.write("test model data")
189
+
190
+ # Call the method
191
+ result = self.pipeline.create_model_from_approved_images("test_model")
192
+
193
+ # Verify the result
194
+ self.assertIsNotNone(result)
195
+ self.assertTrue("model_path" in result)
196
+ self.assertTrue("model_id" in result)
197
+ self.assertTrue("format" in result)
198
+ self.assertEqual(result["format"], "obj")
199
+
200
+ @patch('src.ai.gemini_api.requests.post')
201
+ @patch('src.models.cuda_mvs.subprocess.run')
202
+ def test_complete_workflow(self, mock_run, mock_post):
203
+ """
204
+ Test the complete workflow from text to 3D model.
205
+ """
206
+ # Mock Gemini API response
207
+ mock_response = MagicMock()
208
+ mock_response.status_code = 200
209
+ mock_response.json.return_value = {
210
+ "candidates": [
211
+ {
212
+ "content": {
213
+ "parts": [
214
+ {
215
+ "text": "Generated image description"
216
+ },
217
+ {
218
+ "inlineData": {
219
+ "mimeType": "image/png",
220
+ "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
221
+ }
222
+ }
223
+ ]
224
+ }
225
+ }
226
+ ]
227
+ }
228
+ mock_post.return_value = mock_response
229
+
230
+ # Mock subprocess.run
231
+ mock_process = MagicMock()
232
+ mock_process.returncode = 0
233
+ mock_run.return_value = mock_process
234
+
235
+ # Create a mock model file
236
+ model_path = os.path.join(self.test_models_dir, "test_model.obj")
237
+ with open(model_path, "w") as f:
238
+ f.write("test model data")
239
+
240
+ # Create a pipeline with auto-approve
241
+ auto_pipeline = MultiViewToModelPipeline(
242
+ image_generator=self.image_generator,
243
+ approval_manager=ImageApprovalManager(
244
+ output_dir=self.test_multi_view_dir,
245
+ approved_dir=self.test_approved_dir,
246
+ min_approved_images=3,
247
+ auto_approve=True
248
+ ),
249
+ model_generator=self.cuda_mvs,
250
+ output_dir=self.test_output_dir,
251
+ config=MULTI_VIEW_PIPELINE
252
+ )
253
+
254
+ # Test parameters
255
+ prompt = "A low-poly rabbit"
256
+ num_views = 4
257
+
258
+ # Call the complete workflow
259
+ result = auto_pipeline.complete_workflow(prompt, num_views, "test_model")
260
+
261
+ # Verify the result
262
+ self.assertIsNotNone(result)
263
+ self.assertTrue("model_path" in result)
264
+ self.assertTrue("model_id" in result)
265
+ self.assertTrue("format" in result)
266
+ self.assertEqual(result["format"], "obj")
267
+ self.assertTrue("prompt" in result)
268
+ self.assertEqual(result["prompt"], prompt)
269
+ self.assertTrue("num_views" in result)
270
+ self.assertEqual(result["num_views"], num_views)
271
+ self.assertTrue("approved_images" in result)
272
+ self.assertEqual(len(result["approved_images"]), num_views)
273
+
274
+ @patch('src.remote.cuda_mvs_client.requests.post')
275
+ @patch('src.remote.cuda_mvs_client.requests.get')
276
+ def test_remote_workflow(self, mock_get, mock_post):
277
+ """
278
+ Test the workflow with remote CUDA MVS processing.
279
+ """
280
+ # Mock upload response
281
+ mock_upload_response = MagicMock()
282
+ mock_upload_response.status_code = 200
283
+ mock_upload_response.json.return_value = {
284
+ "job_id": "test_job_123",
285
+ "status": "uploaded",
286
+ "message": "Images uploaded successfully"
287
+ }
288
+
289
+ # Mock process response
290
+ mock_process_response = MagicMock()
291
+ mock_process_response.status_code = 200
292
+ mock_process_response.json.return_value = {
293
+ "job_id": "test_job_123",
294
+ "status": "processing",
295
+ "message": "Job started processing"
296
+ }
297
+
298
+ # Mock status response
299
+ mock_status_response = MagicMock()
300
+ mock_status_response.status_code = 200
301
+ mock_status_response.json.return_value = {
302
+ "job_id": "test_job_123",
303
+ "status": "completed",
304
+ "progress": 100,
305
+ "message": "Job completed successfully"
306
+ }
307
+
308
+ # Mock download response
309
+ mock_download_response = MagicMock()
310
+ mock_download_response.status_code = 200
311
+ mock_download_response.content = b"test model data"
312
+
313
+ # Set up the mock responses
314
+ mock_post.side_effect = [mock_upload_response, mock_process_response]
315
+ mock_get.side_effect = [mock_status_response, mock_download_response]
316
+
317
+ # Create test images
318
+ test_images = []
319
+ for i in range(4):
320
+ image_path = os.path.join(self.test_approved_dir, f"image_{i}.png")
321
+ with open(image_path, "w") as f:
322
+ f.write(f"test image data {i}")
323
+ test_images.append({
324
+ "id": f"image_{i}",
325
+ "local_path": image_path,
326
+ "view_index": i + 1,
327
+ "view_direction": f"view_{i}"
328
+ })
329
+
330
+ # Create a remote CUDA MVS client
331
+ from src.remote.cuda_mvs_client import CUDAMVSClient
332
+ remote_client = CUDAMVSClient(
333
+ api_key=self.api_key,
334
+ output_dir=self.test_models_dir
335
+ )
336
+
337
+ # Create a pipeline with the remote client
338
+ remote_pipeline = MultiViewToModelPipeline(
339
+ image_generator=self.image_generator,
340
+ approval_manager=self.approval_manager,
341
+ model_generator=remote_client,
342
+ output_dir=self.test_output_dir,
343
+ config=MULTI_VIEW_PIPELINE
344
+ )
345
+
346
+ # Add the approved images
347
+ remote_pipeline.add_images_for_approval(test_images)
348
+ for image in test_images:
349
+ remote_pipeline.approve_image(image["id"])
350
+
351
+ # Call the method to create a model using the remote client
352
+ with patch('src.workflow.multi_view_to_model_pipeline.CUDAMVSClient', return_value=remote_client):
353
+ result = remote_pipeline.create_model_from_approved_images("test_model", server_url="http://test-server:8765")
354
+
355
+ # Verify the result
356
+ self.assertIsNotNone(result)
357
+ self.assertTrue("model_path" in result)
358
+ self.assertTrue("model_id" in result)
359
+ self.assertTrue("format" in result)
360
+ self.assertEqual(result["format"], "obj")
361
+ self.assertTrue("job_id" in result)
362
+ self.assertEqual(result["job_id"], "test_job_123")
363
+
364
+ def tearDown(self):
365
+ """
366
+ Clean up after tests.
367
+ """
368
+ # Clean up test output directory
369
+ import shutil
370
+ if os.path.exists(self.test_output_dir):
371
+ shutil.rmtree(self.test_output_dir)
372
+
373
+ if __name__ == "__main__":
374
+ unittest.main()
@@ -0,0 +1,191 @@
1
+ """
2
+ Test script for CUDA Multi-View Stereo integration.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import logging
8
+ import unittest
9
+ from unittest.mock import patch, MagicMock
10
+ from pathlib import Path
11
+
12
+ # Add the src directory to the path
13
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
14
+
15
+ from src.models.cuda_mvs import CUDAMultiViewStereo
16
+
17
+ # Configure logging
18
+ logging.basicConfig(level=logging.INFO)
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class TestCUDAMVS(unittest.TestCase):
22
+ """
23
+ Test cases for CUDA Multi-View Stereo integration.
24
+ """
25
+
26
+ def setUp(self):
27
+ """
28
+ Set up test environment.
29
+ """
30
+ # Create a test output directory
31
+ self.test_output_dir = "output/test_cuda_mvs"
32
+ os.makedirs(self.test_output_dir, exist_ok=True)
33
+
34
+ # Create test image directory
35
+ self.test_images_dir = "output/test_cuda_mvs/images"
36
+ os.makedirs(self.test_images_dir, exist_ok=True)
37
+
38
+ # Create mock CUDA MVS path
39
+ self.cuda_mvs_path = "mock_cuda_mvs"
40
+ os.makedirs(os.path.join(self.cuda_mvs_path, "build"), exist_ok=True)
41
+
42
+ # Create mock executable
43
+ with open(os.path.join(self.cuda_mvs_path, "build", "app_patch_match_mvs"), "w") as f:
44
+ f.write("#!/bin/bash\necho 'Mock CUDA MVS'\n")
45
+ os.chmod(os.path.join(self.cuda_mvs_path, "build", "app_patch_match_mvs"), 0o755)
46
+
47
+ # Create test images
48
+ for i in range(3):
49
+ with open(os.path.join(self.test_images_dir, f"view_{i}.png"), "w") as f:
50
+ f.write(f"Mock image {i}")
51
+
52
+ # Create the CUDA MVS wrapper with the mock path
53
+ with patch('os.path.exists', return_value=True):
54
+ self.cuda_mvs = CUDAMultiViewStereo(
55
+ cuda_mvs_path=self.cuda_mvs_path,
56
+ output_dir=self.test_output_dir
57
+ )
58
+
59
+ @patch('subprocess.Popen')
60
+ def test_generate_model_from_images(self, mock_popen):
61
+ """
62
+ Test generating a 3D model from multiple images.
63
+ """
64
+ # Mock subprocess
65
+ mock_process = MagicMock()
66
+ mock_process.returncode = 0
67
+ mock_process.communicate.return_value = ("Mock stdout", "")
68
+ mock_popen.return_value = mock_process
69
+
70
+ # Mock file creation
71
+ def mock_exists(path):
72
+ if "point_cloud_file" in str(path):
73
+ # Create the mock point cloud file
74
+ os.makedirs(os.path.dirname(path), exist_ok=True)
75
+ with open(path, "w") as f:
76
+ f.write("Mock point cloud")
77
+ return True
78
+ return os.path.exists(path)
79
+
80
+ # Test parameters
81
+ image_paths = [os.path.join(self.test_images_dir, f"view_{i}.png") for i in range(3)]
82
+ output_name = "test_model"
83
+
84
+ # Call the method with patched os.path.exists
85
+ with patch('os.path.exists', side_effect=mock_exists):
86
+ result = self.cuda_mvs.generate_model_from_images(image_paths, output_name=output_name)
87
+
88
+ # Verify the result
89
+ self.assertIsNotNone(result)
90
+ self.assertEqual(result["model_id"], output_name)
91
+ self.assertTrue("point_cloud_file" in result)
92
+ self.assertTrue("camera_params_file" in result)
93
+ self.assertEqual(len(result["input_images"]), 3)
94
+
95
+ # Verify the subprocess call
96
+ mock_popen.assert_called_once()
97
+ args, kwargs = mock_popen.call_args
98
+ self.assertTrue("app_patch_match_mvs" in args[0][0])
99
+
100
+ def test_generate_camera_params(self):
101
+ """
102
+ Test generating camera parameters from images.
103
+ """
104
+ # Test parameters
105
+ image_paths = [os.path.join(self.test_images_dir, f"view_{i}.png") for i in range(3)]
106
+ model_dir = os.path.join(self.test_output_dir, "camera_params_test")
107
+ os.makedirs(model_dir, exist_ok=True)
108
+
109
+ # Mock PIL.Image.open
110
+ mock_image = MagicMock()
111
+ mock_image.size = (800, 600)
112
+
113
+ # Call the method with patched PIL.Image.open
114
+ with patch('PIL.Image.open', return_value=mock_image):
115
+ params_file = self.cuda_mvs._generate_camera_params(image_paths, model_dir)
116
+
117
+ # Verify the result
118
+ self.assertTrue(os.path.exists(params_file))
119
+
120
+ # Read the params file
121
+ import json
122
+ with open(params_file, "r") as f:
123
+ params = json.load(f)
124
+
125
+ # Verify the params
126
+ self.assertEqual(len(params), 3)
127
+ for i, param in enumerate(params):
128
+ self.assertEqual(param["image_id"], i)
129
+ self.assertEqual(param["width"], 800)
130
+ self.assertEqual(param["height"], 600)
131
+ self.assertTrue("camera" in param)
132
+ self.assertEqual(param["camera"]["model"], "PINHOLE")
133
+
134
+ def test_convert_ply_to_obj(self):
135
+ """
136
+ Test converting PLY point cloud to OBJ mesh.
137
+ """
138
+ # Create a mock PLY file
139
+ ply_file = os.path.join(self.test_output_dir, "test.ply")
140
+ with open(ply_file, "w") as f:
141
+ f.write("Mock PLY file")
142
+
143
+ # Call the method
144
+ obj_file = self.cuda_mvs.convert_ply_to_obj(ply_file)
145
+
146
+ # Verify the result
147
+ self.assertTrue(os.path.exists(obj_file))
148
+ self.assertTrue(obj_file.endswith(".obj"))
149
+
150
+ # Read the OBJ file
151
+ with open(obj_file, "r") as f:
152
+ content = f.read()
153
+
154
+ # Verify the content
155
+ self.assertTrue("# Converted from test.ply" in content)
156
+ self.assertTrue("v " in content)
157
+ self.assertTrue("f " in content)
158
+
159
+ def test_error_handling(self):
160
+ """
161
+ Test error handling in the CUDA MVS wrapper.
162
+ """
163
+ # Test parameters
164
+ image_paths = [os.path.join(self.test_images_dir, f"view_{i}.png") for i in range(3)]
165
+ output_name = "error_test"
166
+
167
+ # Mock subprocess with error
168
+ mock_process = MagicMock()
169
+ mock_process.returncode = 1
170
+ mock_process.communicate.return_value = ("", "Mock error")
171
+
172
+ # Call the method with patched subprocess.Popen
173
+ with patch('subprocess.Popen', return_value=mock_process):
174
+ with self.assertRaises(RuntimeError):
175
+ self.cuda_mvs.generate_model_from_images(image_paths, output_name=output_name)
176
+
177
+ def tearDown(self):
178
+ """
179
+ Clean up after tests.
180
+ """
181
+ # Clean up test output directory
182
+ import shutil
183
+ if os.path.exists(self.test_output_dir):
184
+ shutil.rmtree(self.test_output_dir)
185
+
186
+ # Clean up mock CUDA MVS path
187
+ if os.path.exists(self.cuda_mvs_path):
188
+ shutil.rmtree(self.cuda_mvs_path)
189
+
190
+ if __name__ == "__main__":
191
+ unittest.main()