@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,388 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Dict, Any, Tuple, List, Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
class ParameterExtractor:
|
|
9
|
+
"""
|
|
10
|
+
Extract parameters from natural language descriptions.
|
|
11
|
+
Implements dialog flow for collecting specifications and translating them to OpenSCAD parameters.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
"""Initialize the parameter extractor."""
|
|
16
|
+
# Using only millimeters as per project requirements
|
|
17
|
+
self.unit_conversions = {
|
|
18
|
+
'mm': 1.0
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
# Shape recognition patterns with expanded vocabulary
|
|
22
|
+
self.shape_patterns = {
|
|
23
|
+
'cube': r'\b(cube|box|square|rectangular|block|cuboid|brick)\b',
|
|
24
|
+
'sphere': r'\b(sphere|ball|round|circular|globe|orb)\b',
|
|
25
|
+
'cylinder': r'\b(cylinder|tube|pipe|rod|circular column|pillar|column)\b',
|
|
26
|
+
'box': r'\b(hollow box|container|case|enclosure|bin|chest|tray)\b',
|
|
27
|
+
'rounded_box': r'\b(rounded box|rounded container|rounded case|rounded enclosure|smooth box|rounded corners|chamfered box)\b',
|
|
28
|
+
'cone': r'\b(cone|pyramid|tapered cylinder|funnel)\b',
|
|
29
|
+
'torus': r'\b(torus|donut|ring|loop|circular ring)\b',
|
|
30
|
+
'prism': r'\b(prism|triangular prism|wedge|triangular shape)\b',
|
|
31
|
+
'custom': r'\b(custom|complex|special|unique|combined|composite)\b'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Parameter recognition patterns with enhanced unit detection
|
|
35
|
+
self.parameter_patterns = {
|
|
36
|
+
'width': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:wide|width|across|w)',
|
|
37
|
+
'height': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:high|height|tall|h)',
|
|
38
|
+
'depth': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:deep|depth|long|d|length)',
|
|
39
|
+
'radius': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:radius|r)',
|
|
40
|
+
'diameter': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:diameter|dia)',
|
|
41
|
+
'thickness': r'(\d+(?:\.\d+)?)\s*(?:mm|cm|m|in|inch|inches|ft|foot|feet)?\s*(?:thick|thickness|t)',
|
|
42
|
+
'segments': r'(\d+)\s*(?:segments|sides|faces|facets|smoothness)',
|
|
43
|
+
'center': r'\b(centered|center|middle|origin)\b',
|
|
44
|
+
'angle': r'(\d+(?:\.\d+)?)\s*(?:deg|degree|degrees|°)?\s*(?:angle|rotation|rotate|tilt)',
|
|
45
|
+
'scale': r'(\d+(?:\.\d+)?)\s*(?:x|times|scale|scaling|factor)',
|
|
46
|
+
'resolution': r'(\d+(?:\.\d+)?)\s*(?:resolution|quality|detail)'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
# Dialog state for multi-turn conversations
|
|
50
|
+
self.dialog_state = {}
|
|
51
|
+
|
|
52
|
+
def extract_parameters(self, description: str, model_type: Optional[str] = None,
|
|
53
|
+
existing_parameters: Optional[Dict[str, Any]] = None) -> Tuple[str, Dict[str, Any]]:
|
|
54
|
+
"""
|
|
55
|
+
Extract model type and parameters from a natural language description.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
description: Natural language description of the 3D object
|
|
59
|
+
model_type: Optional model type for context (if already known)
|
|
60
|
+
existing_parameters: Optional existing parameters for context (for modifications)
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Tuple of (model_type, parameters)
|
|
64
|
+
"""
|
|
65
|
+
# Use provided model_type or determine from description
|
|
66
|
+
if model_type is None:
|
|
67
|
+
model_type = self._determine_shape_type(description)
|
|
68
|
+
|
|
69
|
+
# Start with existing parameters if provided
|
|
70
|
+
parameters = existing_parameters.copy() if existing_parameters else {}
|
|
71
|
+
|
|
72
|
+
# Extract parameters based on the shape type
|
|
73
|
+
new_parameters = self._extract_shape_parameters(description, model_type)
|
|
74
|
+
|
|
75
|
+
# Update parameters with newly extracted ones
|
|
76
|
+
parameters.update(new_parameters)
|
|
77
|
+
|
|
78
|
+
# Apply default parameters if needed
|
|
79
|
+
parameters = self._apply_default_parameters(model_type, parameters)
|
|
80
|
+
|
|
81
|
+
logger.info(f"Extracted model type: {model_type}, parameters: {parameters}")
|
|
82
|
+
return model_type, parameters
|
|
83
|
+
|
|
84
|
+
def extract_parameters_from_modifications(self, modifications: str, model_type: Optional[str] = None,
|
|
85
|
+
existing_parameters: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
86
|
+
"""
|
|
87
|
+
Extract parameters from modification description with contextual understanding.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
modifications: Description of modifications to make
|
|
91
|
+
model_type: Optional model type for context
|
|
92
|
+
existing_parameters: Optional existing parameters for context
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Dictionary of parameters to update
|
|
96
|
+
"""
|
|
97
|
+
# Start with existing parameters if provided
|
|
98
|
+
parameters = existing_parameters.copy() if existing_parameters else {}
|
|
99
|
+
|
|
100
|
+
# Extract all possible parameters from the modifications
|
|
101
|
+
new_parameters = {}
|
|
102
|
+
for param_name, pattern in self.parameter_patterns.items():
|
|
103
|
+
matches = re.findall(pattern, modifications, re.IGNORECASE)
|
|
104
|
+
if matches:
|
|
105
|
+
# Take the last match if multiple are found
|
|
106
|
+
value = matches[-1]
|
|
107
|
+
if isinstance(value, tuple):
|
|
108
|
+
value = value[0] # Extract from capture group
|
|
109
|
+
new_parameters[param_name] = self._convert_to_mm(value, modifications)
|
|
110
|
+
|
|
111
|
+
# Update parameters with newly extracted ones
|
|
112
|
+
parameters.update(new_parameters)
|
|
113
|
+
|
|
114
|
+
# Apply contextual understanding based on model type
|
|
115
|
+
if model_type and not new_parameters:
|
|
116
|
+
# If no explicit parameters were found, try to infer from context
|
|
117
|
+
# For now, we'll just log this case since inference is complex
|
|
118
|
+
logger.info(f"No explicit parameters found in '{modifications}', using existing parameters")
|
|
119
|
+
|
|
120
|
+
logger.info(f"Extracted modification parameters: {parameters}")
|
|
121
|
+
return parameters
|
|
122
|
+
|
|
123
|
+
def get_missing_parameters(self, model_type: str, parameters: Dict[str, Any]) -> List[str]:
|
|
124
|
+
"""
|
|
125
|
+
Determine which required parameters are missing for a given model type.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
model_type: Type of model
|
|
129
|
+
parameters: Currently extracted parameters
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
List of missing parameter names
|
|
133
|
+
"""
|
|
134
|
+
required_params = self._get_required_parameters(model_type)
|
|
135
|
+
return [param for param in required_params if param not in parameters]
|
|
136
|
+
|
|
137
|
+
def update_dialog_state(self, user_id: str, model_type: Optional[str] = None,
|
|
138
|
+
parameters: Optional[Dict[str, Any]] = None) -> None:
|
|
139
|
+
"""
|
|
140
|
+
Update the dialog state for a user.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
user_id: Unique identifier for the user
|
|
144
|
+
model_type: Optional model type to update
|
|
145
|
+
parameters: Optional parameters to update
|
|
146
|
+
"""
|
|
147
|
+
if user_id not in self.dialog_state:
|
|
148
|
+
self.dialog_state[user_id] = {
|
|
149
|
+
'model_type': None,
|
|
150
|
+
'parameters': {},
|
|
151
|
+
'missing_parameters': [],
|
|
152
|
+
'current_question': None
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if model_type:
|
|
156
|
+
self.dialog_state[user_id]['model_type'] = model_type
|
|
157
|
+
|
|
158
|
+
if parameters:
|
|
159
|
+
self.dialog_state[user_id]['parameters'].update(parameters)
|
|
160
|
+
|
|
161
|
+
# Update missing parameters
|
|
162
|
+
if self.dialog_state[user_id]['model_type']:
|
|
163
|
+
missing = self.get_missing_parameters(
|
|
164
|
+
self.dialog_state[user_id]['model_type'],
|
|
165
|
+
self.dialog_state[user_id]['parameters']
|
|
166
|
+
)
|
|
167
|
+
self.dialog_state[user_id]['missing_parameters'] = missing
|
|
168
|
+
|
|
169
|
+
def get_next_question(self, user_id: str) -> Optional[str]:
|
|
170
|
+
"""
|
|
171
|
+
Get the next question to ask the user based on missing parameters.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
user_id: Unique identifier for the user
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
Question string or None if all parameters are collected
|
|
178
|
+
"""
|
|
179
|
+
if user_id not in self.dialog_state:
|
|
180
|
+
return "What kind of 3D object would you like to create?"
|
|
181
|
+
|
|
182
|
+
state = self.dialog_state[user_id]
|
|
183
|
+
|
|
184
|
+
# If we don't have a model type yet, ask for it
|
|
185
|
+
if not state['model_type']:
|
|
186
|
+
state['current_question'] = "What kind of 3D object would you like to create?"
|
|
187
|
+
return state['current_question']
|
|
188
|
+
|
|
189
|
+
# If we have missing parameters, ask for the first one
|
|
190
|
+
if state['missing_parameters']:
|
|
191
|
+
param = state['missing_parameters'][0]
|
|
192
|
+
question = self._get_parameter_question(param, state['model_type'])
|
|
193
|
+
state['current_question'] = question
|
|
194
|
+
return question
|
|
195
|
+
|
|
196
|
+
# All parameters collected
|
|
197
|
+
state['current_question'] = None
|
|
198
|
+
return None
|
|
199
|
+
|
|
200
|
+
def process_answer(self, user_id: str, answer: str) -> Dict[str, Any]:
|
|
201
|
+
"""
|
|
202
|
+
Process a user's answer to a question.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
user_id: Unique identifier for the user
|
|
206
|
+
answer: User's answer to the current question
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
Updated dialog state
|
|
210
|
+
"""
|
|
211
|
+
if user_id not in self.dialog_state:
|
|
212
|
+
# Initialize with default state
|
|
213
|
+
self.update_dialog_state(user_id)
|
|
214
|
+
|
|
215
|
+
state = self.dialog_state[user_id]
|
|
216
|
+
current_question = state['current_question']
|
|
217
|
+
|
|
218
|
+
# Process based on current question
|
|
219
|
+
if not state['model_type']:
|
|
220
|
+
# Trying to determine the model type
|
|
221
|
+
model_type = self._determine_shape_type(answer)
|
|
222
|
+
self.update_dialog_state(user_id, model_type=model_type)
|
|
223
|
+
elif state['missing_parameters']:
|
|
224
|
+
# Trying to collect a specific parameter
|
|
225
|
+
param = state['missing_parameters'][0]
|
|
226
|
+
value = self._extract_parameter_value(param, answer)
|
|
227
|
+
if value is not None:
|
|
228
|
+
self.update_dialog_state(user_id, parameters={param: value})
|
|
229
|
+
|
|
230
|
+
# Return the updated state
|
|
231
|
+
return self.dialog_state[user_id]
|
|
232
|
+
|
|
233
|
+
def _determine_shape_type(self, description: str) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Determine the shape type from the description.
|
|
236
|
+
Enhanced to support more shape types and better pattern matching.
|
|
237
|
+
"""
|
|
238
|
+
# Check for explicit shape mentions
|
|
239
|
+
for shape, pattern in self.shape_patterns.items():
|
|
240
|
+
if re.search(pattern, description, re.IGNORECASE):
|
|
241
|
+
logger.info(f"Detected shape type: {shape} from pattern: {pattern}")
|
|
242
|
+
return shape
|
|
243
|
+
|
|
244
|
+
# Try to infer shape from context if no explicit mention
|
|
245
|
+
if re.search(r'\b(round|circular|sphere|ball)\b', description, re.IGNORECASE):
|
|
246
|
+
return "sphere"
|
|
247
|
+
elif re.search(r'\b(tall|column|pillar|rod)\b', description, re.IGNORECASE):
|
|
248
|
+
return "cylinder"
|
|
249
|
+
elif re.search(r'\b(box|container|case|enclosure)\b', description, re.IGNORECASE):
|
|
250
|
+
# Determine if it should be a rounded box
|
|
251
|
+
if re.search(r'\b(rounded|smooth|chamfered)\b', description, re.IGNORECASE):
|
|
252
|
+
return "rounded_box"
|
|
253
|
+
return "box"
|
|
254
|
+
|
|
255
|
+
# Default to cube if no shape is detected
|
|
256
|
+
logger.info("No specific shape detected, defaulting to cube")
|
|
257
|
+
return "cube"
|
|
258
|
+
|
|
259
|
+
def _extract_shape_parameters(self, description: str, model_type: str) -> Dict[str, Any]:
|
|
260
|
+
"""Extract parameters for a specific shape type."""
|
|
261
|
+
parameters = {}
|
|
262
|
+
|
|
263
|
+
# Extract all possible parameters
|
|
264
|
+
for param_name, pattern in self.parameter_patterns.items():
|
|
265
|
+
matches = re.findall(pattern, description, re.IGNORECASE)
|
|
266
|
+
if matches:
|
|
267
|
+
# Take the last match if multiple are found
|
|
268
|
+
value = matches[-1]
|
|
269
|
+
if isinstance(value, tuple):
|
|
270
|
+
value = value[0] # Extract from capture group
|
|
271
|
+
parameters[param_name] = self._convert_to_mm(value, description)
|
|
272
|
+
|
|
273
|
+
# Special case for diameter -> radius conversion
|
|
274
|
+
if 'diameter' in parameters and 'radius' not in parameters:
|
|
275
|
+
parameters['radius'] = parameters['diameter'] / 2
|
|
276
|
+
del parameters['diameter']
|
|
277
|
+
|
|
278
|
+
# Special case for center parameter
|
|
279
|
+
if 'center' in parameters:
|
|
280
|
+
center_value = parameters['center']
|
|
281
|
+
if isinstance(center_value, (int, float)):
|
|
282
|
+
# Convert numeric value to boolean string
|
|
283
|
+
parameters['center'] = 'true' if center_value > 0 else 'false'
|
|
284
|
+
else:
|
|
285
|
+
# Convert string value to boolean string
|
|
286
|
+
center_str = str(center_value).lower()
|
|
287
|
+
parameters['center'] = 'true' if center_str in ['true', 'yes', 'y', '1'] else 'false'
|
|
288
|
+
|
|
289
|
+
return parameters
|
|
290
|
+
|
|
291
|
+
def _convert_to_mm(self, value_str: str, context: str) -> float:
|
|
292
|
+
"""
|
|
293
|
+
Convert a value to millimeters.
|
|
294
|
+
As per project requirements, we only use millimeters for design.
|
|
295
|
+
"""
|
|
296
|
+
try:
|
|
297
|
+
value = float(value_str)
|
|
298
|
+
|
|
299
|
+
# Since we're only using millimeters, we just return the value directly
|
|
300
|
+
# This simplifies the conversion logic while maintaining the function interface
|
|
301
|
+
logger.info(f"Using value {value} in millimeters")
|
|
302
|
+
return value
|
|
303
|
+
except ValueError:
|
|
304
|
+
logger.warning(f"Could not convert value to float: {value_str}")
|
|
305
|
+
return 0.0
|
|
306
|
+
|
|
307
|
+
def _apply_default_parameters(self, model_type: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
|
308
|
+
"""Apply default parameters based on the model type."""
|
|
309
|
+
defaults = {
|
|
310
|
+
'cube': {'width': 10, 'depth': 10, 'height': 10, 'center': 'false'},
|
|
311
|
+
'sphere': {'radius': 10, 'segments': 32},
|
|
312
|
+
'cylinder': {'radius': 10, 'height': 20, 'center': 'false', 'segments': 32},
|
|
313
|
+
'box': {'width': 30, 'depth': 20, 'height': 15, 'thickness': 2},
|
|
314
|
+
'rounded_box': {'width': 30, 'depth': 20, 'height': 15, 'radius': 3, 'segments': 32},
|
|
315
|
+
'cone': {'base_radius': 10, 'height': 20, 'center': 'false', 'segments': 32},
|
|
316
|
+
'torus': {'major_radius': 20, 'minor_radius': 5, 'segments': 32},
|
|
317
|
+
'prism': {'width': 20, 'height': 15, 'depth': 20, 'center': 'false'},
|
|
318
|
+
'custom': {'width': 20, 'height': 20, 'depth': 20, 'center': 'false'}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
# Get defaults for the model type
|
|
322
|
+
model_defaults = defaults.get(model_type, {})
|
|
323
|
+
|
|
324
|
+
# Apply defaults for missing parameters
|
|
325
|
+
for param, default_value in model_defaults.items():
|
|
326
|
+
if param not in parameters:
|
|
327
|
+
parameters[param] = default_value
|
|
328
|
+
|
|
329
|
+
return parameters
|
|
330
|
+
|
|
331
|
+
def _get_required_parameters(self, model_type: str) -> List[str]:
|
|
332
|
+
"""Get the list of required parameters for a model type."""
|
|
333
|
+
required_params = {
|
|
334
|
+
'cube': ['width', 'depth', 'height'],
|
|
335
|
+
'sphere': ['radius'],
|
|
336
|
+
'cylinder': ['radius', 'height'],
|
|
337
|
+
'box': ['width', 'depth', 'height', 'thickness'],
|
|
338
|
+
'rounded_box': ['width', 'depth', 'height', 'radius'],
|
|
339
|
+
'cone': ['base_radius', 'height'],
|
|
340
|
+
'torus': ['major_radius', 'minor_radius'],
|
|
341
|
+
'prism': ['width', 'height', 'depth'],
|
|
342
|
+
'custom': ['width', 'height', 'depth']
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return required_params.get(model_type, [])
|
|
346
|
+
|
|
347
|
+
def _get_parameter_question(self, param: str, model_type: str) -> str:
|
|
348
|
+
"""Get a question to ask for a specific parameter."""
|
|
349
|
+
questions = {
|
|
350
|
+
'width': f"What should be the width of the {model_type} in mm?",
|
|
351
|
+
'depth': f"What should be the depth of the {model_type} in mm?",
|
|
352
|
+
'height': f"What should be the height of the {model_type} in mm?",
|
|
353
|
+
'radius': f"What should be the radius of the {model_type} in mm?",
|
|
354
|
+
'thickness': f"What should be the wall thickness of the {model_type} in mm?",
|
|
355
|
+
'segments': f"How many segments should the {model_type} have for smoothness?",
|
|
356
|
+
'base_radius': f"What should be the base radius of the {model_type} in mm?",
|
|
357
|
+
'major_radius': f"What should be the major radius of the {model_type} in mm?",
|
|
358
|
+
'minor_radius': f"What should be the minor radius of the {model_type} in mm?",
|
|
359
|
+
'diameter': f"What should be the diameter of the {model_type} in mm?",
|
|
360
|
+
'angle': f"What should be the angle of the {model_type} in degrees?",
|
|
361
|
+
'scale': f"What should be the scale factor for the {model_type}?",
|
|
362
|
+
'resolution': f"What resolution should the {model_type} have (higher means more detailed)?",
|
|
363
|
+
'center': f"Should the {model_type} be centered? (yes/no)"
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return questions.get(param, f"What should be the {param} of the {model_type}?")
|
|
367
|
+
|
|
368
|
+
def _extract_parameter_value(self, param: str, answer: str) -> Optional[float]:
|
|
369
|
+
"""Extract a parameter value from an answer."""
|
|
370
|
+
pattern = self.parameter_patterns.get(param)
|
|
371
|
+
if not pattern:
|
|
372
|
+
# For parameters without specific patterns, try to extract any number
|
|
373
|
+
pattern = r'(\d+(?:\.\d+)?)'
|
|
374
|
+
|
|
375
|
+
matches = re.findall(pattern, answer, re.IGNORECASE)
|
|
376
|
+
if matches:
|
|
377
|
+
value = matches[-1]
|
|
378
|
+
if isinstance(value, tuple):
|
|
379
|
+
value = value[0] # Extract from capture group
|
|
380
|
+
return self._convert_to_mm(value, answer)
|
|
381
|
+
|
|
382
|
+
# Try to extract just a number
|
|
383
|
+
matches = re.findall(r'(\d+(?:\.\d+)?)', answer)
|
|
384
|
+
if matches:
|
|
385
|
+
value = matches[-1]
|
|
386
|
+
return self._convert_to_mm(value, answer)
|
|
387
|
+
|
|
388
|
+
return None
|
|
File without changes
|