from __future__ import annotations import copy import math import re from typing import Any TEMPLATE_ID = "flange_sleeve_v1" # These defaults describe the pictured family, but the model may override every # dimension that affects the visible form. The backend owns all CDSL plumbing. DEFAULT_PLAN: dict[str, float | str] = { "name": "Parameterized flange sleeve", "flange_width_mm": 120.0, "flange_height_mm": 120.0, "flange_thickness_mm": 14.0, "corner_chamfer_mm": 10.0, "tube_outer_diameter_mm": 70.0, "tube_straight_length_mm": 95.0, "tip_outer_diameter_mm": 62.0, "tip_length_mm": 28.0, "bore_diameter_mm": 46.0, "boss_outer_diameter_mm": 82.0, "boss_height_mm": 5.0, "mount_hole_diameter_mm": 12.0, "mount_counterbore_diameter_mm": 24.0, "mount_counterbore_depth_mm": 5.0, "mount_hole_u_mm": 42.0, "mount_hole_v_mm": 42.0, } _DIMENSION_FIELDS = tuple(key for key in DEFAULT_PLAN if key != "name") _PART_ID = re.compile(r"^[A-Za-z0-9_-]{3,80}$") def flange_sleeve_plan_schema() -> dict[str, Any]: """Return the compact semantic plan accepted by the flange-sleeve tool.""" properties: dict[str, Any] = { "template": {"const": TEMPLATE_ID}, "name": {"type": "string", "minLength": 1, "maxLength": 120}, "part_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{3,80}$"}, } for field in _DIMENSION_FIELDS: minimum = 0 if field == "corner_chamfer_mm" else 0.001 properties[field] = {"type": "number", "exclusiveMinimum": minimum} if minimum else { "type": "number", "minimum": 0, } return { "type": "object", "properties": properties, "required": ["template"], "additionalProperties": False, } def normalize_flange_sleeve_plan(plan: Any) -> dict[str, float | str]: """Validate only semantic dimensions; do not accept CDSL implementation data.""" if not isinstance(plan, dict): raise ValueError("flange sleeve plan must be a JSON object") if plan.get("template") != TEMPLATE_ID: raise ValueError(f"flange sleeve plan template must be {TEMPLATE_ID}") allowed = {"template", "part_id", *DEFAULT_PLAN} unknown = sorted(str(key) for key in plan if key not in allowed) if unknown: raise ValueError(f"flange sleeve plan has unsupported fields: {', '.join(unknown)}") normalized = copy.deepcopy(DEFAULT_PLAN) for field in _DIMENSION_FIELDS: if field not in plan: continue value = plan[field] if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(float(value)): raise ValueError(f"flange sleeve plan {field} must be a finite number") number = float(value) if number < 0 if field == "corner_chamfer_mm" else number <= 0: comparator = "non-negative" if field == "corner_chamfer_mm" else "greater than zero" raise ValueError(f"flange sleeve plan {field} must be {comparator}") normalized[field] = number name = str(plan.get("name") or normalized["name"]).strip() if not name: raise ValueError("flange sleeve plan name must not be empty") normalized["name"] = name[:120] part_id = str(plan.get("part_id") or "flange_sleeve_template") if not _PART_ID.fullmatch(part_id): raise ValueError("flange sleeve plan part_id must use letters, numbers, underscores, or hyphens") normalized["part_id"] = part_id _validate_dimensions(normalized) return normalized def _validate_dimensions(plan: dict[str, float | str]) -> None: number = lambda field: float(plan[field]) width, height = number("flange_width_mm"), number("flange_height_mm") thickness, chamfer = number("flange_thickness_mm"), number("corner_chamfer_mm") tube_od, tip_od, bore = number("tube_outer_diameter_mm"), number("tip_outer_diameter_mm"), number("bore_diameter_mm") boss_od = number("boss_outer_diameter_mm") hole_od, counterbore_od = number("mount_hole_diameter_mm"), number("mount_counterbore_diameter_mm") counterbore_depth = number("mount_counterbore_depth_mm") hole_u, hole_v = number("mount_hole_u_mm"), number("mount_hole_v_mm") if chamfer * 2 >= min(width, height): raise ValueError("flange sleeve plan corner_chamfer_mm must be less than half the flange side") if not bore < min(tube_od, tip_od): raise ValueError("flange sleeve plan bore_diameter_mm must be smaller than both tube diameters") if not tube_od <= boss_od <= min(width, height): raise ValueError("flange sleeve plan boss_outer_diameter_mm must be between tube diameter and flange side") if counterbore_od < hole_od: raise ValueError("flange sleeve plan mount_counterbore_diameter_mm must not be smaller than mount_hole_diameter_mm") if counterbore_depth > thickness: raise ValueError("flange sleeve plan mount_counterbore_depth_mm must not exceed flange_thickness_mm") radius = counterbore_od / 2 if abs(hole_u) + radius >= width / 2 or abs(hole_v) + radius >= height / 2: raise ValueError("flange sleeve plan mounting counterbores must remain inside the flange boundary") if chamfer and abs(hole_u) > width / 2 - chamfer and abs(hole_v) > height / 2 - chamfer: edge_clearance = (width / 2 - abs(hole_u)) + (height / 2 - abs(hole_v)) if edge_clearance < chamfer + radius * math.sqrt(2): raise ValueError("flange sleeve plan mounting counterbores intersect the corner chamfers") def _x_plane(offset_mm: float, normal_x: float = 1.0) -> dict[str, list[float]]: return { "origin_mm": [offset_mm, 0.0, 0.0], "x_dir": [0.0, 1.0, 0.0], "normal": [normal_x, 0.0, 0.0], } def _flange_profile(width: float, height: float, chamfer: float) -> dict[str, Any]: if chamfer == 0: return {"type": "rectangle", "center": [0.0, 0.0], "width_mm": width, "height_mm": height} half_width, half_height = width / 2, height / 2 return { "type": "polygon", "vertices": [ [-half_width + chamfer, -half_height], [half_width - chamfer, -half_height], [half_width, -half_height + chamfer], [half_width, half_height - chamfer], [half_width - chamfer, half_height], [-half_width + chamfer, half_height], [-half_width, half_height - chamfer], [-half_width, -half_height + chamfer], ], } def build_flange_sleeve_cdsl(plan: Any) -> tuple[dict[str, Any], dict[str, float | str]]: """Build a schema-valid CDSL flange sleeve from semantic, editable parameters.""" values = normalize_flange_sleeve_plan(plan) number = lambda field: float(values[field]) width, height = number("flange_width_mm"), number("flange_height_mm") thickness, chamfer = number("flange_thickness_mm"), number("corner_chamfer_mm") tube_radius, tip_radius, bore_radius = number("tube_outer_diameter_mm") / 2, number("tip_outer_diameter_mm") / 2, number("bore_diameter_mm") / 2 straight, tip_length = number("tube_straight_length_mm"), number("tip_length_mm") boss_radius, boss_height = number("boss_outer_diameter_mm") / 2, number("boss_height_mm") hole_radius, counterbore_radius = number("mount_hole_diameter_mm") / 2, number("mount_counterbore_diameter_mm") / 2 hole_u, hole_v = number("mount_hole_u_mm"), number("mount_hole_v_mm") hole_centers = [[u, v] for u in (-hole_u, hole_u) for v in (-hole_v, hole_v)] cdsl = { "schema": "cad.cdsl.llm.v1", "schema_version": "1.3.0", "part_id": str(values["part_id"]), "kind": "part", "meta": {"name": str(values["name"]), "units": "mm"}, "geometry": {"sketches": [ { "id": "flange_outline", "workplane": _x_plane(-thickness), "profile": _flange_profile(width, height, chamfer), }, { "id": "sleeve_profile", "workplane": { "origin_mm": [0.0, 0.0, 0.0], "x_dir": [1.0, 0.0, 0.0], "normal": [0.0, 0.0, 1.0], }, "profile": { "type": "polygon", "vertices": [ [0.0, bore_radius], [0.0, tube_radius], [straight, tube_radius], [straight + tip_length, tip_radius], [straight + tip_length, bore_radius], ], }, }, { "id": "front_boss_ring", "workplane": _x_plane(0.0), "profile": { "type": "annulus", "inner_radius_mm": bore_radius, "outer_radius_mm": boss_radius, "center": [0.0, 0.0], }, }, { "id": "center_bore", "workplane": _x_plane(-thickness), "profile": {"type": "circle", "radius_mm": bore_radius, "center": [0.0, 0.0]}, }, { "id": "mount_holes", "workplane": _x_plane(-thickness), "profile": { "type": "circles", "items": [{"radius_mm": hole_radius, "center": center} for center in hole_centers], }, }, { "id": "mount_counterbores", "workplane": _x_plane(0.0, -1.0), "profile": { "type": "circles", "items": [{"radius_mm": counterbore_radius, "center": center} for center in hole_centers], }, }, ]}, "features": [ { "id": "flange_plate", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "flange_outline", "params": {"distance_mm": thickness}, }, { "id": "sleeve_body", "atomic_id": "revolve_add", "depends_on": ["flange_plate"], "sketch_id": "sleeve_profile", "params": { "angle_deg": 360.0, "axis": {"origin_mm": [0.0, 0.0, 0.0], "direction": [1.0, 0.0, 0.0]}, }, }, { "id": "front_boss", "atomic_id": "extrude_add_blind", "depends_on": ["flange_plate", "sleeve_body"], "sketch_id": "front_boss_ring", "params": {"distance_mm": boss_height}, }, { "id": "center_bore_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["flange_plate", "sleeve_body", "front_boss"], "sketch_id": "center_bore", "params": {"distance_mm": thickness + straight + tip_length + boss_height + 1.0}, }, { "id": "mount_hole_cuts", "atomic_id": "extrude_cut_blind", "depends_on": ["center_bore_cut"], "sketch_id": "mount_holes", "params": {"distance_mm": thickness + 1.0}, }, { "id": "mount_counterbore_cuts", "atomic_id": "extrude_cut_blind", "depends_on": ["mount_hole_cuts"], "sketch_id": "mount_counterbores", "params": {"distance_mm": number("mount_counterbore_depth_mm")}, }, ], } return cdsl, values