@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.
Files changed (192) hide show
  1. package/README.md +116 -0
  2. package/config/mcp/antigravity.json +22 -0
  3. package/config/mcp/claude.json +22 -0
  4. package/config/mcp/codex.toml +25 -0
  5. package/docs/adr/ADR-001-KICAD-OPENSCAD-MCP-STRATEGY.md +334 -0
  6. package/docs/review_documentation.md +121 -0
  7. package/installer.js +358 -0
  8. package/mcp_servers/kicad-mcp-server/.env.example +22 -0
  9. package/mcp_servers/kicad-mcp-server/.github/workflows/ci.yml +36 -0
  10. package/mcp_servers/kicad-mcp-server/CLAUDE.md +487 -0
  11. package/mcp_servers/kicad-mcp-server/README.md +316 -0
  12. package/mcp_servers/kicad-mcp-server/docs/DEVICE_TREE.md +416 -0
  13. package/mcp_servers/kicad-mcp-server/docs/INSTALLATION.md +332 -0
  14. package/mcp_servers/kicad-mcp-server/docs/PIN_ANALYSIS.md +332 -0
  15. package/mcp_servers/kicad-mcp-server/docs/README.md +240 -0
  16. package/mcp_servers/kicad-mcp-server/docs/TESTING.md +613 -0
  17. package/mcp_servers/kicad-mcp-server/docs/VALIDATION.md +268 -0
  18. package/mcp_servers/kicad-mcp-server/pyproject.toml +96 -0
  19. package/mcp_servers/kicad-mcp-server/requirements-dev.txt +16 -0
  20. package/mcp_servers/kicad-mcp-server/requirements-test.txt +24 -0
  21. package/mcp_servers/kicad-mcp-server/requirements.txt +13 -0
  22. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__init__.py +3 -0
  23. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/__main__.py +17 -0
  24. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/config.py +46 -0
  25. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/__init__.py +1 -0
  26. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/models/types.py +87 -0
  27. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/__init__.py +1 -0
  28. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/netlist_parser.py +234 -0
  29. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser.py +375 -0
  30. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/pcb_parser_kicad.py +327 -0
  31. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/parsers/schematic_parser.py +902 -0
  32. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/server.py +71 -0
  33. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/__init__.py +1 -0
  34. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/arduino/connectivity_test.cpp.j2 +189 -0
  35. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/atmega.dts.j2 +77 -0
  36. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/esp32.dts.j2 +77 -0
  37. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/nrf52.dts.j2 +77 -0
  38. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/device_tree/stm32f4.dts.j2 +89 -0
  39. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/esp_idf/test_suite.c.j2 +340 -0
  40. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/pytest/test_connectivity.py.j2 +147 -0
  41. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/st_hal/hal_test.c.j2 +313 -0
  42. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_gpio_test.py.j2 +99 -0
  43. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_i2c_test.py.j2 +117 -0
  44. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_pinmux_test.py.j2 +43 -0
  45. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/pytest_spi_test.py.j2 +94 -0
  46. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_gpio_test.c.j2 +113 -0
  47. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_i2c_test.c.j2 +101 -0
  48. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/tests/unity_spi_test.c.j2 +94 -0
  49. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/templates/unittest/test_schematic.py.j2 +172 -0
  50. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/__init__.py +36 -0
  51. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/device_tree.py +1187 -0
  52. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/hierarchical_analysis.py +211 -0
  53. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/netlist.py +320 -0
  54. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/parts_registry.py +142 -0
  55. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb.py +955 -0
  56. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pcb_layout.py +308 -0
  57. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/pin_analysis.py +765 -0
  58. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/project.py +196 -0
  59. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic.py +319 -0
  60. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_editor.py +674 -0
  61. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/schematic_search.py +158 -0
  62. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/validation.py +866 -0
  63. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/tools/visualization.py +225 -0
  64. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/__init__.py +1 -0
  65. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/file_handlers.py +65 -0
  66. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_cli.py +103 -0
  67. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/kicad_version.py +103 -0
  68. package/mcp_servers/kicad-mcp-server/src/kicad_mcp_server/utils/parts_registry.py +197 -0
  69. package/mcp_servers/kicad-mcp-server/tests/__init__.py +1 -0
  70. package/mcp_servers/kicad-mcp-server/tests/examples/ESP32S3_TEST.md +219 -0
  71. package/mcp_servers/kicad-mcp-server/tests/fixtures/README.md +65 -0
  72. package/mcp_servers/kicad-mcp-server/tests/fixtures/__init__.py +1 -0
  73. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_pcb.kicad_pcb +177 -0
  74. package/mcp_servers/kicad-mcp-server/tests/fixtures/example_schematic.kicad_sch +145 -0
  75. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/child.kicad_sch +24 -0
  76. package/mcp_servers/kicad-mcp-server/tests/fixtures/hier/root.kicad_sch +38 -0
  77. package/mcp_servers/kicad-mcp-server/tests/test_tools/__init__.py +1 -0
  78. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_hierarchical_labels.py +195 -0
  79. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_kicad_cli.py +116 -0
  80. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_netlist_cache_path.py +29 -0
  81. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic.py +189 -0
  82. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_schematic_hierarchy.py +69 -0
  83. package/mcp_servers/kicad-mcp-server/tests/test_tools/test_visualization.py +136 -0
  84. package/mcp_servers/kicad-mcp-server/uv.lock +2873 -0
  85. package/mcp_servers/openscad-mcp-server/.dockerignore +9 -0
  86. package/mcp_servers/openscad-mcp-server/.github/workflows/test.yml +40 -0
  87. package/mcp_servers/openscad-mcp-server/Dockerfile +29 -0
  88. package/mcp_servers/openscad-mcp-server/LICENSE +21 -0
  89. package/mcp_servers/openscad-mcp-server/README.md +154 -0
  90. package/mcp_servers/openscad-mcp-server/docs/audit.md +56 -0
  91. package/mcp_servers/openscad-mcp-server/docs/docker.md +66 -0
  92. package/mcp_servers/openscad-mcp-server/docs/issue-followup.md +19 -0
  93. package/mcp_servers/openscad-mcp-server/docs/jetson.md +17 -0
  94. package/mcp_servers/openscad-mcp-server/glama.json +4 -0
  95. package/mcp_servers/openscad-mcp-server/legacy/README.md +15 -0
  96. package/mcp_servers/openscad-mcp-server/legacy/README.original.md +294 -0
  97. package/mcp_servers/openscad-mcp-server/legacy/implementation_plan.md +100 -0
  98. package/mcp_servers/openscad-mcp-server/legacy/old/download_sam2_checkpoint.py +115 -0
  99. package/mcp_servers/openscad-mcp-server/legacy/old/src/ai/sam_segmentation.py +209 -0
  100. package/mcp_servers/openscad-mcp-server/legacy/old/src/models/threestudio_generator.py +231 -0
  101. package/mcp_servers/openscad-mcp-server/legacy/old/src/workflow/image_to_model_pipeline.py +260 -0
  102. package/mcp_servers/openscad-mcp-server/legacy/old/test_sam2_segmentation.py +96 -0
  103. package/mcp_servers/openscad-mcp-server/legacy/requirements.txt +57 -0
  104. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/README.md +39 -0
  105. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/ai-driven-code-generation.md +122 -0
  106. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/decisions/export-formats.md +76 -0
  107. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/ai/ai_service.py.md +51 -0
  108. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/main.py.md +63 -0
  109. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/models/code_generator.py.md +63 -0
  110. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/files/src/nlp/parameter_extractor.py.md +63 -0
  111. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/ai/natural-language-processing.md +78 -0
  112. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/nlp/parameter-extraction.md +173 -0
  113. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/export-formats.md +91 -0
  114. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/openscad-basics.md +66 -0
  115. package/mcp_servers/openscad-mcp-server/legacy/rtfmd/knowledge/openscad/primitive-testing.md +79 -0
  116. package/mcp_servers/openscad-mcp-server/legacy/src/__init__.py +0 -0
  117. package/mcp_servers/openscad-mcp-server/legacy/src/ai/ai_service.py +257 -0
  118. package/mcp_servers/openscad-mcp-server/legacy/src/ai/gemini_api.py +161 -0
  119. package/mcp_servers/openscad-mcp-server/legacy/src/ai/venice_api.py +203 -0
  120. package/mcp_servers/openscad-mcp-server/legacy/src/config.py +121 -0
  121. package/mcp_servers/openscad-mcp-server/legacy/src/main.py +1456 -0
  122. package/mcp_servers/openscad-mcp-server/legacy/src/main.py.new +404 -0
  123. package/mcp_servers/openscad-mcp-server/legacy/src/main_remote.py +401 -0
  124. package/mcp_servers/openscad-mcp-server/legacy/src/models/__init__.py +0 -0
  125. package/mcp_servers/openscad-mcp-server/legacy/src/models/code_generator.py +321 -0
  126. package/mcp_servers/openscad-mcp-server/legacy/src/models/cuda_mvs.py +209 -0
  127. package/mcp_servers/openscad-mcp-server/legacy/src/models/scad_templates/basic_shapes.scad +144 -0
  128. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/__init__.py +0 -0
  129. package/mcp_servers/openscad-mcp-server/legacy/src/nlp/parameter_extractor.py +388 -0
  130. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/__init__.py +0 -0
  131. package/mcp_servers/openscad-mcp-server/legacy/src/openscad_wrapper/wrapper.py +418 -0
  132. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/__init__.py +1 -0
  133. package/mcp_servers/openscad-mcp-server/legacy/src/printer_discovery/printer_discovery.py +471 -0
  134. package/mcp_servers/openscad-mcp-server/legacy/src/remote/connection_manager.py +537 -0
  135. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_client.py +435 -0
  136. package/mcp_servers/openscad-mcp-server/legacy/src/remote/cuda_mvs_server.py +787 -0
  137. package/mcp_servers/openscad-mcp-server/legacy/src/remote/error_handling.py +415 -0
  138. package/mcp_servers/openscad-mcp-server/legacy/src/testing/__init__.py +0 -0
  139. package/mcp_servers/openscad-mcp-server/legacy/src/testing/primitive_tester.py +203 -0
  140. package/mcp_servers/openscad-mcp-server/legacy/src/testing/test_primitives.py +98 -0
  141. package/mcp_servers/openscad-mcp-server/legacy/src/utils/__init__.py +1 -0
  142. package/mcp_servers/openscad-mcp-server/legacy/src/utils/cad_exporter.py +241 -0
  143. package/mcp_servers/openscad-mcp-server/legacy/src/utils/format_validator.py +206 -0
  144. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_exporter.py +140 -0
  145. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_repair.py +91 -0
  146. package/mcp_servers/openscad-mcp-server/legacy/src/utils/stl_validator.py +123 -0
  147. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/__init__.py +0 -0
  148. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/headless_renderer.py +52 -0
  149. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/renderer.py +177 -0
  150. package/mcp_servers/openscad-mcp-server/legacy/src/visualization/web_interface.py +639 -0
  151. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/image_approval.py +148 -0
  152. package/mcp_servers/openscad-mcp-server/legacy/src/workflow/multi_view_to_model_pipeline.py +338 -0
  153. package/mcp_servers/openscad-mcp-server/legacy/test_complete_workflow.py +374 -0
  154. package/mcp_servers/openscad-mcp-server/legacy/test_cuda_mvs.py +191 -0
  155. package/mcp_servers/openscad-mcp-server/legacy/test_gemini_api.py +168 -0
  156. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval.py +192 -0
  157. package/mcp_servers/openscad-mcp-server/legacy/test_image_approval_workflow.py +251 -0
  158. package/mcp_servers/openscad-mcp-server/legacy/test_image_to_model_pipeline.py +145 -0
  159. package/mcp_servers/openscad-mcp-server/legacy/test_model_selection.py +41 -0
  160. package/mcp_servers/openscad-mcp-server/legacy/test_multi_view_pipeline.py +290 -0
  161. package/mcp_servers/openscad-mcp-server/legacy/test_primitives.sh +13 -0
  162. package/mcp_servers/openscad-mcp-server/legacy/test_rabbit_direct.py +71 -0
  163. package/mcp_servers/openscad-mcp-server/legacy/test_remote_cuda_mvs.py +283 -0
  164. package/mcp_servers/openscad-mcp-server/legacy/test_venice_example.py +69 -0
  165. package/mcp_servers/openscad-mcp-server/pyproject.toml +33 -0
  166. package/mcp_servers/openscad-mcp-server/requirements.txt +2 -0
  167. package/mcp_servers/openscad-mcp-server/scad/simple_cube.scad +2 -0
  168. package/mcp_servers/openscad-mcp-server/scripts/test_docker.py +220 -0
  169. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__init__.py +3 -0
  170. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/__main__.py +3 -0
  171. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/engine.py +94 -0
  172. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/geometry.py +242 -0
  173. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/server.py +245 -0
  174. package/mcp_servers/openscad-mcp-server/src/openscad_mcp/service.py +190 -0
  175. package/mcp_servers/openscad-mcp-server/tests/conftest.py +17 -0
  176. package/mcp_servers/openscad-mcp-server/tests/test_engine.py +37 -0
  177. package/mcp_servers/openscad-mcp-server/tests/test_geometry.py +106 -0
  178. package/mcp_servers/openscad-mcp-server/tests/test_integration.py +172 -0
  179. package/mcp_servers/openscad-mcp-server/tests/test_transports.py +314 -0
  180. package/mcp_servers/openscad-mcp-server/uv.lock +1123 -0
  181. package/package.json +44 -0
  182. package/scripts/install_mcps.sh +86 -0
  183. package/scripts/openscad_wrapper.sh +62 -0
  184. package/scripts/run_kicad_mcp.sh +25 -0
  185. package/scripts/run_openscad_mcp.sh +41 -0
  186. package/scripts/test_mcp_connection.py +626 -0
  187. package/scripts/test_mcp_connection.sh +168 -0
  188. package/skills/code-first-hardware-design/SKILL.md +260 -0
  189. package/skills/pcb-constraint-definition/SKILL.md +236 -0
  190. package/skills/pcb-layout-routing-automation/SKILL.md +153 -0
  191. package/skills/pcb-validation-dfm-signoff/SKILL.md +194 -0
  192. 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