@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,209 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SAM2 (Segment Anything Model 2) integration for object segmentation.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import cv2
|
|
7
|
+
import numpy as np
|
|
8
|
+
import logging
|
|
9
|
+
from typing import Dict, Any, List, Tuple, Optional
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
class SAMSegmenter:
|
|
15
|
+
"""
|
|
16
|
+
Wrapper for Segment Anything Model 2 (SAM2) for object segmentation.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, model_type: str = "vit_h", checkpoint_path: Optional[str] = None,
|
|
20
|
+
use_gpu: bool = True, output_dir: str = "output/masks"):
|
|
21
|
+
"""
|
|
22
|
+
Initialize the SAM2 segmenter.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
model_type: SAM2 model type ("vit_h", "vit_l", "vit_b")
|
|
26
|
+
checkpoint_path: Path to model checkpoint
|
|
27
|
+
use_gpu: Whether to use GPU for inference
|
|
28
|
+
output_dir: Directory to store segmentation results
|
|
29
|
+
"""
|
|
30
|
+
self.model_type = model_type
|
|
31
|
+
self.checkpoint_path = checkpoint_path
|
|
32
|
+
self.use_gpu = use_gpu
|
|
33
|
+
self.output_dir = output_dir
|
|
34
|
+
|
|
35
|
+
# Create output directory if it doesn't exist
|
|
36
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
37
|
+
|
|
38
|
+
# Model will be initialized on first use to avoid loading it unnecessarily
|
|
39
|
+
self.model = None
|
|
40
|
+
self.predictor = None
|
|
41
|
+
|
|
42
|
+
def _initialize_model(self) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Initialize the SAM2 model.
|
|
45
|
+
|
|
46
|
+
Note: This requires PyTorch and the segment-anything-2 package to be installed.
|
|
47
|
+
"""
|
|
48
|
+
try:
|
|
49
|
+
# Import here to avoid dependency issues if SAM2 is not installed
|
|
50
|
+
import torch
|
|
51
|
+
from segment_anything_2 import sam_model_registry, SamPredictor
|
|
52
|
+
|
|
53
|
+
if not self.checkpoint_path:
|
|
54
|
+
raise ValueError("SAM2 checkpoint path is required")
|
|
55
|
+
|
|
56
|
+
# Check if checkpoint exists
|
|
57
|
+
if not os.path.exists(self.checkpoint_path):
|
|
58
|
+
raise FileNotFoundError(f"SAM2 checkpoint not found at {self.checkpoint_path}")
|
|
59
|
+
|
|
60
|
+
# Determine device
|
|
61
|
+
device = "cuda" if self.use_gpu and torch.cuda.is_available() else "cpu"
|
|
62
|
+
|
|
63
|
+
# Load SAM2 model
|
|
64
|
+
self.model = sam_model_registry[self.model_type](checkpoint=self.checkpoint_path)
|
|
65
|
+
self.model.to(device=device)
|
|
66
|
+
self.predictor = SamPredictor(self.model)
|
|
67
|
+
|
|
68
|
+
logger.info(f"Initialized SAM2 model ({self.model_type}) on {device}")
|
|
69
|
+
except ImportError as e:
|
|
70
|
+
logger.error(f"Required packages not installed: {str(e)}")
|
|
71
|
+
raise
|
|
72
|
+
except Exception as e:
|
|
73
|
+
logger.error(f"Error initializing SAM2 model: {str(e)}")
|
|
74
|
+
raise
|
|
75
|
+
|
|
76
|
+
def segment_image(self, image_path: str, points: Optional[List[Tuple[int, int]]] = None,
|
|
77
|
+
output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
78
|
+
"""
|
|
79
|
+
Segment objects in an image using SAM2.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
image_path: Path to input image
|
|
83
|
+
points: Optional list of (x, y) points to guide segmentation
|
|
84
|
+
output_dir: Optional directory to save segmentation results
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
Dictionary containing segmentation masks and metadata
|
|
88
|
+
"""
|
|
89
|
+
# Initialize model if not already initialized
|
|
90
|
+
if self.model is None:
|
|
91
|
+
self._initialize_model()
|
|
92
|
+
|
|
93
|
+
try:
|
|
94
|
+
# Load image
|
|
95
|
+
image = cv2.imread(image_path)
|
|
96
|
+
if image is None:
|
|
97
|
+
raise ValueError(f"Could not load image from {image_path}")
|
|
98
|
+
|
|
99
|
+
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
|
100
|
+
|
|
101
|
+
# Set image in predictor
|
|
102
|
+
self.predictor.set_image(image)
|
|
103
|
+
|
|
104
|
+
# Generate masks
|
|
105
|
+
if points:
|
|
106
|
+
# Convert points to numpy arrays
|
|
107
|
+
import numpy as np
|
|
108
|
+
point_coords = np.array(points)
|
|
109
|
+
point_labels = np.ones(len(points))
|
|
110
|
+
|
|
111
|
+
# Generate masks from points
|
|
112
|
+
masks, scores, logits = self.predictor.predict(
|
|
113
|
+
point_coords=point_coords,
|
|
114
|
+
point_labels=point_labels,
|
|
115
|
+
multimask_output=True
|
|
116
|
+
)
|
|
117
|
+
else:
|
|
118
|
+
# Automatic segmentation (using center point)
|
|
119
|
+
h, w = image.shape[:2]
|
|
120
|
+
center_point = np.array([[w//2, h//2]])
|
|
121
|
+
center_label = np.array([1])
|
|
122
|
+
|
|
123
|
+
masks, scores, logits = self.predictor.predict(
|
|
124
|
+
point_coords=center_point,
|
|
125
|
+
point_labels=center_label,
|
|
126
|
+
multimask_output=True
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Use provided output directory or default
|
|
130
|
+
output_dir = output_dir or os.path.join(self.output_dir, Path(image_path).stem)
|
|
131
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
132
|
+
|
|
133
|
+
# Process results
|
|
134
|
+
masked_images = []
|
|
135
|
+
for i, mask in enumerate(masks):
|
|
136
|
+
# Apply mask to image
|
|
137
|
+
masked_image = self._apply_mask_to_image(image, mask)
|
|
138
|
+
|
|
139
|
+
# Save masked image
|
|
140
|
+
output_path = os.path.join(output_dir, f"mask_{i}.png")
|
|
141
|
+
cv2.imwrite(output_path, cv2.cvtColor(masked_image, cv2.COLOR_RGB2BGR))
|
|
142
|
+
|
|
143
|
+
masked_images.append(output_path)
|
|
144
|
+
|
|
145
|
+
# Convert numpy arrays to lists for JSON serialization
|
|
146
|
+
result = {
|
|
147
|
+
"image_path": image_path,
|
|
148
|
+
"masked_images": masked_images,
|
|
149
|
+
"scores": scores.tolist(),
|
|
150
|
+
"mask_count": len(masks)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
except Exception as e:
|
|
155
|
+
logger.error(f"Error segmenting image: {str(e)}")
|
|
156
|
+
raise
|
|
157
|
+
|
|
158
|
+
def _apply_mask_to_image(self, image: np.ndarray, mask: np.ndarray) -> np.ndarray:
|
|
159
|
+
"""
|
|
160
|
+
Apply mask to image, keeping only the masked region.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
image: Input image as numpy array
|
|
164
|
+
mask: Binary mask as numpy array
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Masked image as numpy array
|
|
168
|
+
"""
|
|
169
|
+
# Create a copy of the image
|
|
170
|
+
masked_image = image.copy()
|
|
171
|
+
|
|
172
|
+
# Apply mask
|
|
173
|
+
masked_image[~mask] = [0, 0, 0] # Set background to black
|
|
174
|
+
|
|
175
|
+
return masked_image
|
|
176
|
+
|
|
177
|
+
def segment_with_auto_points(self, image_path: str, num_points: int = 5,
|
|
178
|
+
output_dir: Optional[str] = None) -> Dict[str, Any]:
|
|
179
|
+
"""
|
|
180
|
+
Segment image using automatically generated points with SAM2.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
image_path: Path to input image
|
|
184
|
+
num_points: Number of points to generate
|
|
185
|
+
output_dir: Optional directory to save segmentation results
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Dictionary containing segmentation masks and metadata
|
|
189
|
+
"""
|
|
190
|
+
# Load image
|
|
191
|
+
image = cv2.imread(image_path)
|
|
192
|
+
if image is None:
|
|
193
|
+
raise ValueError(f"Could not load image from {image_path}")
|
|
194
|
+
|
|
195
|
+
h, w = image.shape[:2]
|
|
196
|
+
|
|
197
|
+
# Generate points in a grid pattern
|
|
198
|
+
points = []
|
|
199
|
+
rows = int(np.sqrt(num_points))
|
|
200
|
+
cols = num_points // rows
|
|
201
|
+
|
|
202
|
+
for i in range(rows):
|
|
203
|
+
for j in range(cols):
|
|
204
|
+
x = int(w * (j + 0.5) / cols)
|
|
205
|
+
y = int(h * (i + 0.5) / rows)
|
|
206
|
+
points.append((x, y))
|
|
207
|
+
|
|
208
|
+
# Segment with generated points
|
|
209
|
+
return self.segment_image(image_path, points, output_dir)
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""
|
|
2
|
+
threestudio integration for 3D model generation from images.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import logging
|
|
8
|
+
import json
|
|
9
|
+
import tempfile
|
|
10
|
+
from typing import Dict, Any, List, Optional
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
class ThreeStudioGenerator:
|
|
16
|
+
"""
|
|
17
|
+
Wrapper for threestudio for 3D model generation from images.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, threestudio_path: str, output_dir: str = "output/models"):
|
|
21
|
+
"""
|
|
22
|
+
Initialize the threestudio generator.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
threestudio_path: Path to threestudio installation
|
|
26
|
+
output_dir: Directory to store output files
|
|
27
|
+
"""
|
|
28
|
+
self.threestudio_path = threestudio_path
|
|
29
|
+
self.output_dir = output_dir
|
|
30
|
+
|
|
31
|
+
# Create output directory if it doesn't exist
|
|
32
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
# Validate threestudio installation
|
|
35
|
+
self._validate_installation()
|
|
36
|
+
|
|
37
|
+
def _validate_installation(self) -> None:
|
|
38
|
+
"""
|
|
39
|
+
Validate threestudio installation.
|
|
40
|
+
|
|
41
|
+
Raises:
|
|
42
|
+
FileNotFoundError: If threestudio installation is not found
|
|
43
|
+
"""
|
|
44
|
+
if not os.path.exists(self.threestudio_path):
|
|
45
|
+
raise FileNotFoundError(f"threestudio not found at {self.threestudio_path}")
|
|
46
|
+
|
|
47
|
+
# Check for required files
|
|
48
|
+
required_files = ["launch.py", "README.md"]
|
|
49
|
+
for file in required_files:
|
|
50
|
+
if not os.path.exists(os.path.join(self.threestudio_path, file)):
|
|
51
|
+
raise FileNotFoundError(f"Required file {file} not found in threestudio directory")
|
|
52
|
+
|
|
53
|
+
def generate_model_from_image(self, image_path: str, method: str = "zero123",
|
|
54
|
+
num_iterations: int = 5000, export_format: str = "obj",
|
|
55
|
+
config_overrides: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
56
|
+
"""
|
|
57
|
+
Generate a 3D model from an image using threestudio.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
image_path: Path to input image
|
|
61
|
+
method: Method to use ("zero123", "sjc", "magic3d", etc.)
|
|
62
|
+
num_iterations: Number of training iterations
|
|
63
|
+
export_format: Format to export ("obj", "glb", "ply")
|
|
64
|
+
config_overrides: Optional configuration overrides
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Dictionary containing paths to generated model files
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
# Create a unique ID for this generation
|
|
71
|
+
model_id = Path(image_path).stem
|
|
72
|
+
|
|
73
|
+
# Create a temporary config file
|
|
74
|
+
config_file = self._create_config_file(image_path, method, num_iterations, config_overrides)
|
|
75
|
+
|
|
76
|
+
# Run threestudio
|
|
77
|
+
output_dir = os.path.join(self.output_dir, model_id)
|
|
78
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
cmd = [
|
|
81
|
+
"python", "launch.py",
|
|
82
|
+
"--config", config_file,
|
|
83
|
+
"--train",
|
|
84
|
+
"--gpu", "0",
|
|
85
|
+
"--output_dir", output_dir
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
logger.info(f"Running threestudio with command: {' '.join(cmd)}")
|
|
89
|
+
|
|
90
|
+
# Execute in threestudio directory
|
|
91
|
+
process = subprocess.Popen(
|
|
92
|
+
cmd,
|
|
93
|
+
cwd=self.threestudio_path,
|
|
94
|
+
stdout=subprocess.PIPE,
|
|
95
|
+
stderr=subprocess.PIPE,
|
|
96
|
+
text=True
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Wait for process to complete
|
|
100
|
+
stdout, stderr = process.communicate()
|
|
101
|
+
|
|
102
|
+
if process.returncode != 0:
|
|
103
|
+
logger.error(f"Error running threestudio: {stderr}")
|
|
104
|
+
raise RuntimeError(f"threestudio failed with exit code {process.returncode}")
|
|
105
|
+
|
|
106
|
+
# Export model
|
|
107
|
+
exported_files = self._export_model(output_dir, export_format)
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
"model_id": model_id,
|
|
111
|
+
"output_dir": output_dir,
|
|
112
|
+
"exported_files": exported_files,
|
|
113
|
+
"preview_images": self._get_preview_images(output_dir)
|
|
114
|
+
}
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.error(f"Error generating 3D model with threestudio: {str(e)}")
|
|
117
|
+
raise
|
|
118
|
+
|
|
119
|
+
def _create_config_file(self, image_path: str, method: str, num_iterations: int,
|
|
120
|
+
config_overrides: Optional[Dict[str, Any]] = None) -> str:
|
|
121
|
+
"""
|
|
122
|
+
Create a configuration file for threestudio.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
image_path: Path to input image
|
|
126
|
+
method: Method to use
|
|
127
|
+
num_iterations: Number of training iterations
|
|
128
|
+
config_overrides: Optional configuration overrides
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
Path to the created configuration file
|
|
132
|
+
"""
|
|
133
|
+
# Base configuration
|
|
134
|
+
config = {
|
|
135
|
+
"method": method,
|
|
136
|
+
"image_path": os.path.abspath(image_path),
|
|
137
|
+
"num_iterations": num_iterations,
|
|
138
|
+
"save_interval": 1000,
|
|
139
|
+
"export_interval": 1000
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# Apply overrides
|
|
143
|
+
if config_overrides:
|
|
144
|
+
config.update(config_overrides)
|
|
145
|
+
|
|
146
|
+
# Write to temporary file
|
|
147
|
+
fd, config_file = tempfile.mkstemp(suffix=".json")
|
|
148
|
+
with os.fdopen(fd, 'w') as f:
|
|
149
|
+
json.dump(config, f, indent=2)
|
|
150
|
+
|
|
151
|
+
return config_file
|
|
152
|
+
|
|
153
|
+
def _export_model(self, output_dir: str, export_format: str) -> List[str]:
|
|
154
|
+
"""
|
|
155
|
+
Export the model in the specified format.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
output_dir: Directory containing the model
|
|
159
|
+
export_format: Format to export
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
List of paths to exported files
|
|
163
|
+
"""
|
|
164
|
+
# Find the latest checkpoint
|
|
165
|
+
checkpoints_dir = os.path.join(output_dir, "checkpoints")
|
|
166
|
+
if not os.path.exists(checkpoints_dir):
|
|
167
|
+
raise FileNotFoundError(f"Checkpoints directory not found: {checkpoints_dir}")
|
|
168
|
+
|
|
169
|
+
# Get the latest checkpoint
|
|
170
|
+
checkpoints = sorted([f for f in os.listdir(checkpoints_dir) if f.endswith(".ckpt")])
|
|
171
|
+
if not checkpoints:
|
|
172
|
+
raise FileNotFoundError("No checkpoints found")
|
|
173
|
+
|
|
174
|
+
latest_checkpoint = os.path.join(checkpoints_dir, checkpoints[-1])
|
|
175
|
+
|
|
176
|
+
# Export command
|
|
177
|
+
cmd = [
|
|
178
|
+
"python", "launch.py",
|
|
179
|
+
"--config", os.path.join(output_dir, "config.yaml"),
|
|
180
|
+
"--export",
|
|
181
|
+
"--gpu", "0",
|
|
182
|
+
"--checkpoint", latest_checkpoint,
|
|
183
|
+
"--export_format", export_format
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
logger.info(f"Exporting model with command: {' '.join(cmd)}")
|
|
187
|
+
|
|
188
|
+
# Execute in threestudio directory
|
|
189
|
+
process = subprocess.Popen(
|
|
190
|
+
cmd,
|
|
191
|
+
cwd=self.threestudio_path,
|
|
192
|
+
stdout=subprocess.PIPE,
|
|
193
|
+
stderr=subprocess.PIPE,
|
|
194
|
+
text=True
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Wait for process to complete
|
|
198
|
+
stdout, stderr = process.communicate()
|
|
199
|
+
|
|
200
|
+
if process.returncode != 0:
|
|
201
|
+
logger.error(f"Error exporting model: {stderr}")
|
|
202
|
+
raise RuntimeError(f"Model export failed with exit code {process.returncode}")
|
|
203
|
+
|
|
204
|
+
# Find exported files
|
|
205
|
+
exports_dir = os.path.join(output_dir, "exports")
|
|
206
|
+
if not os.path.exists(exports_dir):
|
|
207
|
+
raise FileNotFoundError(f"Exports directory not found: {exports_dir}")
|
|
208
|
+
|
|
209
|
+
exported_files = [os.path.join(exports_dir, f) for f in os.listdir(exports_dir)]
|
|
210
|
+
|
|
211
|
+
return exported_files
|
|
212
|
+
|
|
213
|
+
def _get_preview_images(self, output_dir: str) -> List[str]:
|
|
214
|
+
"""
|
|
215
|
+
Get paths to preview images.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
output_dir: Directory containing the model
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
List of paths to preview images
|
|
222
|
+
"""
|
|
223
|
+
# Find preview images
|
|
224
|
+
previews_dir = os.path.join(output_dir, "images")
|
|
225
|
+
if not os.path.exists(previews_dir):
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
preview_images = [os.path.join(previews_dir, f) for f in os.listdir(previews_dir)
|
|
229
|
+
if f.endswith(".png") or f.endswith(".jpg")]
|
|
230
|
+
|
|
231
|
+
return sorted(preview_images)
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow orchestration for the image-to-model pipeline.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import logging
|
|
7
|
+
import uuid
|
|
8
|
+
from typing import Dict, Any, List, Optional, Tuple
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class ImageToModelPipeline:
|
|
14
|
+
"""
|
|
15
|
+
Orchestrates the workflow from text prompt to 3D model:
|
|
16
|
+
1. Generate image with Venice.ai
|
|
17
|
+
2. Segment object with SAM2
|
|
18
|
+
3. Create 3D model with threestudio
|
|
19
|
+
4. Convert to OpenSCAD for parametric editing
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self,
|
|
23
|
+
venice_generator,
|
|
24
|
+
sam_segmenter,
|
|
25
|
+
threestudio_generator,
|
|
26
|
+
openscad_wrapper,
|
|
27
|
+
output_dir: str = "output/pipeline"):
|
|
28
|
+
"""
|
|
29
|
+
Initialize the pipeline.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
venice_generator: Instance of VeniceImageGenerator
|
|
33
|
+
sam_segmenter: Instance of SAMSegmenter
|
|
34
|
+
threestudio_generator: Instance of ThreeStudioGenerator
|
|
35
|
+
openscad_wrapper: Instance of OpenSCADWrapper
|
|
36
|
+
output_dir: Directory to store output files
|
|
37
|
+
"""
|
|
38
|
+
self.venice_generator = venice_generator
|
|
39
|
+
self.sam_segmenter = sam_segmenter
|
|
40
|
+
self.threestudio_generator = threestudio_generator
|
|
41
|
+
self.openscad_wrapper = openscad_wrapper
|
|
42
|
+
self.output_dir = output_dir
|
|
43
|
+
|
|
44
|
+
# Create output directories
|
|
45
|
+
os.makedirs(os.path.join(output_dir, "images"), exist_ok=True)
|
|
46
|
+
os.makedirs(os.path.join(output_dir, "masks"), exist_ok=True)
|
|
47
|
+
os.makedirs(os.path.join(output_dir, "models"), exist_ok=True)
|
|
48
|
+
os.makedirs(os.path.join(output_dir, "scad"), exist_ok=True)
|
|
49
|
+
|
|
50
|
+
def generate_model_from_text(self, prompt: str,
|
|
51
|
+
venice_params: Optional[Dict[str, Any]] = None,
|
|
52
|
+
sam_params: Optional[Dict[str, Any]] = None,
|
|
53
|
+
threestudio_params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
54
|
+
"""
|
|
55
|
+
Generate a 3D model from a text prompt.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
prompt: Text description for image generation
|
|
59
|
+
venice_params: Optional parameters for Venice.ai
|
|
60
|
+
sam_params: Optional parameters for SAM2
|
|
61
|
+
threestudio_params: Optional parameters for threestudio
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Dictionary containing paths to generated files and metadata
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
# Generate a unique ID for this pipeline run
|
|
68
|
+
pipeline_id = str(uuid.uuid4())
|
|
69
|
+
logger.info(f"Starting pipeline {pipeline_id} for prompt: {prompt}")
|
|
70
|
+
|
|
71
|
+
# Step 1: Generate image with Venice.ai
|
|
72
|
+
image_path = os.path.join(self.output_dir, "images", f"{pipeline_id}.png")
|
|
73
|
+
venice_result = self._generate_image(prompt, image_path, venice_params)
|
|
74
|
+
|
|
75
|
+
# Step 2: Segment object with SAM2
|
|
76
|
+
masks_dir = os.path.join(self.output_dir, "masks", pipeline_id)
|
|
77
|
+
sam_result = self._segment_image(image_path, masks_dir, sam_params)
|
|
78
|
+
|
|
79
|
+
# Get the best mask (highest score or first mask if no scores)
|
|
80
|
+
if "scores" in sam_result and sam_result["scores"]:
|
|
81
|
+
best_mask_idx = sam_result["scores"].index(max(sam_result["scores"]))
|
|
82
|
+
best_mask_path = sam_result["mask_paths"][best_mask_idx]
|
|
83
|
+
else:
|
|
84
|
+
# If no scores available, use the first mask
|
|
85
|
+
best_mask_path = sam_result["mask_paths"][0] if sam_result.get("mask_paths") else None
|
|
86
|
+
|
|
87
|
+
if not best_mask_path:
|
|
88
|
+
raise ValueError("No valid mask generated from segmentation")
|
|
89
|
+
|
|
90
|
+
# Step 3: Create 3D model with threestudio
|
|
91
|
+
threestudio_result = self._generate_3d_model(best_mask_path, threestudio_params)
|
|
92
|
+
|
|
93
|
+
# Step 4: Convert to OpenSCAD for parametric editing
|
|
94
|
+
scad_result = self._convert_to_openscad(threestudio_result["exported_files"][0], pipeline_id)
|
|
95
|
+
|
|
96
|
+
# Compile results
|
|
97
|
+
result = {
|
|
98
|
+
"pipeline_id": pipeline_id,
|
|
99
|
+
"prompt": prompt,
|
|
100
|
+
"image": venice_result,
|
|
101
|
+
"segmentation": sam_result,
|
|
102
|
+
"model_3d": threestudio_result,
|
|
103
|
+
"openscad": scad_result
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
logger.info(f"Pipeline {pipeline_id} completed successfully")
|
|
107
|
+
return result
|
|
108
|
+
except Exception as e:
|
|
109
|
+
logger.error(f"Error in pipeline: {str(e)}")
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
def _generate_image(self, prompt: str, output_path: str,
|
|
113
|
+
params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Generate image with Venice.ai.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
prompt: Text description for image generation
|
|
119
|
+
output_path: Path to save the generated image
|
|
120
|
+
params: Optional parameters for Venice.ai
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dictionary containing image data and metadata
|
|
124
|
+
"""
|
|
125
|
+
logger.info(f"Generating image for prompt: {prompt}")
|
|
126
|
+
|
|
127
|
+
# Default parameters
|
|
128
|
+
default_params = {
|
|
129
|
+
"model": "fluently-xl", # Default to fastest model
|
|
130
|
+
"width": 1024,
|
|
131
|
+
"height": 1024
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Merge with provided parameters
|
|
135
|
+
if params:
|
|
136
|
+
default_params.update(params)
|
|
137
|
+
|
|
138
|
+
# Generate image
|
|
139
|
+
result = self.venice_generator.generate_image(
|
|
140
|
+
prompt=prompt,
|
|
141
|
+
output_path=output_path,
|
|
142
|
+
**default_params
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
logger.info(f"Image generated: {output_path}")
|
|
146
|
+
return result
|
|
147
|
+
|
|
148
|
+
def _segment_image(self, image_path: str, output_dir: str,
|
|
149
|
+
params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
150
|
+
"""
|
|
151
|
+
Segment object with SAM2.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
image_path: Path to input image
|
|
155
|
+
output_dir: Directory to save segmentation results
|
|
156
|
+
params: Optional parameters for SAM2
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Dictionary containing segmentation masks and metadata
|
|
160
|
+
"""
|
|
161
|
+
logger.info(f"Segmenting image: {image_path}")
|
|
162
|
+
|
|
163
|
+
# Segment image with SAM2
|
|
164
|
+
# Check if points are provided in params
|
|
165
|
+
points = params.get("points") if params else None
|
|
166
|
+
|
|
167
|
+
if points:
|
|
168
|
+
result = self.sam_segmenter.segment_image(
|
|
169
|
+
image_path=image_path,
|
|
170
|
+
points=points,
|
|
171
|
+
output_dir=output_dir
|
|
172
|
+
)
|
|
173
|
+
else:
|
|
174
|
+
# Use automatic point generation
|
|
175
|
+
result = self.sam_segmenter.segment_with_auto_points(
|
|
176
|
+
image_path=image_path,
|
|
177
|
+
output_dir=output_dir
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
logger.info(f"Image segmented, {result.get('num_masks', 0)} masks generated")
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
def _generate_3d_model(self, image_path: str,
|
|
184
|
+
params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
|
185
|
+
"""
|
|
186
|
+
Generate 3D model with threestudio.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
image_path: Path to input image
|
|
190
|
+
params: Optional parameters for threestudio
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Dictionary containing paths to generated model files
|
|
194
|
+
"""
|
|
195
|
+
logger.info(f"Generating 3D model from image: {image_path}")
|
|
196
|
+
|
|
197
|
+
# Default parameters
|
|
198
|
+
default_params = {
|
|
199
|
+
"method": "zero123",
|
|
200
|
+
"num_iterations": 5000,
|
|
201
|
+
"export_format": "obj"
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
# Merge with provided parameters
|
|
205
|
+
if params:
|
|
206
|
+
default_params.update(params)
|
|
207
|
+
|
|
208
|
+
# Generate 3D model
|
|
209
|
+
result = self.threestudio_generator.generate_model_from_image(
|
|
210
|
+
image_path=image_path,
|
|
211
|
+
**default_params
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
logger.info(f"3D model generated: {result['exported_files']}")
|
|
215
|
+
return result
|
|
216
|
+
|
|
217
|
+
def _convert_to_openscad(self, model_path: str, model_id: str) -> Dict[str, Any]:
|
|
218
|
+
"""
|
|
219
|
+
Convert 3D model to OpenSCAD format.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
model_path: Path to input model
|
|
223
|
+
model_id: Unique identifier for the model
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Dictionary containing paths to generated files
|
|
227
|
+
"""
|
|
228
|
+
logger.info(f"Converting model to OpenSCAD: {model_path}")
|
|
229
|
+
|
|
230
|
+
# Generate OpenSCAD code for importing the model
|
|
231
|
+
scad_code = f"""// Generated OpenSCAD code for model {model_id}
|
|
232
|
+
// Imported from {os.path.basename(model_path)}
|
|
233
|
+
|
|
234
|
+
// Parameters
|
|
235
|
+
scale_factor = 1.0;
|
|
236
|
+
position_x = 0;
|
|
237
|
+
position_y = 0;
|
|
238
|
+
position_z = 0;
|
|
239
|
+
rotation_x = 0;
|
|
240
|
+
rotation_y = 0;
|
|
241
|
+
rotation_z = 0;
|
|
242
|
+
|
|
243
|
+
// Import and transform the model
|
|
244
|
+
translate([position_x, position_y, position_z])
|
|
245
|
+
rotate([rotation_x, rotation_y, rotation_z])
|
|
246
|
+
scale(scale_factor)
|
|
247
|
+
import("{model_path}");
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
# Save SCAD code to file
|
|
251
|
+
scad_file = self.openscad_wrapper.generate_scad(scad_code, model_id)
|
|
252
|
+
|
|
253
|
+
# Generate previews
|
|
254
|
+
previews = self.openscad_wrapper.generate_multi_angle_previews(scad_file)
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
"scad_file": scad_file,
|
|
258
|
+
"previews": previews,
|
|
259
|
+
"model_path": model_path
|
|
260
|
+
}
|