350 lines
14 KiB
Python
350 lines
14 KiB
Python
"""将 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()
|