@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,418 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import uuid
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
class OpenSCADWrapper:
|
|
10
|
+
"""
|
|
11
|
+
Wrapper for OpenSCAD command-line interface.
|
|
12
|
+
Provides methods to generate SCAD code, STL files, and preview images.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, scad_dir: str, output_dir: str):
|
|
16
|
+
"""
|
|
17
|
+
Initialize the OpenSCAD wrapper.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
scad_dir: Directory to store SCAD files
|
|
21
|
+
output_dir: Directory to store output files (STL, PNG)
|
|
22
|
+
"""
|
|
23
|
+
self.scad_dir = scad_dir
|
|
24
|
+
self.output_dir = output_dir
|
|
25
|
+
self.stl_dir = os.path.join(output_dir, "stl")
|
|
26
|
+
self.preview_dir = os.path.join(output_dir, "preview")
|
|
27
|
+
|
|
28
|
+
# Create directories if they don't exist
|
|
29
|
+
os.makedirs(self.scad_dir, exist_ok=True)
|
|
30
|
+
os.makedirs(self.stl_dir, exist_ok=True)
|
|
31
|
+
os.makedirs(self.preview_dir, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
# Basic shape templates
|
|
34
|
+
self.shape_templates = {
|
|
35
|
+
"cube": self._cube_template,
|
|
36
|
+
"sphere": self._sphere_template,
|
|
37
|
+
"cylinder": self._cylinder_template,
|
|
38
|
+
"box": self._box_template,
|
|
39
|
+
"rounded_box": self._rounded_box_template,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def generate_scad_code(self, model_type: str, parameters: Dict[str, Any]) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Generate OpenSCAD code for a given model type and parameters.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
model_type: Type of model to generate (cube, sphere, cylinder, etc.)
|
|
48
|
+
parameters: Dictionary of parameters for the model
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Path to the generated SCAD file
|
|
52
|
+
"""
|
|
53
|
+
model_id = str(uuid.uuid4())
|
|
54
|
+
scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
|
|
55
|
+
|
|
56
|
+
# Get the template function for the model type
|
|
57
|
+
template_func = self.shape_templates.get(model_type)
|
|
58
|
+
if not template_func:
|
|
59
|
+
raise ValueError(f"Unsupported model type: {model_type}")
|
|
60
|
+
|
|
61
|
+
# Generate SCAD code using the template
|
|
62
|
+
scad_code = template_func(parameters)
|
|
63
|
+
|
|
64
|
+
# Write SCAD code to file
|
|
65
|
+
with open(scad_file, 'w') as f:
|
|
66
|
+
f.write(scad_code)
|
|
67
|
+
|
|
68
|
+
logger.info(f"Generated SCAD file: {scad_file}")
|
|
69
|
+
return scad_file
|
|
70
|
+
|
|
71
|
+
def generate_scad(self, scad_code: str, model_id: str) -> str:
|
|
72
|
+
"""
|
|
73
|
+
Save OpenSCAD code to a file with a specific model ID.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
scad_code: OpenSCAD code to save
|
|
77
|
+
model_id: ID to use for the file name
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Path to the saved SCAD file
|
|
81
|
+
"""
|
|
82
|
+
scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
|
|
83
|
+
|
|
84
|
+
# Write SCAD code to file
|
|
85
|
+
with open(scad_file, 'w') as f:
|
|
86
|
+
f.write(scad_code)
|
|
87
|
+
|
|
88
|
+
logger.info(f"Generated SCAD file: {scad_file}")
|
|
89
|
+
return scad_file
|
|
90
|
+
|
|
91
|
+
def update_scad_code(self, model_id: str, parameters: Dict[str, Any]) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Update an existing SCAD file with new parameters.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
model_id: ID of the model to update
|
|
97
|
+
parameters: New parameters for the model
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
Path to the updated SCAD file
|
|
101
|
+
"""
|
|
102
|
+
scad_file = os.path.join(self.scad_dir, f"{model_id}.scad")
|
|
103
|
+
if not os.path.exists(scad_file):
|
|
104
|
+
raise FileNotFoundError(f"SCAD file not found: {scad_file}")
|
|
105
|
+
|
|
106
|
+
# Read the existing SCAD file to determine its type
|
|
107
|
+
with open(scad_file, 'r') as f:
|
|
108
|
+
scad_code = f.read()
|
|
109
|
+
|
|
110
|
+
# Determine model type from the code (simplified approach)
|
|
111
|
+
model_type = None
|
|
112
|
+
for shape_type in self.shape_templates:
|
|
113
|
+
if shape_type in scad_code.lower():
|
|
114
|
+
model_type = shape_type
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
if not model_type:
|
|
118
|
+
raise ValueError("Could not determine model type from existing SCAD file")
|
|
119
|
+
|
|
120
|
+
# Generate new SCAD code
|
|
121
|
+
new_scad_code = self.shape_templates[model_type](parameters)
|
|
122
|
+
|
|
123
|
+
# Write updated SCAD code to file
|
|
124
|
+
with open(scad_file, 'w') as f:
|
|
125
|
+
f.write(new_scad_code)
|
|
126
|
+
|
|
127
|
+
logger.info(f"Updated SCAD file: {scad_file}")
|
|
128
|
+
return scad_file
|
|
129
|
+
|
|
130
|
+
def generate_stl(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Generate an STL file from a SCAD file.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
scad_file: Path to the SCAD file
|
|
136
|
+
parameters: Optional parameters to override in the SCAD file
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Path to the generated STL file
|
|
140
|
+
"""
|
|
141
|
+
model_id = os.path.basename(scad_file).split('.')[0]
|
|
142
|
+
stl_file = os.path.join(self.stl_dir, f"{model_id}.stl")
|
|
143
|
+
|
|
144
|
+
# Build command
|
|
145
|
+
cmd = ["openscad", "-o", stl_file]
|
|
146
|
+
|
|
147
|
+
# Add parameters if provided
|
|
148
|
+
if parameters:
|
|
149
|
+
for key, value in parameters.items():
|
|
150
|
+
cmd.extend(["-D", f"{key}={value}"])
|
|
151
|
+
|
|
152
|
+
# Add input file
|
|
153
|
+
cmd.append(scad_file)
|
|
154
|
+
|
|
155
|
+
# Run OpenSCAD
|
|
156
|
+
try:
|
|
157
|
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
158
|
+
logger.info(f"Generated STL file: {stl_file}")
|
|
159
|
+
logger.debug(result.stdout)
|
|
160
|
+
return stl_file
|
|
161
|
+
except subprocess.CalledProcessError as e:
|
|
162
|
+
logger.error(f"Error generating STL file: {e.stderr}")
|
|
163
|
+
raise RuntimeError(f"Failed to generate STL file: {e.stderr}")
|
|
164
|
+
|
|
165
|
+
def generate_preview(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None,
|
|
166
|
+
camera_position: str = "0,0,0,0,0,0,50",
|
|
167
|
+
image_size: str = "800,600") -> str:
|
|
168
|
+
"""
|
|
169
|
+
Generate a preview image from a SCAD file.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
scad_file: Path to the SCAD file
|
|
173
|
+
parameters: Optional parameters to override in the SCAD file
|
|
174
|
+
camera_position: Camera position in format "tx,ty,tz,rx,ry,rz,dist"
|
|
175
|
+
image_size: Image size in format "width,height"
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
Path to the generated preview image
|
|
179
|
+
"""
|
|
180
|
+
model_id = os.path.basename(scad_file).split('.')[0]
|
|
181
|
+
preview_file = os.path.join(self.preview_dir, f"{model_id}.png")
|
|
182
|
+
|
|
183
|
+
# Build command
|
|
184
|
+
cmd = ["openscad", "--camera", camera_position, "--imgsize", image_size, "-o", preview_file]
|
|
185
|
+
|
|
186
|
+
# Add parameters if provided
|
|
187
|
+
if parameters:
|
|
188
|
+
for key, value in parameters.items():
|
|
189
|
+
cmd.extend(["-D", f"{key}={value}"])
|
|
190
|
+
|
|
191
|
+
# Add input file
|
|
192
|
+
cmd.append(scad_file)
|
|
193
|
+
|
|
194
|
+
# Run OpenSCAD
|
|
195
|
+
try:
|
|
196
|
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
197
|
+
logger.info(f"Generated preview image: {preview_file}")
|
|
198
|
+
logger.debug(result.stdout)
|
|
199
|
+
return preview_file
|
|
200
|
+
except subprocess.CalledProcessError as e:
|
|
201
|
+
logger.error(f"Error generating preview image: {e.stderr}")
|
|
202
|
+
# Since we know there might be issues with headless rendering, we'll create a placeholder
|
|
203
|
+
logger.warning("Using placeholder image due to rendering error")
|
|
204
|
+
return self._create_placeholder_image(preview_file)
|
|
205
|
+
|
|
206
|
+
def _create_placeholder_image(self, output_path: str) -> str:
|
|
207
|
+
"""Create a simple placeholder image when rendering fails."""
|
|
208
|
+
try:
|
|
209
|
+
from PIL import Image, ImageDraw, ImageFont
|
|
210
|
+
|
|
211
|
+
# Create a blank image
|
|
212
|
+
img = Image.new('RGB', (800, 600), color=(240, 240, 240))
|
|
213
|
+
draw = ImageDraw.Draw(img)
|
|
214
|
+
|
|
215
|
+
# Add text
|
|
216
|
+
draw.text((400, 300), "Preview not available", fill=(0, 0, 0))
|
|
217
|
+
|
|
218
|
+
# Save the image
|
|
219
|
+
img.save(output_path)
|
|
220
|
+
return output_path
|
|
221
|
+
except Exception as e:
|
|
222
|
+
logger.error(f"Error creating placeholder image: {str(e)}")
|
|
223
|
+
# If all else fails, return the path anyway
|
|
224
|
+
return output_path
|
|
225
|
+
|
|
226
|
+
def generate_multi_angle_previews(self, scad_file: str, parameters: Optional[Dict[str, Any]] = None) -> Dict[str, str]:
|
|
227
|
+
"""
|
|
228
|
+
Generate preview images from multiple angles for a SCAD file.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
scad_file: Path to the SCAD file
|
|
232
|
+
parameters: Optional parameters to override in the SCAD file
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
Dictionary mapping view names to preview image paths
|
|
236
|
+
"""
|
|
237
|
+
# Define camera positions for different views
|
|
238
|
+
camera_positions = {
|
|
239
|
+
"front": "0,0,0,0,0,0,50",
|
|
240
|
+
"top": "0,0,0,90,0,0,50",
|
|
241
|
+
"right": "0,0,0,0,90,0,50",
|
|
242
|
+
"perspective": "40,30,30,55,0,25,100"
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# Generate preview for each view
|
|
246
|
+
previews = {}
|
|
247
|
+
for view, camera_position in camera_positions.items():
|
|
248
|
+
try:
|
|
249
|
+
model_id = os.path.basename(scad_file).split('.')[0]
|
|
250
|
+
preview_file = os.path.join(self.preview_dir, f"{model_id}_{view}.png")
|
|
251
|
+
|
|
252
|
+
# Build command
|
|
253
|
+
cmd = ["openscad", "--camera", camera_position, "--imgsize", "800,600", "-o", preview_file]
|
|
254
|
+
|
|
255
|
+
# Add parameters if provided
|
|
256
|
+
if parameters:
|
|
257
|
+
for key, value in parameters.items():
|
|
258
|
+
cmd.extend(["-D", f"{key}={value}"])
|
|
259
|
+
|
|
260
|
+
# Add input file
|
|
261
|
+
cmd.append(scad_file)
|
|
262
|
+
|
|
263
|
+
# Run OpenSCAD
|
|
264
|
+
result = subprocess.run(cmd, check=True, capture_output=True, text=True)
|
|
265
|
+
logger.info(f"Generated {view} preview: {preview_file}")
|
|
266
|
+
previews[view] = preview_file
|
|
267
|
+
except subprocess.CalledProcessError as e:
|
|
268
|
+
logger.error(f"Error generating {view} preview: {e.stderr}")
|
|
269
|
+
# Create a placeholder image for this view
|
|
270
|
+
preview_file = os.path.join(self.preview_dir, f"{model_id}_{view}.png")
|
|
271
|
+
previews[view] = self._create_placeholder_image(preview_file)
|
|
272
|
+
|
|
273
|
+
return previews
|
|
274
|
+
|
|
275
|
+
# Template functions for basic shapes
|
|
276
|
+
|
|
277
|
+
def _cube_template(self, params: Dict[str, Any]) -> str:
|
|
278
|
+
"""Generate SCAD code for a cube."""
|
|
279
|
+
size_x = params.get('width', 10)
|
|
280
|
+
size_y = params.get('depth', 10)
|
|
281
|
+
size_z = params.get('height', 10)
|
|
282
|
+
center = params.get('center', 'false').lower() == 'true'
|
|
283
|
+
|
|
284
|
+
return f"""// Cube
|
|
285
|
+
// Parameters:
|
|
286
|
+
// width = {size_x}
|
|
287
|
+
// depth = {size_y}
|
|
288
|
+
// height = {size_z}
|
|
289
|
+
// center = {str(center).lower()}
|
|
290
|
+
|
|
291
|
+
width = {size_x};
|
|
292
|
+
depth = {size_y};
|
|
293
|
+
height = {size_z};
|
|
294
|
+
center = {str(center).lower()};
|
|
295
|
+
|
|
296
|
+
cube([width, depth, height], center=center);
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
def _sphere_template(self, params: Dict[str, Any]) -> str:
|
|
300
|
+
"""Generate SCAD code for a sphere."""
|
|
301
|
+
radius = params.get('radius', 10)
|
|
302
|
+
segments = params.get('segments', 32)
|
|
303
|
+
|
|
304
|
+
return f"""// Sphere
|
|
305
|
+
// Parameters:
|
|
306
|
+
// radius = {radius}
|
|
307
|
+
// segments = {segments}
|
|
308
|
+
|
|
309
|
+
radius = {radius};
|
|
310
|
+
$fn = {segments};
|
|
311
|
+
|
|
312
|
+
sphere(r=radius);
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
def _cylinder_template(self, params: Dict[str, Any]) -> str:
|
|
316
|
+
"""Generate SCAD code for a cylinder."""
|
|
317
|
+
radius = params.get('radius', 10)
|
|
318
|
+
height = params.get('height', 20)
|
|
319
|
+
center = params.get('center', 'false').lower() == 'true'
|
|
320
|
+
segments = params.get('segments', 32)
|
|
321
|
+
|
|
322
|
+
return f"""// Cylinder
|
|
323
|
+
// Parameters:
|
|
324
|
+
// radius = {radius}
|
|
325
|
+
// height = {height}
|
|
326
|
+
// center = {str(center).lower()}
|
|
327
|
+
// segments = {segments}
|
|
328
|
+
|
|
329
|
+
radius = {radius};
|
|
330
|
+
height = {height};
|
|
331
|
+
center = {str(center).lower()};
|
|
332
|
+
$fn = {segments};
|
|
333
|
+
|
|
334
|
+
cylinder(h=height, r=radius, center=center);
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
def _box_template(self, params: Dict[str, Any]) -> str:
|
|
338
|
+
"""Generate SCAD code for a hollow box."""
|
|
339
|
+
width = params.get('width', 30)
|
|
340
|
+
depth = params.get('depth', 20)
|
|
341
|
+
height = params.get('height', 15)
|
|
342
|
+
thickness = params.get('thickness', 2)
|
|
343
|
+
|
|
344
|
+
return f"""// Hollow Box
|
|
345
|
+
// Parameters:
|
|
346
|
+
// width = {width}
|
|
347
|
+
// depth = {depth}
|
|
348
|
+
// height = {height}
|
|
349
|
+
// thickness = {thickness}
|
|
350
|
+
|
|
351
|
+
width = {width};
|
|
352
|
+
depth = {depth};
|
|
353
|
+
height = {height};
|
|
354
|
+
thickness = {thickness};
|
|
355
|
+
|
|
356
|
+
module box(width, depth, height, thickness) {{
|
|
357
|
+
difference() {{
|
|
358
|
+
cube([width, depth, height]);
|
|
359
|
+
translate([thickness, thickness, thickness])
|
|
360
|
+
cube([width - 2*thickness, depth - 2*thickness, height - thickness]);
|
|
361
|
+
}}
|
|
362
|
+
}}
|
|
363
|
+
|
|
364
|
+
box(width, depth, height, thickness);
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
def _rounded_box_template(self, params: Dict[str, Any]) -> str:
|
|
368
|
+
"""Generate SCAD code for a rounded box."""
|
|
369
|
+
width = params.get('width', 30)
|
|
370
|
+
depth = params.get('depth', 20)
|
|
371
|
+
height = params.get('height', 15)
|
|
372
|
+
radius = params.get('radius', 3)
|
|
373
|
+
segments = params.get('segments', 32)
|
|
374
|
+
|
|
375
|
+
return f"""// Rounded Box
|
|
376
|
+
// Parameters:
|
|
377
|
+
// width = {width}
|
|
378
|
+
// depth = {depth}
|
|
379
|
+
// height = {height}
|
|
380
|
+
// radius = {radius}
|
|
381
|
+
// segments = {segments}
|
|
382
|
+
|
|
383
|
+
width = {width};
|
|
384
|
+
depth = {depth};
|
|
385
|
+
height = {height};
|
|
386
|
+
radius = {radius};
|
|
387
|
+
$fn = {segments};
|
|
388
|
+
|
|
389
|
+
module rounded_box(width, depth, height, radius) {{
|
|
390
|
+
hull() {{
|
|
391
|
+
translate([radius, radius, radius])
|
|
392
|
+
sphere(r=radius);
|
|
393
|
+
|
|
394
|
+
translate([width-radius, radius, radius])
|
|
395
|
+
sphere(r=radius);
|
|
396
|
+
|
|
397
|
+
translate([radius, depth-radius, radius])
|
|
398
|
+
sphere(r=radius);
|
|
399
|
+
|
|
400
|
+
translate([width-radius, depth-radius, radius])
|
|
401
|
+
sphere(r=radius);
|
|
402
|
+
|
|
403
|
+
translate([radius, radius, height-radius])
|
|
404
|
+
sphere(r=radius);
|
|
405
|
+
|
|
406
|
+
translate([width-radius, radius, height-radius])
|
|
407
|
+
sphere(r=radius);
|
|
408
|
+
|
|
409
|
+
translate([radius, depth-radius, height-radius])
|
|
410
|
+
sphere(r=radius);
|
|
411
|
+
|
|
412
|
+
translate([width-radius, depth-radius, height-radius])
|
|
413
|
+
sphere(r=radius);
|
|
414
|
+
}}
|
|
415
|
+
}}
|
|
416
|
+
|
|
417
|
+
rounded_box(width, depth, height, radius);
|
|
418
|
+
"""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Printer discovery package
|