@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,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Image approval tool for MCP clients.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
from typing import Dict, Any, List, Optional
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
class ImageApprovalTool:
|
|
13
|
+
"""
|
|
14
|
+
Tool for image approval/denial in MCP clients.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, output_dir: str = "output/approved_images"):
|
|
18
|
+
"""
|
|
19
|
+
Initialize the image approval tool.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
output_dir: Directory to store approved images
|
|
23
|
+
"""
|
|
24
|
+
self.output_dir = output_dir
|
|
25
|
+
|
|
26
|
+
# Create output directory if it doesn't exist
|
|
27
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
def present_image_for_approval(self, image_path: str, metadata: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Present an image to the user for approval.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
image_path: Path to the image
|
|
35
|
+
metadata: Optional metadata about the image
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Dictionary with image path and approval request ID
|
|
39
|
+
"""
|
|
40
|
+
# For MCP server, we just prepare the response
|
|
41
|
+
# The actual approval is handled by the client
|
|
42
|
+
|
|
43
|
+
approval_id = os.path.basename(image_path).split('.')[0]
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"approval_id": approval_id,
|
|
47
|
+
"image_path": image_path,
|
|
48
|
+
"image_url": f"/images/{os.path.basename(image_path)}",
|
|
49
|
+
"metadata": metadata or {}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
def process_approval(self, approval_id: str, approved: bool, image_path: str) -> Dict[str, Any]:
|
|
53
|
+
"""
|
|
54
|
+
Process user's approval or denial of an image.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
approval_id: ID of the approval request
|
|
58
|
+
approved: Whether the image was approved
|
|
59
|
+
image_path: Path to the image
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Dictionary with approval status and image path
|
|
63
|
+
"""
|
|
64
|
+
if approved:
|
|
65
|
+
# Copy approved image to output directory
|
|
66
|
+
approved_path = os.path.join(self.output_dir, os.path.basename(image_path))
|
|
67
|
+
os.makedirs(os.path.dirname(approved_path), exist_ok=True)
|
|
68
|
+
shutil.copy2(image_path, approved_path)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"approval_id": approval_id,
|
|
72
|
+
"approved": True,
|
|
73
|
+
"original_path": image_path,
|
|
74
|
+
"approved_path": approved_path
|
|
75
|
+
}
|
|
76
|
+
else:
|
|
77
|
+
return {
|
|
78
|
+
"approval_id": approval_id,
|
|
79
|
+
"approved": False,
|
|
80
|
+
"original_path": image_path
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def get_approved_images(self, filter_pattern: Optional[str] = None) -> List[str]:
|
|
84
|
+
"""
|
|
85
|
+
Get list of approved images.
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
filter_pattern: Optional pattern to filter image names
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of paths to approved images
|
|
92
|
+
"""
|
|
93
|
+
import glob
|
|
94
|
+
|
|
95
|
+
if filter_pattern:
|
|
96
|
+
pattern = os.path.join(self.output_dir, filter_pattern)
|
|
97
|
+
else:
|
|
98
|
+
pattern = os.path.join(self.output_dir, "*")
|
|
99
|
+
|
|
100
|
+
return glob.glob(pattern)
|
|
101
|
+
|
|
102
|
+
def get_approval_status(self, approval_id: str) -> Dict[str, Any]:
|
|
103
|
+
"""
|
|
104
|
+
Get the approval status for a specific approval ID.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
approval_id: ID of the approval request
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dictionary with approval status
|
|
111
|
+
"""
|
|
112
|
+
# Check if any approved image matches the approval ID
|
|
113
|
+
approved_images = self.get_approved_images()
|
|
114
|
+
|
|
115
|
+
for image_path in approved_images:
|
|
116
|
+
if approval_id in os.path.basename(image_path):
|
|
117
|
+
return {
|
|
118
|
+
"approval_id": approval_id,
|
|
119
|
+
"approved": True,
|
|
120
|
+
"approved_path": image_path
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"approval_id": approval_id,
|
|
125
|
+
"approved": False
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
def batch_process_approvals(self, approvals: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
|
129
|
+
"""
|
|
130
|
+
Process multiple approvals at once.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
approvals: List of dictionaries with approval_id, approved, and image_path
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
List of dictionaries with approval results
|
|
137
|
+
"""
|
|
138
|
+
results = []
|
|
139
|
+
|
|
140
|
+
for approval in approvals:
|
|
141
|
+
result = self.process_approval(
|
|
142
|
+
approval_id=approval["approval_id"],
|
|
143
|
+
approved=approval["approved"],
|
|
144
|
+
image_path=approval["image_path"]
|
|
145
|
+
)
|
|
146
|
+
results.append(result)
|
|
147
|
+
|
|
148
|
+
return results
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow orchestration for the multi-view to model pipeline.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Dict, Any, List, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class MultiViewToModelPipeline:
|
|
14
|
+
"""
|
|
15
|
+
Orchestrates the workflow from image or text prompt to 3D model:
|
|
16
|
+
1. Generate image with Venice.ai or Google Gemini (optional)
|
|
17
|
+
2. Generate multiple views with Google Gemini
|
|
18
|
+
3. Process user approval of views
|
|
19
|
+
4. Create 3D model with CUDA Multi-View Stereo
|
|
20
|
+
5. Convert to OpenSCAD for parametric editing (optional)
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self,
|
|
24
|
+
gemini_generator=None,
|
|
25
|
+
venice_generator=None,
|
|
26
|
+
cuda_mvs=None,
|
|
27
|
+
openscad_wrapper=None,
|
|
28
|
+
approval_tool=None,
|
|
29
|
+
output_dir: str = "output/pipeline"):
|
|
30
|
+
"""
|
|
31
|
+
Initialize the pipeline.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
gemini_generator: Instance of GeminiImageGenerator
|
|
35
|
+
venice_generator: Instance of VeniceImageGenerator (optional)
|
|
36
|
+
cuda_mvs: Instance of CUDAMultiViewStereo
|
|
37
|
+
openscad_wrapper: Instance of OpenSCADWrapper (optional)
|
|
38
|
+
approval_tool: Instance of ImageApprovalTool
|
|
39
|
+
output_dir: Directory to store output files
|
|
40
|
+
"""
|
|
41
|
+
self.gemini_generator = gemini_generator
|
|
42
|
+
self.venice_generator = venice_generator
|
|
43
|
+
self.cuda_mvs = cuda_mvs
|
|
44
|
+
self.openscad_wrapper = openscad_wrapper
|
|
45
|
+
self.approval_tool = approval_tool
|
|
46
|
+
self.output_dir = output_dir
|
|
47
|
+
|
|
48
|
+
# Create output directories
|
|
49
|
+
os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
|
|
50
|
+
os.makedirs(os.path.join(output_dir, "multi_view"), exist_ok=True)
|
|
51
|
+
os.makedirs(os.path.join(output_dir, "approved"), exist_ok=True)
|
|
52
|
+
os.makedirs(os.path.join(output_dir, "models"), exist_ok=True)
|
|
53
|
+
os.makedirs(os.path.join(output_dir, "scad"), exist_ok=True)
|
|
54
|
+
|
|
55
|
+
def generate_model_from_text(self, prompt: str,
|
|
56
|
+
use_venice: bool = False,
|
|
57
|
+
num_views: int = 4,
|
|
58
|
+
gemini_params: Optional[Dict[str, Any]] = None,
|
|
59
|
+
venice_params: Optional[Dict[str, Any]] = None,
|
|
60
|
+
cuda_mvs_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Generate a 3D model from a text prompt.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
prompt: Text description for image generation
|
|
66
|
+
use_venice: Whether to use Venice.ai for initial image
|
|
67
|
+
num_views: Number of views to generate
|
|
68
|
+
gemini_params: Optional parameters for Google Gemini
|
|
69
|
+
venice_params: Optional parameters for Venice.ai
|
|
70
|
+
cuda_mvs_params: Optional parameters for CUDA MVS
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
Dictionary containing paths to generated files and metadata
|
|
74
|
+
"""
|
|
75
|
+
try:
|
|
76
|
+
# Generate a unique ID for this pipeline run
|
|
77
|
+
pipeline_id = str(uuid.uuid4())
|
|
78
|
+
logger.info(f"Starting pipeline {pipeline_id} for prompt: {prompt}")
|
|
79
|
+
|
|
80
|
+
# Step 1: Generate initial image
|
|
81
|
+
if use_venice and self.venice_generator:
|
|
82
|
+
# Use Venice.ai for initial image
|
|
83
|
+
logger.info("Using Venice.ai for initial image generation")
|
|
84
|
+
image_path = os.path.join(self.output_dir, "images", f"{pipeline_id}_venice.png")
|
|
85
|
+
initial_result = self.venice_generator.generate_image(
|
|
86
|
+
prompt=prompt,
|
|
87
|
+
output_path=image_path,
|
|
88
|
+
**(venice_params or {})
|
|
89
|
+
)
|
|
90
|
+
else:
|
|
91
|
+
# Use Google Gemini for initial image
|
|
92
|
+
logger.info("Using Google Gemini for initial image generation")
|
|
93
|
+
image_path = os.path.join(self.output_dir, "images", f"{pipeline_id}_gemini.png")
|
|
94
|
+
initial_result = self.gemini_generator.generate_image(
|
|
95
|
+
prompt=prompt,
|
|
96
|
+
output_path=image_path,
|
|
97
|
+
**(gemini_params or {})
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Step 2: Generate multiple views
|
|
101
|
+
logger.info(f"Generating {num_views} views with Google Gemini")
|
|
102
|
+
multi_view_dir = os.path.join(self.output_dir, "multi_view", pipeline_id)
|
|
103
|
+
|
|
104
|
+
multi_views = self.gemini_generator.generate_multiple_views(
|
|
105
|
+
prompt=prompt,
|
|
106
|
+
num_views=num_views,
|
|
107
|
+
base_image_path=image_path,
|
|
108
|
+
output_dir=multi_view_dir
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Step 3: Present images for approval
|
|
112
|
+
# In a real implementation, this would be handled by the MCP client
|
|
113
|
+
# through the MCP tools interface
|
|
114
|
+
logger.info("Preparing images for approval")
|
|
115
|
+
approval_requests = []
|
|
116
|
+
for view in multi_views:
|
|
117
|
+
approval_request = self.approval_tool.present_image_for_approval(
|
|
118
|
+
image_path=view["local_path"],
|
|
119
|
+
metadata={
|
|
120
|
+
"prompt": view.get("prompt"),
|
|
121
|
+
"view_direction": view.get("view_direction"),
|
|
122
|
+
"view_index": view.get("view_index")
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
approval_requests.append(approval_request)
|
|
126
|
+
|
|
127
|
+
# For the purpose of this implementation, we'll assume all views are approved
|
|
128
|
+
# In a real implementation, this would be handled by the MCP client
|
|
129
|
+
approved_images = []
|
|
130
|
+
for req in approval_requests:
|
|
131
|
+
approval_result = self.approval_tool.process_approval(
|
|
132
|
+
approval_id=req["approval_id"],
|
|
133
|
+
approved=True,
|
|
134
|
+
image_path=req["image_path"]
|
|
135
|
+
)
|
|
136
|
+
if approval_result["approved"]:
|
|
137
|
+
approved_images.append(approval_result["approved_path"])
|
|
138
|
+
|
|
139
|
+
# Step 4: Generate 3D model with CUDA MVS
|
|
140
|
+
logger.info("Generating 3D model with CUDA MVS")
|
|
141
|
+
model_result = self.cuda_mvs.generate_model_from_images(
|
|
142
|
+
image_paths=approved_images,
|
|
143
|
+
output_name=pipeline_id,
|
|
144
|
+
**(cuda_mvs_params or {})
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Step 5: Convert to OpenSCAD (if wrapper is available)
|
|
148
|
+
scad_result = None
|
|
149
|
+
if self.openscad_wrapper and model_result.get("point_cloud_file"):
|
|
150
|
+
logger.info("Converting to OpenSCAD")
|
|
151
|
+
scad_result = self._convert_to_openscad(model_result["point_cloud_file"], pipeline_id)
|
|
152
|
+
|
|
153
|
+
# Compile results
|
|
154
|
+
result = {
|
|
155
|
+
"pipeline_id": pipeline_id,
|
|
156
|
+
"prompt": prompt,
|
|
157
|
+
"initial_image": initial_result,
|
|
158
|
+
"multi_views": multi_views,
|
|
159
|
+
"approved_images": approved_images,
|
|
160
|
+
"model_3d": model_result,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if scad_result:
|
|
164
|
+
result["openscad"] = scad_result
|
|
165
|
+
|
|
166
|
+
logger.info(f"Pipeline {pipeline_id} completed successfully")
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Error in pipeline: {str(e)}")
|
|
171
|
+
raise
|
|
172
|
+
|
|
173
|
+
def generate_model_from_image(self, image_path: str,
|
|
174
|
+
prompt: Optional[str] = None,
|
|
175
|
+
num_views: int = 4,
|
|
176
|
+
gemini_params: Optional[Dict[str, Any]] = None,
|
|
177
|
+
cuda_mvs_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
178
|
+
"""
|
|
179
|
+
Generate a 3D model from an existing image.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
image_path: Path to input image
|
|
183
|
+
prompt: Optional text description to guide multi-view generation
|
|
184
|
+
num_views: Number of views to generate
|
|
185
|
+
gemini_params: Optional parameters for Google Gemini
|
|
186
|
+
cuda_mvs_params: Optional parameters for CUDA MVS
|
|
187
|
+
|
|
188
|
+
Returns:
|
|
189
|
+
Dictionary containing paths to generated files and metadata
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
# Generate a unique ID for this pipeline run
|
|
193
|
+
pipeline_id = str(uuid.uuid4())
|
|
194
|
+
logger.info(f"Starting pipeline {pipeline_id} from image: {image_path}")
|
|
195
|
+
|
|
196
|
+
# Use provided prompt or generate one from the image
|
|
197
|
+
if not prompt:
|
|
198
|
+
# In a real implementation, you might use an image captioning model
|
|
199
|
+
# to generate a description of the image
|
|
200
|
+
prompt = f"3D object in the image {os.path.basename(image_path)}"
|
|
201
|
+
|
|
202
|
+
# Step 1: Generate multiple views
|
|
203
|
+
logger.info(f"Generating {num_views} views with Google Gemini")
|
|
204
|
+
multi_view_dir = os.path.join(self.output_dir, "multi_view", pipeline_id)
|
|
205
|
+
|
|
206
|
+
multi_views = self.gemini_generator.generate_multiple_views(
|
|
207
|
+
prompt=prompt,
|
|
208
|
+
num_views=num_views,
|
|
209
|
+
base_image_path=image_path,
|
|
210
|
+
output_dir=multi_view_dir
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Step 2: Present images for approval
|
|
214
|
+
logger.info("Preparing images for approval")
|
|
215
|
+
approval_requests = []
|
|
216
|
+
for view in multi_views:
|
|
217
|
+
approval_request = self.approval_tool.present_image_for_approval(
|
|
218
|
+
image_path=view["local_path"],
|
|
219
|
+
metadata={
|
|
220
|
+
"prompt": view.get("prompt"),
|
|
221
|
+
"view_direction": view.get("view_direction"),
|
|
222
|
+
"view_index": view.get("view_index")
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
approval_requests.append(approval_request)
|
|
226
|
+
|
|
227
|
+
# For the purpose of this implementation, we'll assume all views are approved
|
|
228
|
+
approved_images = []
|
|
229
|
+
for req in approval_requests:
|
|
230
|
+
approval_result = self.approval_tool.process_approval(
|
|
231
|
+
approval_id=req["approval_id"],
|
|
232
|
+
approved=True,
|
|
233
|
+
image_path=req["image_path"]
|
|
234
|
+
)
|
|
235
|
+
if approval_result["approved"]:
|
|
236
|
+
approved_images.append(approval_result["approved_path"])
|
|
237
|
+
|
|
238
|
+
# Step 3: Generate 3D model with CUDA MVS
|
|
239
|
+
logger.info("Generating 3D model with CUDA MVS")
|
|
240
|
+
model_result = self.cuda_mvs.generate_model_from_images(
|
|
241
|
+
image_paths=approved_images,
|
|
242
|
+
output_name=pipeline_id,
|
|
243
|
+
**(cuda_mvs_params or {})
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Step 4: Convert to OpenSCAD (if wrapper is available)
|
|
247
|
+
scad_result = None
|
|
248
|
+
if self.openscad_wrapper and model_result.get("point_cloud_file"):
|
|
249
|
+
logger.info("Converting to OpenSCAD")
|
|
250
|
+
scad_result = self._convert_to_openscad(model_result["point_cloud_file"], pipeline_id)
|
|
251
|
+
|
|
252
|
+
# Compile results
|
|
253
|
+
result = {
|
|
254
|
+
"pipeline_id": pipeline_id,
|
|
255
|
+
"prompt": prompt,
|
|
256
|
+
"input_image": image_path,
|
|
257
|
+
"multi_views": multi_views,
|
|
258
|
+
"approved_images": approved_images,
|
|
259
|
+
"model_3d": model_result,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if scad_result:
|
|
263
|
+
result["openscad"] = scad_result
|
|
264
|
+
|
|
265
|
+
logger.info(f"Pipeline {pipeline_id} completed successfully")
|
|
266
|
+
return result
|
|
267
|
+
|
|
268
|
+
except Exception as e:
|
|
269
|
+
logger.error(f"Error in pipeline: {str(e)}")
|
|
270
|
+
raise
|
|
271
|
+
|
|
272
|
+
def _convert_to_openscad(self, model_path: str, model_id: str) -> Dict[str, Any]:
|
|
273
|
+
"""
|
|
274
|
+
Convert 3D model to OpenSCAD format.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
model_path: Path to input model (PLY file)
|
|
278
|
+
model_id: Unique identifier for the model
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
Dictionary containing paths to generated files
|
|
282
|
+
"""
|
|
283
|
+
logger.info(f"Converting model to OpenSCAD: {model_path}")
|
|
284
|
+
|
|
285
|
+
# Convert PLY to OBJ if needed
|
|
286
|
+
if model_path.endswith('.ply'):
|
|
287
|
+
obj_path = self.cuda_mvs.convert_ply_to_obj(model_path)
|
|
288
|
+
model_path = obj_path
|
|
289
|
+
|
|
290
|
+
# Generate OpenSCAD code for importing the model
|
|
291
|
+
scad_code = f"""// Generated OpenSCAD code for model {model_id}
|
|
292
|
+
// Imported from {os.path.basename(model_path)}
|
|
293
|
+
|
|
294
|
+
// Parameters
|
|
295
|
+
scale_factor = 1.0;
|
|
296
|
+
position_x = 0;
|
|
297
|
+
position_y = 0;
|
|
298
|
+
position_z = 0;
|
|
299
|
+
rotation_x = 0;
|
|
300
|
+
rotation_y = 0;
|
|
301
|
+
rotation_z = 0;
|
|
302
|
+
|
|
303
|
+
// Import and transform the model
|
|
304
|
+
translate([position_x, position_y, position_z])
|
|
305
|
+
rotate([rotation_x, rotation_y, rotation_z])
|
|
306
|
+
scale(scale_factor)
|
|
307
|
+
import("{model_path}");
|
|
308
|
+
"""
|
|
309
|
+
|
|
310
|
+
# Save SCAD code to file
|
|
311
|
+
scad_file = self.openscad_wrapper.generate_scad(scad_code, model_id)
|
|
312
|
+
|
|
313
|
+
# Generate previews
|
|
314
|
+
previews = self.openscad_wrapper.generate_multi_angle_previews(scad_file)
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
"scad_file": scad_file,
|
|
318
|
+
"previews": previews,
|
|
319
|
+
"model_path": model_path
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
def process_approval_results(self, approval_results: List[Dict[str, Any]]) -> List[str]:
|
|
323
|
+
"""
|
|
324
|
+
Process approval results from the MCP client.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
approval_results: List of approval results from the client
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
List of paths to approved images
|
|
331
|
+
"""
|
|
332
|
+
approved_images = []
|
|
333
|
+
|
|
334
|
+
for result in approval_results:
|
|
335
|
+
if result.get("approved", False) and "approved_path" in result:
|
|
336
|
+
approved_images.append(result["approved_path"])
|
|
337
|
+
|
|
338
|
+
return approved_images
|