@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.
- package/README.md +116 -0
- package/config/mcp/antigravity.json +22 -0
- package/config/mcp/claude.json +22 -0
- package/config/mcp/codex.toml +25 -0
- package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
- package/docs/review_documentation.md +121 -0
- package/installer.js +358 -0
- package/mcp_servers/kicad-mcp-server/.env.example +22 -0
- package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
- package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
- package/mcp_servers/kicad-mcp-server/README.md +316 -0
- package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
- package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
- package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
- package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
- package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
- package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
- package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
- package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
- package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
- package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
- package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
- package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
- package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
- package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
- package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
- package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
- package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
- package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
- package/mcp_servers/openscad-mcp-server/README.md +154 -0
- package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
- package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
- package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
- package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
- package/mcp_servers/openscad-mcp-server/glama.json +4 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
- package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
- package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
- package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
- package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
- package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
- package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
- package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
- package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
- package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
- package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
- package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
- package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
- package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
- package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
- package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
- package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
- package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
- package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
- package/package.json +44 -0
- package/scripts/install_mcps.sh +86 -0
- package/scripts/openscad_wrapper.sh +62 -0
- package/scripts/run_kicad_mcp.sh +25 -0
- package/scripts/run_openscad_mcp.sh +41 -0
- package/scripts/test_mcp_connection.py +626 -0
- package/scripts/test_mcp_connection.sh +168 -0
- package/skills/code-first-hardware-design/SKILL.md +260 -0
- package/skills/pcb-constraint-definition/SKILL.md +236 -0
- package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
- package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
- 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()
|