@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,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
|