优化生成流程
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
"""Compatibility adapters for imported and historical CDSL documents.
|
||||
|
||||
This package is deliberately outside ``cdsl_engine``. It may lower retired
|
||||
source syntax into the engine's generic CDSL contract, but is never imported
|
||||
by the CDSL runtime.
|
||||
"""
|
||||
@@ -0,0 +1,349 @@
|
||||
"""将 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()
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Explicit import/rebuild compatibility for retired CDSL profile macros.
|
||||
|
||||
The CDSL runtime accepts only direct geometric profiles. This adapter is for
|
||||
historical artifacts and importers: it expands a legacy shorthand exactly once
|
||||
into an ``analytic_contours`` profile before generic runtime validation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import Any
|
||||
|
||||
from .legacy_profiles import LEGACY_PROFILE_GENERATORS
|
||||
|
||||
|
||||
class LegacyProfileError(ValueError):
|
||||
"""A legacy profile could not be lowered to generic CDSL geometry."""
|
||||
|
||||
|
||||
def legacy_profile_types() -> tuple[str, ...]:
|
||||
return tuple(sorted(LEGACY_PROFILE_GENERATORS))
|
||||
|
||||
|
||||
def _segment_from_edge(edge: dict[str, Any]) -> dict[str, Any]:
|
||||
kind = str(edge.get("type") or "")
|
||||
if kind == "line":
|
||||
return {
|
||||
"type": "line",
|
||||
"start": list(edge["start_mm"][:2]),
|
||||
"end": list(edge["end_mm"][:2]),
|
||||
}
|
||||
if kind == "arc":
|
||||
segment = {
|
||||
"type": "arc",
|
||||
"start": list(edge["start_mm"][:2]),
|
||||
"end": list(edge["end_mm"][:2]),
|
||||
"center": list(edge["center_mm"][:2]),
|
||||
"radius_mm": float(edge["radius_mm"]),
|
||||
}
|
||||
if "clockwise" in edge:
|
||||
segment["clockwise"] = bool(edge["clockwise"])
|
||||
return segment
|
||||
raise LegacyProfileError(f"Legacy profile emitted unsupported edge type: {kind}")
|
||||
|
||||
|
||||
def _circle_contour(entity: dict[str, Any]) -> dict[str, Any]:
|
||||
radius = entity.get("radius_mm")
|
||||
if not isinstance(radius, (int, float)) or float(radius) <= 0:
|
||||
raise LegacyProfileError("Legacy circle must have radius_mm > 0")
|
||||
center = entity.get("center") or [0.0, 0.0]
|
||||
if not isinstance(center, list) or len(center) < 2:
|
||||
raise LegacyProfileError("Legacy circle must have a two-dimensional center")
|
||||
return {
|
||||
"role": "outer",
|
||||
"closed": True,
|
||||
"segments": [{"type": "circle", "center": [float(center[0]), float(center[1])], "radius_mm": float(radius)}],
|
||||
}
|
||||
|
||||
|
||||
def _edge_contour(edges: list[dict[str, Any]], *, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"role": role,
|
||||
"closed": True,
|
||||
"segments": [_segment_from_edge(edge) for edge in edges],
|
||||
}
|
||||
|
||||
|
||||
def lower_legacy_profile(profile: dict[str, Any], *, sketch_id: str = "") -> dict[str, Any]:
|
||||
"""Expand one named legacy profile to a direct analytic contour profile."""
|
||||
kind = str(profile.get("type") or "")
|
||||
generator = LEGACY_PROFILE_GENERATORS.get(kind)
|
||||
if generator is None:
|
||||
raise LegacyProfileError(f"Unsupported legacy profile: {kind!r}")
|
||||
meta: dict[str, Any] = {"id": sketch_id, "_entities": None, "_contour": None}
|
||||
try:
|
||||
entities, contour = generator(copy.deepcopy(profile), meta)
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
raise LegacyProfileError(f"Could not lower legacy profile {kind!r}: {error}") from error
|
||||
|
||||
contours: list[dict[str, Any]] = []
|
||||
if contour:
|
||||
contours.append(_edge_contour(contour, role="outer"))
|
||||
for region in meta.get("_regions") or []:
|
||||
if not isinstance(region, dict):
|
||||
continue
|
||||
outer = region.get("outer") or []
|
||||
if outer:
|
||||
contours.append(_edge_contour(outer, role="outer"))
|
||||
for hole in region.get("holes") or []:
|
||||
if hole:
|
||||
contours.append(_edge_contour(hole, role="inner"))
|
||||
for entity in entities or []:
|
||||
if entity.get("type") == "circle" and not entity.get("construction"):
|
||||
contours.append(_circle_contour(entity))
|
||||
if not contours:
|
||||
raise LegacyProfileError(f"Legacy profile {kind!r} did not produce a closed contour")
|
||||
return {"type": "analytic_contours", "contours": contours}
|
||||
|
||||
|
||||
def lower_legacy_profiles(cdsl: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return a copy with every self-contained legacy sketch lowered.
|
||||
|
||||
This is opt-in. Calling the runtime directly with a legacy profile still
|
||||
returns an unsupported-profile diagnostic.
|
||||
"""
|
||||
lowered = copy.deepcopy(cdsl)
|
||||
sketches = ((lowered.get("geometry") or {}).get("sketches") or [])
|
||||
for sketch in sketches:
|
||||
if not isinstance(sketch, dict):
|
||||
continue
|
||||
profile = sketch.get("profile")
|
||||
if not isinstance(profile, dict):
|
||||
continue
|
||||
if str(profile.get("type") or "") not in LEGACY_PROFILE_GENERATORS:
|
||||
continue
|
||||
sketch["profile"] = lower_legacy_profile(profile, sketch_id=str(sketch.get("id") or ""))
|
||||
return lowered
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user