@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,321 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import logging
|
|
3
|
+
import uuid
|
|
4
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
class CodeGenerator:
|
|
9
|
+
"""
|
|
10
|
+
Generates OpenSCAD code from natural language descriptions and parameters.
|
|
11
|
+
Implements translation of requirements to OpenSCAD primitives and modules.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self, scad_templates_dir: str, output_dir: str, ai_service=None):
|
|
15
|
+
"""
|
|
16
|
+
Initialize the code generator.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
scad_templates_dir: Directory containing SCAD template files
|
|
20
|
+
output_dir: Directory to store generated SCAD files
|
|
21
|
+
ai_service: Optional AI service for enhanced code generation
|
|
22
|
+
"""
|
|
23
|
+
self.scad_templates_dir = scad_templates_dir
|
|
24
|
+
self.output_dir = output_dir
|
|
25
|
+
self.ai_service = ai_service
|
|
26
|
+
|
|
27
|
+
# Create output directory if it doesn't exist
|
|
28
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
29
|
+
|
|
30
|
+
# Map of shape types to their corresponding module names
|
|
31
|
+
self.shape_module_map = {
|
|
32
|
+
'cube': 'parametric_cube',
|
|
33
|
+
'sphere': 'parametric_sphere',
|
|
34
|
+
'cylinder': 'parametric_cylinder',
|
|
35
|
+
'box': 'hollow_box',
|
|
36
|
+
'rounded_box': 'rounded_box',
|
|
37
|
+
'container': 'rounded_container',
|
|
38
|
+
'tube': 'tube',
|
|
39
|
+
'cone': 'cone',
|
|
40
|
+
'wedge': 'wedge',
|
|
41
|
+
'rounded_cylinder': 'rounded_cylinder',
|
|
42
|
+
'torus': 'torus',
|
|
43
|
+
'hexagonal_prism': 'hexagonal_prism',
|
|
44
|
+
'text': 'text_3d',
|
|
45
|
+
'prism': 'triangular_prism',
|
|
46
|
+
'custom': 'custom_shape'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Parameter mapping from natural language to OpenSCAD parameters
|
|
50
|
+
self.parameter_map = {
|
|
51
|
+
'width': 'width',
|
|
52
|
+
'depth': 'depth',
|
|
53
|
+
'height': 'height',
|
|
54
|
+
'radius': 'radius',
|
|
55
|
+
'thickness': 'thickness',
|
|
56
|
+
'segments': 'segments',
|
|
57
|
+
'center': 'center',
|
|
58
|
+
'inner_radius': 'inner_radius',
|
|
59
|
+
'outer_radius': 'outer_radius',
|
|
60
|
+
'corner_radius': 'corner_radius',
|
|
61
|
+
'text': 'text',
|
|
62
|
+
'size': 'size',
|
|
63
|
+
'font': 'font',
|
|
64
|
+
'base_radius': 'base_radius',
|
|
65
|
+
'major_radius': 'major_radius',
|
|
66
|
+
'minor_radius': 'minor_radius',
|
|
67
|
+
'angle': 'angle',
|
|
68
|
+
'scale': 'scale',
|
|
69
|
+
'resolution': 'resolution'
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
def generate_code(self, model_type: str, parameters: Dict[str, Any], description: Optional[str] = None) -> str:
|
|
73
|
+
"""
|
|
74
|
+
Generate OpenSCAD code for a given model type and parameters.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
model_type: Type of model to generate
|
|
78
|
+
parameters: Dictionary of parameters for the model
|
|
79
|
+
description: Optional natural language description for AI-driven generation
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Path to the generated SCAD file
|
|
83
|
+
"""
|
|
84
|
+
# Generate a unique ID for the model
|
|
85
|
+
model_id = str(uuid.uuid4())
|
|
86
|
+
scad_file = os.path.join(self.output_dir, f"{model_id}.scad")
|
|
87
|
+
|
|
88
|
+
# Check if we should use AI-driven generation for complex models
|
|
89
|
+
if model_type == 'custom' and description and self.ai_service:
|
|
90
|
+
scad_code = self._generate_ai_driven_code(description, parameters)
|
|
91
|
+
else:
|
|
92
|
+
# Get the module name for the model type
|
|
93
|
+
module_name = self.shape_module_map.get(model_type)
|
|
94
|
+
if not module_name:
|
|
95
|
+
raise ValueError(f"Unsupported model type: {model_type}")
|
|
96
|
+
|
|
97
|
+
# Map parameters to OpenSCAD parameter names
|
|
98
|
+
scad_params = self._map_parameters(parameters)
|
|
99
|
+
|
|
100
|
+
# Generate the OpenSCAD code
|
|
101
|
+
scad_code = self._generate_scad_code(module_name, scad_params)
|
|
102
|
+
|
|
103
|
+
# Write the code to a file
|
|
104
|
+
with open(scad_file, 'w') as f:
|
|
105
|
+
f.write(scad_code)
|
|
106
|
+
|
|
107
|
+
logger.info(f"Generated OpenSCAD code: {scad_file}")
|
|
108
|
+
return scad_file
|
|
109
|
+
|
|
110
|
+
def update_code(self, scad_file: str, parameters: Dict[str, Any]) -> str:
|
|
111
|
+
"""
|
|
112
|
+
Update an existing SCAD file with new parameters.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
scad_file: Path to the SCAD file to update
|
|
116
|
+
parameters: New parameters to apply
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Path to the updated SCAD file
|
|
120
|
+
"""
|
|
121
|
+
if not os.path.exists(scad_file):
|
|
122
|
+
raise FileNotFoundError(f"SCAD file not found: {scad_file}")
|
|
123
|
+
|
|
124
|
+
# Read the existing SCAD file
|
|
125
|
+
with open(scad_file, 'r') as f:
|
|
126
|
+
scad_code = f.read()
|
|
127
|
+
|
|
128
|
+
# Determine the module name from the code
|
|
129
|
+
module_name = None
|
|
130
|
+
for shape_type, module in self.shape_module_map.items():
|
|
131
|
+
if module in scad_code:
|
|
132
|
+
module_name = module
|
|
133
|
+
break
|
|
134
|
+
|
|
135
|
+
if not module_name:
|
|
136
|
+
raise ValueError("Could not determine module name from existing SCAD file")
|
|
137
|
+
|
|
138
|
+
# Map parameters to OpenSCAD parameter names
|
|
139
|
+
scad_params = self._map_parameters(parameters)
|
|
140
|
+
|
|
141
|
+
# Generate the updated OpenSCAD code
|
|
142
|
+
updated_code = self._generate_scad_code(module_name, scad_params)
|
|
143
|
+
|
|
144
|
+
# Write the updated code to the file
|
|
145
|
+
with open(scad_file, 'w') as f:
|
|
146
|
+
f.write(updated_code)
|
|
147
|
+
|
|
148
|
+
logger.info(f"Updated OpenSCAD code: {scad_file}")
|
|
149
|
+
return scad_file
|
|
150
|
+
|
|
151
|
+
def combine_models(self, operations: List[Dict[str, Any]]) -> str:
|
|
152
|
+
"""
|
|
153
|
+
Combine multiple models using CSG operations.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
operations: List of operations, each containing:
|
|
157
|
+
- model_type: Type of model
|
|
158
|
+
- parameters: Parameters for the model
|
|
159
|
+
- operation: CSG operation (union, difference, intersection)
|
|
160
|
+
- transform: Optional transformation to apply
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Path to the generated SCAD file
|
|
164
|
+
"""
|
|
165
|
+
# Generate a unique ID for the combined model
|
|
166
|
+
model_id = str(uuid.uuid4())
|
|
167
|
+
scad_file = os.path.join(self.output_dir, f"{model_id}.scad")
|
|
168
|
+
|
|
169
|
+
# Include the basic shapes library
|
|
170
|
+
scad_code = f"""// Combined model
|
|
171
|
+
include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
# Process each operation
|
|
176
|
+
current_op = None
|
|
177
|
+
for i, op in enumerate(operations):
|
|
178
|
+
model_type = op.get('model_type')
|
|
179
|
+
parameters = op.get('parameters', {})
|
|
180
|
+
operation = op.get('operation')
|
|
181
|
+
transform = op.get('transform')
|
|
182
|
+
|
|
183
|
+
# Get the module name for the model type
|
|
184
|
+
if model_type is None:
|
|
185
|
+
raise ValueError("Model type cannot be None")
|
|
186
|
+
module_name = self.shape_module_map.get(str(model_type))
|
|
187
|
+
if not module_name:
|
|
188
|
+
raise ValueError(f"Unsupported model type: {model_type}")
|
|
189
|
+
|
|
190
|
+
# Map parameters to OpenSCAD parameter names
|
|
191
|
+
scad_params = self._map_parameters(parameters)
|
|
192
|
+
|
|
193
|
+
# Format parameters for the module call
|
|
194
|
+
params_str = ", ".join([f"{k}={v}" for k, v in scad_params.items()])
|
|
195
|
+
|
|
196
|
+
# Start or continue the CSG operation chain
|
|
197
|
+
if i == 0:
|
|
198
|
+
# First operation doesn't need an operator
|
|
199
|
+
if operation:
|
|
200
|
+
current_op = operation
|
|
201
|
+
scad_code += f"{operation}() {{\n"
|
|
202
|
+
|
|
203
|
+
# Add the module call with optional transformation
|
|
204
|
+
if transform:
|
|
205
|
+
scad_code += f" {transform} {module_name}({params_str});\n"
|
|
206
|
+
else:
|
|
207
|
+
scad_code += f" {module_name}({params_str});\n"
|
|
208
|
+
else:
|
|
209
|
+
# Check if we need to close the previous operation and start a new one
|
|
210
|
+
if operation and operation != current_op:
|
|
211
|
+
if current_op:
|
|
212
|
+
scad_code += "}\n\n"
|
|
213
|
+
current_op = operation
|
|
214
|
+
scad_code += f"{operation}() {{\n"
|
|
215
|
+
|
|
216
|
+
# Add the module call with optional transformation
|
|
217
|
+
if transform:
|
|
218
|
+
scad_code += f" {transform} {module_name}({params_str});\n"
|
|
219
|
+
else:
|
|
220
|
+
scad_code += f" {module_name}({params_str});\n"
|
|
221
|
+
|
|
222
|
+
# Close the final operation if needed
|
|
223
|
+
if current_op:
|
|
224
|
+
scad_code += "}\n"
|
|
225
|
+
|
|
226
|
+
# Write the code to a file
|
|
227
|
+
with open(scad_file, 'w') as f:
|
|
228
|
+
f.write(scad_code)
|
|
229
|
+
|
|
230
|
+
logger.info(f"Generated combined OpenSCAD code: {scad_file}")
|
|
231
|
+
return scad_file
|
|
232
|
+
|
|
233
|
+
def _map_parameters(self, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
|
234
|
+
"""Map natural language parameters to OpenSCAD parameters."""
|
|
235
|
+
scad_params = {}
|
|
236
|
+
|
|
237
|
+
for param, value in parameters.items():
|
|
238
|
+
# Map the parameter name if it exists in the mapping
|
|
239
|
+
scad_param = self.parameter_map.get(param, param)
|
|
240
|
+
|
|
241
|
+
# Format the value appropriately for OpenSCAD
|
|
242
|
+
if isinstance(value, bool):
|
|
243
|
+
scad_params[scad_param] = str(value).lower()
|
|
244
|
+
elif isinstance(value, str):
|
|
245
|
+
if value.lower() == 'true' or value.lower() == 'false':
|
|
246
|
+
scad_params[scad_param] = value.lower()
|
|
247
|
+
else:
|
|
248
|
+
# For text parameters, add quotes
|
|
249
|
+
scad_params[scad_param] = f'"{value}"'
|
|
250
|
+
else:
|
|
251
|
+
scad_params[scad_param] = value
|
|
252
|
+
|
|
253
|
+
return scad_params
|
|
254
|
+
|
|
255
|
+
def _generate_scad_code(self, module_name: str, parameters: Dict[str, Any]) -> str:
|
|
256
|
+
"""Generate OpenSCAD code for a module with parameters."""
|
|
257
|
+
# Include the basic shapes library
|
|
258
|
+
scad_code = f"""// Generated OpenSCAD code
|
|
259
|
+
include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
|
|
260
|
+
|
|
261
|
+
// Parameters
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
# Add parameter declarations
|
|
265
|
+
for param, value in parameters.items():
|
|
266
|
+
scad_code += f"{param} = {value};\n"
|
|
267
|
+
|
|
268
|
+
# Add the module call
|
|
269
|
+
scad_code += f"\n// Model\n{module_name}("
|
|
270
|
+
|
|
271
|
+
# Add parameters to the module call
|
|
272
|
+
param_list = [f"{param}={param}" for param in parameters.keys()]
|
|
273
|
+
scad_code += ", ".join(param_list)
|
|
274
|
+
|
|
275
|
+
scad_code += ");\n"
|
|
276
|
+
|
|
277
|
+
return scad_code
|
|
278
|
+
|
|
279
|
+
def _generate_ai_driven_code(self, description: str, parameters: Dict[str, Any]) -> str:
|
|
280
|
+
"""
|
|
281
|
+
Generate OpenSCAD code using AI-driven techniques based on natural language description.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
description: Natural language description of the model
|
|
285
|
+
parameters: Dictionary of parameters for the model
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Generated OpenSCAD code
|
|
289
|
+
"""
|
|
290
|
+
if not self.ai_service:
|
|
291
|
+
logger.warning("AI service not available, falling back to basic shape generation")
|
|
292
|
+
# Fall back to a basic cube if AI service is not available
|
|
293
|
+
return self._generate_scad_code('parametric_cube', {'width': 10, 'height': 10, 'depth': 10})
|
|
294
|
+
|
|
295
|
+
try:
|
|
296
|
+
# Use the AI service to generate OpenSCAD code
|
|
297
|
+
logger.info(f"Generating OpenSCAD code from description: {description}")
|
|
298
|
+
|
|
299
|
+
# Prepare context for the AI service
|
|
300
|
+
context = {
|
|
301
|
+
"description": description,
|
|
302
|
+
"parameters": parameters,
|
|
303
|
+
"templates_dir": self.scad_templates_dir
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Call the AI service to generate code
|
|
307
|
+
scad_code = self.ai_service.generate_openscad_code(context)
|
|
308
|
+
|
|
309
|
+
# Ensure the code includes the basic shapes library
|
|
310
|
+
if "include <" not in scad_code:
|
|
311
|
+
scad_code = f"""// AI-generated OpenSCAD code
|
|
312
|
+
include <{os.path.join(self.scad_templates_dir, "basic_shapes.scad")}>;
|
|
313
|
+
|
|
314
|
+
{scad_code}
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
return scad_code
|
|
318
|
+
except Exception as e:
|
|
319
|
+
logger.error(f"Error generating AI-driven code: {e}")
|
|
320
|
+
# Fall back to a basic shape if there's an error
|
|
321
|
+
return self._generate_scad_code('parametric_cube', {'width': 10, 'height': 10, 'depth': 10})
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CUDA Multi-View Stereo wrapper for 3D reconstruction from multiple images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import logging
|
|
8
|
+
import json
|
|
9
|
+
from typing import Dict, Any, List, Optional
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class CUDAMultiViewStereo:
|
|
15
|
+
"""
|
|
16
|
+
Wrapper for CUDA Multi-View Stereo for 3D reconstruction from multiple images.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, cuda_mvs_path: str, output_dir: str = "output/models"):
|
|
20
|
+
"""
|
|
21
|
+
Initialize the CUDA MVS wrapper.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
cuda_mvs_path: Path to CUDA MVS installation
|
|
25
|
+
output_dir: Directory to store output files
|
|
26
|
+
"""
|
|
27
|
+
self.cuda_mvs_path = cuda_mvs_path
|
|
28
|
+
self.output_dir = output_dir
|
|
29
|
+
|
|
30
|
+
# Create output directory if it doesn't exist
|
|
31
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
# Validate installation
|
|
34
|
+
self._validate_installation()
|
|
35
|
+
|
|
36
|
+
def _validate_installation(self) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Validate CUDA MVS installation.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
FileNotFoundError: If CUDA MVS installation is not found
|
|
42
|
+
"""
|
|
43
|
+
if not os.path.exists(self.cuda_mvs_path):
|
|
44
|
+
raise FileNotFoundError(f"CUDA MVS not found at {self.cuda_mvs_path}")
|
|
45
|
+
|
|
46
|
+
# Check for required executables
|
|
47
|
+
required_files = ["app_patch_match_mvs"]
|
|
48
|
+
for file in required_files:
|
|
49
|
+
exec_path = os.path.join(self.cuda_mvs_path, "build", file)
|
|
50
|
+
if not os.path.exists(exec_path):
|
|
51
|
+
raise FileNotFoundError(f"Required executable {file} not found at {exec_path}")
|
|
52
|
+
|
|
53
|
+
def generate_model_from_images(self, image_paths: List[str],
|
|
54
|
+
camera_params: Optional[Dict[str, Any]] = None,
|
|
55
|
+
output_name: str = "model") -> Dict[str, Any]:
|
|
56
|
+
"""
|
|
57
|
+
Generate a 3D model from multiple images using CUDA MVS.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
image_paths: List of paths to input images
|
|
61
|
+
camera_params: Optional camera parameters
|
|
62
|
+
output_name: Name for the output files
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Dictionary containing paths to generated model files
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
# Create a unique directory for this reconstruction
|
|
69
|
+
model_dir = os.path.join(self.output_dir, output_name)
|
|
70
|
+
os.makedirs(model_dir, exist_ok=True)
|
|
71
|
+
|
|
72
|
+
# Create a camera parameters file if provided
|
|
73
|
+
params_file = None
|
|
74
|
+
if camera_params:
|
|
75
|
+
params_file = os.path.join(model_dir, "camera_params.json")
|
|
76
|
+
with open(params_file, 'w') as f:
|
|
77
|
+
json.dump(camera_params, f, indent=2)
|
|
78
|
+
|
|
79
|
+
# Generate camera parameters if not provided
|
|
80
|
+
if not params_file:
|
|
81
|
+
params_file = self._generate_camera_params(image_paths, model_dir)
|
|
82
|
+
|
|
83
|
+
# Generate point cloud
|
|
84
|
+
point_cloud_file = os.path.join(model_dir, f"{output_name}.ply")
|
|
85
|
+
|
|
86
|
+
# Run CUDA MVS
|
|
87
|
+
cmd = [
|
|
88
|
+
os.path.join(self.cuda_mvs_path, "build", "app_patch_match_mvs"),
|
|
89
|
+
"--image_dir", os.path.dirname(image_paths[0]),
|
|
90
|
+
"--camera_params", params_file,
|
|
91
|
+
"--output_file", point_cloud_file
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
logger.info(f"Running CUDA MVS with command: {' '.join(cmd)}")
|
|
95
|
+
|
|
96
|
+
process = subprocess.Popen(
|
|
97
|
+
cmd,
|
|
98
|
+
stdout=subprocess.PIPE,
|
|
99
|
+
stderr=subprocess.PIPE,
|
|
100
|
+
text=True
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Wait for process to complete
|
|
104
|
+
stdout, stderr = process.communicate()
|
|
105
|
+
|
|
106
|
+
if process.returncode != 0:
|
|
107
|
+
logger.error(f"Error running CUDA MVS: {stderr}")
|
|
108
|
+
raise RuntimeError(f"CUDA MVS failed with exit code {process.returncode}")
|
|
109
|
+
|
|
110
|
+
# Check if output file was created
|
|
111
|
+
if not os.path.exists(point_cloud_file):
|
|
112
|
+
raise FileNotFoundError(f"Output point cloud file not found at {point_cloud_file}")
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"model_id": output_name,
|
|
116
|
+
"output_dir": model_dir,
|
|
117
|
+
"point_cloud_file": point_cloud_file,
|
|
118
|
+
"camera_params_file": params_file,
|
|
119
|
+
"input_images": image_paths
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.error(f"Error generating 3D model with CUDA MVS: {str(e)}")
|
|
124
|
+
raise
|
|
125
|
+
|
|
126
|
+
def _generate_camera_params(self, image_paths: List[str], model_dir: str) -> str:
|
|
127
|
+
"""
|
|
128
|
+
Generate camera parameters from images.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
image_paths: List of paths to input images
|
|
132
|
+
model_dir: Directory to save parameter file
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
Path to camera parameters file
|
|
136
|
+
"""
|
|
137
|
+
# This is a simplified version for demonstration
|
|
138
|
+
# In a real implementation, this would use SfM or camera estimation
|
|
139
|
+
|
|
140
|
+
params = []
|
|
141
|
+
for i, img_path in enumerate(image_paths):
|
|
142
|
+
# Extract image dimensions
|
|
143
|
+
from PIL import Image
|
|
144
|
+
with Image.open(img_path) as img:
|
|
145
|
+
width, height = img.size
|
|
146
|
+
|
|
147
|
+
# Generate simple camera parameters
|
|
148
|
+
# In reality, these would be estimated from the images
|
|
149
|
+
# or provided by the user
|
|
150
|
+
params.append({
|
|
151
|
+
"image_id": i,
|
|
152
|
+
"image_name": os.path.basename(img_path),
|
|
153
|
+
"width": width,
|
|
154
|
+
"height": height,
|
|
155
|
+
"camera": {
|
|
156
|
+
"model": "PINHOLE",
|
|
157
|
+
"focal_length": min(width, height),
|
|
158
|
+
"principal_point": [width / 2, height / 2],
|
|
159
|
+
"rotation": [1, 0, 0, 0, 1, 0, 0, 0, 1],
|
|
160
|
+
"translation": [0, 0, 0]
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
# Write parameters to file
|
|
165
|
+
params_file = os.path.join(model_dir, "camera_params.json")
|
|
166
|
+
with open(params_file, 'w') as f:
|
|
167
|
+
json.dump(params, f, indent=2)
|
|
168
|
+
|
|
169
|
+
return params_file
|
|
170
|
+
|
|
171
|
+
def convert_ply_to_obj(self, ply_file: str, output_dir: Optional[str] = None) -> str:
|
|
172
|
+
"""
|
|
173
|
+
Convert PLY point cloud to OBJ mesh.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
ply_file: Path to input PLY file
|
|
177
|
+
output_dir: Directory to save output OBJ file
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Path to output OBJ file
|
|
181
|
+
"""
|
|
182
|
+
# In a real implementation, this would use a mesh reconstruction library
|
|
183
|
+
# such as Open3D or PyMeshLab to convert the point cloud to a mesh
|
|
184
|
+
|
|
185
|
+
if not output_dir:
|
|
186
|
+
output_dir = os.path.dirname(ply_file)
|
|
187
|
+
|
|
188
|
+
# Generate output file path
|
|
189
|
+
obj_file = os.path.join(output_dir, f"{Path(ply_file).stem}.obj")
|
|
190
|
+
|
|
191
|
+
logger.info(f"Converting PLY to OBJ: {ply_file} -> {obj_file}")
|
|
192
|
+
|
|
193
|
+
# This is a placeholder for the actual conversion
|
|
194
|
+
# In a real implementation, you would use a library like Open3D:
|
|
195
|
+
# import open3d as o3d
|
|
196
|
+
# pcd = o3d.io.read_point_cloud(ply_file)
|
|
197
|
+
# mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd)[0]
|
|
198
|
+
# o3d.io.write_triangle_mesh(obj_file, mesh)
|
|
199
|
+
|
|
200
|
+
# For now, we'll just create a dummy OBJ file
|
|
201
|
+
with open(obj_file, 'w') as f:
|
|
202
|
+
f.write(f"# Converted from {os.path.basename(ply_file)}\n")
|
|
203
|
+
f.write("# This is a placeholder OBJ file\n")
|
|
204
|
+
f.write("v 0 0 0\n")
|
|
205
|
+
f.write("v 1 0 0\n")
|
|
206
|
+
f.write("v 0 1 0\n")
|
|
207
|
+
f.write("f 1 2 3\n")
|
|
208
|
+
|
|
209
|
+
return obj_file
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// Basic Shapes Library for OpenSCAD MCP Server
|
|
2
|
+
// Contains reusable modules for common geometric patterns
|
|
3
|
+
|
|
4
|
+
// Basic cube with optional center parameter
|
|
5
|
+
module parametric_cube(width=10, depth=10, height=10, center=false) {
|
|
6
|
+
cube([width, depth, height], center=center);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Basic sphere with customizable segments
|
|
10
|
+
module parametric_sphere(radius=10, segments=32) {
|
|
11
|
+
$fn = segments;
|
|
12
|
+
sphere(r=radius);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Basic cylinder with customizable segments
|
|
16
|
+
module parametric_cylinder(radius=10, height=20, center=false, segments=32) {
|
|
17
|
+
$fn = segments;
|
|
18
|
+
cylinder(h=height, r=radius, center=center);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Hollow box with customizable wall thickness
|
|
22
|
+
module hollow_box(width=30, depth=20, height=15, thickness=2) {
|
|
23
|
+
difference() {
|
|
24
|
+
cube([width, depth, height]);
|
|
25
|
+
translate([thickness, thickness, thickness])
|
|
26
|
+
cube([width - 2*thickness, depth - 2*thickness, height - thickness]);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Rounded box with customizable corner radius
|
|
31
|
+
module rounded_box(width=30, depth=20, height=15, radius=3, segments=32) {
|
|
32
|
+
$fn = segments;
|
|
33
|
+
hull() {
|
|
34
|
+
translate([radius, radius, radius])
|
|
35
|
+
sphere(r=radius);
|
|
36
|
+
|
|
37
|
+
translate([width-radius, radius, radius])
|
|
38
|
+
sphere(r=radius);
|
|
39
|
+
|
|
40
|
+
translate([radius, depth-radius, radius])
|
|
41
|
+
sphere(r=radius);
|
|
42
|
+
|
|
43
|
+
translate([width-radius, depth-radius, radius])
|
|
44
|
+
sphere(r=radius);
|
|
45
|
+
|
|
46
|
+
translate([radius, radius, height-radius])
|
|
47
|
+
sphere(r=radius);
|
|
48
|
+
|
|
49
|
+
translate([width-radius, radius, height-radius])
|
|
50
|
+
sphere(r=radius);
|
|
51
|
+
|
|
52
|
+
translate([radius, depth-radius, height-radius])
|
|
53
|
+
sphere(r=radius);
|
|
54
|
+
|
|
55
|
+
translate([width-radius, depth-radius, height-radius])
|
|
56
|
+
sphere(r=radius);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Rounded hollow box (container with rounded corners)
|
|
61
|
+
module rounded_container(width=30, depth=20, height=15, radius=3, thickness=2, segments=32) {
|
|
62
|
+
$fn = segments;
|
|
63
|
+
difference() {
|
|
64
|
+
rounded_box(width, depth, height, radius, segments);
|
|
65
|
+
translate([thickness, thickness, thickness])
|
|
66
|
+
rounded_box(
|
|
67
|
+
width - 2*thickness,
|
|
68
|
+
depth - 2*thickness,
|
|
69
|
+
height - thickness + 0.01, // Slight overlap to ensure clean difference
|
|
70
|
+
radius - thickness > 0 ? radius - thickness : 0.1,
|
|
71
|
+
segments
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tube (hollow cylinder)
|
|
77
|
+
module tube(outer_radius=10, inner_radius=8, height=20, center=false, segments=32) {
|
|
78
|
+
$fn = segments;
|
|
79
|
+
difference() {
|
|
80
|
+
cylinder(h=height, r=outer_radius, center=center);
|
|
81
|
+
cylinder(h=height+0.01, r=inner_radius, center=center);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Cone
|
|
86
|
+
module cone(bottom_radius=10, top_radius=0, height=20, center=false, segments=32) {
|
|
87
|
+
$fn = segments;
|
|
88
|
+
cylinder(h=height, r1=bottom_radius, r2=top_radius, center=center);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Wedge (triangular prism)
|
|
92
|
+
module wedge(width=20, depth=20, height=10) {
|
|
93
|
+
polyhedron(
|
|
94
|
+
points=[
|
|
95
|
+
[0,0,0], [width,0,0], [width,depth,0], [0,depth,0],
|
|
96
|
+
[0,0,height], [width,0,0], [0,depth,0]
|
|
97
|
+
],
|
|
98
|
+
faces=[
|
|
99
|
+
[0,1,2,3], // bottom
|
|
100
|
+
[4,5,1,0], // front
|
|
101
|
+
[4,0,3,6], // left
|
|
102
|
+
[6,3,2,5], // back
|
|
103
|
+
[4,6,5], // top
|
|
104
|
+
]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Rounded cylinder (cylinder with rounded top and bottom)
|
|
109
|
+
module rounded_cylinder(radius=10, height=20, corner_radius=2, center=false, segments=32) {
|
|
110
|
+
$fn = segments;
|
|
111
|
+
hull() {
|
|
112
|
+
translate([0, 0, corner_radius])
|
|
113
|
+
cylinder(h=height - 2*corner_radius, r=radius, center=center);
|
|
114
|
+
|
|
115
|
+
translate([0, 0, corner_radius])
|
|
116
|
+
rotate_extrude()
|
|
117
|
+
translate([radius - corner_radius, 0, 0])
|
|
118
|
+
circle(r=corner_radius);
|
|
119
|
+
|
|
120
|
+
translate([0, 0, height - corner_radius])
|
|
121
|
+
rotate_extrude()
|
|
122
|
+
translate([radius - corner_radius, 0, 0])
|
|
123
|
+
circle(r=corner_radius);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Torus (donut shape)
|
|
128
|
+
module torus(outer_radius=20, inner_radius=5, segments=32) {
|
|
129
|
+
$fn = segments;
|
|
130
|
+
rotate_extrude()
|
|
131
|
+
translate([outer_radius - inner_radius, 0, 0])
|
|
132
|
+
circle(r=inner_radius);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Hexagonal prism
|
|
136
|
+
module hexagonal_prism(radius=10, height=20, center=false) {
|
|
137
|
+
cylinder(h=height, r=radius, $fn=6, center=center);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Text with customizable parameters
|
|
141
|
+
module text_3d(text="OpenSCAD", size=10, height=3, font="Liberation Sans", halign="center", valign="center") {
|
|
142
|
+
linear_extrude(height=height)
|
|
143
|
+
text(text=text, size=size, font=font, halign=halign, valign=valign);
|
|
144
|
+
}
|
|
File without changes
|