@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,257 @@
1
+ import os
2
+ import logging
3
+ import re
4
+ from typing import Dict, Any, Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class AIService:
9
+ """
10
+ Service for AI-driven OpenSCAD code generation.
11
+ Translates natural language descriptions into OpenSCAD code.
12
+ """
13
+
14
+ def __init__(self, templates_dir: str, model_config: Optional[Dict[str, Any]] = None):
15
+ """
16
+ Initialize the AI service.
17
+
18
+ Args:
19
+ templates_dir: Directory containing OpenSCAD templates
20
+ model_config: Optional configuration for the AI model
21
+ """
22
+ self.templates_dir = templates_dir
23
+ self.model_config = model_config or {}
24
+
25
+ # Load templates
26
+ self.templates = self._load_templates()
27
+
28
+ logger.info(f"Initialized AI service with {len(self.templates)} templates")
29
+
30
+ def generate_openscad_code(self, context: Dict[str, Any]) -> str:
31
+ """
32
+ Generate OpenSCAD code from natural language description.
33
+
34
+ Args:
35
+ context: Dictionary containing:
36
+ - description: Natural language description
37
+ - parameters: Dictionary of parameters
38
+ - templates_dir: Directory containing templates
39
+
40
+ Returns:
41
+ Generated OpenSCAD code
42
+ """
43
+ description = context.get("description", "")
44
+ parameters = context.get("parameters", {})
45
+
46
+ logger.info(f"Generating OpenSCAD code for: {description}")
47
+
48
+ # Parse the description to identify key components
49
+ components = self._parse_description(description)
50
+
51
+ # Generate code based on identified components
52
+ code = self._generate_code_from_components(components, parameters)
53
+
54
+ return code
55
+
56
+ def _load_templates(self) -> Dict[str, str]:
57
+ """Load OpenSCAD code templates from the templates directory."""
58
+ templates = {}
59
+
60
+ # Check if templates directory exists
61
+ if not os.path.exists(self.templates_dir):
62
+ logger.warning(f"Templates directory not found: {self.templates_dir}")
63
+ return templates
64
+
65
+ # Load all .scad files in the templates directory
66
+ for filename in os.listdir(self.templates_dir):
67
+ if filename.endswith(".scad"):
68
+ template_name = os.path.splitext(filename)[0]
69
+ template_path = os.path.join(self.templates_dir, filename)
70
+
71
+ try:
72
+ with open(template_path, 'r') as f:
73
+ templates[template_name] = f.read()
74
+ except Exception as e:
75
+ logger.error(f"Error loading template {template_path}: {e}")
76
+
77
+ return templates
78
+
79
+ def _parse_description(self, description: str) -> Dict[str, Any]:
80
+ """
81
+ Parse a natural language description to identify key components.
82
+
83
+ Args:
84
+ description: Natural language description of the model
85
+
86
+ Returns:
87
+ Dictionary of identified components
88
+ """
89
+ components = {
90
+ "primary_shape": None,
91
+ "operations": [],
92
+ "features": [],
93
+ "modifiers": []
94
+ }
95
+
96
+ # Identify primary shape
97
+ shape_patterns = {
98
+ "cube": r'\b(cube|box|rectangular|block)\b',
99
+ "sphere": r'\b(sphere|ball|round|circular)\b',
100
+ "cylinder": r'\b(cylinder|tube|pipe|rod)\b',
101
+ "cone": r'\b(cone|pyramid|tapered)\b',
102
+ "torus": r'\b(torus|donut|ring)\b'
103
+ }
104
+
105
+ for shape, pattern in shape_patterns.items():
106
+ if re.search(pattern, description, re.IGNORECASE):
107
+ components["primary_shape"] = shape
108
+ break
109
+
110
+ # Identify operations
111
+ operation_patterns = {
112
+ "union": r'\b(combine|join|merge|add)\b',
113
+ "difference": r'\b(subtract|remove|cut|hole|hollow)\b',
114
+ "intersection": r'\b(intersect|common|shared)\b'
115
+ }
116
+
117
+ for operation, pattern in operation_patterns.items():
118
+ if re.search(pattern, description, re.IGNORECASE):
119
+ components["operations"].append(operation)
120
+
121
+ # Identify features
122
+ feature_patterns = {
123
+ "rounded_corners": r'\b(rounded corners|fillets|chamfer)\b',
124
+ "holes": r'\b(holes|perforations|openings)\b',
125
+ "text": r'\b(text|label|inscription)\b',
126
+ "pattern": r'\b(pattern|array|grid|repeat)\b'
127
+ }
128
+
129
+ for feature, pattern in feature_patterns.items():
130
+ if re.search(pattern, description, re.IGNORECASE):
131
+ components["features"].append(feature)
132
+
133
+ # Identify modifiers
134
+ modifier_patterns = {
135
+ "scale": r'\b(scale|resize|proportion)\b',
136
+ "rotate": r'\b(rotate|turn|spin|angle)\b',
137
+ "translate": r'\b(move|shift|position|place)\b',
138
+ "mirror": r'\b(mirror|reflect|flip)\b'
139
+ }
140
+
141
+ for modifier, pattern in modifier_patterns.items():
142
+ if re.search(pattern, description, re.IGNORECASE):
143
+ components["modifiers"].append(modifier)
144
+
145
+ logger.info(f"Parsed components: {components}")
146
+ return components
147
+
148
+ def _generate_code_from_components(self, components: Dict[str, Any], parameters: Dict[str, Any]) -> str:
149
+ """
150
+ Generate OpenSCAD code based on identified components.
151
+
152
+ Args:
153
+ components: Dictionary of identified components
154
+ parameters: Dictionary of parameters
155
+
156
+ Returns:
157
+ Generated OpenSCAD code
158
+ """
159
+ code = []
160
+
161
+ # Add header
162
+ code.append("// AI-generated OpenSCAD code")
163
+ code.append("// Generated from natural language description")
164
+ code.append("")
165
+
166
+ # Add parameter declarations
167
+ code.append("// Parameters")
168
+ for param, value in parameters.items():
169
+ if isinstance(value, str) and not (value.lower() == 'true' or value.lower() == 'false'):
170
+ code.append(f'{param} = "{value}";')
171
+ else:
172
+ code.append(f"{param} = {value};")
173
+ code.append("")
174
+
175
+ # Generate code for primary shape
176
+ primary_shape = components.get("primary_shape")
177
+ if not primary_shape:
178
+ primary_shape = "cube" # Default to cube if no shape is identified
179
+
180
+ # Start with operations if any
181
+ operations = components.get("operations", [])
182
+ if operations:
183
+ for operation in operations:
184
+ code.append(f"{operation}() {{")
185
+ code.append(" // Primary shape")
186
+
187
+ # Add modifiers if any
188
+ modifiers = components.get("modifiers", [])
189
+ indent = " " if operations else ""
190
+
191
+ if modifiers:
192
+ for modifier in modifiers:
193
+ if modifier == "scale":
194
+ scale_value = parameters.get("scale", 1)
195
+ code.append(f"{indent}scale([{scale_value}, {scale_value}, {scale_value}])")
196
+ elif modifier == "rotate":
197
+ angle = parameters.get("angle", 0)
198
+ code.append(f"{indent}rotate([0, 0, {angle}])")
199
+ elif modifier == "translate":
200
+ x = parameters.get("x", 0)
201
+ y = parameters.get("y", 0)
202
+ z = parameters.get("z", 0)
203
+ code.append(f"{indent}translate([{x}, {y}, {z}])")
204
+ elif modifier == "mirror":
205
+ code.append(f"{indent}mirror([0, 0, 1])")
206
+
207
+ # Add the primary shape
208
+ if primary_shape == "cube":
209
+ width = parameters.get("width", 10)
210
+ depth = parameters.get("depth", 10)
211
+ height = parameters.get("height", 10)
212
+ center = parameters.get("center", "true")
213
+ code.append(f"{indent}cube([{width}, {depth}, {height}], center={center});")
214
+ elif primary_shape == "sphere":
215
+ radius = parameters.get("radius", 10)
216
+ segments = parameters.get("segments", 32)
217
+ code.append(f"{indent}sphere(r={radius}, $fn={segments});")
218
+ elif primary_shape == "cylinder":
219
+ radius = parameters.get("radius", 10)
220
+ height = parameters.get("height", 20)
221
+ center = parameters.get("center", "true")
222
+ segments = parameters.get("segments", 32)
223
+ code.append(f"{indent}cylinder(h={height}, r={radius}, center={center}, $fn={segments});")
224
+ elif primary_shape == "cone":
225
+ base_radius = parameters.get("base_radius", 10)
226
+ height = parameters.get("height", 20)
227
+ center = parameters.get("center", "true")
228
+ segments = parameters.get("segments", 32)
229
+ code.append(f"{indent}cylinder(h={height}, r1={base_radius}, r2=0, center={center}, $fn={segments});")
230
+ elif primary_shape == "torus":
231
+ major_radius = parameters.get("major_radius", 20)
232
+ minor_radius = parameters.get("minor_radius", 5)
233
+ segments = parameters.get("segments", 32)
234
+ code.append(f"{indent}rotate_extrude($fn={segments})")
235
+ code.append(f"{indent} translate([{major_radius}, 0, 0])")
236
+ code.append(f"{indent} circle(r={minor_radius}, $fn={segments});")
237
+
238
+ # Add features if any
239
+ features = components.get("features", [])
240
+ if features and "holes" in features:
241
+ code.append("")
242
+ code.append(f"{indent}// Add holes")
243
+ code.append(f"{indent}difference() {{")
244
+ code.append(f"{indent} children(0);") # Reference the primary shape
245
+
246
+ # Add a sample hole
247
+ hole_radius = parameters.get("hole_radius", 2)
248
+ code.append(f"{indent} translate([0, 0, 0])")
249
+ code.append(f"{indent} cylinder(h=100, r={hole_radius}, center=true, $fn=32);")
250
+
251
+ code.append(f"{indent}}}")
252
+
253
+ # Close operations if any
254
+ if operations:
255
+ code.append("}")
256
+
257
+ return "\n".join(code)
@@ -0,0 +1,161 @@
1
+ """
2
+ Google Gemini API integration for image generation.
3
+ """
4
+
5
+ import os
6
+ import logging
7
+ import base64
8
+ from typing import Dict, Any, List, Optional
9
+ from io import BytesIO
10
+ from PIL import Image
11
+ import requests
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class GeminiImageGenerator:
16
+ """
17
+ Wrapper for Google Gemini API for generating images.
18
+ """
19
+
20
+ def __init__(self, api_key: str, output_dir: str = "output/images"):
21
+ """
22
+ Initialize the Gemini image generator.
23
+
24
+ Args:
25
+ api_key: Google Gemini API key
26
+ output_dir: Directory to store generated images
27
+ """
28
+ self.api_key = api_key
29
+ self.output_dir = output_dir
30
+ self.base_url = "https://generativelanguage.googleapis.com/v1beta"
31
+
32
+ # Create output directory if it doesn't exist
33
+ os.makedirs(output_dir, exist_ok=True)
34
+
35
+ def generate_image(self, prompt: str, model: str = "gemini-2.0-flash-exp-image-generation",
36
+ output_path: Optional[str] = None, **kwargs) -> Dict[str, Any]:
37
+ """
38
+ Generate an image using Google Gemini API.
39
+
40
+ Args:
41
+ prompt: Text description for image generation
42
+ model: Gemini model to use
43
+ output_path: Path to save the generated image
44
+ **kwargs: Additional parameters for Gemini API
45
+
46
+ Returns:
47
+ Dictionary containing image data and metadata
48
+ """
49
+ logger.info(f"Generating image with prompt: {prompt}")
50
+
51
+ try:
52
+ # Prepare the request payload
53
+ payload = {
54
+ "contents": [
55
+ {
56
+ "parts": [
57
+ {"text": prompt}
58
+ ]
59
+ }
60
+ ],
61
+ "generationConfig": {
62
+ "responseModalities": ["Text", "Image"]
63
+ }
64
+ }
65
+
66
+ # Add any additional parameters
67
+ for key, value in kwargs.items():
68
+ if key not in payload:
69
+ payload[key] = value
70
+
71
+ # Make API request
72
+ response = requests.post(
73
+ f"{self.base_url}/models/{model}:generateContent",
74
+ headers={
75
+ "Content-Type": "application/json",
76
+ "x-goog-api-key": self.api_key
77
+ },
78
+ json=payload
79
+ )
80
+
81
+ # Check for errors
82
+ response.raise_for_status()
83
+ result = response.json()
84
+
85
+ # Extract image data
86
+ image_data = None
87
+ for part in result["candidates"][0]["content"]["parts"]:
88
+ if "inlineData" in part:
89
+ image_data = base64.b64decode(part["inlineData"]["data"])
90
+ break
91
+
92
+ if not image_data:
93
+ raise ValueError("No image was generated in the response")
94
+
95
+ # Save image if output_path is provided
96
+ if not output_path:
97
+ # Generate output path if not provided
98
+ os.makedirs(self.output_dir, exist_ok=True)
99
+ output_path = os.path.join(self.output_dir, f"{prompt[:20].replace(' ', '_')}.png")
100
+
101
+ # Save image
102
+ image = Image.open(BytesIO(image_data))
103
+ image.save(output_path)
104
+
105
+ logger.info(f"Image saved to {output_path}")
106
+
107
+ return {
108
+ "prompt": prompt,
109
+ "model": model,
110
+ "local_path": output_path,
111
+ "image_data": image_data
112
+ }
113
+
114
+ except Exception as e:
115
+ logger.error(f"Error generating image: {str(e)}")
116
+ raise
117
+
118
+ def generate_multiple_views(self, prompt: str, num_views: int = 4,
119
+ base_image_path: Optional[str] = None,
120
+ output_dir: Optional[str] = None) -> List[Dict[str, Any]]:
121
+ """
122
+ Generate multiple views of the same 3D object.
123
+
124
+ Args:
125
+ prompt: Text description of the object
126
+ num_views: Number of views to generate
127
+ base_image_path: Optional path to a base image
128
+ output_dir: Directory to save the generated images
129
+
130
+ Returns:
131
+ List of dictionaries containing image data and metadata
132
+ """
133
+ if not output_dir:
134
+ output_dir = os.path.join(self.output_dir, prompt[:20].replace(' ', '_'))
135
+
136
+ os.makedirs(output_dir, exist_ok=True)
137
+
138
+ # View directions to include in prompts
139
+ view_directions = [
140
+ "front view", "side view from the right",
141
+ "side view from the left", "back view",
142
+ "top view", "bottom view", "45-degree angle view"
143
+ ]
144
+
145
+ results = []
146
+
147
+ # Generate images for each view direction
148
+ for i in range(min(num_views, len(view_directions))):
149
+ view_prompt = f"{prompt} - {view_directions[i]}, same object, consistent style and details"
150
+
151
+ # Generate the image
152
+ output_path = os.path.join(output_dir, f"view_{i+1}.png")
153
+ result = self.generate_image(view_prompt, output_path=output_path)
154
+
155
+ # Add view direction to result
156
+ result["view_direction"] = view_directions[i]
157
+ result["view_index"] = i + 1
158
+
159
+ results.append(result)
160
+
161
+ return results
@@ -0,0 +1,203 @@
1
+ """
2
+ Venice.ai API client for image generation using the Flux model.
3
+ """
4
+
5
+ import os
6
+ import requests
7
+ import logging
8
+ from typing import Dict, Any, Optional, List, Tuple
9
+ from pathlib import Path
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+ # Venice.ai model mapping and descriptions
14
+ VENICE_MODELS = {
15
+ # Model name: (aliases, description)
16
+ "fluently-xl": (
17
+ ["fast", "quick", "fastest", "speed", "rapid", "efficient"],
18
+ "Fastest model (2.30s) with good quality"
19
+ ),
20
+ "flux-dev": (
21
+ ["high quality", "detailed", "hq", "best quality", "premium"],
22
+ "High-quality model with detailed results"
23
+ ),
24
+ "flux-dev-uncensored": (
25
+ ["uncensored", "unfiltered", "unrestricted"],
26
+ "Uncensored version of the flux-dev model"
27
+ ),
28
+ "stable-diffusion-3.5": (
29
+ ["stable diffusion", "sd3", "sd3.5", "standard"],
30
+ "Stable Diffusion 3.5 model"
31
+ ),
32
+ "pony-realism": (
33
+ ["realistic", "realism", "pony", "photorealistic"],
34
+ "Specialized model for realistic outputs"
35
+ ),
36
+ "lustify-sdxl": (
37
+ ["stylized", "artistic", "creative", "lustify"],
38
+ "Artistic stylization model"
39
+ ),
40
+ }
41
+
42
+ class VeniceImageGenerator:
43
+ """Client for Venice.ai's image generation API."""
44
+
45
+ def __init__(self, api_key: str, output_dir: str = "output/images"):
46
+ """
47
+ Initialize the Venice.ai API client.
48
+
49
+ Args:
50
+ api_key: API key for Venice.ai
51
+ output_dir: Directory to store generated images
52
+ """
53
+ self.api_key = api_key
54
+ if not self.api_key:
55
+ logger.warning("No Venice.ai API key provided")
56
+
57
+ # API endpoint from documentation
58
+ self.base_url = "https://api.venice.ai/api/v1"
59
+ self.api_endpoint = f"{self.base_url}/image/generate"
60
+ self.output_dir = output_dir
61
+
62
+ # Create output directory if it doesn't exist
63
+ os.makedirs(output_dir, exist_ok=True)
64
+
65
+ def map_model_preference(self, preference: str) -> str:
66
+ """
67
+ Map a natural language preference to a Venice.ai model name.
68
+
69
+ Args:
70
+ preference: Natural language description of desired model
71
+
72
+ Returns:
73
+ Name of the matching Venice.ai model
74
+ """
75
+ if not preference or preference.lower() in ["default", "fluently-xl", "fluently xl"]:
76
+ return "fluently-xl"
77
+
78
+ preference = preference.lower()
79
+
80
+ # Check for exact matches first
81
+ for model_name in VENICE_MODELS:
82
+ if model_name.lower() == preference:
83
+ return model_name
84
+
85
+ # Check for keyword matches
86
+ for model_name, (aliases, _) in VENICE_MODELS.items():
87
+ for alias in aliases:
88
+ if alias in preference:
89
+ return model_name
90
+
91
+ # Default to fluently-xl if no match found
92
+ return "fluently-xl"
93
+
94
+ def generate_image(self, prompt: str, model: str = "fluently-xl",
95
+ width: int = 1024, height: int = 1024,
96
+ output_path: Optional[str] = None) -> Dict[str, Any]:
97
+ """
98
+ Generate an image using Venice.ai's API.
99
+
100
+ Args:
101
+ prompt: Text description for image generation
102
+ model: Model to use - can be a specific model name or natural language description:
103
+ - "fluently-xl" (default): Fastest model (2.30s) with good quality
104
+ - "flux-dev": High-quality model with detailed results
105
+ - "flux-dev-uncensored": Uncensored version of the flux-dev model
106
+ - "stable-diffusion-3.5": Stable Diffusion 3.5 model
107
+ - "pony-realism": Specialized model for realistic outputs
108
+ - "lustify-sdxl": Artistic stylization model
109
+ - Or use natural language like "high quality", "fastest", "realistic", etc.
110
+ width: Image width
111
+ height: Image height
112
+ output_path: Optional path to save the generated image
113
+
114
+ Returns:
115
+ Dictionary containing image data and metadata
116
+ """
117
+ if not self.api_key:
118
+ raise ValueError("Venice.ai API key is required")
119
+
120
+ # Map the model preference to a specific model name
121
+ mapped_model = self.map_model_preference(model)
122
+
123
+ # Prepare request payload
124
+ payload = {
125
+ "model": mapped_model,
126
+ "prompt": prompt,
127
+ "height": height,
128
+ "width": width,
129
+ "steps": 20,
130
+ "return_binary": False,
131
+ "hide_watermark": True, # Remove watermark as requested
132
+ "format": "png",
133
+ "embed_exif_metadata": False
134
+ }
135
+
136
+ # Set up headers with API key
137
+ headers = {
138
+ "Authorization": f"Bearer {self.api_key}",
139
+ "Content-Type": "application/json"
140
+ }
141
+
142
+ try:
143
+ # Make API request
144
+ logger.info(f"Sending request to {self.api_endpoint}")
145
+ response = requests.post(
146
+ self.api_endpoint,
147
+ json=payload,
148
+ headers=headers
149
+ )
150
+
151
+ # Check response status
152
+ if response.status_code != 200:
153
+ error_msg = f"Error generating image: {response.status_code} - {response.text}"
154
+ logger.error(error_msg)
155
+ return {"error": error_msg}
156
+
157
+ # Process response
158
+ result = response.json()
159
+
160
+ # Add the mapped model to the result
161
+ result["model"] = mapped_model
162
+
163
+ # Generate output path if not provided
164
+ if not output_path:
165
+ # Create a filename based on the prompt
166
+ filename = f"{prompt[:20].replace(' ', '_')}_{mapped_model}.png"
167
+ output_path = os.path.join(self.output_dir, filename)
168
+
169
+ # Save image if images array is in the result
170
+ if "images" in result and len(result["images"]) > 0:
171
+ image_url = result["images"][0]
172
+ self._download_image(image_url, output_path)
173
+ result["local_path"] = output_path
174
+ result["image_url"] = image_url
175
+
176
+ return result
177
+ except requests.exceptions.RequestException as e:
178
+ logger.error(f"Error generating image with Venice.ai: {str(e)}")
179
+ raise
180
+
181
+ def _download_image(self, image_url: str, output_path: str) -> None:
182
+ """
183
+ Download image from URL and save to local path.
184
+
185
+ Args:
186
+ image_url: URL of the image to download
187
+ output_path: Path to save the downloaded image
188
+ """
189
+ try:
190
+ response = requests.get(image_url, stream=True)
191
+ response.raise_for_status()
192
+
193
+ # Ensure directory exists
194
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
195
+
196
+ with open(output_path, 'wb') as f:
197
+ for chunk in response.iter_content(chunk_size=8192):
198
+ f.write(chunk)
199
+
200
+ logger.info(f"Image saved to {output_path}")
201
+ except Exception as e:
202
+ logger.error(f"Error downloading image: {str(e)}")
203
+ raise