feat: extract workflow bbox services phase 3

This commit is contained in:
2026-04-07 09:42:06 +02:00
parent 8f8d2e68b7
commit 9c93ecef49
6 changed files with 172 additions and 54 deletions
@@ -87,6 +87,18 @@ class AutoPopulateMaterialsResult:
cad_parts: list[str] = field(default_factory=list)
@dataclass(slots=True)
class BBoxResolutionResult:
bbox_data: dict[str, dict[str, float]] | None
source_kind: Literal["glb", "step", "none"]
step_path: str
glb_path: str | None = None
@property
def has_bbox(self) -> bool:
return self.bbox_data is not None
def _emit(emit: EmitFn, order_line_id: str, message: str, level: str | None = None) -> None:
if emit is None:
return
@@ -105,6 +117,86 @@ def _resolve_asset_path(storage_key: str | None) -> Path | None:
return None
def extract_bbox_from_glb(glb_path: str) -> dict[str, dict[str, float]] | None:
"""Extract a bounding box from a GLB file in meters and convert to mm."""
try:
import trimesh
path = Path(glb_path)
if not path.exists():
return None
scene = trimesh.load(str(path), force="scene")
bounds = getattr(scene, "bounds", None)
if bounds is None:
return None
mins, maxs = bounds
dims = maxs - mins
return {
"dimensions_mm": {
"x": round(float(dims[0]) * 1000, 2),
"y": round(float(dims[1]) * 1000, 2),
"z": round(float(dims[2]) * 1000, 2),
},
"bbox_center_mm": {
"x": round(float((mins[0] + maxs[0]) / 2) * 1000, 2),
"y": round(float((mins[1] + maxs[1]) / 2) * 1000, 2),
"z": round(float((mins[2] + maxs[2]) / 2) * 1000, 2),
},
}
except Exception as exc:
logger.debug("extract_bbox_from_glb failed for %s: %s", glb_path, exc)
return None
def extract_bbox_from_step_cadquery(step_path: str) -> dict[str, dict[str, float]] | None:
"""Fallback: extract a bounding box by re-parsing the STEP file via cadquery."""
try:
import cadquery as cq
bb = cq.importers.importStep(step_path).val().BoundingBox()
return {
"dimensions_mm": {
"x": round(bb.xlen, 2),
"y": round(bb.ylen, 2),
"z": round(bb.zlen, 2),
},
"bbox_center_mm": {
"x": round((bb.xmin + bb.xmax) / 2, 2),
"y": round((bb.ymin + bb.ymax) / 2, 2),
"z": round((bb.zmin + bb.zmax) / 2, 2),
},
}
except Exception as exc:
logger.debug("extract_bbox_from_step_cadquery failed for %s: %s", step_path, exc)
return None
def resolve_cad_bbox(
step_path: str,
*,
glb_path: str | None = None,
) -> BBoxResolutionResult:
"""Resolve CAD bounding-box data with the legacy GLB-first fallback order."""
bbox_data = None
source_kind: Literal["glb", "step", "none"] = "none"
if glb_path:
bbox_data = extract_bbox_from_glb(glb_path)
if bbox_data:
source_kind = "glb"
if bbox_data is None:
bbox_data = extract_bbox_from_step_cadquery(step_path)
if bbox_data:
source_kind = "step"
return BBoxResolutionResult(
bbox_data=bbox_data,
source_kind=source_kind,
step_path=step_path,
glb_path=glb_path,
)
def prepare_order_line_render_context(
session: Session,
order_line_id: str,