解决冲突并完成合并
This commit is contained in:
@@ -6,22 +6,19 @@ The package exposes the CDSL-only rebuild API used by the backend Agent.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from .convert_to_cdsl import convert_sw_json_to_cdsl, write_cdsl_outputs
|
||||
from .llm_compiler import compile_cdsl
|
||||
from .llm_engine import run_engine_plan
|
||||
from .rebuild import compare_with_gold, compile_cdsl_to_pack, run_cdsl_only, run_engine, run_rebuild
|
||||
from .semantic_validation import validate_semantic_cdsl
|
||||
from .sketch_solver import SHAPE_GENERATORS, resolve_all_sketches, resolve_required_sketches
|
||||
from .sketch_solver import CORE_SHAPE_GENERATORS, resolve_all_sketches, resolve_required_sketches
|
||||
from .runtime import ALL_ATOMIC_IDS, EXECUTORS, RuntimeExecutionError, analyze_cdsl, rebuild_cdsl
|
||||
from .design_intent import DesignIntentError, design_intent_from_cdsl, validate_design_intent, validate_intent_cdsl
|
||||
|
||||
# The package-level runtime contract is the session executor registry. The
|
||||
# older llm_engine dispatcher remains available only for legacy engine packs.
|
||||
SUPPORTED_ATOMIC_IDS = ALL_ATOMIC_IDS
|
||||
SHAPE_GENERATORS = CORE_SHAPE_GENERATORS
|
||||
|
||||
__all__ = [
|
||||
"convert_sw_json_to_cdsl",
|
||||
"write_cdsl_outputs",
|
||||
"compile_cdsl",
|
||||
"compile_cdsl_to_pack",
|
||||
"run_engine_plan",
|
||||
@@ -31,6 +28,7 @@ __all__ = [
|
||||
"compare_with_gold",
|
||||
"resolve_all_sketches",
|
||||
"resolve_required_sketches",
|
||||
"CORE_SHAPE_GENERATORS",
|
||||
"SHAPE_GENERATORS",
|
||||
"SUPPORTED_ATOMIC_IDS",
|
||||
"validate_semantic_cdsl",
|
||||
@@ -39,10 +37,6 @@ __all__ = [
|
||||
"rebuild_cdsl",
|
||||
"analyze_cdsl",
|
||||
"RuntimeExecutionError",
|
||||
"DesignIntentError",
|
||||
"validate_design_intent",
|
||||
"validate_intent_cdsl",
|
||||
"design_intent_from_cdsl",
|
||||
]
|
||||
|
||||
__version__ = "1.0.0"
|
||||
|
||||
@@ -13,6 +13,7 @@ from collections import Counter
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles
|
||||
from .semantic_validation import validate_semantic_cdsl
|
||||
|
||||
|
||||
@@ -225,7 +226,7 @@ def analyze_document(
|
||||
"failure_category": "preflight_blocked",
|
||||
}
|
||||
try:
|
||||
cdsl = json.loads(path.read_text(encoding="utf-8"))
|
||||
cdsl = lower_legacy_profiles(json.loads(path.read_text(encoding="utf-8")))
|
||||
report["part_id"] = str(cdsl.get("part_id") or report["part_id"])
|
||||
semantic = validate_semantic_cdsl(cdsl)
|
||||
report["semantic_valid"] = True
|
||||
|
||||
@@ -9,10 +9,12 @@ from build123d import Axis, Edge, Face, Plane, Solid, Vector, Wire, export_step
|
||||
|
||||
from .runtime_types import AxisSpec, HoleSpec, PlaneSpec, TopologyRecord, Vector3, canonical_plane_signature
|
||||
|
||||
|
||||
def _vector(value: list[float] | tuple[float, float, float]) -> Vector:
|
||||
# 将三元坐标(list 或 tuple)转换为 build123d 的 Vector 对象。
|
||||
return Vector(float(value[0]), float(value[1]), float(value[2]))
|
||||
|
||||
|
||||
def _arc_midpoint(edge: dict[str, Any], start: Vector, end: Vector, center: Vector) -> Vector:
|
||||
# 计算圆弧中点(配合 Edge.make_three_point_arc 三点画弧),支持显式法向与顺时针/逆时针方向。
|
||||
# 1. 半径:优先取 edge.radius_mm,缺省时由圆心到起点的距离推算。
|
||||
@@ -46,6 +48,7 @@ def _arc_midpoint(edge: dict[str, Any], start: Vector, end: Vector, center: Vect
|
||||
radius_vector = first.normalized() * radius
|
||||
return center + radius_vector * math.cos(half) + normal.cross(radius_vector) * math.sin(half)
|
||||
|
||||
|
||||
class Build123dGeometryAdapter:
|
||||
"""All B-rep construction and mutation lives in this adapter."""
|
||||
|
||||
|
||||
@@ -249,7 +249,8 @@
|
||||
"stable_id": {"type": "string", "minLength": 1},
|
||||
"owner_feature_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"geometry": {"type": "object"},
|
||||
"source": {"enum": ["solidworks", "inferred_from_step"]},
|
||||
"source": {"enum": ["solidworks", "inferred_from_step", "runtime_snapshot", "viewer_selection"]},
|
||||
"snapshot_id": {"type": "string", "minLength": 1},
|
||||
"confidence": {"type": "number", "minimum": 0, "maximum": 1}
|
||||
},
|
||||
"required": ["kind", "stable_id", "source", "confidence"],
|
||||
@@ -258,24 +259,18 @@
|
||||
"analyticSegment": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"enum": ["line", "arc", "circle", "bspline"]},
|
||||
"type": {"enum": ["line", "arc", "circle"]},
|
||||
"start": {"$ref": "#/$defs/point2"},
|
||||
"end": {"$ref": "#/$defs/point2"},
|
||||
"center": {"$ref": "#/$defs/point2"},
|
||||
"radius_mm": {"$ref": "#/$defs/positive"},
|
||||
"clockwise": {"type": "boolean"},
|
||||
"degree": {"type": "integer", "minimum": 1},
|
||||
"control_points": {"type": "array", "items": {"$ref": "#/$defs/point2"}},
|
||||
"knots": {"type": "array", "items": {"$ref": "#/$defs/number"}},
|
||||
"weights": {"type": "array", "items": {"$ref": "#/$defs/positive"}},
|
||||
"periodic": {"type": "boolean"}
|
||||
"clockwise": {"type": "boolean"}
|
||||
},
|
||||
"required": ["type"],
|
||||
"allOf": [
|
||||
{"if": {"properties": {"type": {"const": "line"}}}, "then": {"required": ["start", "end"]}},
|
||||
{"if": {"properties": {"type": {"const": "arc"}}}, "then": {"required": ["start", "end", "center", "radius_mm"]}},
|
||||
{"if": {"properties": {"type": {"const": "circle"}}}, "then": {"required": ["center", "radius_mm"]}},
|
||||
{"if": {"properties": {"type": {"const": "bspline"}}}, "then": {"required": ["degree", "control_points", "knots"]}}
|
||||
{"if": {"properties": {"type": {"const": "circle"}}}, "then": {"required": ["center", "radius_mm"]}}
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
@@ -334,87 +329,11 @@
|
||||
{"if": {"properties": {"atomic_id": {"const": "hole_wizard"}}}, "then": {"properties": {"params": {"$ref": "#/$defs/holeWizardParams"}}}}
|
||||
]
|
||||
},
|
||||
"rectangleBounds": {
|
||||
"type": "object",
|
||||
"properties": {"center": {"$ref": "#/$defs/point2"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "min_mm": {"$ref": "#/$defs/point2"}, "max_mm": {"$ref": "#/$defs/point2"}},
|
||||
"allOf": [
|
||||
{"oneOf": [
|
||||
{"required": ["center", "width_mm", "height_mm"], "not": {"anyOf": [{"required": ["min_mm"]}, {"required": ["max_mm"]}]}},
|
||||
{"required": ["min_mm", "max_mm"], "not": {"anyOf": [{"required": ["center"]}, {"required": ["width_mm"]}, {"required": ["height_mm"]}]}}
|
||||
]}
|
||||
]
|
||||
},
|
||||
"rectangleBoundary": {
|
||||
"type": "object",
|
||||
"properties": {"type": {"enum": ["rectangle", "rectangle_with_fillets"]}, "center": {"$ref": "#/$defs/point2"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "min_mm": {"$ref": "#/$defs/point2"}, "max_mm": {"$ref": "#/$defs/point2"}, "fillet_radius_mm": {"$ref": "#/$defs/positive"}},
|
||||
"allOf": [{"$ref": "#/$defs/rectangleBounds"}],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"profile_type": {"enum": ["circle", "annulus", "circles", "circle_grid", "rectangle", "rectangle_with_circles", "rectangle_with_fillets", "obround", "polygon", "ibone", "rectangle_with_symmetric_notches", "revolve_chamfer", "revolve_chamfer_slanted", "circle_with_arc_notches", "circular_sector_slot", "circle_with_radial_tabs", "filleted_rect_side_slots", "d_shape", "partial_ring", "partial_ring_with_arc_island", "radial_slot", "arc_chain", "patterned_cutouts", "compound_patterned_cutouts", "analytic_contours"]},
|
||||
"motif_type": {"enum": ["circle", "square", "rectangle", "obround", "cross", "d_shape_polygon", "regular_hexagon", "skew_hexagon", "triangle", "teardrop_polygon", "trapezoid", "annular_sector_polygon"]},
|
||||
"layout_type": {"enum": ["ring", "angular", "concentric_rings", "disc_grid", "open_arc", "spiral", "cross_lines", "x_field", "twin_strips", "corner_clusters", "diamond_field"]},
|
||||
"motif": {
|
||||
"oneOf": [
|
||||
{"type": "object", "properties": {"type": {"const": "circle"}, "radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "square"}, "width_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "rectangle"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "width_mm", "height_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "obround"}, "length_mm": {"$ref": "#/$defs/positive"}, "width_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "length_mm", "width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "cross"}, "size_mm": {"$ref": "#/$defs/positive"}, "arm_width_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "size_mm", "arm_width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "d_shape_polygon"}, "stem_length_mm": {"$ref": "#/$defs/positive"}, "nose_depth_mm": {"$ref": "#/$defs/positive"}, "half_height_mm": {"$ref": "#/$defs/positive"}, "arc_segments": {"$ref": "#/$defs/positiveInteger"}}, "required": ["type", "stem_length_mm", "nose_depth_mm", "half_height_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "regular_hexagon"}, "radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "skew_hexagon"}, "nominal_radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "nominal_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "triangle"}, "radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "teardrop_polygon"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "shoulder_fraction": {"type": "number", "exclusiveMinimum": 0, "exclusiveMaximum": 1}, "left_width_mm": {"$ref": "#/$defs/positive"}, "right_width_mm": {"$ref": "#/$defs/positive"}, "tip_height_mm": {"$ref": "#/$defs/positive"}, "bottom_depth_mm": {"$ref": "#/$defs/positive"}, "shoulder_height_mm": {"$ref": "#/$defs/positive"}}, "required": ["type"], "oneOf": [{"required": ["width_mm", "height_mm"], "not": {"anyOf": [{"required": ["left_width_mm"]}, {"required": ["right_width_mm"]}, {"required": ["tip_height_mm"]}, {"required": ["bottom_depth_mm"]}, {"required": ["shoulder_height_mm"]}]}}, {"required": ["left_width_mm", "right_width_mm", "tip_height_mm", "bottom_depth_mm", "shoulder_height_mm"], "not": {"anyOf": [{"required": ["width_mm"]}, {"required": ["height_mm"]}, {"required": ["shoulder_fraction"]}]}}], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "trapezoid"}, "bottom_width_mm": {"$ref": "#/$defs/positive"}, "top_width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "bottom_width_mm", "top_width_mm", "height_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "annular_sector_polygon"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "half_angle_deg": {"$ref": "#/$defs/positive"}, "arc_segments": {"$ref": "#/$defs/positiveInteger"}}, "required": ["type", "inner_radius_mm", "outer_radius_mm", "half_angle_deg"], "additionalProperties": false}
|
||||
]
|
||||
},
|
||||
"layoutCommon": {
|
||||
"type": "object",
|
||||
"properties": {"orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}
|
||||
},
|
||||
"layout": {
|
||||
"oneOf": [
|
||||
{"type": "object", "properties": {"type": {"const": "ring"}, "radius_mm": {"$ref": "#/$defs/positive"}, "count": {"$ref": "#/$defs/positiveInteger"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "angle_step_deg": {"$ref": "#/$defs/number"}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm", "count"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "angular"}, "radius_mm": {"$ref": "#/$defs/number"}, "count": {"$ref": "#/$defs/positiveInteger"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "angle_step_deg": {"$ref": "#/$defs/number"}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "count"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "concentric_rings"}, "rings": {"type": "array", "minItems": 1, "items": {"type": "object", "properties": {"radius_mm": {"$ref": "#/$defs/positive"}, "count": {"$ref": "#/$defs/positiveInteger"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "angle_step_deg": {"$ref": "#/$defs/number"}}, "required": ["radius_mm", "count"], "additionalProperties": false}}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "rings"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "disc_grid"}, "count_x": {"$ref": "#/$defs/positiveInteger"}, "count_y": {"$ref": "#/$defs/positiveInteger"}, "spacing_x_mm": {"$ref": "#/$defs/positive"}, "spacing_y_mm": {"$ref": "#/$defs/positive"}, "center_mm": {"$ref": "#/$defs/point2"}, "max_center_radius_mm": {"$ref": "#/$defs/positive"}, "orientation_offset_deg": {"$ref": "#/$defs/number"}}, "required": ["type", "count_x", "count_y", "spacing_x_mm", "spacing_y_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "open_arc"}, "radius_mm": {"$ref": "#/$defs/positive"}, "count": {"$ref": "#/$defs/positiveInteger"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "end_angle_deg": {"$ref": "#/$defs/number"}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm", "count", "start_angle_deg", "end_angle_deg"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "spiral"}, "count": {"$ref": "#/$defs/positiveInteger"}, "start_radius_mm": {"$ref": "#/$defs/positive"}, "radius_step_mm": {"$ref": "#/$defs/number"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "angle_step_deg": {"$ref": "#/$defs/number"}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "count", "start_radius_mm", "radius_step_mm", "angle_step_deg"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "cross_lines"}, "count_per_axis": {"$ref": "#/$defs/positiveInteger"}, "spacing_mm": {"$ref": "#/$defs/positive"}, "orientation_offset_deg": {"$ref": "#/$defs/number"}}, "required": ["type", "count_per_axis", "spacing_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "x_field"}, "levels": {"$ref": "#/$defs/positiveInteger"}, "spacing_mm": {"$ref": "#/$defs/positive"}, "orientation": {"enum": ["fixed", "radial", "tangential", "snapped_radial", "diagonal_axes"]}, "orientation_offset_deg": {"$ref": "#/$defs/number"}, "orientation_snap_deg": {"$ref": "#/$defs/positive"}}, "required": ["type", "levels", "spacing_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "twin_strips"}, "x_offset_mm": {"$ref": "#/$defs/positive"}, "count_y": {"$ref": "#/$defs/positiveInteger"}, "y_start_mm": {"$ref": "#/$defs/number"}, "y_end_mm": {"$ref": "#/$defs/number"}, "orientation_offset_deg": {"$ref": "#/$defs/number"}}, "required": ["type", "x_offset_mm", "count_y", "y_start_mm", "y_end_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "corner_clusters"}, "levels_mm": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/positive"}}, "orientation_offset_deg": {"$ref": "#/$defs/number"}}, "required": ["type", "levels_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "diamond_field"}, "manhattan_radius": {"type": "integer", "minimum": 0}, "spacing_mm": {"$ref": "#/$defs/positive"}, "orientation_offset_deg": {"$ref": "#/$defs/number"}}, "required": ["type", "manhattan_radius", "spacing_mm"], "additionalProperties": false}
|
||||
]
|
||||
},
|
||||
"pattern": {"type": "object", "properties": {"motif": {"$ref": "#/$defs/motif"}, "layout": {"$ref": "#/$defs/layout"}}, "required": ["motif", "layout"], "additionalProperties": false},
|
||||
"profile_type": {"enum": ["circle", "polygon", "analytic_contours"]},
|
||||
"profile": {
|
||||
"oneOf": [
|
||||
{"type": "object", "properties": {"type": {"const": "circle"}, "center": {"$ref": "#/$defs/point2"}, "radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "annulus"}, "center": {"$ref": "#/$defs/point2"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "inner_radius_mm", "outer_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "circles"}, "items": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "items"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "circle_grid"}, "radius_mm": {"$ref": "#/$defs/positive"}, "count_x": {"$ref": "#/$defs/positiveInteger"}, "count_y": {"$ref": "#/$defs/positiveInteger"}, "spacing_x_mm": {"$ref": "#/$defs/positive"}, "spacing_y_mm": {"$ref": "#/$defs/positive"}, "origin_mm": {"$ref": "#/$defs/point2"}, "center_mm": {"$ref": "#/$defs/point2"}}, "required": ["type", "radius_mm", "count_x", "count_y", "spacing_x_mm", "spacing_y_mm"], "additionalProperties": false, "oneOf": [{"required": ["origin_mm"], "not": {"required": ["center_mm"]}}, {"required": ["center_mm"], "not": {"required": ["origin_mm"]}}]},
|
||||
{"type": "object", "properties": {"type": {"const": "rectangle"}, "center": {"$ref": "#/$defs/point2"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "min_mm": {"$ref": "#/$defs/point2"}, "max_mm": {"$ref": "#/$defs/point2"}}, "required": ["type"], "allOf": [{"$ref": "#/$defs/rectangleBounds"}], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "rectangle_with_circles"}, "boundary": {"$ref": "#/$defs/rectangleBoundary"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "boundary"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "rectangle_with_fillets"}, "center": {"$ref": "#/$defs/point2"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "min_mm": {"$ref": "#/$defs/point2"}, "max_mm": {"$ref": "#/$defs/point2"}, "fillet_radius_mm": {"$ref": "#/$defs/positive"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type"], "allOf": [{"$ref": "#/$defs/rectangleBounds"}], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "obround"}, "center": {"$ref": "#/$defs/point2"}, "length_mm": {"$ref": "#/$defs/positive"}, "width_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "length_mm", "width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "polygon"}, "vertices": {"type": "array", "minItems": 3, "items": {"$ref": "#/$defs/point2"}}}, "required": ["type", "vertices"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "ibone"}, "body_width_mm": {"$ref": "#/$defs/positive"}, "body_height_mm": {"$ref": "#/$defs/positive"}, "flange_width_mm": {"$ref": "#/$defs/positive"}, "flange_height_mm": {"$ref": "#/$defs/positive"}, "corner_radius_mm": {"$ref": "#/$defs/positive"}, "hole_radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["type", "body_width_mm", "body_height_mm", "flange_width_mm", "flange_height_mm", "corner_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "rectangle_with_symmetric_notches"}, "width_mm": {"$ref": "#/$defs/positive"}, "height_mm": {"$ref": "#/$defs/positive"}, "notch": {"type": "object", "properties": {"y_start": {"$ref": "#/$defs/number"}, "y_end": {"$ref": "#/$defs/number"}, "depth_mm": {"$ref": "#/$defs/positive"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "corner_radius_mm": {"$ref": "#/$defs/positive"}}, "required": ["y_start", "y_end", "depth_mm"], "additionalProperties": false}}, "required": ["type", "width_mm", "height_mm", "notch"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "revolve_chamfer"}, "axis_height_mm": {"$ref": "#/$defs/positive"}, "top_width_mm": {"$ref": "#/$defs/positive"}, "bottom_width_mm": {"$ref": "#/$defs/positive"}, "wall_inset_mm": {"$ref": "#/$defs/number"}, "step_inset_mm": {"$ref": "#/$defs/number"}, "on_axis_side": {"enum": ["left", "right"]}}, "required": ["type", "axis_height_mm", "top_width_mm", "bottom_width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "revolve_chamfer_slanted"}, "axis_height_mm": {"$ref": "#/$defs/positive"}, "top_width_mm": {"$ref": "#/$defs/positive"}, "wall_inset_mm": {"$ref": "#/$defs/number"}, "wall_height_mm": {"$ref": "#/$defs/positive"}, "wall_width_mm": {"$ref": "#/$defs/positive"}, "on_axis_side": {"enum": ["left", "right"]}}, "required": ["type", "axis_height_mm", "top_width_mm", "wall_height_mm", "wall_width_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "circle_with_arc_notches"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "notch_radius_mm": {"$ref": "#/$defs/positive"}, "notch_angles_deg": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/number"}}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "outer_radius_mm", "notch_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "circular_sector_slot"}, "arc_radius_mm": {"$ref": "#/$defs/positive"}, "slot_half_width_mm": {"$ref": "#/$defs/positive"}, "chord_half_mm": {"$ref": "#/$defs/positive"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "arc_radius_mm", "slot_half_width_mm", "chord_half_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "circle_with_radial_tabs"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "tab_u_half_mm": {"$ref": "#/$defs/positive"}, "tab_v_offset_mm": {"$ref": "#/$defs/number"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "outer_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "filleted_rect_side_slots"}, "half_width_mm": {"$ref": "#/$defs/positive"}, "half_height_mm": {"$ref": "#/$defs/positive"}, "corner_radius_mm": {"$ref": "#/$defs/positive"}, "slot_radius_mm": {"$ref": "#/$defs/positive"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "half_width_mm", "half_height_mm", "corner_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "d_shape"}, "radius_mm": {"$ref": "#/$defs/positive"}, "chord_sign": {"enum": ["left", "right"]}, "chord_x_mm": {"$ref": "#/$defs/positive"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "partial_ring"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "half_angle_deg": {"$ref": "#/$defs/positive"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "inner_radius_mm", "outer_radius_mm"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "partial_ring_with_arc_island"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "half_angle_deg": {"$ref": "#/$defs/positive"}, "island_radius_mm": {"$ref": "#/$defs/positive"}, "island_gap_mm": {"$ref": "#/$defs/positive"}, "center_angles_deg": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/number"}}, "replicas": {"type": "array", "minItems": 1, "items": {"type": "object", "properties": {"center_angle_deg": {"$ref": "#/$defs/number"}}, "required": ["center_angle_deg"], "additionalProperties": false}}}, "required": ["type", "inner_radius_mm", "outer_radius_mm", "island_radius_mm"], "additionalProperties": false, "oneOf": [{"required": ["center_angles_deg"], "not": {"required": ["replicas"]}}, {"required": ["replicas"], "not": {"required": ["center_angles_deg"]}}]},
|
||||
{"type": "object", "properties": {"type": {"const": "radial_slot"}, "inner_radius_mm": {"$ref": "#/$defs/positive"}, "outer_radius_mm": {"$ref": "#/$defs/positive"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "end_angle_deg": {"$ref": "#/$defs/number"}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "inner_radius_mm", "outer_radius_mm", "start_angle_deg", "end_angle_deg"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "arc_chain"}, "arcs": {"type": "array", "minItems": 2, "items": {"type": "object", "properties": {"center": {"$ref": "#/$defs/point2"}, "radius_mm": {"$ref": "#/$defs/positive"}, "start_angle_deg": {"$ref": "#/$defs/number"}, "end_angle_deg": {"$ref": "#/$defs/number"}}, "required": ["radius_mm", "start_angle_deg", "end_angle_deg"], "additionalProperties": false}}, "circles": {"type": "array", "items": {"$ref": "#/$defs/circleItem"}}}, "required": ["type", "arcs"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "patterned_cutouts"}, "motif": {"$ref": "#/$defs/motif"}, "layout": {"$ref": "#/$defs/layout"}}, "required": ["type", "motif", "layout"], "additionalProperties": false},
|
||||
{"type": "object", "properties": {"type": {"const": "compound_patterned_cutouts"}, "patterns": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/pattern"}}}, "required": ["type", "patterns"], "additionalProperties": false},
|
||||
{"$ref": "#/$defs/analyticProfile"}
|
||||
]
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,331 +0,0 @@
|
||||
"""Validation for the semantic planning artifact between a request and CDSL."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
from jsonschema.exceptions import SchemaError
|
||||
|
||||
|
||||
class DesignIntentError(ValueError):
|
||||
"""A plan-contract violation with a stable error code for the Agent."""
|
||||
|
||||
def __init__(self, code: str, message: str) -> None:
|
||||
super().__init__(message)
|
||||
self.code = code
|
||||
|
||||
|
||||
def _schema_path(engine: Any) -> Path:
|
||||
return Path(str(engine.__file__)).with_name("design_intent_schema.json")
|
||||
|
||||
|
||||
def load_design_intent_schema(engine: Any) -> dict[str, Any]:
|
||||
path = _schema_path(engine)
|
||||
try:
|
||||
schema = json.loads(path.read_text(encoding="utf-8"))
|
||||
Draft202012Validator.check_schema(schema)
|
||||
except (OSError, json.JSONDecodeError, SchemaError) as error:
|
||||
raise RuntimeError("The local DesignIntent JSON Schema is unavailable or invalid") from error
|
||||
return schema
|
||||
|
||||
|
||||
def _location(error: Any) -> str:
|
||||
return "$" + "".join(
|
||||
f"[{item}]" if isinstance(item, int) else f".{item}"
|
||||
for item in error.absolute_path
|
||||
)
|
||||
|
||||
|
||||
def _schema_validate(intent: dict[str, Any], engine: Any) -> None:
|
||||
errors = sorted(
|
||||
Draft202012Validator(load_design_intent_schema(engine)).iter_errors(intent),
|
||||
key=lambda error: (list(error.absolute_path), error.message),
|
||||
)
|
||||
if errors:
|
||||
error = errors[0]
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"DesignIntent schema violation at {_location(error)}: {error.message}",
|
||||
)
|
||||
|
||||
|
||||
def _runtime_capabilities(engine: Any) -> tuple[set[str], set[str], dict[str, dict[str, Any]]]:
|
||||
try:
|
||||
profile_schema = json.loads(Path(str(engine.__file__)).with_name("profile_schema.json").read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as error:
|
||||
raise RuntimeError("The local CDSL profile schema is unavailable or invalid") from error
|
||||
atomic_ids = set(profile_schema.get("runtime_supported_atomic_ids") or getattr(engine, "SUPPORTED_ATOMIC_IDS", ()))
|
||||
profiles = {
|
||||
name
|
||||
for name, contract in (profile_schema.get("profiles") or {}).items()
|
||||
if isinstance(contract, dict) and contract.get("agent_allowed") is True
|
||||
}
|
||||
atomic_contracts = {
|
||||
str(atomic_id): contract
|
||||
for atomic_id, contract in (profile_schema.get("feature_atomic_ids") or {}).items()
|
||||
if isinstance(contract, dict)
|
||||
}
|
||||
return {str(item) for item in atomic_ids}, {str(item) for item in profiles}, atomic_contracts
|
||||
|
||||
|
||||
def _blocking(intent: dict[str, Any]) -> bool:
|
||||
questions = intent.get("open_questions") or []
|
||||
gaps = intent.get("capability_gaps") or []
|
||||
return any(isinstance(item, dict) and item.get("blocking") is True for item in [*questions, *gaps])
|
||||
|
||||
|
||||
def validate_design_intent(
|
||||
intent: dict[str, Any],
|
||||
engine: Any,
|
||||
*,
|
||||
current_revision_id: str = "",
|
||||
) -> dict[str, Any]:
|
||||
"""Validate DesignIntent syntax and semantic ordering against runtime capability."""
|
||||
if not isinstance(intent, dict):
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent must be a JSON object")
|
||||
_schema_validate(intent, engine)
|
||||
|
||||
normalized = deepcopy(intent)
|
||||
mode = str(normalized["mode"])
|
||||
base_revision_id = str(normalized["base_revision_id"] or "")
|
||||
if mode == "create" and base_revision_id:
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "create DesignIntent must not set base_revision_id")
|
||||
if mode == "revise":
|
||||
if not base_revision_id:
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "revise DesignIntent requires base_revision_id")
|
||||
if current_revision_id and base_revision_id != current_revision_id:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
"revise DesignIntent base_revision_id must be the current successful revision",
|
||||
)
|
||||
|
||||
structures = normalized["structures"]
|
||||
ids = [str(item["id"]) for item in structures]
|
||||
feature_ids = [str(item["cdsl_feature_id"]) for item in structures]
|
||||
if len(set(ids)) != len(ids):
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent structure ids must be unique")
|
||||
if len(set(feature_ids)) != len(feature_ids):
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent cdsl_feature_id values must be unique")
|
||||
|
||||
feature_order = [str(item) for item in normalized["feature_order"]]
|
||||
if len(set(feature_order)) != len(feature_order) or set(feature_order) != set(ids):
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
"feature_order must list every DesignIntent structure exactly once",
|
||||
)
|
||||
order = {structure_id: index for index, structure_id in enumerate(feature_order)}
|
||||
atomic_ids, profile_types, atomic_contracts = _runtime_capabilities(engine)
|
||||
for structure in structures:
|
||||
structure_id = str(structure["id"])
|
||||
for dependency in structure["depends_on"]:
|
||||
dependency_id = str(dependency)
|
||||
if dependency_id not in order:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Structure {structure_id} depends on an unknown structure {dependency_id}",
|
||||
)
|
||||
if order[dependency_id] >= order[structure_id]:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Structure {structure_id} must depend only on earlier feature_order entries",
|
||||
)
|
||||
strategy = structure["cdsl_strategy"]
|
||||
atomic_id = str(strategy["atomic_id"])
|
||||
if atomic_id not in atomic_ids:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Structure {structure_id} uses unsupported atomic_id {atomic_id}",
|
||||
)
|
||||
profile_type = str(strategy.get("profile_type") or "")
|
||||
if atomic_contracts.get(atomic_id, {}).get("requires_sketch") and not profile_type:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Structure {structure_id} uses {atomic_id} and requires a profile_type",
|
||||
)
|
||||
if profile_type and profile_type not in profile_types:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Structure {structure_id} uses unsupported or agent-disallowed profile_type {profile_type}",
|
||||
)
|
||||
|
||||
if normalized["status"] == "ready" and _blocking(normalized):
|
||||
raise DesignIntentError(
|
||||
"DESIGN_INTENT_BLOCKED",
|
||||
"A DesignIntent with blocking questions or capability gaps must need clarification",
|
||||
)
|
||||
if normalized["status"] == "needs_clarification" and not _blocking(normalized):
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
"needs_clarification requires a blocking question or capability gap",
|
||||
)
|
||||
if normalized["status"] == "ready" and not structures:
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready DesignIntent needs at least one structure")
|
||||
if normalized["status"] == "ready" and mode == "create" and not any(item["role"] == "base" for item in structures):
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready create DesignIntent needs a base structure")
|
||||
nonblocking_gaps = [item for item in normalized["capability_gaps"] if not item["blocking"]]
|
||||
if nonblocking_gaps and not normalized["assumptions"]:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
"Non-blocking capability gaps require an explicit approximation assumption",
|
||||
)
|
||||
for gap in normalized["capability_gaps"]:
|
||||
if str(gap["structure_id"]) not in set(ids):
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"Capability gap references an unknown structure {gap['structure_id']}",
|
||||
)
|
||||
valid_expectation_ids = set(ids) | set(feature_ids)
|
||||
for expectation in normalized["verification_expectations"]:
|
||||
if expectation["type"] == "feature_count" and str(expectation["feature_id"]) not in valid_expectation_ids:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
f"feature_count expectation references an unknown structure or CDSL feature {expectation['feature_id']}",
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def _has_selector_evidence(value: Any, role: str) -> bool:
|
||||
if isinstance(value, dict):
|
||||
for key, child in value.items():
|
||||
if role in {"selector", "selectors"} and key == "selectors" and isinstance(child, list) and child:
|
||||
return True
|
||||
if key == role and child not in (None, "", [], {}):
|
||||
if not (isinstance(child, dict) and set(child) == {"unresolved"}):
|
||||
return True
|
||||
if _has_selector_evidence(child, role):
|
||||
return True
|
||||
elif isinstance(value, list):
|
||||
return any(_has_selector_evidence(item, role) for item in value)
|
||||
return False
|
||||
|
||||
|
||||
def validate_intent_cdsl(
|
||||
intent: dict[str, Any],
|
||||
cdsl: dict[str, Any],
|
||||
engine: Any,
|
||||
*,
|
||||
current_cdsl: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Ensure an executable CDSL document faithfully realizes one accepted plan."""
|
||||
normalized = validate_design_intent(intent, engine)
|
||||
if normalized["status"] != "ready":
|
||||
raise DesignIntentError("DESIGN_INTENT_BLOCKED", "CDSL generation is blocked until the DesignIntent is ready")
|
||||
if not isinstance(cdsl, dict):
|
||||
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must be a JSON object")
|
||||
features = cdsl.get("features")
|
||||
sketches = (cdsl.get("geometry") or {}).get("sketches")
|
||||
if not isinstance(features, list) or not isinstance(sketches, list):
|
||||
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must contain features and sketches")
|
||||
feature_by_id = {str(feature.get("id") or ""): feature for feature in features if isinstance(feature, dict)}
|
||||
if len(feature_by_id) != len(features):
|
||||
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL features must have unique ids")
|
||||
structure_by_id = {str(item["id"]): item for item in normalized["structures"]}
|
||||
expected_feature_ids = [str(structure_by_id[item]["cdsl_feature_id"]) for item in normalized["feature_order"]]
|
||||
if normalized["mode"] == "revise" and isinstance(current_cdsl, dict):
|
||||
base_feature_ids = {
|
||||
str(feature.get("id") or "")
|
||||
for feature in current_cdsl.get("features") or []
|
||||
if isinstance(feature, dict) and feature.get("id")
|
||||
}
|
||||
if not base_feature_ids.issubset(set(expected_feature_ids)):
|
||||
missing = ", ".join(sorted(base_feature_ids - set(expected_feature_ids)))
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
f"revise DesignIntent must explicitly preserve current features: {missing}",
|
||||
)
|
||||
actual_feature_ids = [str(feature.get("id") or "") for feature in features]
|
||||
if actual_feature_ids != expected_feature_ids:
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
"CDSL feature ids and order must exactly match DesignIntent feature_order",
|
||||
)
|
||||
sketch_by_id = {str(sketch.get("id") or ""): sketch for sketch in sketches if isinstance(sketch, dict)}
|
||||
feature_for_structure = {
|
||||
str(structure["id"]): feature_by_id[str(structure["cdsl_feature_id"])]
|
||||
for structure in normalized["structures"]
|
||||
}
|
||||
for structure in normalized["structures"]:
|
||||
structure_id = str(structure["id"])
|
||||
feature = feature_for_structure[structure_id]
|
||||
strategy = structure["cdsl_strategy"]
|
||||
if feature.get("atomic_id") != strategy["atomic_id"]:
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
f"CDSL feature {feature.get('id')} atomic_id does not match structure {structure_id}",
|
||||
)
|
||||
expected_dependencies = {
|
||||
str(structure_by_id[dependency]["cdsl_feature_id"])
|
||||
for dependency in structure["depends_on"]
|
||||
}
|
||||
actual_dependencies = {str(item) for item in feature.get("depends_on") or []}
|
||||
if not expected_dependencies.issubset(actual_dependencies):
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
f"CDSL feature {feature.get('id')} is missing planned dependencies",
|
||||
)
|
||||
profile_type = str(strategy.get("profile_type") or "")
|
||||
if profile_type:
|
||||
sketch_id = str(feature.get("sketch_id") or "")
|
||||
sketch = sketch_by_id.get(sketch_id)
|
||||
actual_profile_type = str(((sketch or {}).get("profile") or {}).get("type") or "")
|
||||
if actual_profile_type != profile_type:
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
f"CDSL feature {feature.get('id')} must use profile_type {profile_type}",
|
||||
)
|
||||
for selector_role in strategy["selector_roles"]:
|
||||
if not _has_selector_evidence(feature, str(selector_role)):
|
||||
raise DesignIntentError(
|
||||
"INTENT_CDSL_MISMATCH",
|
||||
f"CDSL feature {feature.get('id')} is missing selector evidence for {selector_role}",
|
||||
)
|
||||
|
||||
|
||||
def design_intent_from_cdsl(cdsl: dict[str, Any], *, request: str, mode: str = "create", base_revision_id: str = "") -> dict[str, Any]:
|
||||
"""Create a conservative audit-only plan for legacy revisions when needed."""
|
||||
sketches = (cdsl.get("geometry") or {}).get("sketches") or []
|
||||
profile_by_sketch_id = {
|
||||
str(sketch.get("id") or ""): str((sketch.get("profile") or {}).get("type") or "")
|
||||
for sketch in sketches
|
||||
if isinstance(sketch, dict)
|
||||
}
|
||||
structures: list[dict[str, Any]] = []
|
||||
for index, feature in enumerate(cdsl.get("features") or []):
|
||||
if not isinstance(feature, dict):
|
||||
continue
|
||||
feature_id = str(feature.get("id") or f"feature_{index + 1}")
|
||||
atomic_id = str(feature.get("atomic_id") or "")
|
||||
profile_type = profile_by_sketch_id.get(str(feature.get("sketch_id") or ""), "")
|
||||
strategy: dict[str, Any] = {
|
||||
"atomic_id": atomic_id,
|
||||
"parameter_roles": sorted(str(key) for key in (feature.get("params") or {}).keys()),
|
||||
"selector_roles": [],
|
||||
}
|
||||
if profile_type:
|
||||
strategy["profile_type"] = profile_type
|
||||
structures.append({
|
||||
"id": feature_id,
|
||||
"cdsl_feature_id": feature_id,
|
||||
"role": "base" if index == 0 else "subtractive" if "cut" in atomic_id or atomic_id.startswith("hole") else "additive",
|
||||
"purpose": str(feature.get("name") or feature_id),
|
||||
"depends_on": [str(item) for item in feature.get("depends_on") or []],
|
||||
"cdsl_strategy": strategy,
|
||||
})
|
||||
return {
|
||||
"schema": "cad.cdsl.design-intent.v1",
|
||||
"schema_version": "1.0",
|
||||
"mode": mode,
|
||||
"request": request,
|
||||
"base_revision_id": base_revision_id,
|
||||
"structures": structures,
|
||||
"feature_order": [item["id"] for item in structures],
|
||||
"assumptions": ["Synthesized from a legacy CDSL revision."],
|
||||
"open_questions": [],
|
||||
"capability_gaps": [],
|
||||
"verification_expectations": [],
|
||||
"status": "ready",
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://cdsl.local/schema/cad.cdsl.design-intent.v1",
|
||||
"title": "CDSL design intent",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"schema": {"const": "cad.cdsl.design-intent.v1"},
|
||||
"schema_version": {"const": "1.0"},
|
||||
"mode": {"enum": ["create", "revise"]},
|
||||
"request": {"type": "string", "minLength": 1},
|
||||
"base_revision_id": {"type": "string"},
|
||||
"structures": {"type": "array", "items": {"$ref": "#/$defs/structure"}},
|
||||
"feature_order": {"type": "array", "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"}},
|
||||
"assumptions": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
||||
"open_questions": {"type": "array", "items": {"$ref": "#/$defs/openQuestion"}},
|
||||
"capability_gaps": {"type": "array", "items": {"$ref": "#/$defs/capabilityGap"}},
|
||||
"verification_expectations": {"type": "array", "items": {"$ref": "#/$defs/verificationExpectation"}},
|
||||
"status": {"enum": ["ready", "needs_clarification"]},
|
||||
"intent_id": {"type": "string", "pattern": "^intent_[a-z0-9]{12}$"},
|
||||
"created_at": {"type": "string", "minLength": 1},
|
||||
"part_skill_ids": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
||||
"part_skill_selection": {"type": "object"}
|
||||
},
|
||||
"required": [
|
||||
"schema",
|
||||
"schema_version",
|
||||
"mode",
|
||||
"request",
|
||||
"base_revision_id",
|
||||
"structures",
|
||||
"feature_order",
|
||||
"assumptions",
|
||||
"open_questions",
|
||||
"capability_gaps",
|
||||
"verification_expectations",
|
||||
"status"
|
||||
],
|
||||
"additionalProperties": false,
|
||||
"$defs": {
|
||||
"structure": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"cdsl_feature_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"role": {"enum": ["base", "reference", "additive", "subtractive", "dressup", "pattern"]},
|
||||
"purpose": {"type": "string", "minLength": 1},
|
||||
"depends_on": {"type": "array", "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"}},
|
||||
"cdsl_strategy": {"$ref": "#/$defs/cdslStrategy"}
|
||||
},
|
||||
"required": ["id", "cdsl_feature_id", "role", "purpose", "depends_on", "cdsl_strategy"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"cdslStrategy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"atomic_id": {"type": "string", "minLength": 1},
|
||||
"profile_type": {"type": "string", "minLength": 1},
|
||||
"parameter_roles": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
||||
"selector_roles": {"type": "array", "items": {"type": "string", "minLength": 1}}
|
||||
},
|
||||
"required": ["atomic_id", "parameter_roles", "selector_roles"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"openQuestion": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"question": {"type": "string", "minLength": 1},
|
||||
"blocking": {"type": "boolean"}
|
||||
},
|
||||
"required": ["id", "question", "blocking"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"capabilityGap": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,100}$"},
|
||||
"structure_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"message": {"type": "string", "minLength": 1},
|
||||
"blocking": {"type": "boolean"}
|
||||
},
|
||||
"required": ["code", "structure_id", "message", "blocking"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"verificationExpectation": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"const": "feature_count"},
|
||||
"feature_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"expected": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"required": ["type", "feature_id", "expected"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {"type": {"const": "symmetry"}, "axis": {"enum": ["x", "y", "z"]}},
|
||||
"required": ["type", "axis"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {"const": "bbox"},
|
||||
"expected_mm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number", "exclusiveMinimum": 0},
|
||||
"y": {"type": "number", "exclusiveMinimum": 0},
|
||||
"z": {"type": "number", "exclusiveMinimum": 0}
|
||||
},
|
||||
"required": ["x", "y", "z"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["type", "expected_mm"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,349 +0,0 @@
|
||||
"""将 output3 的程序化圆柱样本蒸馏为自足的短 CDSL。
|
||||
|
||||
这些样本的 SolidWorks 历史把每个切口存成独立草图/切除特征。本脚本按
|
||||
文件名中的设计族选择一个可复用的 motif + layout 语义描述;不会复制
|
||||
草图 entities、逐切口坐标、compiler_context 或任何编码后的几何。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
SPECS: dict[str, dict[str, Any]] = {
|
||||
"cylinder_001": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 16.0, "outer_radius_mm": 25.0, "half_angle_deg": 4.583662},
|
||||
"layout": {"type": "angular", "count": 12, "start_angle_deg": 15.0, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_002": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 8.0, "outer_radius_mm": 12.5, "half_angle_deg": 4.583662},
|
||||
"layout": {"type": "angular", "count": 12, "start_angle_deg": 15.0, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_003": {
|
||||
"motif": {"type": "circle", "radius_mm": 2.4},
|
||||
"layout": {"type": "ring", "radius_mm": 34.0, "count": 32},
|
||||
},
|
||||
"cylinder_004": {
|
||||
"motif": {"type": "square", "width_mm": 3.3},
|
||||
"layout": {"type": "ring", "radius_mm": 32.0, "count": 24},
|
||||
},
|
||||
"cylinder_005": {
|
||||
"motif": {"type": "square", "width_mm": 4.242641},
|
||||
"layout": {"type": "ring", "radius_mm": 32.0, "count": 24},
|
||||
},
|
||||
"cylinder_006": {
|
||||
"motif": {"type": "circle", "radius_mm": 2.2},
|
||||
"layout": {
|
||||
"type": "disc_grid", "count_x": 7, "count_y": 7,
|
||||
"spacing_x_mm": 10.0, "spacing_y_mm": 10.0,
|
||||
"center_mm": [0.0, 0.0], "max_center_radius_mm": 36.1,
|
||||
},
|
||||
},
|
||||
"cylinder_007": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 12.0, "outer_radius_mm": 20.0, "half_angle_deg": 4.583662},
|
||||
"layout": {
|
||||
"type": "concentric_rings", "orientation": "radial",
|
||||
"rings": [
|
||||
{"type": "angular", "count": 12, "start_angle_deg": 15.0},
|
||||
{"type": "angular", "count": 24, "start_angle_deg": 7.5},
|
||||
],
|
||||
},
|
||||
"ring_motifs": [
|
||||
{"inner_radius_mm": 12.0, "outer_radius_mm": 20.0, "half_angle_deg": 4.583662, "count": 12, "start_angle_deg": 15.0},
|
||||
{"inner_radius_mm": 28.0, "outer_radius_mm": 40.0, "half_angle_deg": 2.864789, "count": 24, "start_angle_deg": 7.5},
|
||||
],
|
||||
},
|
||||
"cylinder_008": {
|
||||
"motif": {"type": "obround", "length_mm": 7.0, "width_mm": 2.8},
|
||||
"layout": {"type": "ring", "radius_mm": 34.0, "count": 24, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_009": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 10.0, "outer_radius_mm": 42.0, "half_angle_deg": 3.437747},
|
||||
"layout": {"type": "angular", "count": 12, "start_angle_deg": 15.0, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_010": {
|
||||
"motif": {"type": "circle", "radius_mm": 2.3},
|
||||
"layout": {
|
||||
"type": "concentric_rings",
|
||||
"rings": [
|
||||
{"radius_mm": 18.0, "count": 12, "start_angle_deg": 0.0},
|
||||
{"radius_mm": 36.0, "count": 24, "start_angle_deg": 0.0},
|
||||
],
|
||||
},
|
||||
},
|
||||
"cylinder_011": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 10.0, "outer_radius_mm": 20.0, "half_angle_deg": 4.010705},
|
||||
"layout": {"type": "angular", "count": 8, "start_angle_deg": 22.5, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_012": {
|
||||
"motif": {"type": "circle", "radius_mm": 1.9},
|
||||
"layout": {"type": "ring", "radius_mm": 26.0, "count": 24},
|
||||
},
|
||||
"cylinder_013": {
|
||||
"motif": {"type": "square", "width_mm": 2.7},
|
||||
"layout": {"type": "ring", "radius_mm": 25.0, "count": 16},
|
||||
},
|
||||
"cylinder_014": {
|
||||
"motif": {"type": "square", "width_mm": 3.535534},
|
||||
"layout": {"type": "ring", "radius_mm": 25.0, "count": 16},
|
||||
},
|
||||
"cylinder_015": {
|
||||
"motif": {"type": "circle", "radius_mm": 1.8},
|
||||
"layout": {
|
||||
"type": "disc_grid", "count_x": 6, "count_y": 5,
|
||||
"spacing_x_mm": 9.0, "spacing_y_mm": 9.0,
|
||||
"center_mm": [0.0, 0.0],
|
||||
},
|
||||
},
|
||||
"cylinder_016": {
|
||||
"motif": {"type": "d_shape_polygon", "stem_length_mm": 1.9, "nose_depth_mm": 1.9, "half_height_mm": 1.3775},
|
||||
"layout": {
|
||||
"type": "open_arc", "radius_mm": 35.0, "count": 18,
|
||||
"start_angle_deg": 18.0, "end_angle_deg": 262.8,
|
||||
},
|
||||
},
|
||||
"cylinder_017": {
|
||||
"motif": {"type": "cross", "size_mm": 4.62, "arm_width_mm": 2.9568},
|
||||
"layout": {
|
||||
"type": "open_arc", "radius_mm": 31.0, "count": 14,
|
||||
"start_angle_deg": 189.0, "end_angle_deg": 387.0,
|
||||
},
|
||||
},
|
||||
"cylinder_018": {
|
||||
"motif": {"type": "obround", "length_mm": 5.75, "width_mm": 2.3},
|
||||
"layout": {"type": "ring", "radius_mm": 27.0, "count": 16, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_019": {
|
||||
"motif": {"type": "circle", "radius_mm": 1.8},
|
||||
"layout": {
|
||||
"type": "concentric_rings",
|
||||
"rings": [
|
||||
{"radius_mm": 14.0, "count": 8, "start_angle_deg": 0.0},
|
||||
{"radius_mm": 30.0, "count": 16, "start_angle_deg": 0.0},
|
||||
],
|
||||
},
|
||||
},
|
||||
"cylinder_020": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 9.0, "outer_radius_mm": 36.0, "half_angle_deg": 3.151268},
|
||||
"layout": {"type": "angular", "count": 10, "start_angle_deg": 18.0, "orientation": "radial"},
|
||||
},
|
||||
"cylinder_021": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 12.0, "outer_radius_mm": 20.0, "half_angle_deg": 4.010705},
|
||||
"layout": {"type": "angular", "count": 8, "start_angle_deg": 22.5, "orientation": "radial"},
|
||||
"ring_motifs": [
|
||||
{"inner_radius_mm": 12.0, "outer_radius_mm": 20.0, "half_angle_deg": 4.010705, "count": 8, "start_angle_deg": 22.5},
|
||||
{"inner_radius_mm": 28.0, "outer_radius_mm": 39.0, "half_angle_deg": 2.57831, "count": 16, "start_angle_deg": 11.25},
|
||||
],
|
||||
},
|
||||
"cylinder_022": {
|
||||
"motif": {"type": "skew_hexagon", "nominal_radius_mm": 1.875},
|
||||
"layout": {
|
||||
"type": "spiral", "count": 28, "start_radius_mm": 8.054,
|
||||
"radius_step_mm": 0.851, "start_angle_deg": -0.105, "angle_step_deg": 33.2394,
|
||||
"orientation": "snapped_radial", "orientation_snap_deg": 45.0,
|
||||
"orientation_offset_deg": 0.0,
|
||||
},
|
||||
},
|
||||
"cylinder_023": {
|
||||
"motif": {"type": "skew_hexagon", "nominal_radius_mm": 1.9},
|
||||
"layout": {
|
||||
"type": "cross_lines", "count_per_axis": 6, "spacing_mm": 13.6,
|
||||
"orientation_offset_deg": 0.0,
|
||||
},
|
||||
},
|
||||
"cylinder_024": {
|
||||
"motif": {"type": "triangle", "radius_mm": 2.3},
|
||||
"layout": {
|
||||
"type": "x_field", "levels": 5, "spacing_mm": 11.0,
|
||||
"orientation": "diagonal_axes",
|
||||
},
|
||||
},
|
||||
"cylinder_025": {
|
||||
"motif": {
|
||||
"type": "teardrop_polygon",
|
||||
"left_width_mm": 1.116, "right_width_mm": 1.548,
|
||||
"tip_height_mm": 2.79, "bottom_depth_mm": 1.476,
|
||||
"shoulder_height_mm": 1.242,
|
||||
},
|
||||
"layout": {
|
||||
"type": "twin_strips", "x_offset_mm": 15.0, "count_y": 12,
|
||||
"y_start_mm": -34.0, "y_end_mm": 34.0,
|
||||
},
|
||||
},
|
||||
"cylinder_026": {
|
||||
"motif": {"type": "trapezoid", "bottom_width_mm": 4.32, "top_width_mm": 2.376, "height_mm": 3.24},
|
||||
"layout": {
|
||||
"type": "concentric_rings", "orientation": "radial",
|
||||
"rings": [
|
||||
{"radius_mm": 12.0, "count": 8, "start_angle_deg": 0.0},
|
||||
{"radius_mm": 31.0, "count": 16, "start_angle_deg": 0.0},
|
||||
],
|
||||
},
|
||||
},
|
||||
"cylinder_027": {
|
||||
"motif": {"type": "d_shape_polygon", "stem_length_mm": 1.8, "nose_depth_mm": 1.8, "half_height_mm": 1.305},
|
||||
"layout": {"type": "corner_clusters", "levels_mm": [12.0, 18.5, 25.0]},
|
||||
},
|
||||
"cylinder_028": {
|
||||
"motif": {"type": "square", "width_mm": 2.969848},
|
||||
"layout": {"type": "diamond_field", "manhattan_radius": 3, "spacing_mm": 8.0},
|
||||
},
|
||||
"cylinder_029": {
|
||||
"motif": {"type": "annular_sector_polygon", "inner_radius_mm": 11.0, "outer_radius_mm": 22.0, "half_angle_deg": 4.010705},
|
||||
"layout": {"type": "angular", "count": 10, "start_angle_deg": 18.0, "orientation": "radial"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _pattern_sketches(spec: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
base = {
|
||||
"id": "sketch_001",
|
||||
"name": "草图1",
|
||||
"workplane": {
|
||||
"origin_mm": [0.0, 0.0, 0.0],
|
||||
"x_dir": [1.0, 0.0, 0.0],
|
||||
"y_dir": [0.0, 1.0, 0.0],
|
||||
"normal": [0.0, 0.0, 1.0],
|
||||
},
|
||||
"profile": {"type": "circle", "center": [0.0, 0.0], "radius_mm": 50.0},
|
||||
}
|
||||
cut_workplane = {
|
||||
"origin_mm": [0.0, 0.0, 20.0],
|
||||
"x_dir": [1.0, 0.0, 0.0],
|
||||
"y_dir": [0.0, 1.0, 0.0],
|
||||
"normal": [0.0, 0.0, 1.0],
|
||||
}
|
||||
|
||||
ring_motifs = spec.get("ring_motifs")
|
||||
if not ring_motifs:
|
||||
cut = {
|
||||
"id": "sketch_002",
|
||||
"name": "图案草图",
|
||||
"workplane": cut_workplane,
|
||||
"profile": {
|
||||
"type": "patterned_cutouts",
|
||||
"motif": spec["motif"],
|
||||
"layout": spec["layout"],
|
||||
},
|
||||
}
|
||||
return [base, cut]
|
||||
|
||||
# 两组扇区的内外半径不同,仍合并成一个切除特征;组合只包含
|
||||
# 两个有名字的程序化子图案,不保存任何逐实例坐标。
|
||||
cut = {
|
||||
"id": "sketch_002",
|
||||
"name": "双环图案草图",
|
||||
"workplane": cut_workplane,
|
||||
"profile": {
|
||||
"type": "compound_patterned_cutouts",
|
||||
"patterns": [
|
||||
{
|
||||
"motif": {
|
||||
"type": "annular_sector_polygon",
|
||||
"inner_radius_mm": item["inner_radius_mm"],
|
||||
"outer_radius_mm": item["outer_radius_mm"],
|
||||
"half_angle_deg": item["half_angle_deg"],
|
||||
},
|
||||
"layout": {
|
||||
"type": "angular",
|
||||
"count": item["count"],
|
||||
"start_angle_deg": item["start_angle_deg"],
|
||||
"orientation": "radial",
|
||||
},
|
||||
}
|
||||
for item in ring_motifs
|
||||
],
|
||||
},
|
||||
}
|
||||
return [base, cut]
|
||||
|
||||
|
||||
def make_cdsl(source: Path, spec: dict[str, Any]) -> dict[str, Any]:
|
||||
part_id = source.name.removesuffix(".solidworks_evidence_v2.json")
|
||||
return {
|
||||
"schema": "cad.cdsl.llm.v1",
|
||||
"schema_version": "1.0.0",
|
||||
"kind": "part",
|
||||
"part_id": part_id,
|
||||
"features": [
|
||||
{
|
||||
"id": "f01",
|
||||
"atomic_id": "extrude_add_blind",
|
||||
"depends_on": [],
|
||||
"name": "凸台-拉伸1",
|
||||
"params": {"distance_mm": 20.0},
|
||||
"sketch_id": "sketch_001",
|
||||
},
|
||||
{
|
||||
"id": "f02",
|
||||
"atomic_id": "extrude_cut_blind",
|
||||
"depends_on": ["f01"],
|
||||
"name": "程序化图案切除",
|
||||
"params": {"distance_mm": 8.0, "reverse": True},
|
||||
"sketch_id": "sketch_002",
|
||||
},
|
||||
],
|
||||
"geometry": {"sketches": _pattern_sketches(spec)},
|
||||
"meta": {
|
||||
"source": source.name,
|
||||
"parameterized_sketches": 2,
|
||||
"profile_from_sketches": 0,
|
||||
"notes": [
|
||||
"CDSL-only drawable: repeated source sketches are represented by one procedural motif and layout",
|
||||
"No compiler_context, source entities, per-instance coordinates, or encoded geometry",
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def distill(input_dir: Path, output_dir: Path) -> list[Path]:
|
||||
sources = sorted(input_dir.glob("cylinder_*.solidworks_evidence_v2.json"))
|
||||
written: list[Path] = []
|
||||
missing: list[str] = []
|
||||
for source in sources:
|
||||
key = source.name[:12]
|
||||
spec = SPECS.get(key)
|
||||
if spec is None:
|
||||
missing.append(source.name)
|
||||
continue
|
||||
part_id = source.name.removesuffix(".solidworks_evidence_v2.json")
|
||||
part_dir = output_dir / part_id
|
||||
part_dir.mkdir(parents=True, exist_ok=True)
|
||||
out = part_dir / f"{part_id}.cdsl.json"
|
||||
text = json.dumps(make_cdsl(source, spec), ensure_ascii=False, indent=2)
|
||||
# 与 b005/b006 一致:短标量数组保持单行,结构数组仍按层级展开。
|
||||
scalar_array = re.compile(
|
||||
r"\[\n(?P<body>(?:[ \t]+(?:-?\d+(?:\.\d+)?|true|false|null|\"[^\"\\n]*\"),?\n)+)[ \t]*\]"
|
||||
)
|
||||
|
||||
def compact(match: re.Match[str]) -> str:
|
||||
values = [line.strip().rstrip(",") for line in match.group("body").splitlines()]
|
||||
inline = "[" + ", ".join(values) + "]"
|
||||
return inline if len(inline) <= 100 else match.group(0)
|
||||
|
||||
text = scalar_array.sub(compact, text)
|
||||
out.write_text(
|
||||
text + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
written.append(out)
|
||||
if missing:
|
||||
raise RuntimeError("No semantic specification for: " + ", ".join(missing))
|
||||
return written
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input_dir", type=Path)
|
||||
parser.add_argument("--out", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
paths = distill(args.input_dir, args.out)
|
||||
print(f"wrote {len(paths)} CDSL files to {args.out}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -11,6 +11,7 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from .capabilities import sketch_ids_required_by_contract
|
||||
from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles
|
||||
from .semantic_validation import validate_semantic_cdsl
|
||||
from .sketch_solver import resolve_required_sketches
|
||||
|
||||
@@ -21,7 +22,7 @@ P3_ATOMIC_IDS = frozenset({
|
||||
})
|
||||
P4_ATOMIC_IDS = P3_ATOMIC_IDS | frozenset({"hole_wizard"})
|
||||
P6_ATOMIC_IDS = P4_ATOMIC_IDS | frozenset({"pattern_linear", "pattern_mirror"})
|
||||
P3_PROFILE_TYPES = frozenset({"analytic_contours", "circle", "circles", "annulus"})
|
||||
P3_PROFILE_TYPES = frozenset({"analytic_contours", "circle", "polygon"})
|
||||
# Static pool membership asks whether the exported history is an extrude/
|
||||
# revolve history. Whether a first cut has a preceding active body remains a
|
||||
# runtime preflight question, not a reason to erase it from the input pool.
|
||||
@@ -66,6 +67,9 @@ def is_static_phase_ready(cdsl: dict[str, Any], phase: str) -> bool:
|
||||
allowed = _PHASE_ATOMIC_IDS.get(phase)
|
||||
if allowed is None:
|
||||
raise ValueError(f"Unknown CDSL runtime phase {phase!r}")
|
||||
# Corpus pools retain historical profiles on disk but evaluate them after
|
||||
# the importer compatibility lowering used by the batch path.
|
||||
cdsl = lower_legacy_profiles(cdsl)
|
||||
semantic = validate_semantic_cdsl(cdsl)
|
||||
if semantic["unresolved"]:
|
||||
return False
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"schema": "cdsl.engine.schema.v1",
|
||||
"schema_version": "1.3.0",
|
||||
"cdsl_json_schema_file": "cdsl_schema.json",
|
||||
"maintenance_rule": "The semantic CDSL contract is a superset of the current runtime. runtime_supported_atomic_ids and runtime_supported_profiles must stay synchronized with runtime.py EXECUTORS, sketch_solver.py SHAPE_GENERATORS, and the package-level capability tests. Legacy llm_compiler.py and llm_engine.py are not the CDSL-only runtime contract.",
|
||||
"maintenance_rule": "The CDSL-only runtime contract is limited to direct generic profiles and runtime.py EXECUTORS. Legacy macro profiles are importer compatibility syntax and must be lowered by cdsl_importer.legacy_profile_adapter before generic runtime validation.",
|
||||
"coordinate_convention": "All profile dimensions use millimetres. Two-dimensional points are [u, v] in the sketch workplane.",
|
||||
"runtime_supported_atomic_ids": ["extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "revolve_add", "revolve_cut", "hole_blind", "hole_countersink", "hole_counterbore", "sphere_add", "reference_plane", "reference_axis", "hole_wizard", "fillet", "chamfer", "pattern_linear", "pattern_mirror"],
|
||||
"runtime_supported_profiles": ["circle", "annulus", "circles", "circle_grid", "rectangle", "rectangle_with_circles", "rectangle_with_fillets", "obround", "polygon", "ibone", "rectangle_with_symmetric_notches", "revolve_chamfer", "revolve_chamfer_slanted", "circle_with_arc_notches", "circular_sector_slot", "circle_with_radial_tabs", "filleted_rect_side_slots", "d_shape", "partial_ring", "partial_ring_with_arc_island", "arc_chain", "radial_slot", "patterned_cutouts", "compound_patterned_cutouts", "analytic_contours", "complex_arc_shape", "unknown_shape"],
|
||||
"runtime_supported_profiles": ["circle", "polygon", "analytic_contours"],
|
||||
"feature_atomic_ids": {
|
||||
"extrude_add_blind": {"summary": "Add the closed profile by one signed extrusion distance.", "required_params": ["distance_mm"], "optional_params": ["reverse"], "requires_sketch": true},
|
||||
"extrude_add_two_sided": {"summary": "Add the closed profile with independently captured forward and reverse terminations.", "required_params": ["distance_mm", "reverse_distance_mm"], "optional_params": ["reverse", "end_condition", "reverse_end_condition"], "requires_sketch": true},
|
||||
@@ -26,202 +26,18 @@
|
||||
},
|
||||
"profiles": {
|
||||
"circle": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A single circular closed profile.",
|
||||
"required": ["radius_mm"],
|
||||
"optional": ["center"],
|
||||
"constraints": ["radius_mm > 0", "center defaults to [0, 0]"]
|
||||
},
|
||||
"annulus": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A concentric ring.",
|
||||
"required": ["inner_radius_mm", "outer_radius_mm"],
|
||||
"optional": ["center"],
|
||||
"constraints": ["0 < inner_radius_mm < outer_radius_mm"]
|
||||
},
|
||||
"circles": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A non-empty list of circles.",
|
||||
"required": ["items"],
|
||||
"item_schema": {"required": ["radius_mm"], "optional": ["center"]}
|
||||
},
|
||||
"circle_grid": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A rectangular grid of equal circles.",
|
||||
"required": ["radius_mm", "count_x", "count_y", "spacing_x_mm", "spacing_y_mm"],
|
||||
"optional": ["origin_mm", "center_mm"],
|
||||
"constraints": ["radius_mm > 0", "count_x and count_y are integers >= 1", "use origin_mm or center_mm, not both"]
|
||||
},
|
||||
"rectangle": {
|
||||
"agent_allowed": true,
|
||||
"summary": "An axis-aligned rectangle.",
|
||||
"one_of": [["center", "width_mm", "height_mm"], ["min_mm", "max_mm"]],
|
||||
"constraints": ["width_mm > 0 and height_mm > 0 when using center"]
|
||||
},
|
||||
"rectangle_with_circles": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A rectangle or filleted rectangle with optional internal circles.",
|
||||
"required": ["boundary"],
|
||||
"optional": ["circles"],
|
||||
"nested": {"boundary": "rectangle schema plus optional type=rectangle|rectangle_with_fillets and fillet_radius_mm"}
|
||||
},
|
||||
"rectangle_with_fillets": {
|
||||
"agent_allowed": true,
|
||||
"summary": "An axis-aligned rectangle with corner fillets and optional internal circles.",
|
||||
"one_of": [["center", "width_mm", "height_mm"], ["min_mm", "max_mm"]],
|
||||
"optional": ["fillet_radius_mm", "circles"],
|
||||
"constraints": ["fillet_radius_mm defaults to 0"]
|
||||
},
|
||||
"obround": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A horizontal capsule/slot.",
|
||||
"required": ["length_mm", "width_mm"],
|
||||
"optional": ["center"],
|
||||
"constraints": ["length_mm > 0", "width_mm > 0", "center defaults to [0, 0]"]
|
||||
},
|
||||
"polygon": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A closed straight-edge polygon.",
|
||||
"required": ["vertices"],
|
||||
"constraints": ["vertices contains at least three [u, v] points"]
|
||||
},
|
||||
"ibone": {
|
||||
"agent_allowed": true,
|
||||
"summary": "I-shaped lug with optional four holes.",
|
||||
"required": ["body_width_mm", "body_height_mm", "flange_width_mm", "flange_height_mm", "corner_radius_mm"],
|
||||
"optional": ["hole_radius_mm"]
|
||||
},
|
||||
"rectangle_with_symmetric_notches": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A rectangular plate with four symmetric side notches.",
|
||||
"required": ["width_mm", "height_mm", "notch"],
|
||||
"nested": {"notch": {"required": ["y_start", "y_end", "depth_mm"], "optional": ["inner_radius_mm", "corner_radius_mm"]}}
|
||||
},
|
||||
"revolve_chamfer": {
|
||||
"agent_allowed": true,
|
||||
"summary": "Five-edge trapezoid profile for a revolve cut.",
|
||||
"required": ["axis_height_mm", "top_width_mm", "bottom_width_mm"],
|
||||
"optional": ["wall_inset_mm", "step_inset_mm", "on_axis_side"],
|
||||
"constraints": ["on_axis_side is left or right"]
|
||||
},
|
||||
"revolve_chamfer_slanted": {
|
||||
"agent_allowed": true,
|
||||
"summary": "Five-edge slanted profile for a revolve cut.",
|
||||
"required": ["axis_height_mm", "top_width_mm", "wall_height_mm", "wall_width_mm"],
|
||||
"optional": ["wall_inset_mm", "on_axis_side"],
|
||||
"constraints": ["on_axis_side is left or right"]
|
||||
},
|
||||
"circle_with_arc_notches": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A circular boundary with arc-shaped notches.",
|
||||
"required": ["outer_radius_mm", "notch_radius_mm"],
|
||||
"optional": ["notch_angles_deg", "circles"],
|
||||
"constraints": ["notch_angles_deg defaults to [0, 90, 180, 270]"]
|
||||
},
|
||||
"circular_sector_slot": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A circular-sector boundary with a central rectangular slot.",
|
||||
"required": ["arc_radius_mm", "slot_half_width_mm", "chord_half_mm"],
|
||||
"optional": ["circles"]
|
||||
},
|
||||
"circle_with_radial_tabs": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A circle with two mirrored radial tabs.",
|
||||
"required": ["outer_radius_mm"],
|
||||
"optional": ["tab_u_half_mm", "tab_v_offset_mm", "circles"]
|
||||
},
|
||||
"filleted_rect_side_slots": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A filleted rectangle with one semicircular slot at each side centre.",
|
||||
"required": ["half_width_mm", "half_height_mm", "corner_radius_mm"],
|
||||
"optional": ["slot_radius_mm", "circles"]
|
||||
},
|
||||
"d_shape": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A D-shaped boundary made from one chord and one arc.",
|
||||
"required": ["radius_mm"],
|
||||
"optional": ["chord_sign", "chord_x_mm", "circles"],
|
||||
"constraints": ["chord_sign is left or right"]
|
||||
},
|
||||
"partial_ring": {
|
||||
"agent_allowed": true,
|
||||
"summary": "An annular sector.",
|
||||
"required": ["inner_radius_mm", "outer_radius_mm"],
|
||||
"optional": ["half_angle_deg", "circles"],
|
||||
"constraints": ["0 < inner_radius_mm < outer_radius_mm"]
|
||||
},
|
||||
"partial_ring_with_arc_island": {
|
||||
"agent_allowed": true,
|
||||
"summary": "One or more annular sectors with an arc island.",
|
||||
"required": ["inner_radius_mm", "outer_radius_mm", "island_radius_mm"],
|
||||
"optional": ["half_angle_deg", "island_gap_mm", "center_angles_deg", "replicas"],
|
||||
"nested": {"replicas": {"required": ["center_angle_deg"]}}
|
||||
},
|
||||
"arc_chain": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A closed chain of two or more arcs, commonly used for revolve sections.",
|
||||
"required": ["arcs"],
|
||||
"optional": ["circles"],
|
||||
"item_schema": {"required": ["radius_mm", "start_angle_deg", "end_angle_deg"], "optional": ["center"]},
|
||||
"constraints": ["arcs contains at least two endpoint-connected arcs"]
|
||||
},
|
||||
"radial_slot": {
|
||||
"agent_allowed": true,
|
||||
"summary": "A rounded annular sector slot.",
|
||||
"required": ["inner_radius_mm", "outer_radius_mm", "start_angle_deg", "end_angle_deg"],
|
||||
"optional": ["circles"],
|
||||
"constraints": ["0 < inner_radius_mm < outer_radius_mm"]
|
||||
},
|
||||
"patterned_cutouts": {
|
||||
"agent_allowed": true,
|
||||
"summary": "One procedural cutout motif repeated by one procedural layout.",
|
||||
"required": ["motif", "layout"],
|
||||
"nested": {"motif": "See motif_types", "layout": "See layout_types"}
|
||||
},
|
||||
"compound_patterned_cutouts": {
|
||||
"agent_allowed": true,
|
||||
"summary": "Multiple procedural motif/layout pairs merged into one cutout sketch.",
|
||||
"required": ["patterns"],
|
||||
"nested": {"patterns": "Non-empty list of {motif, layout}; see patterned_cutouts."}
|
||||
},
|
||||
"complex_arc_shape": {
|
||||
"agent_allowed": false,
|
||||
"summary": "Legacy compiler-context fallback only. Never generate it."
|
||||
},
|
||||
"analytic_contours": {
|
||||
"agent_allowed": false,
|
||||
"summary": "Exact analytic line, arc and circle contours emitted by the Evidence v2 converter. The runtime resolves them into closed regions without compiler_context; B-spline currently produces an explicit unsupported diagnostic."
|
||||
},
|
||||
"unknown_shape": {
|
||||
"agent_allowed": false,
|
||||
"summary": "Legacy compiler-context fallback only. Never generate it."
|
||||
"summary": "Closed direct line, arc and circle contours. B-splines are unsupported."
|
||||
}
|
||||
},
|
||||
"motif_types": {
|
||||
"circle": ["radius_mm"],
|
||||
"square": ["width_mm"],
|
||||
"rectangle": ["width_mm", "height_mm"],
|
||||
"obround": ["length_mm", "width_mm"],
|
||||
"cross": ["size_mm", "arm_width_mm"],
|
||||
"d_shape_polygon": ["stem_length_mm", "nose_depth_mm", "half_height_mm"],
|
||||
"regular_hexagon": ["radius_mm"],
|
||||
"skew_hexagon": ["nominal_radius_mm"],
|
||||
"triangle": ["radius_mm"],
|
||||
"teardrop_polygon": ["width_mm", "height_mm"],
|
||||
"trapezoid": ["bottom_width_mm", "top_width_mm", "height_mm"],
|
||||
"annular_sector_polygon": ["inner_radius_mm", "outer_radius_mm", "half_angle_deg"]
|
||||
},
|
||||
"layout_types": {
|
||||
"ring": ["radius_mm", "count"],
|
||||
"angular": ["count"],
|
||||
"concentric_rings": ["rings"],
|
||||
"disc_grid": ["count_x", "count_y", "spacing_x_mm", "spacing_y_mm"],
|
||||
"open_arc": ["radius_mm", "count", "start_angle_deg", "end_angle_deg"],
|
||||
"spiral": ["count", "start_radius_mm", "radius_step_mm", "angle_step_deg"],
|
||||
"cross_lines": ["count_per_axis", "spacing_mm"],
|
||||
"x_field": ["levels", "spacing_mm"],
|
||||
"twin_strips": ["x_offset_mm", "count_y", "y_start_mm", "y_end_mm"],
|
||||
"corner_clusters": ["levels_mm"],
|
||||
"diamond_field": ["manhattan_radius", "spacing_mm"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,25 +15,30 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
from .sketch_solver import SHAPE_GENERATORS, resolve_all_sketches
|
||||
from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles
|
||||
from .sketch_solver import SHAPE_GENERATORS, resolve_all_sketches
|
||||
from .llm_compiler import compile_cdsl
|
||||
from .llm_engine import run_engine_plan
|
||||
from .translator import generate_build123d_code, normalize_to_ir
|
||||
except ImportError: # 允许直接 python rebuild.py
|
||||
from sketch_solver import SHAPE_GENERATORS, resolve_all_sketches
|
||||
from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles
|
||||
from sketch_solver import SHAPE_GENERATORS, resolve_all_sketches
|
||||
from llm_compiler import compile_cdsl
|
||||
from llm_engine import run_engine_plan
|
||||
from translator import generate_build123d_code, normalize_to_ir
|
||||
|
||||
|
||||
def run_rebuild(cdsl: dict[str, Any], out_step: Path, ctx_file: Path | None = None, gold_step: Path | None = None,
|
||||
force_exact: bool = False) -> dict[str, Any]:
|
||||
def run_rebuild(cdsl: dict[str, Any], out_step: Path, ctx_file: Path | None = None, gold_step: Path | None = None,
|
||||
force_exact: bool = False) -> dict[str, Any]:
|
||||
"""主重建入口。
|
||||
|
||||
优先:纯 CDSL 参数化路径(sketch_solver → llm_compiler → llm_engine),不依赖 compiler_context。
|
||||
回退:CDSL + compiler_context 的 translator 路径。
|
||||
"""
|
||||
sketches = cdsl.get("geometry", {}).get("sketches", [])
|
||||
# Compatibility entry point only: new CDSL-only runtime calls do not use
|
||||
# macro profiles and therefore never invoke this adapter.
|
||||
cdsl = lower_legacy_profiles(cdsl)
|
||||
sketches = cdsl.get("geometry", {}).get("sketches", [])
|
||||
all_drawable = bool(sketches) and all(
|
||||
_sketch_is_cdsl_drawable(s) for s in sketches
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ from .runtime_types import (
|
||||
RuntimeDiagnostic, SelectorResolution, TopologyRecord, TopologyRegistry,
|
||||
vector_add, vector_cross, vector_dot, vector_scale, vector_subtract, vector_unit,
|
||||
)
|
||||
from .sketch_solver import SHAPE_GENERATORS, resolve_required_sketches
|
||||
from .sketch_solver import CORE_SHAPE_GENERATORS, resolve_required_sketches
|
||||
|
||||
|
||||
ALL_ATOMIC_IDS = frozenset({
|
||||
@@ -89,6 +89,7 @@ class ExecutionSession:
|
||||
results: dict[str, FeatureResult] = field(default_factory=dict)
|
||||
replay_definitions: dict[str, FeaturePlanNode] = field(default_factory=dict)
|
||||
selector_resolutions: list[dict[str, Any]] = field(default_factory=list)
|
||||
active_feature_id: str = ""
|
||||
|
||||
def register_body(self, feature_id: str, body: Any, *, replay_node: FeaturePlanNode | None = None) -> None:
|
||||
self.body = body
|
||||
@@ -103,7 +104,9 @@ class ExecutionSession:
|
||||
|
||||
def resolve(self, selector: dict[str, Any]) -> SelectorResolution:
|
||||
resolution = self.topology.resolve(selector, active_body_id=self.body_id)
|
||||
self.selector_resolutions.append(resolution.as_dict())
|
||||
evidence = resolution.as_dict()
|
||||
evidence["feature_id"] = self.active_feature_id
|
||||
self.selector_resolutions.append(evidence)
|
||||
return resolution
|
||||
|
||||
def result(self, node: FeaturePlanNode, *, context: PlaneSpec | AxisSpec | None = None, diagnostics: list[RuntimeDiagnostic] | None = None) -> FeatureResult:
|
||||
@@ -743,7 +746,12 @@ def _execute_node(node: FeaturePlanNode, session: ExecutionSession, sketch_overr
|
||||
executor = EXECUTORS.get(node.atomic_id)
|
||||
if executor is None:
|
||||
raise ValueError(f"No executor registered for {node.atomic_id!r}")
|
||||
return executor(node, session, sketch_override)
|
||||
previous_feature_id = session.active_feature_id
|
||||
session.active_feature_id = node.feature_id
|
||||
try:
|
||||
return executor(node, session, sketch_override)
|
||||
finally:
|
||||
session.active_feature_id = previous_feature_id
|
||||
|
||||
|
||||
ExecutorFunction = Callable[[FeaturePlanNode, ExecutionSession, dict[str, Any] | None], FeatureResult]
|
||||
@@ -824,7 +832,7 @@ def analyze_cdsl(cdsl: dict[str, Any]):
|
||||
resolved = resolve_required_sketches(
|
||||
deepcopy(cdsl), sketch_ids_required_by_contract(cdsl), errors=sketch_errors,
|
||||
)
|
||||
analyzer = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=SHAPE_GENERATORS)
|
||||
analyzer = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS)
|
||||
return analyzer.analyze(resolved, sketch_errors=sketch_errors)
|
||||
|
||||
|
||||
@@ -834,7 +842,7 @@ def rebuild_cdsl(cdsl: dict[str, Any], out_step: Path, *, strict: bool = True) -
|
||||
resolved = resolve_required_sketches(
|
||||
deepcopy(cdsl), sketch_ids_required_by_contract(cdsl), errors=sketch_errors,
|
||||
)
|
||||
analysis = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=SHAPE_GENERATORS).analyze(
|
||||
analysis = CapabilityAnalyzer(atomic_ids=EXECUTORS, profile_types=CORE_SHAPE_GENERATORS).analyze(
|
||||
resolved, sketch_errors=sketch_errors,
|
||||
)
|
||||
if strict and not analysis.runtime_eligible:
|
||||
|
||||
@@ -381,6 +381,13 @@ class SelectorResolution:
|
||||
}
|
||||
if self.record is not None:
|
||||
output["record"] = self.record.public_dict()
|
||||
output["selected"] = self.record.public_dict()
|
||||
score = next(
|
||||
(candidate.get("score") for candidate in self.candidates if candidate.get("record_id") == self.record.record_id),
|
||||
None,
|
||||
)
|
||||
if score is not None:
|
||||
output["score"] = score
|
||||
if self.diagnostic is not None:
|
||||
output["diagnostic"] = self.diagnostic.as_dict()
|
||||
return output
|
||||
@@ -589,6 +596,66 @@ class TopologyRegistry:
|
||||
if owner:
|
||||
candidates = [record for record in candidates if owner in record.owners]
|
||||
geometry = normalize_selector_geometry(selector.get("geometry"))
|
||||
if selector.get("snapshot_id") and not owner:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=(),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_owner_required",
|
||||
message="A snapshot selector requires owner_feature_id",
|
||||
detail={"minimum_score": minimum_score},
|
||||
),
|
||||
)
|
||||
stable_id = str(selector.get("stable_id") or "").strip()
|
||||
if stable_id:
|
||||
exact = [record for record in candidates if record.record_id == stable_id]
|
||||
if len(exact) == 1:
|
||||
record = exact[0]
|
||||
# A stable ID is only a lookup accelerator for snapshot-aware
|
||||
# selectors. It cannot revive a B-rep entity whose geometric
|
||||
# signature changed after an upstream rebuild.
|
||||
if selector.get("snapshot_id"):
|
||||
score = self._geometry_score(geometry, record.geometry) if geometry else None
|
||||
if score is None or score < minimum_score:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=({"score": round(float(score or 0), 6), **record.public_dict()},),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_geometry_mismatch",
|
||||
message="The stable selector record no longer matches its geometry signature",
|
||||
detail={"stable_id": stable_id, "score": score, "minimum_score": minimum_score},
|
||||
),
|
||||
)
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="resolved",
|
||||
record=record,
|
||||
candidates=({"score": round(float(score), 6) if selector.get("snapshot_id") else 1.0, **record.public_dict()},),
|
||||
)
|
||||
if len(exact) > 1:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="ambiguous",
|
||||
candidates=tuple({"score": 1.0, **record.public_dict()} for record in exact),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_ambiguous",
|
||||
message="More than one runtime topology record has the requested stable_id",
|
||||
detail={"stable_id": stable_id, "candidate_count": len(exact)},
|
||||
),
|
||||
)
|
||||
if selector.get("snapshot_id") and not geometry:
|
||||
return SelectorResolution(
|
||||
selector=selector,
|
||||
status="not_found",
|
||||
candidates=(),
|
||||
diagnostic=RuntimeDiagnostic(
|
||||
code="selector_geometry_mismatch",
|
||||
message="A snapshot selector requires a geometry signature",
|
||||
detail={"minimum_score": minimum_score},
|
||||
),
|
||||
)
|
||||
scored: list[tuple[float, TopologyRecord]] = []
|
||||
for candidate in candidates:
|
||||
# An owner-qualified context selector is deterministic when it has
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user