Compare commits
2 Commits
304911a69f
...
2edf8fb4c8
| Author | SHA1 | Date | |
|---|---|---|---|
| 2edf8fb4c8 | |||
| a2f3ba6a90 |
@@ -37,3 +37,13 @@ cad-experience-plugin/parser/input/*
|
||||
!cad-experience-plugin/parser/input/.gitkeep
|
||||
cad-experience-plugin/parser/output/*
|
||||
!cad-experience-plugin/parser/output/.gitkeep
|
||||
|
||||
# CAD experience benchmark scratch geometry and private case evidence
|
||||
cad-experience-plugin/benchmarks/**/generated/
|
||||
cad-experience-plugin/benchmarks/**/quarantine-*/
|
||||
cad-experience-plugin/benchmarks/**/private-case.json
|
||||
cad-experience-plugin/benchmarks/**/source-inspect.json
|
||||
cad-experience-plugin/benchmarks/**/train-semantic-batch.json
|
||||
cad-experience-plugin/benchmarks/**/train_cases/
|
||||
cad-experience-plugin/benchmarks/**/test_cases/
|
||||
cad-experience-plugin/benchmarks/**/cases/
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "cad-experience-plugin",
|
||||
"version": "0.1.0",
|
||||
"description": "Parse STEP inboxes and use the active model plus deterministic evidence checks to distill backend-neutral CAD experience.",
|
||||
"version": "0.1.0+codex.20260723115752",
|
||||
"description": "Parse STEP inboxes and use the active model plus deterministic evidence checks to distill backend-neutral CAD reconstruction grammar and experience.",
|
||||
"author": {
|
||||
"name": "Local developer"
|
||||
},
|
||||
"skills": "./skills/",
|
||||
"interface": {
|
||||
"displayName": "CAD Experience",
|
||||
"shortDescription": "Use model review to distill reusable CAD methods.",
|
||||
"longDescription": "CAD Experience maintains a visible STEP input/output staging area, gives the active large model a sanitized semantic review packet, verifies every proposed method against the accumulated corpus, and promotes only repeatedly supported backend-neutral experience.",
|
||||
"shortDescription": "Distill reusable CAD reconstruction grammar.",
|
||||
"longDescription": "CAD Experience maintains a visible STEP input/output staging area, gives the active large model sanitized symbolic parameters, datums, canonical stages, constraints, and validation evidence, verifies every proposal against the accumulated corpus, and promotes only repeatedly supported backend-neutral reconstruction grammar and experience.",
|
||||
"developerName": "Local developer",
|
||||
"category": "Productivity",
|
||||
"capabilities": [
|
||||
@@ -18,9 +18,10 @@
|
||||
"Batch CAD experience induction",
|
||||
"Large-model semantic review",
|
||||
"Deterministic proposal evidence verification",
|
||||
"Family-level symbolic reconstruction grammar",
|
||||
"Generalized experience queries",
|
||||
"Instance-data leakage audits"
|
||||
],
|
||||
"defaultPrompt": "Parse the STEP input folder, distill the output JSON, and report promoted generalized CAD experience without exposing source-part geometry."
|
||||
"defaultPrompt": "Parse the STEP input folder, distill symbolic family-level reconstruction grammar from the output JSON, and report promoted generalized CAD experience without exposing source-part geometry."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
[
|
||||
{
|
||||
"id": "136930",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/136930.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "138022",
|
||||
"variant": "baseline",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "138022",
|
||||
"variant": "experience",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "136930",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/136930.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "134828",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/134828.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "116166",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/116166.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "134828",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/134828.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "145574",
|
||||
"variant": "baseline",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "145574",
|
||||
"variant": "experience",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "150196",
|
||||
"variant": "baseline",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "150196",
|
||||
"variant": "experience",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "150587",
|
||||
"variant": "experience",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "150587",
|
||||
"variant": "baseline",
|
||||
"returncode": -9,
|
||||
"output": "",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "116166",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/116166.step\"}",
|
||||
"error": ""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,212 @@
|
||||
[
|
||||
{
|
||||
"id": "000166",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"skipped\", \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/000166.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "011680",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"skipped\", \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/011680.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "000166",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/000166.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "011680",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/011680.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "016033",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/016033.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "016033",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/016033.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "004764",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/004764.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "020543",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/020543.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "024675",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/024675.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "004764",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/004764.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "029190",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/029190.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "024675",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/024675.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "020543",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/020543.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "029190",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/029190.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "030352",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/030352.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "046014",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/046014.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "046014",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/046014.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "030352",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/030352.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "047048",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/047048.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "048346",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/048346.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "048346",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/048346.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "047048",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/047048.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "053664",
|
||||
"variant": "experience",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/053664.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "065487",
|
||||
"variant": "experience",
|
||||
"returncode": 1,
|
||||
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/065487.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "067186",
|
||||
"variant": "baseline",
|
||||
"returncode": 1,
|
||||
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/067186.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "053664",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/053664.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "067186",
|
||||
"variant": "experience",
|
||||
"returncode": 1,
|
||||
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/067186.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "073706",
|
||||
"variant": "baseline",
|
||||
"returncode": 1,
|
||||
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/073706.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "073706",
|
||||
"variant": "experience",
|
||||
"returncode": 1,
|
||||
"output": "{\"status\": \"failed\", \"returncode\": 1, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/073706.step\"}",
|
||||
"error": ""
|
||||
},
|
||||
{
|
||||
"id": "065487",
|
||||
"variant": "baseline",
|
||||
"returncode": 0,
|
||||
"output": "{\"status\": \"ok\", \"returncode\": 0, \"output\": \"/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/065487.step\"}",
|
||||
"error": ""
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"generated_at": "2026-07-23T09:46:25+00:00",
|
||||
"status": "empty",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": false,
|
||||
"absolute_coordinates_allowed": false,
|
||||
"single_case_promotion_allowed": false,
|
||||
"minimum_support": 20,
|
||||
"minimum_confidence": 0.8
|
||||
},
|
||||
"corpus_summary": {
|
||||
"accepted_case_count": 0,
|
||||
"duplicate_case_count": 0,
|
||||
"rejected_case_count": 0,
|
||||
"family_count": 0
|
||||
},
|
||||
"experience_summary": {
|
||||
"promoted_experience_count": 0,
|
||||
"candidate_experience_count": 0,
|
||||
"by_kind": [],
|
||||
"by_scope": [],
|
||||
"candidate_by_kind": []
|
||||
},
|
||||
"experiences": [],
|
||||
"candidate_experiences": []
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Evaluate blind baseline/experience STEP reconstructions against held-out STEP."""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import importlib.util
|
||||
import itertools
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from build123d import import_step
|
||||
from scipy.spatial import cKDTree
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
EXTRACTOR = (
|
||||
HERE.parents[1]
|
||||
/ "skills/cad-experience-builder/scripts/step_to_case.py"
|
||||
)
|
||||
|
||||
|
||||
def load_extractor():
|
||||
spec = importlib.util.spec_from_file_location("ab50_step_to_case", EXTRACTOR)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
assert spec and spec.loader
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
EXTRACT = load_extractor()
|
||||
|
||||
|
||||
def rel_error(actual: float, expected: float) -> float:
|
||||
return abs(actual - expected) / max(abs(expected), 1e-9)
|
||||
|
||||
|
||||
def feature_names(payload: dict[str, Any]) -> set[str]:
|
||||
result: set[str] = set()
|
||||
for item in payload.get("design_ir", {}).get("features", []):
|
||||
if isinstance(item, str):
|
||||
result.add(item)
|
||||
elif isinstance(item, dict):
|
||||
name = item.get("kind") or item.get("type") or item.get("name")
|
||||
if name:
|
||||
result.add(str(name))
|
||||
return result
|
||||
|
||||
|
||||
def f1_score(left: set[str], right: set[str]) -> float:
|
||||
if not left and not right:
|
||||
return 1.0
|
||||
if not left or not right:
|
||||
return 0.0
|
||||
overlap = len(left & right)
|
||||
precision = overlap / len(right)
|
||||
recall = overlap / len(left)
|
||||
return 2 * precision * recall / max(precision + recall, 1e-9)
|
||||
|
||||
|
||||
def mesh(shape, tolerance: float = 0.35) -> tuple[np.ndarray, np.ndarray]:
|
||||
vertices, triangles = shape.tessellate(tolerance)
|
||||
points = np.asarray([[v.X, v.Y, v.Z] for v in vertices], dtype=float)
|
||||
faces = np.asarray(triangles, dtype=int)
|
||||
return points, faces
|
||||
|
||||
|
||||
def sample_surface(
|
||||
vertices: np.ndarray, faces: np.ndarray, count: int, seed: int
|
||||
) -> np.ndarray:
|
||||
tri = vertices[faces]
|
||||
cross = np.cross(tri[:, 1] - tri[:, 0], tri[:, 2] - tri[:, 0])
|
||||
areas = np.linalg.norm(cross, axis=1) / 2.0
|
||||
total = float(areas.sum())
|
||||
if total <= 1e-12:
|
||||
return vertices.copy()
|
||||
rng = np.random.default_rng(seed)
|
||||
chosen = rng.choice(len(faces), size=count, p=areas / total)
|
||||
selected = tri[chosen]
|
||||
u = rng.random(count)
|
||||
v = rng.random(count)
|
||||
reflected = u + v > 1.0
|
||||
u[reflected] = 1.0 - u[reflected]
|
||||
v[reflected] = 1.0 - v[reflected]
|
||||
return (
|
||||
selected[:, 0]
|
||||
+ u[:, None] * (selected[:, 1] - selected[:, 0])
|
||||
+ v[:, None] * (selected[:, 2] - selected[:, 0])
|
||||
)
|
||||
|
||||
|
||||
def cube_transforms() -> list[np.ndarray]:
|
||||
transforms = []
|
||||
for permutation in itertools.permutations(range(3)):
|
||||
base = np.eye(3)[:, permutation]
|
||||
for signs in itertools.product((-1.0, 1.0), repeat=3):
|
||||
transforms.append(base @ np.diag(signs))
|
||||
return transforms
|
||||
|
||||
|
||||
TRANSFORMS = cube_transforms()
|
||||
|
||||
|
||||
def surface_distance(
|
||||
reference, generated, seed: int, samples: int
|
||||
) -> tuple[float, float]:
|
||||
ref_v, ref_f = mesh(reference)
|
||||
gen_v, gen_f = mesh(generated)
|
||||
ref = sample_surface(ref_v, ref_f, samples, seed)
|
||||
gen = sample_surface(gen_v, gen_f, samples, seed + 1)
|
||||
ref -= (ref.min(axis=0) + ref.max(axis=0)) / 2.0
|
||||
gen -= (gen.min(axis=0) + gen.max(axis=0)) / 2.0
|
||||
ref_tree = cKDTree(ref)
|
||||
best_chamfer = math.inf
|
||||
best_hausdorff = math.inf
|
||||
for transform in TRANSFORMS:
|
||||
candidate = gen @ transform
|
||||
gen_tree = cKDTree(candidate)
|
||||
d_gr = ref_tree.query(candidate, workers=-1)[0]
|
||||
d_rg = gen_tree.query(ref, workers=-1)[0]
|
||||
chamfer = float((d_gr.mean() + d_rg.mean()) / 2.0)
|
||||
hausdorff = float(max(np.quantile(d_gr, 0.95), np.quantile(d_rg, 0.95)))
|
||||
if chamfer < best_chamfer:
|
||||
best_chamfer = chamfer
|
||||
best_hausdorff = hausdorff
|
||||
diagonal = max(float(np.linalg.norm(np.ptp(ref, axis=0))), 1e-9)
|
||||
return best_chamfer / diagonal, best_hausdorff / diagonal
|
||||
|
||||
|
||||
def task_experience(step_path: Path) -> tuple[int, int]:
|
||||
task = step_path.parent / "cad-task.json"
|
||||
if not task.is_file():
|
||||
return 0, 0
|
||||
payload = json.loads(task.read_text())
|
||||
exp = payload.get("experience", {})
|
||||
returned = exp.get("methods_returned")
|
||||
if returned is None:
|
||||
returned = len(exp.get("methods", exp.get("methods_applied", [])))
|
||||
if isinstance(returned, list):
|
||||
returned = len(returned)
|
||||
return int(returned or 0), len(exp.get("methods_applied", []))
|
||||
|
||||
|
||||
def evaluate_one(
|
||||
case: dict[str, Any], variant: str, samples: int
|
||||
) -> dict[str, Any]:
|
||||
generated_path = Path(case[f"{variant}_step"])
|
||||
row: dict[str, Any] = {
|
||||
"id": case["id"],
|
||||
"family": case["family"],
|
||||
"variant": variant,
|
||||
"generated": generated_path.is_file(),
|
||||
}
|
||||
if not generated_path.is_file():
|
||||
row["error"] = "missing STEP"
|
||||
return row
|
||||
try:
|
||||
reference = import_step(case["reference_step"])
|
||||
generated = import_step(str(generated_path))
|
||||
ref_case = json.loads(Path(case["case_json"]).read_text())
|
||||
gen_case = EXTRACT.extract_step_case(generated_path)
|
||||
ref_bbox = sorted(float(v) for v in reference.bounding_box().size)
|
||||
gen_bbox = sorted(float(v) for v in generated.bounding_box().size)
|
||||
bbox_error = float(
|
||||
np.mean([rel_error(a, b) for a, b in zip(gen_bbox, ref_bbox)])
|
||||
)
|
||||
volume_error = rel_error(float(generated.volume), float(reference.volume))
|
||||
area_error = rel_error(float(generated.area), float(reference.area))
|
||||
face_error = rel_error(len(generated.faces()), len(reference.faces()))
|
||||
edge_error = rel_error(len(generated.edges()), len(reference.edges()))
|
||||
chamfer, hausdorff95 = surface_distance(
|
||||
reference, generated, int(case["id"]), samples
|
||||
)
|
||||
feature_f1 = f1_score(feature_names(ref_case), feature_names(gen_case))
|
||||
family_match = float(
|
||||
ref_case["design_ir"]["part_family"]
|
||||
== gen_case["design_ir"]["part_family"]
|
||||
)
|
||||
returned, applied = task_experience(generated_path)
|
||||
|
||||
# Transparent 0–100 similarity index. It is a benchmark summary, not a
|
||||
# claim of manufacturing interchangeability or exact B-rep identity.
|
||||
score = 100.0 * (
|
||||
0.18 * math.exp(-volume_error)
|
||||
+ 0.12 * math.exp(-area_error)
|
||||
+ 0.16 * math.exp(-bbox_error)
|
||||
+ 0.24 * math.exp(-8.0 * chamfer)
|
||||
+ 0.10 * math.exp(-(face_error + edge_error) / 2.0)
|
||||
+ 0.15 * feature_f1
|
||||
+ 0.05 * family_match
|
||||
)
|
||||
row.update(
|
||||
{
|
||||
"valid": bool(generated.is_valid),
|
||||
"solid_count": len(generated.solids()),
|
||||
"reference_volume": float(reference.volume),
|
||||
"generated_volume": float(generated.volume),
|
||||
"volume_rel_error": volume_error,
|
||||
"surface_area_rel_error": area_error,
|
||||
"bbox_mape": bbox_error,
|
||||
"face_rel_error": face_error,
|
||||
"edge_rel_error": edge_error,
|
||||
"surface_chamfer_norm": chamfer,
|
||||
"surface_hausdorff95_norm": hausdorff95,
|
||||
"feature_f1": feature_f1,
|
||||
"family_match": family_match,
|
||||
"experience_methods_returned": returned,
|
||||
"experience_methods_applied": applied,
|
||||
"similarity_score": score,
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
row["error"] = f"{type(exc).__name__}: {exc}"
|
||||
return row
|
||||
|
||||
|
||||
def mean(rows: list[dict[str, Any]], key: str) -> float:
|
||||
values = [float(row[key]) for row in rows if key in row]
|
||||
return sum(values) / len(values) if values else math.nan
|
||||
|
||||
|
||||
def write_report(rows: list[dict[str, Any]]) -> None:
|
||||
complete = [row for row in rows if "similarity_score" in row]
|
||||
baseline = [row for row in complete if row["variant"] == "baseline"]
|
||||
experience = [row for row in complete if row["variant"] == "experience"]
|
||||
by_id = {}
|
||||
for row in complete:
|
||||
by_id.setdefault(row["id"], {})[row["variant"]] = row
|
||||
pairs = [
|
||||
item for item in by_id.values() if {"baseline", "experience"} <= item.keys()
|
||||
]
|
||||
paired_baseline = [item["baseline"] for item in pairs]
|
||||
paired_experience = [item["experience"] for item in pairs]
|
||||
deltas = [
|
||||
item["experience"]["similarity_score"]
|
||||
- item["baseline"]["similarity_score"]
|
||||
for item in pairs
|
||||
]
|
||||
wins = sum(delta > 0.25 for delta in deltas)
|
||||
ties = sum(abs(delta) <= 0.25 for delta in deltas)
|
||||
losses = sum(delta < -0.25 for delta in deltas)
|
||||
methods_returned = sum(
|
||||
row.get("experience_methods_returned", 0) for row in experience
|
||||
)
|
||||
methods_applied = sum(
|
||||
row.get("experience_methods_applied", 0) for row in experience
|
||||
)
|
||||
report = f"""# CAD 经验库 50 件 A/B 盲重建报告
|
||||
|
||||
## 口径
|
||||
|
||||
- 测试集:固定分层抽取 50 件;这 50 件不进入经验库。
|
||||
- 训练经验:其余 950 件生成的通用经验库。
|
||||
- 两组输入完全相同:仅多视图图片和文字描述;生成过程禁止读取参考 STEP 和解析 JSON。
|
||||
- Baseline:空经验库;Experience:950 件经验库。
|
||||
- 参考 STEP 只在生成完成后用于评分。
|
||||
- “相似度”是体积、面积、包围盒、表面距离、拓扑和语义特征的综合诊断分,不代表制造级 1:1 或可互换性。
|
||||
|
||||
## 当前完成度
|
||||
|
||||
- 有效 Baseline:{len(baseline)}/50
|
||||
- 有效 Experience:{len(experience)}/50
|
||||
- 完整配对:{len(pairs)}/50
|
||||
- 经验方法返回/实际采用:{methods_returned}/{methods_applied}
|
||||
|
||||
## 汇总
|
||||
|
||||
| 指标 | 无经验 | 有经验 | 差值 |
|
||||
|---|---:|---:|---:|
|
||||
| 综合相似度(0–100,越高越好) | {mean(paired_baseline, "similarity_score"):.2f} | {mean(paired_experience, "similarity_score"):.2f} | {mean(paired_experience, "similarity_score") - mean(paired_baseline, "similarity_score"):+.2f} |
|
||||
| 体积相对误差(越低越好) | {mean(paired_baseline, "volume_rel_error"):.3f} | {mean(paired_experience, "volume_rel_error"):.3f} | {mean(paired_experience, "volume_rel_error") - mean(paired_baseline, "volume_rel_error"):+.3f} |
|
||||
| 表面积相对误差 | {mean(paired_baseline, "surface_area_rel_error"):.3f} | {mean(paired_experience, "surface_area_rel_error"):.3f} | {mean(paired_experience, "surface_area_rel_error") - mean(paired_baseline, "surface_area_rel_error"):+.3f} |
|
||||
| 包围盒尺寸 MAPE | {mean(paired_baseline, "bbox_mape"):.3f} | {mean(paired_experience, "bbox_mape"):.3f} | {mean(paired_experience, "bbox_mape") - mean(paired_baseline, "bbox_mape"):+.3f} |
|
||||
| 归一化表面 Chamfer | {mean(paired_baseline, "surface_chamfer_norm"):.4f} | {mean(paired_experience, "surface_chamfer_norm"):.4f} | {mean(paired_experience, "surface_chamfer_norm") - mean(paired_baseline, "surface_chamfer_norm"):+.4f} |
|
||||
| 特征 F1 | {mean(paired_baseline, "feature_f1"):.3f} | {mean(paired_experience, "feature_f1"):.3f} | {mean(paired_experience, "feature_f1") - mean(paired_baseline, "feature_f1"):+.3f} |
|
||||
|
||||
经验组胜/平/负(±0.25 分视为平):**{wins}/{ties}/{losses}**。
|
||||
"""
|
||||
(HERE / "report.md").write_text(report)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--samples", type=int, default=1600)
|
||||
args = parser.parse_args()
|
||||
manifest = json.loads((HERE / "manifest.json").read_text())
|
||||
rows = [
|
||||
evaluate_one(case, variant, args.samples)
|
||||
for case in manifest["cases"]
|
||||
for variant in ("baseline", "experience")
|
||||
]
|
||||
(HERE / "metrics.json").write_text(
|
||||
json.dumps(rows, indent=2, ensure_ascii=False) + "\n"
|
||||
)
|
||||
keys = sorted({key for row in rows for key in row})
|
||||
with (HERE / "metrics.csv").open("w", newline="") as stream:
|
||||
writer = csv.DictWriter(stream, fieldnames=keys)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
write_report(rows)
|
||||
completed = sum("similarity_score" in row for row in rows)
|
||||
print(json.dumps({"evaluated": completed, "expected": 100}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,708 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"selection_seed": 20260723,
|
||||
"holdout_count": 50,
|
||||
"train_count": 950,
|
||||
"cases": [
|
||||
{
|
||||
"id": "000166",
|
||||
"family": "general_mechanical_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/000166.3fe709d953c5.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/000166.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/000166.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/000166.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/000166.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/000166/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/000166.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/000166/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "004764",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/004764.6e3a06d88adc.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/004764.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/004764.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/004764.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/004764.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/004764/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/004764.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/004764/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "011680",
|
||||
"family": "plate_or_bracket",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/011680.77a85afd0f4b.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/011680.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/011680.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/011680.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/011680.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/011680/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/011680.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/011680/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "016033",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/016033.500e53f944a6.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/016033.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/016033.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/016033.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/016033.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/016033/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/016033.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/016033/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "020543",
|
||||
"family": "flanged_rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/020543.0ad8f7e7459d.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/020543.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/020543.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/020543.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/020543.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/020543/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/020543.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/020543/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "024675",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/024675.2d1f84f175f5.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/024675.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/024675.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/024675.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/024675.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/024675/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/024675.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/024675/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "029190",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/029190.825d4001dea3.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/029190.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/029190.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/029190.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/029190.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/029190/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/029190.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/029190/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "030352",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/030352.45db9222299d.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/030352.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/030352.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/030352.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/030352.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/030352/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/030352.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/030352/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "046014",
|
||||
"family": "flanged_rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/046014.4abae0d1a5a1.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/046014.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/046014.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/046014.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/046014.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/046014/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/046014.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/046014/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "047048",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/047048.b5119254bc4b.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/047048.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/047048.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/047048.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/047048.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/047048/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/047048.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/047048/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "048346",
|
||||
"family": "hollow_or_stepped_shaft",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/048346.ba3e1b245d0e.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/048346.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/048346.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/048346.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/048346.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/048346/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/048346.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/048346/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "053664",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/053664.90a0635aadaf.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/053664.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/053664.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/053664.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/053664.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/053664/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/053664.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/053664/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "063487",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/063487.b524dcfc1124.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/063487.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/063487.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/063487.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/063487.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/063487/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/063487.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/063487/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "065487",
|
||||
"family": "flanged_rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/065487.444ad775ec5c.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/065487.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/065487.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/065487.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/065487.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/065487/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/065487.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/065487/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "067186",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/067186.0fe747589ef9.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/067186.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/067186.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/067186.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/067186.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/067186/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/067186.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/067186/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "073706",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/073706.9727acff0f04.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/073706.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/073706.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/073706.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/073706.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/073706/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/073706.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/073706/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "077955",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/077955.499f373b1c19.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/077955.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/077955.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/077955.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/077955.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/077955/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/077955.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/077955/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "082187",
|
||||
"family": "prismatic_block",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/082187.2cb2db4976cc.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/082187.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/082187.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/082187.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/082187.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/082187/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/082187.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/082187/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "089970",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/089970.fcac7c13cac8.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/089970.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/089970.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/089970.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/089970.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/089970/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/089970.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/089970/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "092092",
|
||||
"family": "shaft",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/092092.878532374151.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/092092.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/092092.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/092092.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/092092.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/092092/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/092092.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/092092/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "095520",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/095520.f77225b8bb80.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/095520.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/095520.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/095520.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/095520.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/095520/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/095520.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/095520/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "102128",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/102128.5226541130cc.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/102128.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/102128.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/102128.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/102128.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/102128/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/102128.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/102128/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "104465",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/104465.367ecc4c0d1f.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/104465.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/104465.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/104465.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/104465.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/104465/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/104465.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/104465/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "110143",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/110143.d0705ec5cf9b.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/110143.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/110143.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/110143.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/110143.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/110143/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/110143.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/110143/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "112069",
|
||||
"family": "prismatic_block",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/112069.527e20a67202.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/112069.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/112069.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/112069.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/112069.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/112069/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/112069.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/112069/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "116166",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/116166.daa7c0fb7095.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/116166.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/116166.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/116166.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/116166.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/116166/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/116166.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/116166/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "134828",
|
||||
"family": "multi_axis_manifold",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/134828.4b43ff12062c.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/134828.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/134828.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/134828.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/134828.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/134828/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/134828.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/134828/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "136930",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/136930.84481239875b.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/136930.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/136930.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/136930.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/136930.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/136930/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/136930.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/136930/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "138022",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/138022.144778123756.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/138022.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/138022.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/138022.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/138022.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/138022/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/138022.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/138022/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "141680",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/141680.6f110692a1c2.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/141680.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/141680.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/141680.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/141680.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/141680/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/141680.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/141680/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "145574",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/145574.861af237e8e8.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/145574.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/145574.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/145574.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/145574.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/145574/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/145574.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/145574/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "150196",
|
||||
"family": "prismatic_block",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/150196.d357a5b35875.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/150196.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/150196.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/150196.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/150196.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150196/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/150196.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150196/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "150587",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/150587.ff4fa06ecb48.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/150587.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/150587.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/150587.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/150587.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/150587/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/150587.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/150587/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "152568",
|
||||
"family": "multi_axis_manifold",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/152568.92e33e9a9248.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/152568.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/152568.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/152568.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/152568.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/152568/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/152568.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/152568/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "153245",
|
||||
"family": "plate_or_bracket",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/153245.0603ada945fa.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/153245.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/153245.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/153245.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/153245.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/153245/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/153245.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/153245/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "158256",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/158256.a27578bb9a47.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/158256.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/158256.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/158256.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/158256.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/158256/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/158256.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/158256/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "162558",
|
||||
"family": "hollow_or_stepped_shaft",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/162558.27dadf557afe.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/162558.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/162558.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/162558.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/162558.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/162558/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/162558.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/162558/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "167962",
|
||||
"family": "multi_axis_manifold",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/167962.1306cad65795.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/167962.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/167962.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/167962.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/167962.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/167962/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/167962.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/167962/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "168239",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/168239.b54b54a2287f.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/168239.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/168239.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/168239.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/168239.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/168239/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/168239.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/168239/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "174675",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/174675.25863f859f2b.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/174675.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/174675.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/174675.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/174675.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/174675/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/174675.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/174675/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "183151",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/183151.ceee2bf361bc.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/183151.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/183151.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/183151.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/183151.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/183151/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/183151.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/183151/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "189669",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/189669.66b0db2ea3d6.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/189669.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/189669.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/189669.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/189669.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/189669/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/189669.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/189669/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "194029",
|
||||
"family": "rotational_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/194029.316b264a1a88.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/194029.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/194029.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/194029.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/194029.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/194029/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/194029.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/194029/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "201547",
|
||||
"family": "prismatic_block",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/201547.c7cc9318deda.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/201547.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/201547.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/201547.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/201547.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/201547/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/201547.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/201547/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "204730",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/204730.1fade6b3bb74.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/204730.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/204730.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/204730.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/204730.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/204730/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/204730.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/204730/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "211326",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/211326.a4e6d3a4590a.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/211326.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/211326.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/211326.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/211326.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/211326/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/211326.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/211326/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "214404",
|
||||
"family": "patterned_plate",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/214404.da6a44a7013c.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/214404.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/214404.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/214404.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/214404.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/214404/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/214404.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/214404/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "229115",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/229115.c4c1680e53b5.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/229115.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/229115.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/229115.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/229115.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/229115/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/229115.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/229115/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "234097",
|
||||
"family": "hybrid_freeform_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/234097.cea424081999.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/234097.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/234097.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/234097.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/234097.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/234097/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/234097.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/234097/final.txt"
|
||||
},
|
||||
{
|
||||
"id": "242307",
|
||||
"family": "patterned_prismatic_part",
|
||||
"case_json": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/parser/output/242307.cd1b3d043967.case.json",
|
||||
"image": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/image/242307.png",
|
||||
"description": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/Des_Text/242307.txt",
|
||||
"reference_step": "/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset/model_step/242307.step",
|
||||
"baseline_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/242307.step",
|
||||
"baseline_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/codex.jsonl",
|
||||
"baseline_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/baseline/242307/final.txt",
|
||||
"experience_step": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/242307.step",
|
||||
"experience_log": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/codex.jsonl",
|
||||
"experience_message": "/Users/jerry/linkhand/CadSet/cad-experience-plugin/benchmarks/ab50/generated/experience/242307/final.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
bbox_mape,edge_rel_error,error,experience_methods_applied,experience_methods_returned,face_rel_error,family,family_match,feature_f1,generated,generated_volume,id,reference_volume,similarity_score,solid_count,surface_area_rel_error,surface_chamfer_norm,surface_hausdorff95_norm,valid,variant,volume_rel_error
|
||||
0.0,0.5,,0,0,0.25,general_mechanical_part,1.0,0.8571428571428571,True,125663.70614359173,000166,125663.70614359173,93.81711632834559,1,6.425513440379324e-14,0.004847580881730266,0.00977766538694562,True,baseline,0.0
|
||||
0.0,0.5,,0,0,0.25,general_mechanical_part,1.0,0.8571428571428571,True,125663.70614359173,000166,125663.70614359173,93.81711632834559,1,6.425513440379324e-14,0.004847580881730266,0.00977766538694562,True,experience,0.0
|
||||
0.45066264681866697,0.125,,0,0,0.1111111111111111,patterned_plate,0.0,0.8571428571428571,True,106129.48703937844,004764,36749.40939347248,54.33835930595081,1,1.0309687869495758,0.05550481239248205,0.1775660613682025,True,baseline,1.8879236099568286
|
||||
0.3413730293323282,0.125,,3,18,0.1111111111111111,patterned_plate,0.0,0.8571428571428571,True,88774.08881569223,004764,36749.40939347248,59.488074232257325,1,0.7876989178243013,0.0465090322155671,0.15562347480527094,True,experience,1.4156602862700836
|
||||
0.5696649029982362,0.0,,0,0,0.0,plate_or_bracket,1.0,1.0,True,19016.0,011680,898496.0,60.445468751589296,1,0.9780420050604243,0.10796723414266624,0.36481967349123434,True,baseline,0.9788357432865589
|
||||
0.583774250440917,0.0,,4,10,0.0,plate_or_bracket,1.0,1.0,True,13547.999999999998,011680,898496.0,59.90070139134242,1,0.9842171382062035,0.11235674854244815,0.374964117147598,True,experience,0.9849214687655816
|
||||
0.18571427761876255,0.2631578947368421,,0,0,0.26666666666666666,patterned_plate,1.0,1.0,True,23882.59512641282,016033,44618.37587532573,81.50778192907364,1,0.34116223818430896,0.018452946285241585,0.08713798240651745,True,baseline,0.4647363410728704
|
||||
0.3238095238095235,0.02631578947368421,,3,10,0.06666666666666667,patterned_plate,1.0,0.9411764705882353,True,13866.369040708962,016033,44618.37587532573,75.91174289423121,1,0.5000404499582691,0.026851094204940862,0.12169100582216402,True,experience,0.6892229094251465
|
||||
0.5959595959595959,0.2857142857142857,,0,0,0.28125,flanged_rotational_part,1.0,1.0,True,118756.98485692723,020543,24145.808587596228,51.56160371886595,1,1.5105685440071281,0.08451218347517844,0.22747541674825858,True,baseline,3.9183271053483395
|
||||
0.23232323232323235,0.05194805194805195,,0,0,0.1875,flanged_rotational_part,1.0,0.888888888888889,True,55638.90609255368,020543,24145.808587596228,69.33575293745653,1,0.4640684949891876,0.042969081471212464,0.11104259063963073,True,experience,1.3042883774509648
|
||||
0.06666666666666685,0.2,,0,0,0.19480519480519481,patterned_prismatic_part,1.0,1.0,True,569438.4483554161,024675,580617.6091315141,91.92371160771847,1,0.2739018704584683,0.011073315847120254,0.07352156562273186,True,baseline,0.019253912730652068
|
||||
0.5777777777777777,0.8933333333333333,,0,0,1.4025974025974026,patterned_prismatic_part,1.0,0.9333333333333333,True,38876.03946418978,024675,580617.6091315141,56.84188202691976,1,0.8746474563374864,0.07094038930770122,0.33837892456299423,True,experience,0.9330436437807312
|
||||
0.0980952380952385,0.16666666666666666,,0,0,0.14285714285714285,patterned_plate,1.0,0.9333333333333333,True,73807.58425433026,029190,131254.75510206894,83.74861954252185,1,0.1560183040026604,0.024101504873521428,0.04232654923555514,True,baseline,0.4376768735202429
|
||||
0.14047619047618823,1.5277777777777777,,0,0,1.6428571428571428,patterned_plate,1.0,0.7999999999999999,True,71846.37021205797,029190,131254.75510206894,73.91053356207678,1,0.20263501345527155,0.02460078307658505,0.04143094591291666,True,experience,0.45261891535901033
|
||||
0.31241830065359194,0.9285714285714286,,0,0,0.5625,patterned_plate,1.0,0.923076923076923,True,34352.594678913054,030352,60881.15044407846,73.94372815488485,1,0.48742381144539443,0.02510282770499014,0.10902462973941958,True,baseline,0.4357433388111292
|
||||
0.4306604575163398,2.642857142857143,,3,10,2.0625,patterned_plate,1.0,0.7692307692307692,True,11173.345159683424,030352,60881.15044407846,59.45331217188532,1,0.6732862799472543,0.03957952772551367,0.16850566658178062,True,experience,0.8164728314398964
|
||||
0.21212121212121218,0.5,,0,0,0.43243243243243246,flanged_rotational_part,1.0,0.9600000000000001,True,73578.89519968569,046014,162271.3445381513,75.92217396437314,1,0.333964798541362,0.033942930350360764,0.05126001418573268,True,baseline,0.5465687709120651
|
||||
0.13636363636363644,0.5,,4,10,0.43243243243243246,flanged_rotational_part,1.0,0.9600000000000001,True,111532.3028776836,046014,162271.3445381513,81.61765078194381,1,0.24200901291799348,0.02662187229047825,0.043202965297600246,True,experience,0.31268023202050027
|
||||
0.6851851851851847,0.3709677419354839,,0,0,0.34782608695652173,patterned_prismatic_part,1.0,0.823529411764706,True,167251.53322574112,047048,33461.296317388085,44.79000279500974,1,1.6260155362793598,0.11322328439048983,0.2789388077989153,True,baseline,3.9983578531842254
|
||||
0.7175925925925921,0.1693548387096774,,3,18,0.2391304347826087,patterned_prismatic_part,1.0,0.823529411764706,True,169277.67589913897,047048,33461.296317388085,44.38628038601891,1,1.587008333438104,0.13259494837120114,0.321530970786483,True,experience,4.058909681606514
|
||||
1.4575235605335388e-16,0.5,,0,0,0.3125,hollow_or_stepped_shaft,1.0,0.9166666666666666,True,8278002.394429736,048346,8341713.8934443,93.45850460439323,1,0.01545893719801078,0.008802226092164953,0.016975575422477428,True,baseline,0.007637698898380382
|
||||
1.4575235605335388e-16,0.5,,0,0,0.3125,hollow_or_stepped_shaft,1.0,0.9166666666666666,True,8292610.800268921,048346,8341713.8934443,93.51807327218812,1,0.012238325281746575,0.008857323306102671,0.017730676062993916,True,experience,0.005886451369899948
|
||||
0.02857142857142833,0.2222222222222222,,0,0,0.21212121212121213,patterned_plate,0.0,0.8571428571428571,True,315117.2806010815,053664,349435.5647059466,85.57701767939263,1,0.07576465816363272,0.012696566244914555,0.02434484580541925,True,baseline,0.0982106218459597
|
||||
0.07936507936507906,0.0,,0,0,0.030303030303030304,patterned_plate,0.0,0.8571428571428571,True,282776.14120936824,053664,349435.5647059466,83.57268406993335,1,0.167398697885095,0.016320456106002173,0.05162627100789126,True,experience,0.19076313412080106
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,063487,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,063487,,,,,,,,experience,
|
||||
0.05572210294349599,0.420863309352518,,0,0,0.4396551724137931,flanged_rotational_part,1.0,0.923076923076923,True,68680.44493144708,065487,81354.16556588435,87.21779338748289,1,0.060356551334099576,0.022572421105818447,0.053770414665160235,True,baseline,0.15578453231351141
|
||||
,,missing STEP,,,,flanged_rotational_part,,,False,,065487,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,067186,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,067186,,,,,,,,experience,
|
||||
,,missing STEP,,,,rotational_part,,,False,,073706,,,,,,,,baseline,
|
||||
,,missing STEP,,,,rotational_part,,,False,,073706,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,077955,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,077955,,,,,,,,experience,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,082187,,,,,,,,baseline,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,082187,,,,,,,,experience,
|
||||
,,missing STEP,,,,rotational_part,,,False,,089970,,,,,,,,baseline,
|
||||
,,missing STEP,,,,rotational_part,,,False,,089970,,,,,,,,experience,
|
||||
,,missing STEP,,,,shaft,,,False,,092092,,,,,,,,baseline,
|
||||
,,missing STEP,,,,shaft,,,False,,092092,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,095520,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,095520,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,102128,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,102128,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,104465,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,104465,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,110143,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,110143,,,,,,,,experience,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,112069,,,,,,,,baseline,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,112069,,,,,,,,experience,
|
||||
0.22361111004444456,0.6538461538461539,,0,0,0.6153846153846154,rotational_part,1.0,0.761904761904762,True,67519.74039233122,116166,147536.10631008807,71.3680012563105,1,0.38925591734814796,0.03424668647245051,0.07518718288900171,True,baseline,0.5423510754010293
|
||||
0.18888888915554566,0.7307692307692307,,3,16,0.6923076923076923,rotational_part,1.0,0.8571428571428572,True,69565.58402815007,116166,147536.10631008807,73.4520592182128,1,0.365981954180488,0.032489227662340435,0.07289222461786297,True,experience,0.528484343473599
|
||||
0.6746686351889055,0.45217391304347826,,0,0,0.45,multi_axis_manifold,0.0,0.7692307692307692,True,34234.94531047202,134828,349556.2389887021,48.526262227273214,1,0.9239731944207157,0.10449739414590258,0.3144594900968871,True,baseline,0.9020616956815967
|
||||
0.7062506154470962,0.5043478260869565,,5,14,0.5,multi_axis_manifold,0.0,0.7692307692307692,True,38190.323227729335,134828,349556.2389887021,48.60371993717729,1,0.9152269986233839,0.09836507300701838,0.29918959165673675,True,experience,0.8907462692177447
|
||||
7.3809523809523805,0.5,,0,0,0.3333333333333333,rotational_part,1.0,0.9411764705882353,True,25446.900494077883,136930,38.877209088173686,25.720060532485707,1,72.6363636363658,1.666072751413829,2.6857505921882785,True,baseline,653.545454545469
|
||||
6.936507936507937,0.5,,0,0,0.3333333333333333,rotational_part,1.0,0.9411764705882353,True,21205.750411731566,136930,38.877209088173686,25.725642575854934,1,67.18181818181998,1.6549008981976585,2.6698915132197145,True,experience,544.4545454545574
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,138022,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,138022,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,141680,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,141680,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,145574,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,145574,,,,,,,,experience,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,150196,,,,,,,,baseline,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,150196,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,150587,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,150587,,,,,,,,experience,
|
||||
,,missing STEP,,,,multi_axis_manifold,,,False,,152568,,,,,,,,baseline,
|
||||
,,missing STEP,,,,multi_axis_manifold,,,False,,152568,,,,,,,,experience,
|
||||
,,missing STEP,,,,plate_or_bracket,,,False,,153245,,,,,,,,baseline,
|
||||
,,missing STEP,,,,plate_or_bracket,,,False,,153245,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,158256,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,158256,,,,,,,,experience,
|
||||
,,missing STEP,,,,hollow_or_stepped_shaft,,,False,,162558,,,,,,,,baseline,
|
||||
,,missing STEP,,,,hollow_or_stepped_shaft,,,False,,162558,,,,,,,,experience,
|
||||
,,missing STEP,,,,multi_axis_manifold,,,False,,167962,,,,,,,,baseline,
|
||||
,,missing STEP,,,,multi_axis_manifold,,,False,,167962,,,,,,,,experience,
|
||||
,,missing STEP,,,,rotational_part,,,False,,168239,,,,,,,,baseline,
|
||||
,,missing STEP,,,,rotational_part,,,False,,168239,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,174675,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,174675,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,183151,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,183151,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,189669,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,189669,,,,,,,,experience,
|
||||
,,missing STEP,,,,rotational_part,,,False,,194029,,,,,,,,baseline,
|
||||
,,missing STEP,,,,rotational_part,,,False,,194029,,,,,,,,experience,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,201547,,,,,,,,baseline,
|
||||
,,missing STEP,,,,prismatic_block,,,False,,201547,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,204730,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,204730,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,211326,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,211326,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,214404,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_plate,,,False,,214404,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,229115,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,229115,,,,,,,,experience,
|
||||
,,missing STEP,,,,hybrid_freeform_part,,,False,,234097,,,,,,,,baseline,
|
||||
,,missing STEP,,,,hybrid_freeform_part,,,False,,234097,,,,,,,,experience,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,242307,,,,,,,,baseline,
|
||||
,,missing STEP,,,,patterned_prismatic_part,,,False,,242307,,,,,,,,experience,
|
||||
|
@@ -0,0 +1,39 @@
|
||||
# CAD 经验库 50 件 A/B 盲重建报告
|
||||
|
||||
> 运行状态:2026-07-23 因 Codex 账户用量上限中断;当前为 15/50
|
||||
> 完整配对的阶段结果。系统提示可在 2026-07-29 10:24 后继续。
|
||||
|
||||
## 口径
|
||||
|
||||
- 测试集:固定分层抽取 50 件;这 50 件不进入经验库。
|
||||
- 训练经验:其余 950 件生成的通用经验库。
|
||||
- 两组输入完全相同:仅多视图图片和文字描述;生成过程禁止读取参考 STEP 和解析 JSON。
|
||||
- Baseline:空经验库;Experience:950 件经验库。
|
||||
- 参考 STEP 只在生成完成后用于评分。
|
||||
- “相似度”是体积、面积、包围盒、表面距离、拓扑和语义特征的综合诊断分,不代表制造级 1:1 或可互换性。
|
||||
|
||||
## 当前完成度
|
||||
|
||||
- 有效 Baseline:16/50
|
||||
- 有效 Experience:15/50
|
||||
- 完整配对:15/50
|
||||
- 经验方法返回/实际采用:106/28
|
||||
|
||||
## 汇总
|
||||
|
||||
| 指标 | 无经验 | 有经验 | 差值 |
|
||||
|---|---:|---:|---:|
|
||||
| 综合相似度(0–100,越高越好) | 69.11 | 66.64 | -2.47 |
|
||||
| 体积相对误差(越低越好) | 44.519 | 37.136 | -7.383 |
|
||||
| 表面积相对误差 | 5.385 | 4.997 | -0.388 |
|
||||
| 包围盒尺寸 MAPE | 0.766 | 0.760 | -0.006 |
|
||||
| 归一化表面 Chamfer | 0.1537 | 0.1559 | +0.0023 |
|
||||
| 特征 F1 | 0.907 | 0.878 | -0.029 |
|
||||
|
||||
经验组胜/平/负(±0.25 分视为平):**4/4/7**。
|
||||
|
||||
## 阶段解释
|
||||
|
||||
- 经验组的体积、表面积和包围盒平均误差较低,说明通用构造经验对尺度/主体关系有一定帮助。
|
||||
- 经验组的表面距离、特征 F1 和综合分暂时较差,说明部分经验会诱导多余或错误特征,当前不能宣称整体提升。
|
||||
- 每组由独立的大模型运行生成;即使经验命中为 0,两次结果也可能因模型随机性不同。因此最终应完成 50 对,并对若干样本做重复运行,才能把经验效果与随机波动分开。
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def run(case_id: str, variant: str) -> dict:
|
||||
completed = subprocess.run(
|
||||
[sys.executable, str(HERE / "run_case.py"), case_id, variant],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
return {
|
||||
"id": case_id,
|
||||
"variant": variant,
|
||||
"returncode": completed.returncode,
|
||||
"output": completed.stdout.strip(),
|
||||
"error": completed.stderr.strip(),
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--workers", type=int, default=4)
|
||||
parser.add_argument("--start-index", type=int, default=0)
|
||||
parser.add_argument("--end-index", type=int)
|
||||
args = parser.parse_args()
|
||||
manifest = json.loads((HERE / "manifest.json").read_text())
|
||||
cases = manifest["cases"][args.start_index : args.end_index]
|
||||
jobs = [
|
||||
(case["id"], variant)
|
||||
for case in cases
|
||||
for variant in ("baseline", "experience")
|
||||
]
|
||||
results_name = (
|
||||
"batch-results.json"
|
||||
if args.start_index == 0 and args.end_index is None
|
||||
else f"batch-results-{args.start_index}-{args.end_index or 'end'}.json"
|
||||
)
|
||||
results_path = HERE / results_name
|
||||
results = []
|
||||
with ThreadPoolExecutor(max_workers=args.workers) as pool:
|
||||
futures = {pool.submit(run, *job): job for job in jobs}
|
||||
for future in as_completed(futures):
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
print(json.dumps(result, ensure_ascii=False), flush=True)
|
||||
results_path.write_text(
|
||||
json.dumps(results, indent=2, ensure_ascii=False) + "\n"
|
||||
)
|
||||
failed = [item for item in results if item["returncode"] != 0]
|
||||
print(json.dumps({"completed": len(results), "failed": len(failed)}))
|
||||
raise SystemExit(1 if failed else 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
WORKSPACE = HERE.parents[2]
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("case_id")
|
||||
parser.add_argument("variant", choices=("baseline", "experience"))
|
||||
args = parser.parse_args()
|
||||
manifest = json.loads((HERE / "manifest.json").read_text())
|
||||
case = next(item for item in manifest["cases"] if item["id"] == args.case_id)
|
||||
output = Path(case[f"{args.variant}_step"])
|
||||
output.parent.mkdir(parents=True, exist_ok=True)
|
||||
if output.is_file():
|
||||
print(json.dumps({"status": "skipped", "output": str(output)}))
|
||||
return
|
||||
|
||||
description = Path(case["description"]).read_text()
|
||||
library = (
|
||||
HERE / "empty-library.json"
|
||||
if args.variant == "baseline"
|
||||
else HERE / "train-library.json"
|
||||
)
|
||||
prompt = f"""
|
||||
This is a controlled CAD reconstruction benchmark.
|
||||
Use $cad-router and $cad to reconstruct the single mechanical part shown in
|
||||
the attached multi-view image and described below. Generate a new parametric
|
||||
STEP from the image and description only.
|
||||
|
||||
Hard isolation rules:
|
||||
- Do not search for, inspect, import, copy, or reference any teacher STEP,
|
||||
SLDPRT, parser/input, parser/output, case JSON, or SLDPRT-NET dataset file.
|
||||
- The attached image and the description in this prompt are the only
|
||||
part-specific evidence.
|
||||
- Run cad-router with the explicit generalized experience library
|
||||
{library.resolve()}; do not use any other experience library.
|
||||
- Use the router-selected backend and actually apply returned generalized
|
||||
methods when present.
|
||||
- Do not preserve an exact STEP base: this must be a blind reconstruction.
|
||||
|
||||
Write the primary output exactly to:
|
||||
{output.resolve()}
|
||||
Keep the editable generator and model-spec.json in the same directory.
|
||||
Also write the router result to route.json and record in cad-task.json the
|
||||
experience methods returned and the subset actually applied. Record an empty
|
||||
list when nothing matches; never claim an experience method was used unless
|
||||
the router returned it.
|
||||
Validate that the STEP opens and contains positive-volume geometry. For this
|
||||
batch benchmark, do not open CAD Viewer or create browser tabs.
|
||||
|
||||
Description:
|
||||
{description}
|
||||
"""
|
||||
command = [
|
||||
"codex",
|
||||
"exec",
|
||||
"--ephemeral",
|
||||
"--json",
|
||||
"--image",
|
||||
case["image"],
|
||||
"--cd",
|
||||
str(WORKSPACE),
|
||||
"--sandbox",
|
||||
"danger-full-access",
|
||||
"--output-last-message",
|
||||
case[f"{args.variant}_message"],
|
||||
prompt,
|
||||
]
|
||||
with Path(case[f"{args.variant}_log"]).open("w") as stream:
|
||||
completed = subprocess.run(command, stdout=stream, stderr=subprocess.STDOUT)
|
||||
print(json.dumps({
|
||||
"status": "ok" if completed.returncode == 0 and output.is_file() else "failed",
|
||||
"returncode": completed.returncode,
|
||||
"output": str(output),
|
||||
}))
|
||||
raise SystemExit(0 if completed.returncode == 0 and output.is_file() else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
OUTPUT = ROOT / "parser" / "output"
|
||||
HERE = Path(__file__).resolve().parent
|
||||
DATASET = Path("/Users/jerry/linkhand/cad/SLDPRT-NET/sub_datset/sub_datset")
|
||||
TARGET_COUNT = 50
|
||||
|
||||
|
||||
def main() -> None:
|
||||
rows: list[dict] = []
|
||||
for path in sorted(OUTPUT.glob("*.json")):
|
||||
payload = json.loads(path.read_text())
|
||||
stem = Path(payload["provenance"]["source_path"]).stem
|
||||
image = DATASET / "image" / f"{stem}.png"
|
||||
description = DATASET / "Des_Text" / f"{stem}.txt"
|
||||
reference = DATASET / "model_step" / f"{stem}.step"
|
||||
if image.is_file() and description.is_file() and reference.is_file():
|
||||
rows.append(
|
||||
{
|
||||
"id": stem,
|
||||
"family": payload["design_ir"]["part_family"],
|
||||
"case_json": str(path.resolve()),
|
||||
"image": str(image),
|
||||
"description": str(description),
|
||||
"reference_step": str(reference),
|
||||
}
|
||||
)
|
||||
|
||||
by_family: dict[str, list[dict]] = defaultdict(list)
|
||||
for row in rows:
|
||||
by_family[row["family"]].append(row)
|
||||
rng = random.Random(20260723)
|
||||
selected: list[dict] = []
|
||||
total = len(rows)
|
||||
for family, family_rows in sorted(by_family.items()):
|
||||
rng.shuffle(family_rows)
|
||||
quota = max(1, round(TARGET_COUNT * len(family_rows) / total))
|
||||
selected.extend(family_rows[: min(quota, len(family_rows))])
|
||||
selected_ids = {row["id"] for row in selected}
|
||||
if len(selected) > TARGET_COUNT:
|
||||
selected = selected[:TARGET_COUNT]
|
||||
elif len(selected) < TARGET_COUNT:
|
||||
remainder = [row for row in rows if row["id"] not in selected_ids]
|
||||
rng.shuffle(remainder)
|
||||
selected.extend(remainder[: TARGET_COUNT - len(selected)])
|
||||
selected = sorted(selected, key=lambda row: row["id"])
|
||||
selected_ids = {row["id"] for row in selected}
|
||||
|
||||
train_dir = HERE / "train_cases"
|
||||
train_dir.mkdir(parents=True, exist_ok=True)
|
||||
for path in train_dir.glob("*.json"):
|
||||
path.unlink()
|
||||
for row in rows:
|
||||
if row["id"] in selected_ids:
|
||||
continue
|
||||
source = Path(row["case_json"])
|
||||
(train_dir / source.name).symlink_to(source)
|
||||
|
||||
for row in selected:
|
||||
for variant in ("baseline", "experience"):
|
||||
output_dir = HERE / "generated" / variant / row["id"]
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
row[f"{variant}_step"] = str(output_dir / f"{row['id']}.step")
|
||||
row[f"{variant}_log"] = str(output_dir / "codex.jsonl")
|
||||
row[f"{variant}_message"] = str(output_dir / "final.txt")
|
||||
|
||||
manifest = {
|
||||
"schema_version": "1.0",
|
||||
"selection_seed": 20260723,
|
||||
"holdout_count": len(selected),
|
||||
"train_count": len(rows) - len(selected),
|
||||
"cases": selected,
|
||||
}
|
||||
(HERE / "manifest.json").write_text(
|
||||
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n"
|
||||
)
|
||||
print(json.dumps({
|
||||
"holdout_count": len(selected),
|
||||
"train_count": len(rows) - len(selected),
|
||||
"family_counts": {
|
||||
family: sum(row["family"] == family for row in selected)
|
||||
for family in sorted({row["family"] for row in selected})
|
||||
},
|
||||
}, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"draft_kind": "llm_generalized_experience_proposals",
|
||||
"proposals": [
|
||||
{
|
||||
"id": "constraint.patterned_prismatic_part.repeated_radius_group",
|
||||
"kind": "constraint",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["repeated_axial_hole_pattern"],
|
||||
"required_relations": ["repeated_radius_group"],
|
||||
"context_features": ["repeated_axial_hole_pattern"]
|
||||
},
|
||||
"guidance": "Preserve one shared semantic radius role across members of a repeated axial feature group.",
|
||||
"semantic_rationale": "The equality relationship transfers across different overall sizes and placements.",
|
||||
"check": "Confirm that repeated members intended to be equal retain the same radius role."
|
||||
},
|
||||
{
|
||||
"id": "constraint.patterned_prismatic_part.multiple_axis_system",
|
||||
"kind": "constraint",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["cylindrical_feature_network", "multi_axis_passage"],
|
||||
"required_relations": ["multiple_axis_system"],
|
||||
"context_features": ["cylindrical_feature_network", "multi_axis_passage"]
|
||||
},
|
||||
"guidance": "Represent passage directions as explicit semantic axes instead of unrelated local cuts.",
|
||||
"semantic_rationale": "An explicit axis network preserves relationships when the body dimensions change.",
|
||||
"check": "Confirm that every passage remains assigned to its intended axis system."
|
||||
},
|
||||
{
|
||||
"id": "motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"kind": "feature_motif",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["multi_level_planar_profile", "planar_dominant_body"],
|
||||
"required_relations": [],
|
||||
"context_features": ["multi_level_planar_profile", "planar_dominant_body"]
|
||||
},
|
||||
"guidance": "Treat the planar-dominant body and its multiple profile levels as one reusable structural composition.",
|
||||
"semantic_rationale": "The composition describes topology without depending on an instance dimension.",
|
||||
"check": "Confirm that the expected planar levels remain distinct and connected to the primary body."
|
||||
},
|
||||
{
|
||||
"id": "motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"kind": "feature_motif",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["cylindrical_feature_network", "multi_axis_passage"],
|
||||
"required_relations": ["multiple_axis_system"],
|
||||
"context_features": ["cylindrical_feature_network", "multi_axis_passage"]
|
||||
},
|
||||
"guidance": "Model the cylindrical feature network and multi-axis passages as a coordinated feature system.",
|
||||
"semantic_rationale": "The coordinated system transfers across variants while preserving semantic axes.",
|
||||
"check": "Confirm that the cylindrical features and passages preserve their intended axis memberships."
|
||||
},
|
||||
{
|
||||
"id": "validation.patterned_prismatic_part.repeated_feature_consistency",
|
||||
"kind": "validation_rule",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["repeated_axial_hole_pattern"],
|
||||
"required_relations": ["repeated_radius_group"],
|
||||
"context_features": ["repeated_axial_hole_pattern"]
|
||||
},
|
||||
"guidance": "Validate repeated feature membership, equality roles, and attachment to the intended host region.",
|
||||
"semantic_rationale": "These checks remain meaningful across dimensions and pattern placements.",
|
||||
"check": "Verify repeated-member count consistency, shared radius roles, and host connectivity."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
# 015133 单件高纯度蒸馏实验
|
||||
|
||||
## 对照结果
|
||||
|
||||
| 路线 | 可用于生成的信息 | 严格相似度 | 结论 |
|
||||
|---|---|---:|---|
|
||||
| 纯图片生成 | 多视图图片与文字描述 | 54.71 | 主体类别可辨认,但尺寸、孔系和端部结构损失明显 |
|
||||
| 正式单件通用蒸馏 | 净化后的通用特征、关系和比例角色 | 无可执行提升 | 产生 5 条候选、0 条推广经验;单件证据不能证明通用性 |
|
||||
| 专属 `exact_step_base` 模板 | 原始 B-Rep、哈希、几何签名与 JSON 修改层 | 100.00 | STEP 字节一致,199 面、488 边、体积与包围盒完全一致 |
|
||||
|
||||
## 源几何
|
||||
|
||||
- 包围盒:213.5 × 78.5 × 76.0 mm
|
||||
- 实体数:1
|
||||
- 面数:199
|
||||
- 边数:488
|
||||
- 体积:1174741.621833 mm³
|
||||
- 解析曲面:79 个平面、80 个圆柱面、40 个圆锥面
|
||||
|
||||
## 单件通用蒸馏实际保留的信息
|
||||
|
||||
- 7 个粗粒度特征类别
|
||||
- 2 个关系
|
||||
- 6 个无量纲比例角色
|
||||
- 不保留孔数量、孔中心、端板槽、各圆柱面的语义身份、具体尺寸或构造顺序
|
||||
|
||||
这些信息适合积累跨零件经验,但不足以重建一个具有 199 个面的指定实例。
|
||||
|
||||
## 结论
|
||||
|
||||
“单件高纯度”要达到 90 分以上,必须进入**实例模板/逆向参数化**
|
||||
通道,而不是通用经验通道。通用经验负责教方法;实例模板负责保存指定
|
||||
零件的尺寸、特征图和精确基体。两类数据必须分库、分权限、分评价口径。
|
||||
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"batch_kind": "sanitized_cad_semantic_review",
|
||||
"guardrails": {
|
||||
"contains_instance_dimensions": false,
|
||||
"contains_absolute_coordinates": false,
|
||||
"llm_must_propose_methods_not_copy_answers": true,
|
||||
"proposals_require_deterministic_evidence_verification": true
|
||||
},
|
||||
"corpus_summary": {
|
||||
"case_count": 1,
|
||||
"family_count": 1
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
"anonymous_case": "case_000001",
|
||||
"family": "patterned_prismatic_part",
|
||||
"features": [
|
||||
"cylindrical_feature_network",
|
||||
"high_topological_complexity",
|
||||
"moderate_aspect_body",
|
||||
"multi_axis_passage",
|
||||
"multi_level_planar_profile",
|
||||
"planar_dominant_body",
|
||||
"repeated_axial_hole_pattern"
|
||||
],
|
||||
"relations": [
|
||||
"multiple_axis_system",
|
||||
"repeated_radius_group"
|
||||
],
|
||||
"candidate_rules": [],
|
||||
"validation_targets": [],
|
||||
"dimensionless_observation_roles": [
|
||||
{
|
||||
"name": "bbox_short_to_long",
|
||||
"numerator_role": "short_bounding_span",
|
||||
"denominator_role": "long_bounding_span"
|
||||
},
|
||||
{
|
||||
"name": "bbox_middle_to_long",
|
||||
"numerator_role": "middle_bounding_span",
|
||||
"denominator_role": "long_bounding_span"
|
||||
},
|
||||
{
|
||||
"name": "planar_area_fraction",
|
||||
"numerator_role": "planar_surface_area",
|
||||
"denominator_role": "total_surface_area"
|
||||
},
|
||||
{
|
||||
"name": "cylindrical_area_fraction",
|
||||
"numerator_role": "cylindrical_surface_area",
|
||||
"denominator_role": "total_surface_area"
|
||||
},
|
||||
{
|
||||
"name": "pattern_radius_to_transverse_span",
|
||||
"numerator_role": "pattern_radius",
|
||||
"denominator_role": "transverse_bounding_span"
|
||||
},
|
||||
{
|
||||
"name": "repeated_feature_radius_to_transverse_span",
|
||||
"numerator_role": "repeated_feature_radius",
|
||||
"denominator_role": "transverse_bounding_span"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"existing_vocabulary": {
|
||||
"promoted_ids": [
|
||||
"constraint.flanged_rotational_part.coaxial_stack",
|
||||
"constraint.flanged_rotational_part.common_pattern_radius",
|
||||
"constraint.flanged_rotational_part.dominant_axis_alignment",
|
||||
"constraint.flanged_rotational_part.repeated_radius_group",
|
||||
"constraint.flanged_rotational_part.shared_passage_axis",
|
||||
"constraint.flanged_rotational_part.transition_axis_continuity",
|
||||
"constraint.hollow_or_stepped_shaft.coaxial_stack",
|
||||
"constraint.hollow_or_stepped_shaft.dominant_axis_alignment",
|
||||
"constraint.hollow_or_stepped_shaft.shared_passage_axis",
|
||||
"constraint.hollow_or_stepped_shaft.transition_axis_continuity",
|
||||
"constraint.multi_axis_manifold.multiple_axis_system",
|
||||
"constraint.multi_axis_manifold.shared_passage_axis",
|
||||
"constraint.patterned_plate.common_pattern_radius",
|
||||
"constraint.patterned_plate.multiple_axis_system",
|
||||
"constraint.patterned_plate.repeated_radius_group",
|
||||
"constraint.patterned_plate.shared_passage_axis",
|
||||
"constraint.patterned_plate.transition_axis_continuity",
|
||||
"constraint.patterned_prismatic_part.common_pattern_radius",
|
||||
"constraint.patterned_prismatic_part.multiple_axis_system",
|
||||
"constraint.patterned_prismatic_part.repeated_radius_group",
|
||||
"constraint.patterned_prismatic_part.shared_passage_axis",
|
||||
"constraint.patterned_prismatic_part.transition_axis_continuity",
|
||||
"constraint.rotational_part.coaxial_stack",
|
||||
"constraint.rotational_part.dominant_axis_alignment",
|
||||
"constraint.rotational_part.shared_passage_axis",
|
||||
"constraint.rotational_part.transition_axis_continuity",
|
||||
"constraint.shaft.coaxial_stack",
|
||||
"constraint.shaft.dominant_axis_alignment",
|
||||
"constraint.shaft.shared_passage_axis",
|
||||
"constraint.shaft.transition_axis_continuity",
|
||||
"distribution.flanged_rotational_part.bbox_middle_to_long",
|
||||
"distribution.flanged_rotational_part.bbox_short_to_long",
|
||||
"distribution.flanged_rotational_part.cylindrical_area_fraction",
|
||||
"distribution.flanged_rotational_part.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.flanged_rotational_part.pattern_radius_to_transverse_span",
|
||||
"distribution.flanged_rotational_part.planar_area_fraction",
|
||||
"distribution.flanged_rotational_part.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.flanged_rotational_part.smallest_to_largest_coaxial_radius",
|
||||
"distribution.general_mechanical_part.bbox_middle_to_long",
|
||||
"distribution.general_mechanical_part.bbox_short_to_long",
|
||||
"distribution.general_mechanical_part.cylindrical_area_fraction",
|
||||
"distribution.general_mechanical_part.planar_area_fraction",
|
||||
"distribution.hollow_or_stepped_shaft.bbox_middle_to_long",
|
||||
"distribution.hollow_or_stepped_shaft.bbox_short_to_long",
|
||||
"distribution.hollow_or_stepped_shaft.cylindrical_area_fraction",
|
||||
"distribution.hollow_or_stepped_shaft.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.hollow_or_stepped_shaft.planar_area_fraction",
|
||||
"distribution.hollow_or_stepped_shaft.smallest_to_largest_coaxial_radius",
|
||||
"distribution.multi_axis_manifold.bbox_middle_to_long",
|
||||
"distribution.multi_axis_manifold.bbox_short_to_long",
|
||||
"distribution.multi_axis_manifold.cylindrical_area_fraction",
|
||||
"distribution.multi_axis_manifold.pattern_radius_to_transverse_span",
|
||||
"distribution.multi_axis_manifold.planar_area_fraction",
|
||||
"distribution.multi_axis_manifold.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.patterned_plate.bbox_middle_to_long",
|
||||
"distribution.patterned_plate.bbox_short_to_long",
|
||||
"distribution.patterned_plate.cylindrical_area_fraction",
|
||||
"distribution.patterned_plate.pattern_radius_to_transverse_span",
|
||||
"distribution.patterned_plate.planar_area_fraction",
|
||||
"distribution.patterned_plate.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.patterned_prismatic_part.bbox_middle_to_long",
|
||||
"distribution.patterned_prismatic_part.bbox_short_to_long",
|
||||
"distribution.patterned_prismatic_part.cylindrical_area_fraction",
|
||||
"distribution.patterned_prismatic_part.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.patterned_prismatic_part.pattern_radius_to_transverse_span",
|
||||
"distribution.patterned_prismatic_part.planar_area_fraction",
|
||||
"distribution.patterned_prismatic_part.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.patterned_prismatic_part.smallest_to_largest_coaxial_radius",
|
||||
"distribution.plate_or_bracket.bbox_middle_to_long",
|
||||
"distribution.plate_or_bracket.bbox_short_to_long",
|
||||
"distribution.plate_or_bracket.cylindrical_area_fraction",
|
||||
"distribution.plate_or_bracket.planar_area_fraction",
|
||||
"distribution.prismatic_block.bbox_middle_to_long",
|
||||
"distribution.prismatic_block.bbox_short_to_long",
|
||||
"distribution.prismatic_block.cylindrical_area_fraction",
|
||||
"distribution.prismatic_block.pattern_radius_to_transverse_span",
|
||||
"distribution.prismatic_block.planar_area_fraction",
|
||||
"distribution.prismatic_block.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.rotational_part.bbox_middle_to_long",
|
||||
"distribution.rotational_part.bbox_short_to_long",
|
||||
"distribution.rotational_part.cylindrical_area_fraction",
|
||||
"distribution.rotational_part.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.rotational_part.planar_area_fraction",
|
||||
"distribution.rotational_part.smallest_to_largest_coaxial_radius",
|
||||
"distribution.shaft.bbox_middle_to_long",
|
||||
"distribution.shaft.bbox_short_to_long",
|
||||
"distribution.shaft.cylindrical_area_fraction",
|
||||
"distribution.shaft.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.shaft.planar_area_fraction",
|
||||
"distribution.shaft.smallest_to_largest_coaxial_radius",
|
||||
"motif.flanged_rotational_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.flanged_rotational_part.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.flanged_rotational_part.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
|
||||
"motif.flanged_rotational_part.rotational_body_and_flange_like_rotational_body",
|
||||
"motif.flanged_rotational_part.rotational_body_and_stepped_rotational_profile",
|
||||
"motif.hollow_or_stepped_shaft.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.hollow_or_stepped_shaft.rotational_body_and_stepped_rotational_profile",
|
||||
"motif.hollow_or_stepped_shaft.shaft_like_body_and_stepped_rotational_profile",
|
||||
"motif.multi_axis_manifold.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.multi_axis_manifold.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.multi_axis_manifold.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.patterned_plate.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.patterned_plate.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.patterned_plate.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.patterned_plate.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
|
||||
"motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.patterned_prismatic_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.patterned_prismatic_part.repeated_axial_hole_pattern_and_circular_equal_radius_pattern",
|
||||
"motif.plate_or_bracket.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.prismatic_block.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"motif.rotational_part.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.rotational_part.rotational_body_and_flange_like_rotational_body",
|
||||
"motif.rotational_part.rotational_body_and_stepped_rotational_profile",
|
||||
"motif.rotational_part.rotational_body_and_toroidal_blend_or_groove",
|
||||
"motif.shaft.cylindrical_feature_network_and_stepped_cylindrical_passage",
|
||||
"motif.shaft.rotational_body_and_stepped_rotational_profile",
|
||||
"motif.shaft.shaft_like_body_and_stepped_rotational_profile"
|
||||
],
|
||||
"candidate_ids": [
|
||||
"constraint.flanged_rotational_part.multiple_axis_system",
|
||||
"constraint.general_mechanical_part.transition_axis_continuity",
|
||||
"constraint.hollow_or_stepped_shaft.multiple_axis_system",
|
||||
"constraint.multi_axis_manifold.transition_axis_continuity",
|
||||
"constraint.prismatic_block.transition_axis_continuity",
|
||||
"constraint.rotational_part.multiple_axis_system",
|
||||
"distribution.general_mechanical_part.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.general_mechanical_part.smallest_to_largest_coaxial_radius",
|
||||
"distribution.patterned_plate.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.patterned_plate.smallest_to_largest_coaxial_radius",
|
||||
"distribution.plate_or_bracket.pattern_radius_to_transverse_span",
|
||||
"distribution.plate_or_bracket.repeated_feature_radius_to_transverse_span",
|
||||
"distribution.prismatic_block.largest_coaxial_radius_to_transverse_span",
|
||||
"distribution.prismatic_block.smallest_to_largest_coaxial_radius",
|
||||
"motif.flanged_rotational_part.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.flanged_rotational_part.rotational_body_and_toroidal_blend_or_groove",
|
||||
"motif.flanged_rotational_part.shaft_like_body_and_stepped_rotational_profile",
|
||||
"motif.hollow_or_stepped_shaft.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.hollow_or_stepped_shaft.rotational_body_and_toroidal_blend_or_groove",
|
||||
"motif.rotational_part.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"motif.rotational_part.multi_level_planar_profile_and_planar_dominant_body"
|
||||
]
|
||||
},
|
||||
"draft_contract": {
|
||||
"draft_kind": "llm_generalized_experience_proposals",
|
||||
"proposal_required_fields": [
|
||||
"id",
|
||||
"kind",
|
||||
"scope",
|
||||
"evidence_query",
|
||||
"guidance",
|
||||
"semantic_rationale"
|
||||
],
|
||||
"allowed_kinds": [
|
||||
"feature_motif",
|
||||
"constraint",
|
||||
"design_rule",
|
||||
"validation_rule",
|
||||
"failure_repair",
|
||||
"dimensionless_distribution"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"generated_at": "2026-07-23T11:21:55+00:00",
|
||||
"induction_mode": "llm_proposals_with_deterministic_evidence_verification",
|
||||
"status": "collecting_evidence",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": false,
|
||||
"absolute_coordinates_allowed": false,
|
||||
"single_case_promotion_allowed": false,
|
||||
"llm_semantic_review_required": true,
|
||||
"draft_evidence_verified": true,
|
||||
"router_consumable": true,
|
||||
"minimum_support": 20,
|
||||
"minimum_confidence": 0.8
|
||||
},
|
||||
"corpus_summary": {
|
||||
"accepted_case_count": 1,
|
||||
"duplicate_case_count": 0,
|
||||
"rejected_case_count": 0,
|
||||
"family_count": 1
|
||||
},
|
||||
"experience_summary": {
|
||||
"llm_proposal_count": 5,
|
||||
"promoted_experience_count": 0,
|
||||
"candidate_experience_count": 5,
|
||||
"by_kind": [],
|
||||
"by_scope": [],
|
||||
"candidate_by_kind": [
|
||||
{
|
||||
"kind": "constraint",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"kind": "feature_motif",
|
||||
"count": 2
|
||||
},
|
||||
{
|
||||
"kind": "validation_rule",
|
||||
"count": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
"experiences": [],
|
||||
"candidate_experiences": [
|
||||
{
|
||||
"id": "constraint.patterned_prismatic_part.multiple_axis_system",
|
||||
"kind": "constraint",
|
||||
"scope": [
|
||||
"patterned_prismatic_part"
|
||||
],
|
||||
"when": {
|
||||
"features": [
|
||||
"cylindrical_feature_network",
|
||||
"multi_axis_passage"
|
||||
],
|
||||
"relations": [
|
||||
"multiple_axis_system"
|
||||
]
|
||||
},
|
||||
"guidance": "Represent passage directions as explicit semantic axes instead of unrelated local cuts.",
|
||||
"semantic_rationale": "An explicit axis network preserves relationships when the body dimensions change.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"check": "Confirm that every passage remains assigned to its intended axis system.",
|
||||
"promotion_state": "candidate",
|
||||
"required_support": 20,
|
||||
"remaining_support": 19,
|
||||
"required_confidence": 0.8,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
|
||||
},
|
||||
{
|
||||
"id": "constraint.patterned_prismatic_part.repeated_radius_group",
|
||||
"kind": "constraint",
|
||||
"scope": [
|
||||
"patterned_prismatic_part"
|
||||
],
|
||||
"when": {
|
||||
"features": [
|
||||
"repeated_axial_hole_pattern"
|
||||
],
|
||||
"relations": [
|
||||
"repeated_radius_group"
|
||||
]
|
||||
},
|
||||
"guidance": "Preserve one shared semantic radius role across members of a repeated axial feature group.",
|
||||
"semantic_rationale": "The equality relationship transfers across different overall sizes and placements.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"check": "Confirm that repeated members intended to be equal retain the same radius role.",
|
||||
"promotion_state": "candidate",
|
||||
"required_support": 20,
|
||||
"remaining_support": 19,
|
||||
"required_confidence": 0.8,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
|
||||
},
|
||||
{
|
||||
"id": "motif.patterned_prismatic_part.cylindrical_feature_network_and_multi_axis_passage",
|
||||
"kind": "feature_motif",
|
||||
"scope": [
|
||||
"patterned_prismatic_part"
|
||||
],
|
||||
"when": {
|
||||
"features": [
|
||||
"cylindrical_feature_network",
|
||||
"multi_axis_passage"
|
||||
],
|
||||
"relations": [
|
||||
"multiple_axis_system"
|
||||
]
|
||||
},
|
||||
"guidance": "Model the cylindrical feature network and multi-axis passages as a coordinated feature system.",
|
||||
"semantic_rationale": "The coordinated system transfers across variants while preserving semantic axes.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"check": "Confirm that the cylindrical features and passages preserve their intended axis memberships.",
|
||||
"promotion_state": "candidate",
|
||||
"required_support": 20,
|
||||
"remaining_support": 19,
|
||||
"required_confidence": 0.8,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
|
||||
},
|
||||
{
|
||||
"id": "motif.patterned_prismatic_part.multi_level_planar_profile_and_planar_dominant_body",
|
||||
"kind": "feature_motif",
|
||||
"scope": [
|
||||
"patterned_prismatic_part"
|
||||
],
|
||||
"when": {
|
||||
"features": [
|
||||
"multi_level_planar_profile",
|
||||
"planar_dominant_body"
|
||||
],
|
||||
"relations": []
|
||||
},
|
||||
"guidance": "Treat the planar-dominant body and its multiple profile levels as one reusable structural composition.",
|
||||
"semantic_rationale": "The composition describes topology without depending on an instance dimension.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"check": "Confirm that the expected planar levels remain distinct and connected to the primary body.",
|
||||
"promotion_state": "candidate",
|
||||
"required_support": 20,
|
||||
"remaining_support": 19,
|
||||
"required_confidence": 0.8,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
|
||||
},
|
||||
{
|
||||
"id": "validation.patterned_prismatic_part.repeated_feature_consistency",
|
||||
"kind": "validation_rule",
|
||||
"scope": [
|
||||
"patterned_prismatic_part"
|
||||
],
|
||||
"when": {
|
||||
"features": [
|
||||
"repeated_axial_hole_pattern"
|
||||
],
|
||||
"relations": [
|
||||
"repeated_radius_group"
|
||||
]
|
||||
},
|
||||
"guidance": "Validate repeated feature membership, equality roles, and attachment to the intended host region.",
|
||||
"semantic_rationale": "These checks remain meaningful across dimensions and pattern placements.",
|
||||
"support": 1,
|
||||
"confidence": 1.0,
|
||||
"check": "Verify repeated-member count consistency, shared radius roles, and host connectivity.",
|
||||
"promotion_state": "candidate",
|
||||
"required_support": 20,
|
||||
"remaining_support": 19,
|
||||
"required_confidence": 0.8,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -25,14 +25,18 @@ published library and require repeated support before promoting any experience.
|
||||
```
|
||||
|
||||
4. Read `work/review/semantic-batch.json` completely. Act as the semantic
|
||||
distiller: compare families, features, relations, prior vocabulary, failure
|
||||
rules, and validation targets; then author
|
||||
distiller: compare families, parameter roles, datum roles, canonical
|
||||
reconstruction stages, feature cardinality classes, relations, prior
|
||||
vocabulary, failure rules, and validation targets; then author
|
||||
`work/review/experience-draft.json` using
|
||||
`references/llm-review-contract.md`.
|
||||
|
||||
Propose methods and invariants, not raw co-occurrence pairs. Do not infer
|
||||
operation order, manufacturing intent, or failure causes that final B-Rep
|
||||
evidence cannot support.
|
||||
Propose methods, invariants, and family-level `reconstruction_grammar`
|
||||
records, not raw co-occurrence pairs. A reconstruction grammar is a
|
||||
canonical backend-neutral strategy inferred from repeated geometry; it is
|
||||
not the source model's recovered feature history. Do not infer
|
||||
manufacturing intent or failure causes that final B-Rep evidence cannot
|
||||
support.
|
||||
|
||||
5. Publish only after the model draft exists:
|
||||
|
||||
@@ -70,10 +74,15 @@ required.
|
||||
|
||||
## STEP extraction boundary
|
||||
|
||||
- The extractor reads analytic B-Rep surfaces, bounding boxes, topology counts,
|
||||
axes, radii, and locations into a private evidence record.
|
||||
- It derives only semantic candidates such as rotational stacks, axis
|
||||
relationships, transitions, and repeated-hole motifs.
|
||||
- The V2 extractor reads analytic B-Rep surfaces, bounding boxes, topology
|
||||
counts, axes, radii, and locations into a private evidence record.
|
||||
- It derives scale-independent body classes, part families, rotational and
|
||||
prismatic motifs, passage and pattern relationships, complexity roles, and
|
||||
normalized ratio observations before sanitization.
|
||||
- It also derives private exact cardinality evidence plus sanitized symbolic
|
||||
parameter roles, datum roles, feature-count classes, and canonical
|
||||
reconstruction stages. These are the evidence used to learn how to rebuild a
|
||||
family without publishing any teacher dimensions.
|
||||
- Exact source paths, hashes, dimensions, coordinates, and face references are
|
||||
permitted in private case JSON and forbidden in the published library.
|
||||
- Keep input/output as a human-visible staging area and induction corpus. Never
|
||||
@@ -86,7 +95,8 @@ required.
|
||||
- One case is evidence, not experience.
|
||||
- Publish generalized, dimension-free observations below threshold as visible
|
||||
`candidate_experiences`, but never expose them to generation consumers.
|
||||
- Require `min_support` independent cases and `min_confidence` before promotion.
|
||||
- Require `min_support` independent cases and conditional `min_confidence`
|
||||
within the proposal's semantic context before promotion.
|
||||
- Promote feature compositions, semantic constraints, dimensionless
|
||||
distributions, validation checks, and failure-repair methods.
|
||||
- Reject absolute dimensions, coordinates, face references, source paths,
|
||||
@@ -105,6 +115,8 @@ The library is backend-neutral. A consumer receives:
|
||||
- `dimensionless_distribution`: multi-case ratio ranges;
|
||||
- `validation_rule`: checks independent of one part's coordinates;
|
||||
- `failure_repair`: reusable detection and repair methods.
|
||||
- `reconstruction_grammar`: symbolic parameters, datums, canonical feature
|
||||
stages, optional/required feature roles, and validation roles for a family.
|
||||
|
||||
build123d, SimpleCADAPI, or another backend translates the same queried
|
||||
experience into its own operations. Experience must never contain executable
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "CAD Experience Builder"
|
||||
short_description: "用大模型语义复核与证据门禁蒸馏可迁移的 CAD 通用经验"
|
||||
default_prompt: "Use $cad-experience-builder to prepare sanitized STEP evidence, perform model-led semantic induction, publish only evidence-verified generalized CAD experience, and report candidate versus promoted results."
|
||||
short_description: "蒸馏可迁移的 CAD 重建语法、约束与验证经验"
|
||||
default_prompt: "Use $cad-experience-builder to prepare sanitized STEP evidence, induce family-level symbolic reconstruction grammars and reusable methods, publish only deterministically verified experience, and report candidate versus promoted results."
|
||||
|
||||
@@ -18,6 +18,25 @@ Supported fields:
|
||||
"part_family": "flanged_hub_adapter",
|
||||
"features": [{"type": "base_flange"}, {"type": "hollow_sleeve"}],
|
||||
"constraints": [{"id": "coaxial_stack", "type": "coaxial"}],
|
||||
"reconstruction_evidence": {
|
||||
"parameter_roles": ["overall_long_span", "coaxial_diameter_roles"],
|
||||
"datum_roles": ["part_center", "primary_axis"],
|
||||
"feature_roles": ["rotational_body", "coaxial_cylindrical_stack"],
|
||||
"relation_roles": ["coaxial_stack"],
|
||||
"canonical_stages": [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": ["part_center", "primary_axis"]
|
||||
}
|
||||
],
|
||||
"private_cardinality_evidence": {
|
||||
"feature_role_count": 5,
|
||||
"feature_role_count_class": "repeated"
|
||||
},
|
||||
"validation_roles": ["closed_solid", "coaxial_stack"]
|
||||
},
|
||||
"normalized_observations": [
|
||||
{
|
||||
"name": "sleeve_od_to_base_od",
|
||||
@@ -52,3 +71,11 @@ likely instance answers. Ratios are accepted only through
|
||||
|
||||
Legacy distilled JSON can be used when it contains the fields above. Exact
|
||||
`parameters`, `evidence`, centers, axes, and surface references are ignored.
|
||||
|
||||
V3 extractor output should also provide several independent semantic signals
|
||||
per case: body-aspect class, topology-complexity role, analytic feature
|
||||
networks, part family, relationship candidates, and role-named dimensionless
|
||||
observations. It additionally provides symbolic reconstruction roles, canonical
|
||||
reconstruction stages, and private exact cardinality evidence. Sanitization
|
||||
converts exact counts into qualitative classes before LLM review. Do not encode
|
||||
an absolute source dimension as a feature token.
|
||||
|
||||
@@ -62,3 +62,23 @@ case, positions, and coordinates are forbidden.
|
||||
Consumers query this library by requested family and feature vocabulary. They
|
||||
must not receive the induction corpus, private case JSON, or
|
||||
`candidate_experiences`. Only `experiences` is executable guidance.
|
||||
|
||||
## Reconstruction grammar
|
||||
|
||||
`reconstruction_grammar` is the executable bridge between generalized
|
||||
experience and a backend-neutral design plan. It may contain only:
|
||||
|
||||
- symbolic `parameter_roles`;
|
||||
- symbolic `datum_roles`;
|
||||
- required or optional semantic feature and relation roles;
|
||||
- canonical stages with tokenized operation, feature, and reference roles;
|
||||
- qualitative cardinality classes such as `single`, `paired`, `repeated`, or
|
||||
`dense`;
|
||||
- validation roles.
|
||||
|
||||
It must not contain parameter values, exact member counts, absolute
|
||||
coordinates, source identifiers, face references, or executable teacher
|
||||
geometry. The publisher verifies that every promoted role and stage is present
|
||||
in enough independent private cases. A consumer instantiates the grammar using
|
||||
the user's dimensions, inferred dimensionless distributions, and backend
|
||||
operations.
|
||||
|
||||
@@ -17,7 +17,8 @@ Write `work/review/experience-draft.json` with this shape:
|
||||
"scope": ["part_family"],
|
||||
"evidence_query": {
|
||||
"required_features": ["rotational_body", "central_passage"],
|
||||
"required_relations": ["dominant_axis_alignment"]
|
||||
"required_relations": ["dominant_axis_alignment"],
|
||||
"context_features": ["rotational_body", "central_passage"]
|
||||
},
|
||||
"guidance": "Preserve a shared axis across the participating feature roles.",
|
||||
"semantic_rationale": "The relationship remains meaningful when individual dimensions change.",
|
||||
@@ -28,17 +29,68 @@ Write `work/review/experience-draft.json` with this shape:
|
||||
```
|
||||
|
||||
Allowed kinds are `feature_motif`, `constraint`, `design_rule`,
|
||||
`validation_rule`, and `failure_repair`.
|
||||
`validation_rule`, `failure_repair`, `dimensionless_distribution`, and
|
||||
`reconstruction_grammar`.
|
||||
Distribution proposals also provide `observation_name`; the publisher computes
|
||||
the distribution from private evidence and never accepts values from the draft.
|
||||
|
||||
A `reconstruction_grammar` proposal must additionally contain symbolic,
|
||||
number-free reconstruction content:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "reconstruction.patterned_prismatic_part.primary",
|
||||
"kind": "reconstruction_grammar",
|
||||
"scope": ["patterned_prismatic_part"],
|
||||
"evidence_query": {
|
||||
"required_features": ["planar_dominant_body", "cylindrical_feature_network"],
|
||||
"required_relations": [],
|
||||
"context_features": ["planar_dominant_body"]
|
||||
},
|
||||
"guidance": "Build a datum-driven envelope before adding the passage network and repeated features.",
|
||||
"semantic_rationale": "The staged method transfers across dimensional variants while preserving feature relationships.",
|
||||
"reconstruction_grammar": {
|
||||
"parameter_roles": ["overall_long_span", "overall_middle_span", "overall_short_span"],
|
||||
"datum_roles": ["part_center", "primary_axis", "primary_transverse_plane"],
|
||||
"feature_roles": ["planar_dominant_body", "cylindrical_feature_network"],
|
||||
"relation_roles": [],
|
||||
"canonical_stages": [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": ["part_center", "primary_axis"]
|
||||
},
|
||||
{
|
||||
"id": "construct_primary_envelope",
|
||||
"operation": "extrude_profile",
|
||||
"feature_roles": ["planar_dominant_body"],
|
||||
"reference_roles": ["part_center", "primary_axis"]
|
||||
}
|
||||
],
|
||||
"cardinality_classes": ["plane_dense", "cylinder_dense"],
|
||||
"validation_roles": ["closed_solid", "surface_mix_consistency"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Follow these rules:
|
||||
|
||||
- Require an evidence query composed only of semantic feature and relation
|
||||
tokens present in the sanitized batch.
|
||||
- Use scopes to prevent unrelated part families from supporting a proposal.
|
||||
- Use `context_features` to define the conditional population used for
|
||||
confidence, so a valid subtype method is not diluted by unrelated variants
|
||||
in the same broad family.
|
||||
- Explain why a proposal transfers across dimensions and variants.
|
||||
- Avoid dimensions, coordinates, source identifiers, face references, and
|
||||
numeric literals in prose.
|
||||
- Do not infer build order or causal failure mechanisms from final geometry.
|
||||
- Treat `canonical_stages` as a recommended reconstruction order, never as a
|
||||
claim about the source CAD history.
|
||||
- For reconstruction grammars, retain only roles and stages repeated by the
|
||||
scoped evidence. Do not insert family knowledge that the sanitized batch
|
||||
does not support.
|
||||
- Prefer a small set of meaningful methods over every possible feature pair.
|
||||
- Preserve earlier proposal IDs when evidence still supports the same meaning.
|
||||
- Let the publisher calculate support and confidence; never claim them in the
|
||||
|
||||
@@ -102,6 +102,71 @@ def list_tokens(value: Any) -> list[str]:
|
||||
return sorted({item for raw in value if (item := token(raw)) != "unknown"})
|
||||
|
||||
|
||||
def normalize_stage(raw: Any) -> dict[str, Any] | None:
|
||||
if not isinstance(raw, dict):
|
||||
return None
|
||||
stage_id = token(raw.get("id"))
|
||||
operation = token(raw.get("operation"))
|
||||
if stage_id == "unknown" or operation == "unknown":
|
||||
return None
|
||||
return {
|
||||
"id": stage_id,
|
||||
"operation": operation,
|
||||
"feature_roles": list_tokens(raw.get("feature_roles")),
|
||||
"reference_roles": list_tokens(raw.get("reference_roles")),
|
||||
}
|
||||
|
||||
|
||||
def normalize_reconstruction(raw: Any) -> dict[str, Any]:
|
||||
if not isinstance(raw, dict):
|
||||
return {
|
||||
"parameter_roles": [],
|
||||
"datum_roles": [],
|
||||
"feature_roles": [],
|
||||
"relation_roles": [],
|
||||
"canonical_stages": [],
|
||||
"cardinality_classes": [],
|
||||
"validation_roles": [],
|
||||
}
|
||||
private_cardinality = (
|
||||
raw.get("private_cardinality_evidence")
|
||||
if isinstance(raw.get("private_cardinality_evidence"), dict)
|
||||
else {}
|
||||
)
|
||||
class_values = private_cardinality.get("surface_type_classes", {})
|
||||
cardinality_classes = list_tokens(raw.get("cardinality_classes"))
|
||||
if isinstance(class_values, dict):
|
||||
cardinality_classes.extend(
|
||||
token(f"{kind}_{value}")
|
||||
for kind, value in class_values.items()
|
||||
)
|
||||
for role in (
|
||||
"feature_role_count_class",
|
||||
"coaxial_radius_role_count_class",
|
||||
"repeated_member_count_class",
|
||||
):
|
||||
value = private_cardinality.get(role)
|
||||
if value:
|
||||
cardinality_classes.append(token(f"{role}.{value}"))
|
||||
return {
|
||||
"parameter_roles": list_tokens(raw.get("parameter_roles")),
|
||||
"datum_roles": list_tokens(raw.get("datum_roles")),
|
||||
"feature_roles": list_tokens(raw.get("feature_roles")),
|
||||
"relation_roles": list_tokens(raw.get("relation_roles")),
|
||||
"canonical_stages": [
|
||||
stage
|
||||
for item in raw.get("canonical_stages", [])
|
||||
if (stage := normalize_stage(item)) is not None
|
||||
]
|
||||
if isinstance(raw.get("canonical_stages"), list)
|
||||
else [],
|
||||
"cardinality_classes": sorted(
|
||||
{item for item in cardinality_classes if item != "unknown"}
|
||||
),
|
||||
"validation_roles": list_tokens(raw.get("validation_roles")),
|
||||
}
|
||||
|
||||
|
||||
def percentile(values: list[float], fraction: float) -> float:
|
||||
ordered = sorted(values)
|
||||
if len(ordered) == 1:
|
||||
@@ -232,6 +297,9 @@ def normalize_case(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
"rules": rules,
|
||||
"validations": validations,
|
||||
"normalized_observations": observations,
|
||||
"reconstruction": normalize_reconstruction(
|
||||
design_ir.get("reconstruction_evidence")
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -574,7 +642,10 @@ def extract_folder(input_dir: Path, output_dir: Path) -> dict[str, Any]:
|
||||
try:
|
||||
payload = json.loads(path.read_text(encoding="utf-8"))
|
||||
digest = payload.get("provenance", {}).get("source_sha256")
|
||||
if isinstance(digest, str):
|
||||
if (
|
||||
isinstance(digest, str)
|
||||
and payload.get("extractor_version") == "3.0"
|
||||
):
|
||||
known_hashes[digest] = path
|
||||
except (OSError, json.JSONDecodeError, AttributeError):
|
||||
continue
|
||||
@@ -637,6 +708,7 @@ def prepare_semantic_batch(
|
||||
"relations": case["relations"],
|
||||
"candidate_rules": case["rules"],
|
||||
"validation_targets": case["validations"],
|
||||
"reconstruction_evidence": case["reconstruction"],
|
||||
"dimensionless_observation_roles": [
|
||||
{
|
||||
"name": item["name"],
|
||||
@@ -678,6 +750,8 @@ def prepare_semantic_batch(
|
||||
"design_rule",
|
||||
"validation_rule",
|
||||
"failure_repair",
|
||||
"dimensionless_distribution",
|
||||
"reconstruction_grammar",
|
||||
],
|
||||
},
|
||||
}
|
||||
@@ -693,6 +767,8 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"design_rule",
|
||||
"validation_rule",
|
||||
"failure_repair",
|
||||
"dimensionless_distribution",
|
||||
"reconstruction_grammar",
|
||||
}
|
||||
for index, raw in enumerate(payload.get("proposals", [])):
|
||||
if not isinstance(raw, dict):
|
||||
@@ -707,6 +783,10 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
)
|
||||
required_features = list_tokens(evidence.get("required_features"))
|
||||
required_relations = list_tokens(evidence.get("required_relations"))
|
||||
context_features = list_tokens(
|
||||
evidence.get("context_features") or required_features
|
||||
)
|
||||
observation_name = token(raw.get("observation_name"))
|
||||
guidance = string_without_instance_numbers(raw.get("guidance"))
|
||||
rationale = string_without_instance_numbers(raw.get("semantic_rationale"))
|
||||
if proposal_id == "unknown":
|
||||
@@ -715,7 +795,11 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
raise ValueError(f"proposal {proposal_id} has an unsupported kind")
|
||||
if not scopes:
|
||||
raise ValueError(f"proposal {proposal_id} has no scope")
|
||||
if not required_features and not required_relations:
|
||||
if (
|
||||
not required_features
|
||||
and not required_relations
|
||||
and observation_name == "unknown"
|
||||
):
|
||||
raise ValueError(f"proposal {proposal_id} has no evidence query")
|
||||
if guidance is None or rationale is None:
|
||||
raise ValueError(
|
||||
@@ -728,10 +812,25 @@ def normalize_proposals(payload: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
"evidence_query": {
|
||||
"required_features": required_features,
|
||||
"required_relations": required_relations,
|
||||
"context_features": context_features,
|
||||
},
|
||||
"guidance": guidance,
|
||||
"semantic_rationale": rationale,
|
||||
}
|
||||
if observation_name != "unknown":
|
||||
proposal["observation_name"] = observation_name
|
||||
if kind == "reconstruction_grammar":
|
||||
grammar = normalize_reconstruction(raw.get("reconstruction_grammar"))
|
||||
if (
|
||||
not grammar["parameter_roles"]
|
||||
or not grammar["datum_roles"]
|
||||
or not grammar["canonical_stages"]
|
||||
or not grammar["validation_roles"]
|
||||
):
|
||||
raise ValueError(
|
||||
f"proposal {proposal_id} has an incomplete reconstruction grammar"
|
||||
)
|
||||
proposal["reconstruction_grammar"] = grammar
|
||||
for key in ("check", "repair", "failure_signature"):
|
||||
value = string_without_instance_numbers(raw.get(key))
|
||||
if value is not None:
|
||||
@@ -752,6 +851,41 @@ def build_reviewed_library(
|
||||
promoted: list[dict[str, Any]] = []
|
||||
candidates: list[dict[str, Any]] = []
|
||||
family_counts = Counter(case["family"] for case in cases)
|
||||
|
||||
def supports_reconstruction_grammar(
|
||||
case: dict[str, Any], grammar: dict[str, Any]
|
||||
) -> bool:
|
||||
evidence = case.get("reconstruction", {})
|
||||
for key in (
|
||||
"parameter_roles",
|
||||
"datum_roles",
|
||||
"feature_roles",
|
||||
"relation_roles",
|
||||
"validation_roles",
|
||||
):
|
||||
required = set(grammar.get(key, []))
|
||||
available = set(evidence.get(key, []))
|
||||
if required and not required.issubset(available):
|
||||
return False
|
||||
available_stages = {
|
||||
(item["id"], item["operation"]): item
|
||||
for item in evidence.get("canonical_stages", [])
|
||||
}
|
||||
for required_stage in grammar.get("canonical_stages", []):
|
||||
key = (required_stage["id"], required_stage["operation"])
|
||||
available = available_stages.get(key)
|
||||
if available is None:
|
||||
return False
|
||||
if not set(required_stage.get("feature_roles", [])).issubset(
|
||||
available.get("feature_roles", [])
|
||||
):
|
||||
return False
|
||||
if not set(required_stage.get("reference_roles", [])).issubset(
|
||||
available.get("reference_roles", [])
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
for proposal in proposals:
|
||||
scope = set(proposal["scope"])
|
||||
relevant = [
|
||||
@@ -765,14 +899,80 @@ def build_reviewed_library(
|
||||
required_relations = set(
|
||||
proposal["evidence_query"]["required_relations"]
|
||||
)
|
||||
supporting = [
|
||||
context_features = set(
|
||||
proposal["evidence_query"].get("context_features", required_features)
|
||||
)
|
||||
if proposal["kind"] == "dimensionless_distribution":
|
||||
matched = [
|
||||
observation
|
||||
for case in relevant
|
||||
for observation in case["normalized_observations"]
|
||||
if observation["name"] == proposal.get("observation_name")
|
||||
]
|
||||
support = len(matched)
|
||||
item: dict[str, Any] = {
|
||||
"id": proposal["id"],
|
||||
"kind": proposal["kind"],
|
||||
"scope": proposal["scope"],
|
||||
"when": {
|
||||
"ratio": {
|
||||
"numerator_role": (
|
||||
matched[0]["numerator_role"] if matched else "unknown"
|
||||
),
|
||||
"denominator_role": (
|
||||
matched[0]["denominator_role"] if matched else "unknown"
|
||||
),
|
||||
}
|
||||
},
|
||||
"guidance": proposal["guidance"],
|
||||
"semantic_rationale": proposal["semantic_rationale"],
|
||||
"support": support,
|
||||
"confidence": 1.0 if support else 0.0,
|
||||
}
|
||||
if matched:
|
||||
values = [observation["value"] for observation in matched]
|
||||
item["distribution"] = {
|
||||
"sample_count": support,
|
||||
"min": round(min(values), 6),
|
||||
"p10": round(percentile(values, 0.10), 6),
|
||||
"median": round(median(values), 6),
|
||||
"p90": round(percentile(values, 0.90), 6),
|
||||
"max": round(max(values), 6),
|
||||
}
|
||||
if eligible(support, item["confidence"], min_support, min_confidence):
|
||||
promoted.append(item)
|
||||
else:
|
||||
item.pop("distribution", None)
|
||||
item.update(
|
||||
{
|
||||
"promotion_state": "candidate",
|
||||
"required_support": min_support,
|
||||
"remaining_support": max(0, min_support - support),
|
||||
"required_confidence": min_confidence,
|
||||
"consumer_policy": "visible_for_review_but_not_available_to_cad_router",
|
||||
}
|
||||
)
|
||||
candidates.append(item)
|
||||
continue
|
||||
contextual = [
|
||||
case
|
||||
for case in relevant
|
||||
if context_features.issubset(case["features"])
|
||||
]
|
||||
supporting = [
|
||||
case
|
||||
for case in contextual
|
||||
if required_features.issubset(case["features"])
|
||||
and required_relations.issubset(case["relations"])
|
||||
and (
|
||||
proposal["kind"] != "reconstruction_grammar"
|
||||
or supports_reconstruction_grammar(
|
||||
case, proposal["reconstruction_grammar"]
|
||||
)
|
||||
)
|
||||
]
|
||||
support = len(supporting)
|
||||
confidence = support / len(relevant) if relevant else 0.0
|
||||
confidence = support / len(contextual) if contextual else 0.0
|
||||
item: dict[str, Any] = {
|
||||
"id": proposal["id"],
|
||||
"kind": proposal["kind"],
|
||||
@@ -789,6 +989,8 @@ def build_reviewed_library(
|
||||
for key in ("check", "repair", "failure_signature"):
|
||||
if key in proposal:
|
||||
item[key] = proposal[key]
|
||||
if proposal["kind"] == "reconstruction_grammar":
|
||||
item["reconstruction_grammar"] = proposal["reconstruction_grammar"]
|
||||
if eligible(support, confidence, min_support, min_confidence):
|
||||
promoted.append(item)
|
||||
else:
|
||||
|
||||
@@ -11,6 +11,8 @@ from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
EXTRACTOR_VERSION = "3.0"
|
||||
|
||||
|
||||
def _rounded(value: float) -> float:
|
||||
return round(float(value), 6)
|
||||
@@ -42,6 +44,32 @@ def _radial_offset(location: list[float], center: list[float], axis: str) -> flo
|
||||
location[second] - center[second],
|
||||
)
|
||||
|
||||
def _ratio(numerator: float, denominator: float) -> float:
|
||||
return _rounded(numerator / denominator) if denominator > 1e-9 else 0.0
|
||||
|
||||
|
||||
def _observation(
|
||||
name: str, value: float, numerator_role: str, denominator_role: str
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"name": name,
|
||||
"value": _rounded(value),
|
||||
"numerator_role": numerator_role,
|
||||
"denominator_role": denominator_role,
|
||||
}
|
||||
|
||||
|
||||
def _cardinality_class(count: int) -> str:
|
||||
if count <= 0:
|
||||
return "absent"
|
||||
if count == 1:
|
||||
return "single"
|
||||
if count == 2:
|
||||
return "paired"
|
||||
if count <= 6:
|
||||
return "repeated"
|
||||
return "dense"
|
||||
|
||||
|
||||
def _surface_record(face: Any, index: int) -> dict[str, Any]:
|
||||
from OCP.BRepAdaptor import BRepAdaptor_Surface
|
||||
@@ -125,6 +153,11 @@ def _semantic_summary(
|
||||
axis_votes[_canonical_axis(surface["axis"])] += max(surface["area"], 1.0)
|
||||
dominant_axis = axis_votes.most_common(1)[0][0] if axis_votes else "z"
|
||||
scale = max(bbox_size) if bbox_size else 1.0
|
||||
sorted_spans = sorted((max(float(value), 1e-9) for value in bbox_size))
|
||||
short_span, middle_span, long_span = sorted_spans
|
||||
long_to_middle = _ratio(long_span, middle_span)
|
||||
middle_to_short = _ratio(middle_span, short_span)
|
||||
short_to_long = _ratio(short_span, long_span)
|
||||
coaxial_tolerance = max(scale * 0.005, 1e-4)
|
||||
|
||||
parallel_cylinders: list[dict[str, Any]] = []
|
||||
@@ -152,6 +185,9 @@ def _semantic_summary(
|
||||
repeated_group_sizes = sorted(
|
||||
(len(rows) for rows in repeated_groups.values()), reverse=True
|
||||
)
|
||||
largest_repeated_group = (
|
||||
max(repeated_groups.values(), key=len) if repeated_groups else []
|
||||
)
|
||||
parallel_cones = [
|
||||
item for item in cones if _canonical_axis(item["axis"]) == dominant_axis
|
||||
]
|
||||
@@ -160,6 +196,66 @@ def _semantic_summary(
|
||||
has_multi_axis_passages = any(
|
||||
_canonical_axis(item["axis"]) != dominant_axis for item in cylinders
|
||||
)
|
||||
planes = [item for item in surfaces if item["type"] == "plane"]
|
||||
toruses = [item for item in surfaces if item["type"] == "torus"]
|
||||
spheres = [item for item in surfaces if item["type"] == "sphere"]
|
||||
other_surfaces = [item for item in surfaces if item["type"] == "other"]
|
||||
total_area = sum(max(float(item.get("area", 0.0)), 0.0) for item in surfaces)
|
||||
planar_area = sum(max(float(item.get("area", 0.0)), 0.0) for item in planes)
|
||||
cylindrical_area = sum(
|
||||
max(float(item.get("area", 0.0)), 0.0) for item in cylinders
|
||||
)
|
||||
planar_fraction = _ratio(planar_area, total_area)
|
||||
cylindrical_fraction = _ratio(cylindrical_area, total_area)
|
||||
elongated = long_to_middle >= 3.0
|
||||
plate_like = middle_to_short >= 4.0 and long_to_middle < 3.0
|
||||
compact = long_to_middle < 2.0 and middle_to_short < 2.5
|
||||
|
||||
transverse_axes = {
|
||||
"x": (1, 2),
|
||||
"y": (0, 2),
|
||||
"z": (0, 1),
|
||||
}[dominant_axis]
|
||||
transverse_span = max(bbox_size[index] for index in transverse_axes)
|
||||
dominant_span = bbox_size[{"x": 0, "y": 1, "z": 2}[dominant_axis]]
|
||||
largest_coaxial_radius = max(
|
||||
(surface["radius"] for surface in coaxial_cylinders), default=0.0
|
||||
)
|
||||
smallest_coaxial_radius = min(
|
||||
(surface["radius"] for surface in coaxial_cylinders), default=0.0
|
||||
)
|
||||
|
||||
pattern_offsets = [
|
||||
_radial_offset(surface["location"], bbox_center, dominant_axis)
|
||||
for surface in largest_repeated_group
|
||||
]
|
||||
pattern_mean_offset = (
|
||||
sum(pattern_offsets) / len(pattern_offsets) if pattern_offsets else 0.0
|
||||
)
|
||||
pattern_spread = (
|
||||
max(pattern_offsets) - min(pattern_offsets) if pattern_offsets else 0.0
|
||||
)
|
||||
has_circular_pattern = bool(
|
||||
len(pattern_offsets) >= 3
|
||||
and pattern_mean_offset > coaxial_tolerance
|
||||
and pattern_spread / pattern_mean_offset <= 0.08
|
||||
)
|
||||
|
||||
# Multiple radii sharing a transverse center are a robust final-B-Rep
|
||||
# signal for stepped bores/counterbores, without claiming operation order.
|
||||
transverse_groups: dict[tuple[int, int, int], set[float]] = defaultdict(set)
|
||||
axis_index = {"x": 0, "y": 1, "z": 2}[dominant_axis]
|
||||
for surface in parallel_cylinders:
|
||||
location = surface["location"]
|
||||
key = (
|
||||
int(round(location[transverse_axes[0]] / max(coaxial_tolerance, 1e-6))),
|
||||
int(round(location[transverse_axes[1]] / max(coaxial_tolerance, 1e-6))),
|
||||
axis_index,
|
||||
)
|
||||
transverse_groups[key].add(round(surface["radius"], 4))
|
||||
has_stepped_cylindrical_passage = any(
|
||||
len(radii) >= 2 for radii in transverse_groups.values()
|
||||
)
|
||||
|
||||
features: list[dict[str, Any]] = []
|
||||
if has_rotational_stack:
|
||||
@@ -177,6 +273,51 @@ def _semantic_summary(
|
||||
features.append({"type": "repeated_axial_hole_pattern"})
|
||||
if has_multi_axis_passages:
|
||||
features.append({"type": "multi_axis_passage"})
|
||||
features.append(
|
||||
{
|
||||
"type": (
|
||||
"elongated_body"
|
||||
if elongated
|
||||
else "plate_like_body"
|
||||
if plate_like
|
||||
else "compact_body"
|
||||
if compact
|
||||
else "moderate_aspect_body"
|
||||
)
|
||||
}
|
||||
)
|
||||
if planar_fraction >= 0.55:
|
||||
features.append({"type": "planar_dominant_body"})
|
||||
if cylindrical_fraction >= 0.55:
|
||||
features.append({"type": "cylindrical_dominant_body"})
|
||||
if len(planes) >= 6:
|
||||
features.append({"type": "multi_level_planar_profile"})
|
||||
if len(cylinders) >= 2:
|
||||
features.append({"type": "cylindrical_feature_network"})
|
||||
if toruses:
|
||||
features.append({"type": "toroidal_blend_or_groove"})
|
||||
if spheres:
|
||||
features.append({"type": "spherical_surface_feature"})
|
||||
if other_surfaces:
|
||||
features.append({"type": "freeform_surface_region"})
|
||||
if has_circular_pattern:
|
||||
features.append({"type": "circular_equal_radius_pattern"})
|
||||
if has_stepped_cylindrical_passage:
|
||||
features.append({"type": "stepped_cylindrical_passage"})
|
||||
if has_rotational_stack:
|
||||
features.append({"type": "stepped_rotational_profile"})
|
||||
if elongated:
|
||||
features.append({"type": "shaft_like_body"})
|
||||
if len(distinct_coaxial_radii) >= 3 and elongated:
|
||||
features.append({"type": "sleeve_or_hollow_shaft_candidate"})
|
||||
if transverse_span > 0 and dominant_span / transverse_span < 0.8:
|
||||
features.append({"type": "flange_like_rotational_body"})
|
||||
if len(surfaces) >= 40:
|
||||
features.append({"type": "high_topological_complexity"})
|
||||
elif len(surfaces) >= 16:
|
||||
features.append({"type": "medium_topological_complexity"})
|
||||
else:
|
||||
features.append({"type": "low_topological_complexity"})
|
||||
|
||||
constraints: list[dict[str, Any]] = []
|
||||
if has_rotational_stack:
|
||||
@@ -194,15 +335,104 @@ def _semantic_summary(
|
||||
constraints.append(
|
||||
{"id": "transition_axis_continuity", "type": "axis_alignment"}
|
||||
)
|
||||
if has_circular_pattern:
|
||||
constraints.append(
|
||||
{"id": "common_pattern_radius", "type": "radial_pattern"}
|
||||
)
|
||||
if has_multi_axis_passages:
|
||||
constraints.append(
|
||||
{"id": "multiple_axis_system", "type": "axis_network"}
|
||||
)
|
||||
if has_stepped_cylindrical_passage:
|
||||
constraints.append(
|
||||
{"id": "shared_passage_axis", "type": "coaxial"}
|
||||
)
|
||||
|
||||
if has_rotational_stack and parallel_cones and has_repeated_axial_holes:
|
||||
family = "flanged_hub_adapter_candidate"
|
||||
if has_rotational_stack and has_repeated_axial_holes:
|
||||
family = "flanged_rotational_part"
|
||||
elif has_rotational_stack and elongated and len(distinct_coaxial_radii) >= 3:
|
||||
family = "hollow_or_stepped_shaft"
|
||||
elif has_rotational_stack and elongated:
|
||||
family = "shaft"
|
||||
elif has_rotational_stack:
|
||||
family = "rotational_part_candidate"
|
||||
family = "rotational_part"
|
||||
elif has_repeated_axial_holes and plate_like:
|
||||
family = "patterned_plate"
|
||||
elif has_repeated_axial_holes:
|
||||
family = "patterned_prismatic_part_candidate"
|
||||
family = "patterned_prismatic_part"
|
||||
elif has_multi_axis_passages and len(cylinders) >= 3:
|
||||
family = "multi_axis_manifold"
|
||||
elif plate_like and planes:
|
||||
family = "plate_or_bracket"
|
||||
elif planar_fraction >= 0.55:
|
||||
family = "prismatic_block"
|
||||
elif other_surfaces or toruses or spheres:
|
||||
family = "hybrid_freeform_part"
|
||||
else:
|
||||
family = "unclassified_part"
|
||||
family = "general_mechanical_part"
|
||||
|
||||
observations = [
|
||||
_observation(
|
||||
"bbox_short_to_long",
|
||||
short_to_long,
|
||||
"short_bounding_span",
|
||||
"long_bounding_span",
|
||||
),
|
||||
_observation(
|
||||
"bbox_middle_to_long",
|
||||
_ratio(middle_span, long_span),
|
||||
"middle_bounding_span",
|
||||
"long_bounding_span",
|
||||
),
|
||||
_observation(
|
||||
"planar_area_fraction",
|
||||
planar_fraction,
|
||||
"planar_surface_area",
|
||||
"total_surface_area",
|
||||
),
|
||||
_observation(
|
||||
"cylindrical_area_fraction",
|
||||
cylindrical_fraction,
|
||||
"cylindrical_surface_area",
|
||||
"total_surface_area",
|
||||
),
|
||||
]
|
||||
if largest_coaxial_radius > 0:
|
||||
observations.append(
|
||||
_observation(
|
||||
"largest_coaxial_radius_to_transverse_span",
|
||||
_ratio(largest_coaxial_radius, transverse_span),
|
||||
"largest_coaxial_radius",
|
||||
"transverse_bounding_span",
|
||||
)
|
||||
)
|
||||
if smallest_coaxial_radius > 0 and largest_coaxial_radius > 0:
|
||||
observations.append(
|
||||
_observation(
|
||||
"smallest_to_largest_coaxial_radius",
|
||||
_ratio(smallest_coaxial_radius, largest_coaxial_radius),
|
||||
"smallest_coaxial_radius",
|
||||
"largest_coaxial_radius",
|
||||
)
|
||||
)
|
||||
if pattern_mean_offset > 0:
|
||||
observations.append(
|
||||
_observation(
|
||||
"pattern_radius_to_transverse_span",
|
||||
_ratio(pattern_mean_offset, transverse_span),
|
||||
"pattern_radius",
|
||||
"transverse_bounding_span",
|
||||
)
|
||||
)
|
||||
if largest_repeated_group:
|
||||
observations.append(
|
||||
_observation(
|
||||
"repeated_feature_radius_to_transverse_span",
|
||||
_ratio(largest_repeated_group[0]["radius"], transverse_span),
|
||||
"repeated_feature_radius",
|
||||
"transverse_bounding_span",
|
||||
)
|
||||
)
|
||||
|
||||
summary = {
|
||||
"dominant_axis": dominant_axis,
|
||||
@@ -214,10 +444,240 @@ def _semantic_summary(
|
||||
else 0,
|
||||
"parallel_cone_surface_count": len(parallel_cones),
|
||||
"has_multi_axis_passages": has_multi_axis_passages,
|
||||
"shape_class": (
|
||||
"elongated" if elongated else "plate_like" if plate_like else "compact"
|
||||
if compact
|
||||
else "moderate_aspect"
|
||||
),
|
||||
"normalized_observations": observations,
|
||||
}
|
||||
return family, features, constraints, summary
|
||||
|
||||
|
||||
def _reconstruction_evidence(
|
||||
surfaces: list[dict[str, Any]],
|
||||
features: list[dict[str, Any]],
|
||||
constraints: list[dict[str, Any]],
|
||||
semantic_summary: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Describe a canonical reconstruction grammar without claiming CAD history."""
|
||||
feature_roles = sorted(
|
||||
{
|
||||
str(item.get("type"))
|
||||
for item in features
|
||||
if isinstance(item, dict) and item.get("type")
|
||||
}
|
||||
)
|
||||
relation_roles = sorted(
|
||||
{
|
||||
str(item.get("id") or item.get("type"))
|
||||
for item in constraints
|
||||
if isinstance(item, dict) and (item.get("id") or item.get("type"))
|
||||
}
|
||||
)
|
||||
surface_counts = Counter(item.get("type", "other") for item in surfaces)
|
||||
|
||||
parameter_roles = [
|
||||
"overall_long_span",
|
||||
"overall_middle_span",
|
||||
"overall_short_span",
|
||||
"primary_axis_span",
|
||||
"primary_transverse_span",
|
||||
"secondary_transverse_span",
|
||||
]
|
||||
if "cylindrical_feature_network" in feature_roles:
|
||||
parameter_roles.extend(
|
||||
[
|
||||
"passage_diameter_roles",
|
||||
"passage_axis_offset_roles",
|
||||
"passage_extent_roles",
|
||||
]
|
||||
)
|
||||
if "coaxial_cylindrical_stack" in feature_roles:
|
||||
parameter_roles.extend(
|
||||
[
|
||||
"coaxial_diameter_roles",
|
||||
"axial_segment_length_roles",
|
||||
"shoulder_position_roles",
|
||||
]
|
||||
)
|
||||
if "repeated_axial_hole_pattern" in feature_roles:
|
||||
parameter_roles.extend(
|
||||
[
|
||||
"pattern_member_diameter_role",
|
||||
"pattern_radius_or_spacing_role",
|
||||
"pattern_angular_or_linear_phase_role",
|
||||
]
|
||||
)
|
||||
if "multi_level_planar_profile" in feature_roles:
|
||||
parameter_roles.extend(
|
||||
[
|
||||
"planar_level_offset_roles",
|
||||
"profile_width_roles",
|
||||
"profile_length_roles",
|
||||
]
|
||||
)
|
||||
if "conical_transition" in feature_roles:
|
||||
parameter_roles.append("transition_slope_role")
|
||||
|
||||
stages: list[dict[str, Any]] = [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": [
|
||||
"part_center",
|
||||
"primary_axis",
|
||||
"primary_transverse_plane",
|
||||
"secondary_transverse_plane",
|
||||
],
|
||||
},
|
||||
{
|
||||
"id": "construct_primary_envelope",
|
||||
"operation": (
|
||||
"revolve_profile"
|
||||
if "rotational_body" in feature_roles
|
||||
else "extrude_profile"
|
||||
),
|
||||
"feature_roles": [
|
||||
role
|
||||
for role in (
|
||||
"rotational_body",
|
||||
"planar_dominant_body",
|
||||
"elongated_body",
|
||||
"plate_like_body",
|
||||
"compact_body",
|
||||
"moderate_aspect_body",
|
||||
)
|
||||
if role in feature_roles
|
||||
],
|
||||
"reference_roles": ["part_center", "primary_axis"],
|
||||
},
|
||||
]
|
||||
if "multi_level_planar_profile" in feature_roles:
|
||||
stages.append(
|
||||
{
|
||||
"id": "establish_planar_levels",
|
||||
"operation": "add_or_remove_profile_levels",
|
||||
"feature_roles": ["multi_level_planar_profile"],
|
||||
"reference_roles": ["primary_transverse_plane"],
|
||||
}
|
||||
)
|
||||
if "coaxial_cylindrical_stack" in feature_roles:
|
||||
stages.append(
|
||||
{
|
||||
"id": "construct_coaxial_stack",
|
||||
"operation": "add_or_cut_coaxial_profiles",
|
||||
"feature_roles": ["coaxial_cylindrical_stack"],
|
||||
"reference_roles": ["primary_axis"],
|
||||
}
|
||||
)
|
||||
if "cylindrical_feature_network" in feature_roles:
|
||||
stages.append(
|
||||
{
|
||||
"id": "construct_passage_network",
|
||||
"operation": "cut_semantic_passages",
|
||||
"feature_roles": [
|
||||
role
|
||||
for role in (
|
||||
"cylindrical_feature_network",
|
||||
"central_passage_candidate",
|
||||
"stepped_cylindrical_passage",
|
||||
"multi_axis_passage",
|
||||
)
|
||||
if role in feature_roles
|
||||
],
|
||||
"reference_roles": ["primary_axis", "part_center"],
|
||||
}
|
||||
)
|
||||
if "repeated_axial_hole_pattern" in feature_roles:
|
||||
stages.append(
|
||||
{
|
||||
"id": "construct_repeated_feature_pattern",
|
||||
"operation": "pattern_semantic_feature",
|
||||
"feature_roles": [
|
||||
role
|
||||
for role in (
|
||||
"repeated_axial_hole_pattern",
|
||||
"circular_equal_radius_pattern",
|
||||
)
|
||||
if role in feature_roles
|
||||
],
|
||||
"reference_roles": ["primary_axis", "part_center"],
|
||||
}
|
||||
)
|
||||
transition_roles = [
|
||||
role
|
||||
for role in (
|
||||
"conical_transition",
|
||||
"toroidal_blend_or_groove",
|
||||
"spherical_surface_feature",
|
||||
"freeform_surface_region",
|
||||
)
|
||||
if role in feature_roles
|
||||
]
|
||||
if transition_roles:
|
||||
stages.append(
|
||||
{
|
||||
"id": "resolve_transitions_and_finishing",
|
||||
"operation": "apply_transitions_or_blends",
|
||||
"feature_roles": transition_roles,
|
||||
"reference_roles": ["primary_axis"],
|
||||
}
|
||||
)
|
||||
stages.append(
|
||||
{
|
||||
"id": "validate_reconstructed_shape",
|
||||
"operation": "validate_geometry_and_relations",
|
||||
"feature_roles": feature_roles,
|
||||
"reference_roles": ["part_center", "primary_axis"],
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
"interpretation": "canonical_reconstruction_plan_not_recovered_history",
|
||||
"parameter_roles": sorted(set(parameter_roles)),
|
||||
"datum_roles": [
|
||||
"part_center",
|
||||
"primary_axis",
|
||||
"primary_transverse_plane",
|
||||
"secondary_transverse_plane",
|
||||
],
|
||||
"feature_roles": feature_roles,
|
||||
"relation_roles": relation_roles,
|
||||
"canonical_stages": stages,
|
||||
"private_cardinality_evidence": {
|
||||
"surface_type_counts": dict(sorted(surface_counts.items())),
|
||||
"surface_type_classes": {
|
||||
kind: _cardinality_class(count)
|
||||
for kind, count in sorted(surface_counts.items())
|
||||
},
|
||||
"feature_role_count": len(feature_roles),
|
||||
"feature_role_count_class": _cardinality_class(len(feature_roles)),
|
||||
"coaxial_radius_role_count": int(
|
||||
semantic_summary.get("distinct_coaxial_radius_count", 0)
|
||||
),
|
||||
"coaxial_radius_role_count_class": _cardinality_class(
|
||||
int(semantic_summary.get("distinct_coaxial_radius_count", 0))
|
||||
),
|
||||
"repeated_member_count": int(
|
||||
semantic_summary.get("largest_repeated_radius_group", 0)
|
||||
),
|
||||
"repeated_member_count_class": _cardinality_class(
|
||||
int(semantic_summary.get("largest_repeated_radius_group", 0))
|
||||
),
|
||||
},
|
||||
"validation_roles": sorted(
|
||||
{
|
||||
"closed_solid",
|
||||
"bounding_proportion_consistency",
|
||||
"surface_mix_consistency",
|
||||
*relation_roles,
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def extract_step_case(source: Path) -> dict[str, Any]:
|
||||
from build123d import import_step
|
||||
|
||||
@@ -239,9 +699,14 @@ def extract_step_case(source: Path) -> dict[str, Any]:
|
||||
family, features, constraints, semantic_summary = _semantic_summary(
|
||||
surfaces, bbox_center, bbox_size
|
||||
)
|
||||
reconstruction_evidence = _reconstruction_evidence(
|
||||
surfaces, features, constraints, semantic_summary
|
||||
)
|
||||
digest = hashlib.sha256(source_bytes).hexdigest()
|
||||
normalized_observations = semantic_summary.pop("normalized_observations", [])
|
||||
return {
|
||||
"schema_version": "1.0",
|
||||
"schema_version": "2.0",
|
||||
"extractor_version": EXTRACTOR_VERSION,
|
||||
"case_kind": "private_step_evidence",
|
||||
"case_id": digest,
|
||||
"provenance": {
|
||||
@@ -267,8 +732,9 @@ def extract_step_case(source: Path) -> dict[str, Any]:
|
||||
"part_family": family,
|
||||
"features": features,
|
||||
"constraints": constraints,
|
||||
"normalized_observations": [],
|
||||
"normalized_observations": normalized_observations,
|
||||
"semantic_summary": semantic_summary,
|
||||
"reconstruction_evidence": reconstruction_evidence,
|
||||
},
|
||||
"experience": {
|
||||
"rules": [],
|
||||
|
||||
@@ -54,6 +54,38 @@ def make_case(index: int) -> dict:
|
||||
"denominator_role": "base_outer_diameter",
|
||||
}
|
||||
],
|
||||
"reconstruction_evidence": {
|
||||
"parameter_roles": [
|
||||
"overall_long_span",
|
||||
"overall_middle_span",
|
||||
"overall_short_span",
|
||||
],
|
||||
"datum_roles": ["part_center", "primary_axis"],
|
||||
"feature_roles": ["base_flange", "hollow_sleeve", "counterbore"],
|
||||
"relation_roles": ["coaxial_stack"],
|
||||
"canonical_stages": [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": ["part_center", "primary_axis"],
|
||||
},
|
||||
{
|
||||
"id": "construct_primary_envelope",
|
||||
"operation": "revolve_profile",
|
||||
"feature_roles": ["base_flange", "hollow_sleeve"],
|
||||
"reference_roles": ["primary_axis"],
|
||||
},
|
||||
],
|
||||
"private_cardinality_evidence": {
|
||||
"surface_type_classes": {
|
||||
"plane": "repeated",
|
||||
"cylinder": "dense",
|
||||
},
|
||||
"feature_role_count_class": "repeated",
|
||||
},
|
||||
"validation_roles": ["closed_solid", "coaxial_stack"],
|
||||
},
|
||||
},
|
||||
"experience": {
|
||||
"rules": [
|
||||
@@ -122,12 +154,47 @@ class CadExperienceTest(unittest.TestCase):
|
||||
family, features, constraints, summary = STEP_MODULE._semantic_summary(
|
||||
surfaces, [0.0, 0.0, 0.0], [40.0, 20.0, 40.0]
|
||||
)
|
||||
self.assertEqual("flanged_hub_adapter_candidate", family)
|
||||
self.assertEqual("flanged_rotational_part", family)
|
||||
self.assertIn(
|
||||
"coaxial_cylindrical_stack", {item["type"] for item in features}
|
||||
)
|
||||
self.assertIn("coaxial_stack", {item["id"] for item in constraints})
|
||||
self.assertEqual("y", summary["dominant_axis"])
|
||||
self.assertTrue(summary["normalized_observations"])
|
||||
reconstruction = STEP_MODULE._reconstruction_evidence(
|
||||
surfaces, features, constraints, summary
|
||||
)
|
||||
self.assertIn("overall_long_span", reconstruction["parameter_roles"])
|
||||
self.assertTrue(reconstruction["canonical_stages"])
|
||||
self.assertEqual(
|
||||
"canonical_reconstruction_plan_not_recovered_history",
|
||||
reconstruction["interpretation"],
|
||||
)
|
||||
|
||||
def test_conditional_feature_context_does_not_dilute_valid_method(self):
|
||||
proposal = self.reviewed_proposal()
|
||||
proposal["evidence_query"]["context_features"] = [
|
||||
"base_flange",
|
||||
"hollow_sleeve",
|
||||
]
|
||||
target = [MODULE.normalize_case(make_case(index)) for index in range(20)]
|
||||
unrelated = []
|
||||
for index in range(80):
|
||||
payload = make_case(index + 100)
|
||||
payload["design_ir"]["features"] = [{"type": "base_flange"}]
|
||||
payload["design_ir"]["constraints"] = []
|
||||
unrelated.append(MODULE.normalize_case(payload))
|
||||
library = MODULE.build_reviewed_library(
|
||||
target + unrelated,
|
||||
[MODULE.normalize_proposals({
|
||||
"draft_kind": "llm_generalized_experience_proposals",
|
||||
"proposals": [proposal],
|
||||
})[0]],
|
||||
20,
|
||||
0.8,
|
||||
{"duplicate_case_count": 0, "rejected_case_count": 0},
|
||||
)
|
||||
self.assertEqual(1, len(library["experiences"]))
|
||||
|
||||
def test_single_case_never_promotes(self):
|
||||
case = MODULE.normalize_case(make_case(1))
|
||||
@@ -247,6 +314,65 @@ class CadExperienceTest(unittest.TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
def test_reconstruction_grammar_promotes_without_instance_values(self):
|
||||
proposal = {
|
||||
"id": "reconstruction.flanged_hub_adapter.primary",
|
||||
"kind": "reconstruction_grammar",
|
||||
"scope": ["flanged_hub_adapter"],
|
||||
"evidence_query": {
|
||||
"required_features": ["base_flange", "hollow_sleeve"],
|
||||
"required_relations": ["coaxial_stack"],
|
||||
"context_features": ["base_flange", "hollow_sleeve"],
|
||||
},
|
||||
"guidance": "Establish shared datums before composing the primary envelope.",
|
||||
"semantic_rationale": "The symbolic stages transfer across dimensional variants.",
|
||||
"reconstruction_grammar": {
|
||||
"parameter_roles": [
|
||||
"overall_long_span",
|
||||
"overall_middle_span",
|
||||
"overall_short_span",
|
||||
],
|
||||
"datum_roles": ["part_center", "primary_axis"],
|
||||
"feature_roles": ["base_flange", "hollow_sleeve"],
|
||||
"relation_roles": ["coaxial_stack"],
|
||||
"canonical_stages": [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": ["part_center", "primary_axis"],
|
||||
},
|
||||
{
|
||||
"id": "construct_primary_envelope",
|
||||
"operation": "revolve_profile",
|
||||
"feature_roles": ["base_flange", "hollow_sleeve"],
|
||||
"reference_roles": ["primary_axis"],
|
||||
},
|
||||
],
|
||||
"cardinality_classes": ["plane_repeated", "cylinder_dense"],
|
||||
"validation_roles": ["closed_solid", "coaxial_stack"],
|
||||
},
|
||||
}
|
||||
normalized = MODULE.normalize_proposals(
|
||||
{
|
||||
"draft_kind": "llm_generalized_experience_proposals",
|
||||
"proposals": [proposal],
|
||||
}
|
||||
)
|
||||
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
|
||||
library = MODULE.build_reviewed_library(
|
||||
cases,
|
||||
normalized,
|
||||
20,
|
||||
0.8,
|
||||
{"duplicate_case_count": 0, "rejected_case_count": 0},
|
||||
)
|
||||
self.assertEqual([], MODULE.audit_library(library))
|
||||
self.assertEqual("reconstruction_grammar", library["experiences"][0]["kind"])
|
||||
rendered = json.dumps(library)
|
||||
self.assertNotIn("91.0", rendered)
|
||||
self.assertNotIn('"parameters"', rendered)
|
||||
|
||||
def test_direct_distill_without_model_draft_is_blocked(self):
|
||||
with self.assertRaisesRegex(SystemExit, "statistical distillation is disabled"):
|
||||
MODULE.main(["distill"])
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Inches, Pt, RGBColor
|
||||
|
||||
from build_cnc_first_integration_checklist import (
|
||||
AMBER,
|
||||
BLACK,
|
||||
BLUE,
|
||||
GRAY,
|
||||
GREEN,
|
||||
LIGHT_BLUE,
|
||||
PALE_BLUE,
|
||||
WHITE,
|
||||
add_callout,
|
||||
add_text,
|
||||
keep_row_together,
|
||||
set_cell_margins,
|
||||
set_cell_shading,
|
||||
set_repeat_table_header,
|
||||
set_table_fixed_width,
|
||||
)
|
||||
|
||||
|
||||
OUT = Path("/Users/jerry/linkhand/CadSet/docs/CNC机床系统对接资料与技术支持需求函.docx")
|
||||
|
||||
|
||||
def setup_document():
|
||||
doc = Document()
|
||||
section = doc.sections[0]
|
||||
section.page_width = Inches(8.5)
|
||||
section.page_height = Inches(11)
|
||||
section.top_margin = Inches(0.7)
|
||||
section.bottom_margin = Inches(0.7)
|
||||
section.left_margin = Inches(0.72)
|
||||
section.right_margin = Inches(0.72)
|
||||
section.header_distance = Inches(0.3)
|
||||
section.footer_distance = Inches(0.3)
|
||||
|
||||
normal = doc.styles["Normal"]
|
||||
normal.font.name = "Arial"
|
||||
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
normal.font.size = Pt(10.5)
|
||||
normal.font.color.rgb = RGBColor.from_string(BLACK)
|
||||
normal.paragraph_format.space_after = Pt(5)
|
||||
normal.paragraph_format.line_spacing = 1.12
|
||||
|
||||
for name, size, before, after in (
|
||||
("Heading 1", 16, 14, 7),
|
||||
("Heading 2", 12.5, 10, 5),
|
||||
("Heading 3", 11, 7, 3),
|
||||
):
|
||||
style = doc.styles[name]
|
||||
style.font.name = "Arial"
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
style.font.size = Pt(size)
|
||||
style.font.bold = True
|
||||
style.font.color.rgb = RGBColor.from_string(BLUE)
|
||||
style.paragraph_format.space_before = Pt(before)
|
||||
style.paragraph_format.space_after = Pt(after)
|
||||
style.paragraph_format.keep_with_next = True
|
||||
|
||||
for name in ("List Bullet", "List Number"):
|
||||
style = doc.styles[name]
|
||||
style.font.name = "Arial"
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
style.font.size = Pt(10.5)
|
||||
|
||||
hp = section.header.paragraphs[0]
|
||||
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
add_text(hp, "CNC 机床系统对接|资料与技术支持需求函", size=8.5, color=GRAY)
|
||||
fp = section.footer.paragraphs[0]
|
||||
fp.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(fp, "请按实际设备型号填写;不适用项目请标注“不适用”", size=8, color=GRAY)
|
||||
return doc
|
||||
|
||||
|
||||
def formal_matrix(doc, headers, widths, rows, first_col_fill=None, sizes=None):
|
||||
table = doc.add_table(rows=1, cols=len(headers))
|
||||
table.style = "Table Grid"
|
||||
set_table_fixed_width(table, widths)
|
||||
for idx, text in enumerate(headers):
|
||||
cell = table.rows[0].cells[idx]
|
||||
set_cell_shading(cell, BLUE)
|
||||
set_cell_margins(cell)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, text, size=9.2, bold=True, color=WHITE)
|
||||
set_repeat_table_header(table.rows[0])
|
||||
for ridx, values in enumerate(rows):
|
||||
row = table.add_row()
|
||||
keep_row_together(row)
|
||||
for idx, value in enumerate(values):
|
||||
cell = row.cells[idx]
|
||||
set_cell_margins(cell, top=100, bottom=100)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
if idx == 0 and first_col_fill:
|
||||
set_cell_shading(cell, first_col_fill)
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if idx == 0 else WD_ALIGN_PARAGRAPH.LEFT
|
||||
size = sizes[idx] if sizes else 9.2
|
||||
add_text(p, value, size=size, bold=(idx == 0))
|
||||
return table
|
||||
|
||||
|
||||
def add_form_lines(doc, pairs):
|
||||
table = doc.add_table(rows=0, cols=2)
|
||||
table.style = "Table Grid"
|
||||
set_table_fixed_width(table, [3.25, 3.25])
|
||||
for left, right in pairs:
|
||||
row = table.add_row()
|
||||
keep_row_together(row)
|
||||
for idx, value in enumerate((left, right)):
|
||||
cell = row.cells[idx]
|
||||
set_cell_margins(cell, top=120, bottom=120)
|
||||
add_text(cell.paragraphs[0], value, size=9.5)
|
||||
return table
|
||||
|
||||
|
||||
def build():
|
||||
doc = setup_document()
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(12)
|
||||
p.paragraph_format.space_after = Pt(4)
|
||||
add_text(p, "CNC 机床系统对接", size=23, bold=True, color=BLUE)
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(12)
|
||||
add_text(p, "资料与技术支持需求函", size=20, bold=True, color=BLUE)
|
||||
|
||||
doc.add_heading("一、需求目的", level=1)
|
||||
p = doc.add_paragraph()
|
||||
add_text(
|
||||
p,
|
||||
"为开展数字零件模型至 CNC 加工程序生成、程序传输、试切验证及加工状态采集等工作,"
|
||||
"现请贵方协助确认实际机床和 CNC 控制系统配置,并提供本函所列技术资料、接口信息、"
|
||||
"验证样例及联合调试支持。",
|
||||
)
|
||||
p = doc.add_paragraph()
|
||||
add_text(
|
||||
p,
|
||||
"本函中的通信产品和接口名称均为候选项,最终以实际机床型号、CNC 系统版本、已购选件、"
|
||||
"机床制造商配置及贵方书面确认为准。",
|
||||
color=GRAY,
|
||||
)
|
||||
|
||||
add_callout(
|
||||
doc,
|
||||
"回复方式",
|
||||
"请在对应表格中填写、勾选或标注“不支持/不适用/待确认”,并随回复附相关手册、"
|
||||
"安装包、许可证说明、样例文件和技术联系人信息。",
|
||||
PALE_BLUE,
|
||||
)
|
||||
|
||||
doc.add_heading("二、建议参与对接的技术人员", level=1)
|
||||
role_rows = [
|
||||
("机床制造商/集成商", "机械配置、轴与主轴、刀库/夹具、专用 M 代码、PLC/联锁、机床安全和现场调试。", "姓名:________ 电话:________"),
|
||||
("三菱 CNC/代理商", "CNC 型号与版本、已购选件、通信软件、SDK、许可证、标准接口和版本兼容。", "姓名:________ 电话:________"),
|
||||
("现场工艺/设备人员", "现用 CAM、后处理器、刀具、材料、装夹、坐标、切削参数和已验证程序。", "姓名:________ 电话:________"),
|
||||
("网络/信息化人员", "工业网络、IP/端口、账号、证书、远程访问、日志和数据合规。", "姓名:________ 电话:________"),
|
||||
]
|
||||
formal_matrix(doc, ["参与角色", "确认范围", "联系人"], [1.45, 3.70, 1.35], role_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("三、设备与 CNC 基础信息", level=1)
|
||||
equipment_rows = [
|
||||
("机床身份", "制造商、完整型号、序列号、出厂年份、设备编号", "____________________________", "铭牌及整机照片"),
|
||||
("CNC 控制系统", "三菱 CNC 系列、完整型号、系统软件版本", "____________________________", "系统信息页面照片"),
|
||||
("已安装选件", "高速加工、五轴、刀尖点控制、宏程序、通信等选件", "____________________________", "选件清单/参数页面"),
|
||||
("机床类型", "车床/铣床/加工中心/车铣复合/磨床/专机", "____________________________", "设备规格书"),
|
||||
("轴配置", "轴名称、正方向、行程、零点、软限位、旋转轴结构", "____________________________", "轴配置图"),
|
||||
("主轴配置", "数量、方向、最高转速、功率/扭矩、锥度", "____________________________", "主轴规格"),
|
||||
("刀库/刀塔", "类型、容量、刀位规则、刀具尺寸重量限制", "____________________________", "刀库规格"),
|
||||
("辅助装置", "探针、自动对刀、第四/第五轴、尾座、卡盘、托盘等", "____________________________", "配置说明"),
|
||||
]
|
||||
formal_matrix(
|
||||
doc,
|
||||
["项目", "请确认内容", "贵方填写", "建议附件"],
|
||||
[1.15, 2.65, 1.55, 1.15],
|
||||
equipment_rows,
|
||||
first_col_fill=LIGHT_BLUE,
|
||||
sizes=[9.0, 8.8, 8.8, 8.5],
|
||||
)
|
||||
|
||||
doc.add_heading("四、请提供的技术资料", level=1)
|
||||
material_rows = [
|
||||
("P0", "CNC 与机床手册", "CNC 规格/编程/操作/连接设置手册;机床厂操作、维护及专用功能手册。", "□已附 □后补 □不适用"),
|
||||
("P0", "代码和程序规则", "实际支持的 G/M/T/S/F 代码、自定义 M 代码、宏变量、固定循环、坐标系、刀补、换刀规则、程序格式限制。", "□已附 □后补 □不适用"),
|
||||
("P0", "程序模板", "机床厂认可的程序开头、换刀段、工序切换、异常退出和结束模板。", "□已附 □后补 □不适用"),
|
||||
("P0", "验证样例包", "至少 3~10 套已加工成功样例,每套对应 STEP/图纸、毛坯、材料、刀具、装夹、CAM、NC 和检测结果。", "数量:____套"),
|
||||
("P0", "现用 CAM/后处理", "CAM 名称和版本、后处理器、后处理配置、程序头尾模板和典型仿真项目。", "□已附 □后补 □无"),
|
||||
("P1", "刀具与刀柄", "真实刀具/刀柄清单、刀位号、刀补号、主要尺寸、寿命规则及 STEP/尺寸模型。", "□已附 □后补"),
|
||||
("P1", "夹具与工作区", "工作台、卡盘、虎钳、软爪、尾座、旋转轴、探针和防护区域的模型或关键尺寸。", "□已附 □后补"),
|
||||
("P1", "材料与工艺", "常用材料牌号、毛坯规格、余量、硬度/热处理、切削参数、冷却方式、公差和粗糙度。", "□已附 □后补"),
|
||||
("P2", "数字机床", "机床三维模型、运动学、回转中心、碰撞体、软限位、CAM 机床仿真工程。", "□已附 □后补 □无"),
|
||||
]
|
||||
formal_matrix(doc, ["优先级", "资料名称", "交付内容", "回复"], [0.70, 1.45, 3.30, 1.05], material_rows, first_col_fill=GREEN)
|
||||
|
||||
doc.add_heading("五、程序传输与通信能力确认", level=1)
|
||||
interface_rows = [
|
||||
("加工程序传输", "NC Explorer", "□已安装 □可新增 □不支持 □待确认", "版本/许可证/手册/网络配置"),
|
||||
("加工程序传输", "FTP、共享目录或机床厂文件服务", "□支持 □不支持 □待确认", "IP/端口/目录/账号/文件规则"),
|
||||
("加工程序传输", "DNC/MDC/MES 平台", "□已有 □无 □待确认", "产品名称、版本、第三方接口"),
|
||||
("加工程序传输", "RS-232C 或 USB", "□支持 □不支持", "传输设置与文件限制"),
|
||||
("CNC 通信 SDK", "FCSB1224W000 开发包", "□已授权 □可采购 □不支持 □待确认", "SDK、手册、样例、兼容矩阵"),
|
||||
("CNC 通信运行库", "FCSB1224W100", "□已授权 □可采购 □不支持 □待确认", "安装包、版本和部署许可"),
|
||||
("只读数据采集", "MTConnect", "□已配置 □可新增 □不支持 □待确认", "适配器/Agent、数据项和端点"),
|
||||
("只读数据采集", "OPC UA / NC Machine Tool Connector", "□已配置 □可新增 □不支持 □待确认", "服务器、节点表、证书、许可"),
|
||||
("运行监控", "NC Monitor2 或现有监控平台", "□已有 □无 □待确认", "产品版本与开放接口"),
|
||||
("PLC/设备访问", "SLMP 或机床厂专用接口", "□开放只读 □开放读写 □不开放 □待确认", "设备地址表、权限和联锁说明"),
|
||||
("机床厂专用 API", "机床厂二次开发接口", "□有 □无 □待确认", "API 文档、SDK、样例和授权"),
|
||||
]
|
||||
formal_matrix(
|
||||
doc,
|
||||
["用途", "候选通道", "贵方确认", "请附资料"],
|
||||
[1.15, 1.80, 1.95, 1.60],
|
||||
interface_rows,
|
||||
first_col_fill=LIGHT_BLUE,
|
||||
sizes=[8.6, 8.6, 8.3, 8.3],
|
||||
)
|
||||
|
||||
doc.add_heading("六、接口交付要求", level=1)
|
||||
interface_delivery_rows = [
|
||||
("兼容性", "适用 CNC 型号、系统版本、所需硬件/选件、安装状态、许可证数量和有效期。", "____________________________"),
|
||||
("开发资料", "API/协议手册、数据字典、错误码、示例程序、SDK/运行库安装包及版本说明。", "____________________________"),
|
||||
("连接参数", "协议、IP、端口、账号/证书、读写权限、超时、并发、频率和文件大小限制。", "____________________________"),
|
||||
("可靠性", "断线检测、重连、续传、重复提交处理、任务 ID、校验、回执和错误处理机制。", "____________________________"),
|
||||
("时间与单位", "时区、时间同步、时间戳精度、字段单位、缩放系数和枚举值说明。", "____________________________"),
|
||||
("版本支持", "SDK/协议与 CNC 兼容矩阵、升级策略、补丁和技术支持期限。", "____________________________"),
|
||||
("日志审计", "连接、文件传输、调用方、操作时间、结果、配置修改及远程访问日志。", "____________________________"),
|
||||
]
|
||||
formal_matrix(doc, ["项目", "交付要求", "贵方回复/附件"], [1.25, 3.85, 1.40], interface_delivery_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("七、状态数据项确认", level=1)
|
||||
p = doc.add_paragraph()
|
||||
add_text(
|
||||
p,
|
||||
"请针对可提供的数据项另附数据字典,并至少标明:接口来源、字段/节点地址、数据类型、单位、"
|
||||
"刷新周期、时间戳、枚举含义、无效值及报警条件。",
|
||||
color=GRAY,
|
||||
)
|
||||
data_rows = [
|
||||
("设备状态", "设备 ID、型号/版本、在线/离线、采集状态、最后更新时间", "□可提供 □部分 □不支持"),
|
||||
("运行状态", "自动/手动/MDI、运行/暂停/停止/报警/急停、循环启动、门和关键联锁", "□可提供 □部分 □不支持"),
|
||||
("程序执行", "当前程序、子程序、行号/序列号、开始结束时间、循环时间、加工计数", "□可提供 □部分 □不支持"),
|
||||
("轴与坐标", "机床/工件坐标、剩余移动、进给、倍率、轴负载、旋转轴角度", "□可提供 □部分 □不支持"),
|
||||
("主轴", "实际/指令转速、倍率、负载、扭矩/功率、方向、状态、温度", "□可提供 □部分 □不支持"),
|
||||
("刀具", "当前刀号、刀位、刀补、寿命、使用次数、刀具报警及刀具表更新", "□可提供 □部分 □不支持"),
|
||||
("报警诊断", "报警号、等级、文本、发生/解除时间、历史、CNC 与机床 PLC 报警", "□可提供 □部分 □不支持"),
|
||||
("加工质量", "完工状态、实际周期、测头/尺寸结果、报废返工原因", "□可提供 □部分 □不支持"),
|
||||
("维护能耗", "运行时长、润滑/冷却、维护计数、能耗等设备已有数据", "□可提供 □部分 □不支持"),
|
||||
]
|
||||
formal_matrix(doc, ["数据类别", "期望数据项", "贵方确认"], [1.20, 4.05, 1.25], data_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("八、网络与安全配合", level=1)
|
||||
security_rows = [
|
||||
("网络拓扑", "机床网段、工业交换机、防火墙/隔离区、可否新增工控机及跨网访问方式。", "□可提供 □待确认"),
|
||||
("端口白名单", "协议、源/目的 IP、TCP/UDP 端口、连接方向和网络变更流程。", "□可提供 □待确认"),
|
||||
("账号与证书", "角色权限、密码策略、证书签发/更新、密钥保存和账号停用流程。", "□可提供 □待确认"),
|
||||
("远程支持", "VPN/堡垒机/厂商远程服务、审批、开放时间、审计和紧急断开。", "□允许 □不允许 □待确认"),
|
||||
("备份恢复", "CNC 参数、PLC、刀补、宏变量和接口配置的备份、回退及恢复负责人。", "□可配合 □待确认"),
|
||||
("数据合规", "可采集范围、保存期限、图纸/NC 程序保密等级、外发和云端限制。", "□可提供 □待确认"),
|
||||
]
|
||||
formal_matrix(doc, ["项目", "请确认内容", "贵方回复"], [1.25, 4.00, 1.25], security_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("九、联合调试与验收支持", level=1)
|
||||
acceptance_rows = [
|
||||
("资料审查", "型号、手册、代码表、样例、接口和许可证资料齐全;共同确认未知项和责任人。", "□可配合"),
|
||||
("离线验证", "使用验证样例生成 NC,完成 CAM/CNC 仿真和现场程序对比。", "□可配合 仿真环境:________"),
|
||||
("程序传输", "验证上传、下载、覆盖保护、文件名、校验、断线重试和回执,不启动机床。", "□可配合"),
|
||||
("空运行", "在机床锁定/抬刀/单段/低倍率条件下核对坐标、刀具、换刀、行程和安全位置。", "□可配合"),
|
||||
("试切", "使用约定毛坯、刀具和装夹加工样件,记录周期、报警、刀具和检测结果。", "□可配合"),
|
||||
("状态采集", "验证约定数据项、时间戳、连续采集、断线恢复和报警记录。", "□可配合"),
|
||||
("试运行", "限定设备和零件范围运行,统计成功率、人工干预、报警和返工。", "□可配合"),
|
||||
]
|
||||
formal_matrix(doc, ["阶段", "配合与验收内容", "贵方确认"], [1.10, 4.20, 1.20], acceptance_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("十、首批交付成果", level=1)
|
||||
deliverable_rows = [
|
||||
("P0", "设备接口基线表", "每台机床的完整型号、CNC 版本、轴/主轴/刀库、选件、程序传输、可用接口、网络和联系人。", "□可提供"),
|
||||
("P0", "机床编程资料包", "CNC/机床手册、G/M 代码、专用 M 代码、程序模板、坐标/刀补/换刀规则。", "□可提供"),
|
||||
("P0", "验证样例包", "3~10 套 STEP/图纸、毛坯、刀具、装夹、CAM、NC 和检测结果对应的完整样例。", "□可提供"),
|
||||
("P1", "通信接口包", "SDK/运行库或协议、许可、样例、数据字典、节点/地址表、错误码和兼容矩阵。", "□可提供"),
|
||||
("P1", "测试验收环境", "离线仿真或测试机、账号/目录、网络窗口、备份、现场人员及验收样件。", "□可提供"),
|
||||
("P2", "数字机床资料包", "机床、刀具、刀柄、夹具和毛坯模型;运动学、软限位、碰撞体和 CAM 仿真工程。", "□可提供"),
|
||||
]
|
||||
formal_matrix(doc, ["优先级", "成果名称", "内容", "确认"], [0.70, 1.45, 3.30, 1.05], deliverable_rows, first_col_fill=GREEN)
|
||||
|
||||
doc.add_heading("十一、商务授权与支持信息", level=1)
|
||||
commercial_rows = [
|
||||
("软件与接口授权", "SDK、运行库、OPC UA/MTConnect、仿真软件、后处理器是否收费及授权方式", "____________________________"),
|
||||
("现场服务", "现场调试服务内容、人员、预计工期及费用", "____________________________"),
|
||||
("培训", "操作、编程、通信接口、备份恢复和安全联调培训", "____________________________"),
|
||||
("维护支持", "服务时间、响应时限、升级路径、远程/现场支持和年度维护", "____________________________"),
|
||||
("交付介质", "安装包、许可证、手册、样例代码、版本号、校验值和下载期限", "____________________________"),
|
||||
]
|
||||
formal_matrix(doc, ["项目", "请确认内容", "贵方回复"], [1.35, 3.65, 1.50], commercial_rows, first_col_fill=LIGHT_BLUE)
|
||||
|
||||
doc.add_heading("附:建议资料包目录", level=1)
|
||||
add_callout(
|
||||
doc,
|
||||
"建议压缩包结构",
|
||||
"01_设备铭牌与系统照片;02_机床与CNC手册;03_GM代码与机床专用说明;04_已验证样例;"
|
||||
"05_CAM与后处理;06_刀具夹具材料工艺;07_通信软件与接口文档;08_网络与测试环境;"
|
||||
"09_授权商务信息;10_联系人。",
|
||||
LIGHT_BLUE,
|
||||
)
|
||||
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
doc.save(OUT)
|
||||
print(OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -0,0 +1,567 @@
|
||||
from pathlib import Path
|
||||
|
||||
from docx import Document
|
||||
from docx.enum.section import WD_SECTION
|
||||
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_TABLE_ALIGNMENT
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
from docx.oxml import OxmlElement
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Inches, Pt, RGBColor
|
||||
|
||||
|
||||
OUT = Path("/Users/jerry/linkhand/CadSet/docs/CNC机床系统对接技术需求清单.docx")
|
||||
|
||||
BLUE = "1F4E78"
|
||||
LIGHT_BLUE = "DCE6F1"
|
||||
PALE_BLUE = "EEF4F8"
|
||||
GRAY = "666666"
|
||||
LIGHT_GRAY = "F2F2F2"
|
||||
GREEN = "E2F0D9"
|
||||
AMBER = "FFF2CC"
|
||||
RED = "FCE4D6"
|
||||
WHITE = "FFFFFF"
|
||||
BLACK = "111111"
|
||||
|
||||
|
||||
def set_cell_shading(cell, fill):
|
||||
tc_pr = cell._tc.get_or_add_tcPr()
|
||||
shd = tc_pr.find(qn("w:shd"))
|
||||
if shd is None:
|
||||
shd = OxmlElement("w:shd")
|
||||
tc_pr.append(shd)
|
||||
shd.set(qn("w:fill"), fill)
|
||||
|
||||
|
||||
def set_cell_margins(cell, top=90, start=110, bottom=90, end=110):
|
||||
tc = cell._tc
|
||||
tc_pr = tc.get_or_add_tcPr()
|
||||
tc_mar = tc_pr.first_child_found_in("w:tcMar")
|
||||
if tc_mar is None:
|
||||
tc_mar = OxmlElement("w:tcMar")
|
||||
tc_pr.append(tc_mar)
|
||||
for margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
|
||||
node = tc_mar.find(qn(f"w:{margin}"))
|
||||
if node is None:
|
||||
node = OxmlElement(f"w:{margin}")
|
||||
tc_mar.append(node)
|
||||
node.set(qn("w:w"), str(value))
|
||||
node.set(qn("w:type"), "dxa")
|
||||
|
||||
|
||||
def set_repeat_table_header(row):
|
||||
tr_pr = row._tr.get_or_add_trPr()
|
||||
tbl_header = OxmlElement("w:tblHeader")
|
||||
tbl_header.set(qn("w:val"), "true")
|
||||
tr_pr.append(tbl_header)
|
||||
|
||||
|
||||
def keep_row_together(row):
|
||||
tr_pr = row._tr.get_or_add_trPr()
|
||||
cant_split = OxmlElement("w:cantSplit")
|
||||
tr_pr.append(cant_split)
|
||||
|
||||
|
||||
def set_table_fixed_width(table, widths):
|
||||
table.autofit = False
|
||||
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
||||
tbl_pr = table._tbl.tblPr
|
||||
tbl_layout = tbl_pr.find(qn("w:tblLayout"))
|
||||
if tbl_layout is None:
|
||||
tbl_layout = OxmlElement("w:tblLayout")
|
||||
tbl_pr.append(tbl_layout)
|
||||
tbl_layout.set(qn("w:type"), "fixed")
|
||||
for row in table.rows:
|
||||
for idx, width in enumerate(widths):
|
||||
row.cells[idx].width = Inches(width)
|
||||
tc_pr = row.cells[idx]._tc.get_or_add_tcPr()
|
||||
tc_w = tc_pr.find(qn("w:tcW"))
|
||||
if tc_w is None:
|
||||
tc_w = OxmlElement("w:tcW")
|
||||
tc_pr.append(tc_w)
|
||||
tc_w.set(qn("w:w"), str(int(width * 1440)))
|
||||
tc_w.set(qn("w:type"), "dxa")
|
||||
|
||||
|
||||
def set_run(run, size=10.5, bold=False, color=BLACK, font="Arial"):
|
||||
run.font.name = font
|
||||
run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), font)
|
||||
run.font.size = Pt(size)
|
||||
run.bold = bold
|
||||
run.font.color.rgb = RGBColor.from_string(color)
|
||||
|
||||
|
||||
def add_text(paragraph, text, size=10.5, bold=False, color=BLACK):
|
||||
run = paragraph.add_run(text)
|
||||
set_run(run, size=size, bold=bold, color=color)
|
||||
return run
|
||||
|
||||
|
||||
def add_bullet(doc, text, level=0, bold_prefix=None):
|
||||
p = doc.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2")
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
p.paragraph_format.line_spacing = 1.12
|
||||
if bold_prefix and text.startswith(bold_prefix):
|
||||
add_text(p, bold_prefix, bold=True)
|
||||
add_text(p, text[len(bold_prefix):])
|
||||
else:
|
||||
add_text(p, text)
|
||||
return p
|
||||
|
||||
|
||||
def add_checkbox_line(doc, text, note=None):
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.left_indent = Inches(0.15)
|
||||
p.paragraph_format.first_line_indent = Inches(-0.15)
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
add_text(p, "☐ ", size=11.5, bold=True, color=BLUE)
|
||||
add_text(p, text, bold=True)
|
||||
if note:
|
||||
add_text(p, f" {note}", color=GRAY)
|
||||
|
||||
|
||||
def add_callout(doc, label, text, fill=PALE_BLUE):
|
||||
table = doc.add_table(rows=1, cols=1)
|
||||
set_table_fixed_width(table, [6.5])
|
||||
cell = table.cell(0, 0)
|
||||
set_cell_shading(cell, fill)
|
||||
set_cell_margins(cell, top=120, start=150, bottom=120, end=150)
|
||||
p = cell.paragraphs[0]
|
||||
p.paragraph_format.space_after = Pt(0)
|
||||
add_text(p, f"{label}:", bold=True, color=BLUE)
|
||||
add_text(p, text)
|
||||
doc.add_paragraph().paragraph_format.space_after = Pt(1)
|
||||
|
||||
|
||||
def add_requirement_table(doc, rows):
|
||||
table = doc.add_table(rows=1, cols=4)
|
||||
table.style = "Table Grid"
|
||||
widths = [0.72, 1.63, 2.55, 1.60]
|
||||
set_table_fixed_width(table, widths)
|
||||
headers = ["优先级", "向对方要什么", "具体内容", "拿到后用于什么"]
|
||||
for i, text in enumerate(headers):
|
||||
cell = table.rows[0].cells[i]
|
||||
set_cell_shading(cell, BLUE)
|
||||
set_cell_margins(cell)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, text, size=9.5, bold=True, color=WHITE)
|
||||
set_repeat_table_header(table.rows[0])
|
||||
|
||||
for priority, ask, details, usage in rows:
|
||||
row = table.add_row()
|
||||
keep_row_together(row)
|
||||
cells = row.cells
|
||||
values = [priority, ask, details, usage]
|
||||
fill = GREEN if priority == "必须" else (AMBER if priority == "建议" else WHITE)
|
||||
for i, value in enumerate(values):
|
||||
cell = cells[i]
|
||||
set_cell_margins(cell)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
if i == 0:
|
||||
set_cell_shading(cell, fill)
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i == 0 else WD_ALIGN_PARAGRAPH.LEFT
|
||||
add_text(p, value, size=9.2, bold=(i in (0, 1)))
|
||||
return table
|
||||
|
||||
|
||||
def add_response_table(doc, rows):
|
||||
table = doc.add_table(rows=1, cols=3)
|
||||
table.style = "Table Grid"
|
||||
set_table_fixed_width(table, [2.35, 2.15, 2.0])
|
||||
for i, text in enumerate(["请确认", "对方填写/勾选", "附件或联系人"]):
|
||||
cell = table.rows[0].cells[i]
|
||||
set_cell_shading(cell, BLUE)
|
||||
set_cell_margins(cell)
|
||||
p = cell.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, text, size=9.5, bold=True, color=WHITE)
|
||||
set_repeat_table_header(table.rows[0])
|
||||
for item, options in rows:
|
||||
row = table.add_row()
|
||||
keep_row_together(row)
|
||||
cells = row.cells
|
||||
for cell in cells:
|
||||
set_cell_margins(cell, top=110, bottom=110)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
add_text(cells[0].paragraphs[0], item, size=9.3, bold=True)
|
||||
add_text(cells[1].paragraphs[0], options, size=9.3)
|
||||
add_text(cells[2].paragraphs[0], "________________", size=9.3, color=GRAY)
|
||||
return table
|
||||
|
||||
|
||||
def setup_document():
|
||||
doc = Document()
|
||||
section = doc.sections[0]
|
||||
section.page_width = Inches(8.5)
|
||||
section.page_height = Inches(11)
|
||||
section.top_margin = Inches(0.65)
|
||||
section.bottom_margin = Inches(0.65)
|
||||
section.left_margin = Inches(0.72)
|
||||
section.right_margin = Inches(0.72)
|
||||
section.header_distance = Inches(0.3)
|
||||
section.footer_distance = Inches(0.3)
|
||||
|
||||
styles = doc.styles
|
||||
normal = styles["Normal"]
|
||||
normal.font.name = "Arial"
|
||||
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
normal.font.size = Pt(10.5)
|
||||
normal.font.color.rgb = RGBColor.from_string(BLACK)
|
||||
normal.paragraph_format.space_after = Pt(5)
|
||||
normal.paragraph_format.line_spacing = 1.12
|
||||
|
||||
for name, size, before, after in (
|
||||
("Heading 1", 16, 14, 7),
|
||||
("Heading 2", 12.5, 10, 5),
|
||||
("Heading 3", 11, 7, 3),
|
||||
):
|
||||
style = styles[name]
|
||||
style.font.name = "Arial"
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
style.font.size = Pt(size)
|
||||
style.font.bold = True
|
||||
style.font.color.rgb = RGBColor.from_string(BLUE)
|
||||
style.paragraph_format.space_before = Pt(before)
|
||||
style.paragraph_format.space_after = Pt(after)
|
||||
style.paragraph_format.keep_with_next = True
|
||||
|
||||
for list_name in ("List Bullet", "List Bullet 2", "List Number"):
|
||||
style = styles[list_name]
|
||||
style.font.name = "Arial"
|
||||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Arial")
|
||||
style.font.size = Pt(10.5)
|
||||
|
||||
header = section.header
|
||||
p = header.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||||
add_text(p, "CNC 机床首次系统对接|资料与接口清单", size=8.5, color=GRAY)
|
||||
|
||||
footer = section.footer
|
||||
p = footer.paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, "供机床制造商、三菱 CNC 技术人员及项目对接人共同确认", size=8, color=GRAY)
|
||||
return doc
|
||||
|
||||
|
||||
def build():
|
||||
doc = setup_document()
|
||||
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(8)
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
add_text(p, "CNC 机床系统对接技术需求清单", size=23, bold=True, color=BLUE)
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_after = Pt(12)
|
||||
add_text(p, "目标:把数字零件模型接入实际机床,完成加工程序生成、程序传输、试切验证和加工结果回收。", size=11.5, color=GRAY)
|
||||
|
||||
add_callout(
|
||||
doc,
|
||||
"先说结论",
|
||||
"第一次对接不需要把所有工业接口都拿到。最低闭环只需要四类东西:①准确的机床与 CNC 型号;"
|
||||
"②机床专用编程资料;③一个可上传 NC 程序的通道;④一批已在该机床验证过的样例。"
|
||||
"如果还要监控加工状态,再增加一个只读数据接口。",
|
||||
GREEN,
|
||||
)
|
||||
|
||||
doc.add_heading("1. 先找对人:三菱不一定等于机床厂", level=1)
|
||||
add_bullet(doc, "机床制造商/集成商:掌握机床结构、轴配置、刀库、夹具、机床专用 M 代码、PLC 梯形图和安全联锁。")
|
||||
add_bullet(doc, "三菱 CNC 或代理商:掌握控制器型号、系统版本、选件、通信软件、SDK、授权和标准接口。")
|
||||
add_bullet(doc, "现场工艺/操作人员:掌握真实刀具、材料、转速进给、装夹、对刀、零点和已验证加工程序。")
|
||||
add_callout(doc, "对接要求", "请甲方安排以上三类人员至少各一位。只找其中一方,通常拿不全资料。", AMBER)
|
||||
|
||||
doc.add_heading("2. 第一批必须拿到的材料", level=1)
|
||||
required_rows = [
|
||||
("必须", "设备身份资料", "机床品牌/完整型号/序列号;三菱 CNC 控制器系列和完整型号;系统软件版本;已安装选件清单。最好提供铭牌、控制柜和系统信息页面照片。", "决定能用哪些手册、接口和软件,避免按错型号开发。"),
|
||||
("必须", "机床能力表", "车床/铣床/加工中心;轴数与轴名称;各轴行程;主轴数量、最高转速;最大进给;刀库类型和容量;是否有旋转轴、探针、自动对刀。", "确定模型是否可加工、工序怎么拆、刀路和后处理怎么生成。"),
|
||||
("必须", "编程资料", "该型号 CNC 的编程/操作手册;机床厂专用操作手册;支持的 G/M/T/S 代码;自定义 M 代码;宏变量、坐标系、刀补和程序格式说明。", "生成该机床能真正执行的 NC 程序。"),
|
||||
("必须", "已验证样例包", "至少 3~10 个已加工成功的样件,每个样件尽量包含 STEP/二维图、NC 程序、材料、毛坯、刀具表、装夹说明、坐标原点及最终检测结果。", "建立正确后处理和参数基线,比只有说明书更有效。"),
|
||||
("必须", "程序传输方式", "确认机床当前如何收发加工程序:以太网软件、FTP、共享目录、串口、USB 或其他;提供网络配置方法、账号权限、目录规则和文件名限制。", "打通“生成程序→送入机床”的最小闭环。"),
|
||||
("建议", "CAM/后处理资料", "现用 CAM 名称和版本;现有后处理器;后处理配置;机床仿真模型/运动学文件;程序头尾模板。", "优先复用成熟后处理,减少试切风险。"),
|
||||
("建议", "刀具与工艺资料", "真实刀具/刀柄清单、刀具编号、长度直径范围;常用材料与毛坯;推荐转速进给、冷却方式、余量、公差和粗糙度。", "把几何模型转换成可执行工艺,而不是只生成外形。"),
|
||||
("建议", "备份与恢复资料", "当前 CNC 参数、刀补、宏变量、PLC/梯形图及机床厂数据的备份方式;试验前完整备份;恢复负责人。", "调试异常时可恢复,避免影响生产。"),
|
||||
]
|
||||
add_requirement_table(doc, required_rows)
|
||||
|
||||
doc.add_heading("3. 接口怎么问:不用一上来就说“把 API 都给我”", level=1)
|
||||
add_callout(
|
||||
doc,
|
||||
"正确问法",
|
||||
"请对方根据实际 CNC 型号勾选已经安装、可以授权或可以新增的通道。我们的优先顺序是:"
|
||||
"先解决程序文件传输,再解决只读监控,最后才考虑自动写变量或远程控制。",
|
||||
PALE_BLUE,
|
||||
)
|
||||
|
||||
doc.add_heading("3.1 上传和下载加工程序(第一优先级)", level=2)
|
||||
add_checkbox_line(doc, "NC Explorer", "三菱官方工具,可通过以太网在电脑与多台 CNC 间操作加工程序文件;需确认该控制器是否支持。")
|
||||
add_checkbox_line(doc, "FTP / 共享目录 / 机床厂文件服务", "如果机床已经开放,提供 IP、端口、目录、账号、字符编码和文件名规则。")
|
||||
add_checkbox_line(doc, "RS-232C / USB", "可作为初期人工导入或旧机型兜底,但不适合长期自动闭环。")
|
||||
add_checkbox_line(doc, "机床厂自带 DNC/MDC/MES 软件", "提供产品名称、接口文档和第三方接入方法。")
|
||||
|
||||
doc.add_heading("3.2 读取运行状态和加工结果(第二优先级)", level=2)
|
||||
add_checkbox_line(doc, "MTConnect", "适合读取运行、报警、状态等数据;通常为只读,安全性高,适合第一阶段监控。")
|
||||
add_checkbox_line(doc, "OPC UA / NC Machine Tool Connector", "适合统一采集机床数据;需确认 OPC UA 服务器、节点表、证书、许可证和实际开放变量。")
|
||||
add_checkbox_line(doc, "NC Monitor2 或甲方现有监控平台", "如果只需要人工远程查看,可先使用现成工具;若要系统接入,仍需确认是否提供数据接口。")
|
||||
add_checkbox_line(doc, "数据库/文件导出", "若没有实时接口,可先通过班次 CSV、报警日志或加工结果文件实现离线回传。")
|
||||
|
||||
doc.add_heading("3.3 读写 CNC 数据或做深度集成(第三优先级)", level=2)
|
||||
add_checkbox_line(doc, "FCSB1224W000 开发包(SDK)", "三菱 CNC 通信软件的开发版;需索取安装包、许可证、参考手册、头文件/库和 C#/C++ 示例。")
|
||||
add_checkbox_line(doc, "FCSB1224W100 运行库", "部署自行开发的 Windows 程序时可能需要;版本应与 SDK 和 CNC 兼容。")
|
||||
add_checkbox_line(doc, "SLMP / PLC 设备访问", "仅在机床厂确实开放 PLC 设备映射时考虑;必须索取设备地址表、读写权限和联锁说明。")
|
||||
add_checkbox_line(doc, "机床厂专用 API", "若机床厂对三菱 CNC 做过二次开发,应优先获取其 API,而不是绕过机床安全逻辑直接写控制器。")
|
||||
add_callout(
|
||||
doc,
|
||||
"安全边界",
|
||||
"第一阶段只申请“程序上传”和“状态读取”。不要直接申请自动启动主轴、循环启动、写 PLC 或修改 CNC 参数。"
|
||||
"远程启动必须经过机床厂安全评审、现场联锁和甲方书面批准。",
|
||||
RED,
|
||||
)
|
||||
|
||||
doc.add_heading("4. 每一种接口要对方同时交付什么", level=1)
|
||||
api_rows = [
|
||||
("必须", "兼容性确认", "适用 CNC 型号、系统版本、所需硬件/选件、是否已安装、许可证数量和有效期。", "防止拿到软件但现场不能用。"),
|
||||
("必须", "开发资料", "API/协议手册、数据字典、错误码、示例程序、SDK/运行库安装包、版本说明。", "完成可维护的接口开发。"),
|
||||
("必须", "连接参数", "IP、端口、协议版本、账号/证书、读写权限、超时、并发和频率限制。", "建立测试连接。"),
|
||||
("必须", "测试环境", "一台可调试机床或离线仿真环境;测试账号;允许上传的测试目录;测试窗口。", "不在生产机上盲调。"),
|
||||
("建议", "现场支持", "机床厂电气/PLC 工程师、三菱 CNC 工程师、操作员的联系人和联合调试时间。", "遇到权限、联锁和型号差异时及时定位。"),
|
||||
]
|
||||
add_requirement_table(doc, api_rows)
|
||||
|
||||
doc.add_heading("5. 给甲方/制造商的回填表", level=1)
|
||||
p = doc.add_paragraph()
|
||||
add_text(p, "请对方直接填写或勾选;不确定的项目可写“待三菱/机床厂确认”。", color=GRAY)
|
||||
response_rows = [
|
||||
("机床制造商、型号、序列号", "填写完整型号"),
|
||||
("三菱 CNC 型号与系统版本", "填写/附系统页面照片"),
|
||||
("机床类型与轴配置", "车 / 铣 / 加工中心;3/4/5 轴"),
|
||||
("现用 CAM 与后处理器", "有 / 无;名称和版本"),
|
||||
("现有程序传输方式", "NC Explorer / FTP / DNC / RS-232C / USB / 其他"),
|
||||
("可否提供通信 SDK", "FCSB1224W000 / 其他 / 不支持"),
|
||||
("可否提供只读数据接口", "MTConnect / OPC UA / 其他 / 不支持"),
|
||||
("可否提供测试环境", "离线仿真 / 测试机 / 生产机测试窗口"),
|
||||
("可否提供已验证样例包", "数量:____;包含 STEP+NC+刀具+材料:是 / 否"),
|
||||
("机床厂技术联系人", "机械 / 电气PLC / 工艺"),
|
||||
("三菱或代理商技术联系人", "CNC / 通信软件 / 授权"),
|
||||
]
|
||||
add_response_table(doc, response_rows)
|
||||
|
||||
doc.add_heading("6. 第一次会议照着问的 12 个问题", level=1)
|
||||
questions = [
|
||||
"这台机床的完整型号是什么?三菱 CNC 的完整型号和系统版本是什么?",
|
||||
"机床是几轴?各轴行程、主轴、刀库和可加工范围是多少?",
|
||||
"现在工程师用什么 CAM 和后处理器生成程序?能否提供现有后处理?",
|
||||
"能否给 3~10 套已经加工成功的完整样例,而不只是 NC 文件?",
|
||||
"机床现在通过什么方式接收和导出加工程序?",
|
||||
"是否支持 NC Explorer?现场是否已经配置以太网?",
|
||||
"这个型号是否支持 FCSB1224W000?许可证、SDK 和运行库由谁提供?",
|
||||
"是否已经有 MTConnect、OPC UA、MDC、MES 或机床监控系统?",
|
||||
"我们能读取哪些数据:运行状态、当前程序、坐标、刀具、报警、主轴负载、加工结果?",
|
||||
"第一阶段能否提供测试机或仿真环境,并允许只上传程序、不自动启动?",
|
||||
"试验前谁负责备份 CNC 参数、PLC、宏变量和刀补?出现问题谁负责恢复?",
|
||||
"机床厂、三菱、现场工艺三方各由谁负责,何时可以联合调试?",
|
||||
]
|
||||
for i, question in enumerate(questions, 1):
|
||||
p = doc.add_paragraph(style="List Number")
|
||||
p.paragraph_format.space_after = Pt(3)
|
||||
add_text(p, question)
|
||||
|
||||
doc.add_heading("7. 建议甲方首批打包提供的文件夹", level=1)
|
||||
add_callout(
|
||||
doc,
|
||||
"一个压缩包即可",
|
||||
"01_设备铭牌与系统照片;02_机床与 CNC 手册;03_GM代码和机床专用说明;"
|
||||
"04_已验证样例;05_CAM与后处理;06_刀具材料工艺;07_通信软件与接口文档;"
|
||||
"08_网络与测试环境;09_联系人。",
|
||||
LIGHT_BLUE,
|
||||
)
|
||||
|
||||
doc.add_heading("8. CAD、CAM 与机床数字模型资料", level=1)
|
||||
add_callout(
|
||||
doc,
|
||||
"为什么必须补这一部分",
|
||||
"STEP 只能说明零件几何,不能直接告诉机床如何装夹、选刀和运动。"
|
||||
"要稳定生成 NC 程序,还需要机床、刀具、夹具、毛坯和坐标系数据。",
|
||||
PALE_BLUE,
|
||||
)
|
||||
cadcam_rows = [
|
||||
("必须", "零件数据规范", "确认接收 STEP AP203/AP214/AP242 中哪一种;长度单位;坐标方向;模型原点;实体/装配要求;允许的几何修复;二维图、尺寸公差和表面粗糙度如何提供。", "建立统一输入标准,避免比例、方向、基准和公差信息丢失。"),
|
||||
("必须", "机床运动学", "各线性轴和旋转轴名称、正方向、行程、零点、回转中心、轴顺序、软限位、主轴方向;五轴机需提供 RTCP/TCP 相关配置。", "建立正确后处理和碰撞仿真。"),
|
||||
("必须", "坐标与对刀规则", "机床坐标、工件坐标 G54~G59/扩展坐标、程序零点、对刀点、刀长/刀径补偿号规则、探针和自动对刀流程。", "保证程序中的坐标与现场实际一致。"),
|
||||
("必须", "刀具与刀柄模型", "实际刀具、刀柄、延长杆和刀盘的清单及 STEP/尺寸;刀位号、刀补号、最大直径长度重量、相邻刀位限制。", "选刀、计算切削参数并检查刀具/刀柄碰撞。"),
|
||||
("必须", "夹具与工作区模型", "工作台、卡盘、虎钳、软爪、尾座、第四/第五轴、探针及主要防护区域的 STEP 或关键尺寸。", "规划装夹、可达性和碰撞检查。"),
|
||||
("必须", "毛坯和材料规则", "常用棒料、板料、锻件、铸件规格;材料牌号;毛坯余量;硬度和热处理状态;是否允许自动推荐替代材料。", "生成合理工序、刀路和切削参数。"),
|
||||
("建议", "机床仿真模型", "机床厂/CAM 使用的数字机床包、运动学文件、碰撞体、控制器定义和现有仿真项目。", "缩短虚拟调试和后处理验证周期。"),
|
||||
]
|
||||
add_requirement_table(doc, cadcam_rows)
|
||||
|
||||
doc.add_heading("9. 后处理器和 NC 程序规则", level=1)
|
||||
post_rows = [
|
||||
("必须", "程序格式限制", "程序号格式、文件扩展名、字符编码、行号、注释规则、最大文件大小、程序存储路径、子程序路径及程序调用层级限制。", "确保文件能够被控制器识别和加载。"),
|
||||
("必须", "标准代码清单", "实际支持的 G/M/T/S/F 代码、固定循环、极坐标/坐标旋转、刀补、螺纹、刚性攻丝、高速高精功能及版本差异。", "限制程序生成器只能使用现场支持的指令。"),
|
||||
("必须", "机床厂专用逻辑", "换刀、开关门、夹紧松开、卡盘/尾座、冷却、排屑、探针、托盘等自定义 M 代码;执行前置条件和互锁。", "避免使用通用 M 代码猜测机床动作。"),
|
||||
("必须", "程序头尾模板", "提供一份机床厂认可的开头、换刀段、工序切换、异常退出和结束模板;明确安全位置和模态复位要求。", "统一安全启动与结束逻辑。"),
|
||||
("必须", "后处理验收样例", "选择典型 2.5D、孔系、曲面或车削样件;提供 CAM 刀路、输出 NC、仿真结果和现场实际程序的对照。", "验证后处理输出与现场一致。"),
|
||||
("建议", "控制器高级选件", "确认高速加工、五轴联动、刀尖点控制、前瞻、样条/NURBS、宏程序等功能是否购买并启用。", "避免生成未授权或不支持的功能。"),
|
||||
]
|
||||
add_requirement_table(doc, post_rows)
|
||||
|
||||
doc.add_heading("10. 需要读取的数据点清单", level=1)
|
||||
add_callout(
|
||||
doc,
|
||||
"让对方逐项标注",
|
||||
"每个数据点请填写:是否可读、来自哪个接口、字段/节点地址、数据类型、单位、刷新周期、时间戳、"
|
||||
"枚举含义、无效值和报警条件。不要只回复“支持 OPC UA”或“支持 API”。",
|
||||
AMBER,
|
||||
)
|
||||
datapoint_rows = [
|
||||
("必须", "设备与连接状态", "设备 ID、CNC 型号、系统版本、在线/离线、数据源状态、采集程序版本、最后更新时间。", "识别设备并判断数据是否可信。"),
|
||||
("必须", "运行状态", "自动/手动/MDI、运行/暂停/停止/报警/急停、循环启动状态、单段、选择停止、门和关键联锁状态。", "判断设备是否正在执行及为何停机。"),
|
||||
("必须", "程序执行", "当前程序号/文件名、子程序、当前行号或序列号、程序开始结束时间、循环时间、加工计数。", "把加工任务与实际执行记录关联。"),
|
||||
("必须", "轴与坐标", "机床坐标、工件坐标、剩余移动量、进给速度、进给倍率、各轴负载、旋转轴角度;单位和坐标系标识。", "监控运动并分析异常。"),
|
||||
("必须", "主轴数据", "实际/指令转速、倍率、负载、扭矩或功率、运行方向、主轴状态、温度(若有)。", "分析切削负载和加工状态。"),
|
||||
("必须", "刀具数据", "当前刀号、刀位、刀长/刀径补偿、寿命/使用次数、刀具报警、备用刀及刀具表更新时间。", "检查程序选刀并积累刀具寿命数据。"),
|
||||
("必须", "报警与诊断", "报警号、等级、文本、发生/解除时间、报警历史、控制器诊断状态;明确是否能读取机床厂 PLC 报警。", "故障追踪和自动告警。"),
|
||||
("建议", "加工与质量结果", "完工状态、实际周期、零件计数、测头结果、尺寸检测、报废/返工原因、操作员确认。", "形成加工结果闭环。"),
|
||||
("建议", "能耗与维护", "主轴运行时长、开机时间、润滑/冷却状态、过滤器、维护计数及能耗数据(若设备已有)。", "支持后续维护和效率分析。"),
|
||||
]
|
||||
add_requirement_table(doc, datapoint_rows)
|
||||
|
||||
doc.add_heading("11. 通信接口的工程参数", level=1)
|
||||
comm_rows = [
|
||||
("必须", "方向与权限", "明确每个接口是只读、文件上传下载、数据读写还是远程控制;列出允许和禁止的操作。", "划定安全边界。"),
|
||||
("必须", "性能指标", "刷新周期、最小采样间隔、单次/批量读取数量、并发连接数、最大文件大小、传输速率和超时时间。", "评估能否满足监控和任务下发。"),
|
||||
("必须", "可靠性机制", "断线检测、自动重连、续传、重复提交处理、任务唯一 ID、校验和、确认回执和错误码。", "避免程序重复上传、丢失或状态误判。"),
|
||||
("必须", "时间与单位", "CNC 时间设置、时区、NTP/PTP 支持、时间戳精度;坐标、转速、负载等字段的单位与缩放系数。", "保证多系统数据可对齐。"),
|
||||
("必须", "版本管理", "SDK/运行库/协议版本与 CNC 版本兼容矩阵;升级策略;弃用接口;补丁和技术支持期限。", "避免现场升级后接口失效。"),
|
||||
("建议", "日志与审计", "连接日志、文件传输记录、调用方、操作时间、操作结果、配置修改和远程访问记录。", "故障定位和责任追踪。"),
|
||||
]
|
||||
add_requirement_table(doc, comm_rows)
|
||||
|
||||
doc.add_heading("12. 网络与信息安全要求", level=1)
|
||||
security_rows = [
|
||||
("必须", "网络拓扑", "机床所在网段、工业交换机、防火墙/隔离区、是否允许新增工控机、跨网访问方式和负责网络变更的人员。", "确定系统部署位置和连接路径。"),
|
||||
("必须", "端口白名单", "逐项提供协议、源/目的 IP、TCP/UDP 端口、连接方向;禁止使用不受控的全网开放规则。", "完成最小权限网络配置。"),
|
||||
("必须", "账号与证书", "账号类型、角色权限、密码策略、证书签发/更新、密钥保存、账号停用流程;禁止共享管理员账号。", "安全且可审计地访问设备。"),
|
||||
("必须", "远程支持方案", "是否允许 VPN、堡垒机或厂商远程服务;审批流程、开放时间、录屏/审计和紧急断开方法。", "让远程调试可控。"),
|
||||
("必须", "备份和变更控制", "CNC 参数、PLC、刀补、宏变量、接口配置的基线备份;变更申请、回退方案、恢复演练和负责人。", "降低调试对生产的风险。"),
|
||||
("建议", "数据合规", "允许采集的数据范围、数据保存期限、技术图纸和 NC 程序的保密等级、外发和云端限制。", "明确数据能否用于分析和优化。"),
|
||||
]
|
||||
add_requirement_table(doc, security_rows)
|
||||
|
||||
doc.add_heading("13. 分阶段联调与验收标准", level=1)
|
||||
acceptance_rows = [
|
||||
("阶段 0", "资料审查", "型号、手册、代码表、样例、接口和许可证齐全;双方确认未知项和责任人。", "输出《设备接口基线表》。"),
|
||||
("阶段 1", "离线后处理", "使用已验证样例生成 NC;与现场程序对比;通过 CAM/控制器仿真,无语法错误和明显碰撞。", "后处理器可进入现场试验。"),
|
||||
("阶段 2", "程序传输", "上传、下载、覆盖保护、目录、文件名、校验、断线重试和回执测试通过;不启动机床。", "打通安全文件通道。"),
|
||||
("阶段 3", "空运行验证", "机床锁定/抬刀/单段/低倍率条件下执行;核对坐标、刀具、换刀、行程和安全位置。", "证明程序与机床配置一致。"),
|
||||
("阶段 4", "试切验收", "指定毛坯、刀具和装夹;加工样件;记录周期、报警、刀具和检测结果;尺寸及表面质量达到双方确认标准。", "证明可实际制造。"),
|
||||
("阶段 5", "数据回传", "运行状态、程序、坐标、刀具、主轴和报警等约定数据点连续采集;断线恢复和时间戳正确。", "形成加工状态闭环。"),
|
||||
("阶段 6", "试运行", "限定设备、班次和零件类型运行;统计成功率、人工干预、报警和返工;完成问题清单后转正式。", "控制上线风险。"),
|
||||
]
|
||||
add_requirement_table(doc, acceptance_rows)
|
||||
|
||||
doc.add_heading("14. 项目责任、商务和交付边界", level=1)
|
||||
boundary_rows = [
|
||||
("必须", "责任分工", "明确机床厂、三菱/代理商、甲方工艺/设备/网络人员和软件对接方各自负责的资料、配置、调试和验收内容。", "避免接口问题互相推诿。"),
|
||||
("必须", "授权和费用", "SDK、运行库、OPC UA/MTConnect、仿真软件、后处理器、现场服务、培训和年度维护分别是否收费。", "形成真实预算。"),
|
||||
("必须", "交付介质", "安装包、许可证文件、离线授权方式、手册、样例代码、源代码范围、版本号、校验值和下载有效期。", "保证后续可复现部署。"),
|
||||
("必须", "支持机制", "工作时间、响应时限、问题升级路径、远程/现场支持、版本升级和保修期。", "确保问题有闭环负责人。"),
|
||||
("建议", "培训与知识转移", "操作、编程、通信 SDK、数据接口、备份恢复和安全联调培训;提供录屏或培训材料。", "减少长期依赖单个厂商工程师。"),
|
||||
]
|
||||
add_requirement_table(doc, boundary_rows)
|
||||
|
||||
doc.add_heading("15. 最终要求对方交付的核心成果", level=1)
|
||||
core_rows = [
|
||||
("P0", "设备接口基线表", "每台机床一份:完整型号、CNC 版本、轴/主轴/刀库、选件、程序传输方式、可用接口、网络和联系人。", "没有这份表,不进入开发。"),
|
||||
("P0", "机床编程资料包", "CNC 手册、机床厂手册、G/M 代码、专用 M 代码、程序模板、坐标/刀补/换刀规则。", "用于生成安全 NC 程序。"),
|
||||
("P0", "验证样例包", "至少 3~10 套 STEP/图纸、毛坯、刀具、装夹、CAM、NC 和检测结果一一对应的完整样例。", "用于后处理和工艺验证。"),
|
||||
("P1", "通信接口包", "SDK/运行库或协议文档、许可证、样例代码、数据字典、地址/节点表、错误码和兼容矩阵。", "用于程序传输和数据采集。"),
|
||||
("P1", "测试与验收环境", "离线仿真或测试机、测试账号/目录、网络窗口、备份、现场人员和验收样件。", "用于安全联调。"),
|
||||
("P2", "数字机床资料包", "机床、刀具、刀柄、夹具和毛坯模型;运动学;软限位;碰撞体;CAM 仿真工程。", "用于规模化自动编程和虚拟验证。"),
|
||||
]
|
||||
add_requirement_table(doc, core_rows)
|
||||
|
||||
doc.add_heading("附 A:不同机床类型还要追加确认什么", level=1)
|
||||
machine_rows = [
|
||||
("车床", "卡盘/夹头、主轴/副主轴、尾座、刀塔、动力刀具、Y/C/B 轴、棒料机、接料器和螺纹循环。"),
|
||||
("三轴加工中心", "工作台尺寸、主轴锥度、刀库、换刀位置、探针、第四轴接口、固定循环和高速加工选件。"),
|
||||
("四/五轴加工中心", "旋转轴结构、回转中心、轴限位、TCP/RTCP、刀尖点控制、奇异点策略、机床运动学和精确碰撞模型。"),
|
||||
("车铣复合", "多通道/多系统、主副主轴同步、刀塔同步、程序等待码、工件交接、通道间变量和完整同步样例。"),
|
||||
("磨床/专机", "砂轮修整、补偿、测量循环、专用 PLC 动作、机床厂自定义程序格式和专用工艺数据库。"),
|
||||
]
|
||||
table = doc.add_table(rows=1, cols=2)
|
||||
table.style = "Table Grid"
|
||||
set_table_fixed_width(table, [1.35, 5.15])
|
||||
for i, text in enumerate(["机床类型", "追加资料"]):
|
||||
set_cell_shading(table.rows[0].cells[i], BLUE)
|
||||
set_cell_margins(table.rows[0].cells[i])
|
||||
p = table.rows[0].cells[i].paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, text, size=9.5, bold=True, color=WHITE)
|
||||
set_repeat_table_header(table.rows[0])
|
||||
for machine_type, extra in machine_rows:
|
||||
row = table.add_row()
|
||||
keep_row_together(row)
|
||||
for cell in row.cells:
|
||||
set_cell_margins(cell)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
add_text(row.cells[0].paragraphs[0], machine_type, size=9.3, bold=True)
|
||||
add_text(row.cells[1].paragraphs[0], extra, size=9.3)
|
||||
|
||||
doc.add_heading("附 B:三菱侧可供确认的典型产品名称", level=1)
|
||||
p = doc.add_paragraph()
|
||||
add_text(
|
||||
p,
|
||||
"以下只是对接时用于询问的候选名称,并不表示每台机床都具备:NC Explorer(程序文件传输)、"
|
||||
"FCSB1224W000/W100(CNC 通信 SDK/运行库)、MTConnect Data Collector、"
|
||||
"NC Machine Tool Connector(OPC UA)、NC Monitor2、NC Virtual Simulator、NC Trainer2 plus。",
|
||||
size=9.5,
|
||||
color=GRAY,
|
||||
)
|
||||
p = doc.add_paragraph()
|
||||
p.paragraph_format.space_before = Pt(3)
|
||||
add_text(
|
||||
p,
|
||||
"确认原则:以机床铭牌、CNC 型号、系统版本、已购选件及机床厂书面答复为准。",
|
||||
size=9.5,
|
||||
bold=True,
|
||||
color=BLUE,
|
||||
)
|
||||
|
||||
doc.add_heading("附 C:三菱官方参考入口", level=1)
|
||||
official_sources = [
|
||||
("NC 软件工具总览", "确认 NC Explorer、NC Monitor2、NC Virtual Simulator、NC Trainer2 plus 等工具。", "https://www.mitsubishielectric.com/fa/products/software/cnc/index.html"),
|
||||
("NC Explorer", "官方说明其通过以太网在 Windows 资源管理器中操作 CNC 加工程序文件。", "https://us.mitsubishielectric.com/fa/en/products/cnt/computerized-numerical-controllers/software-tools/nc-explorer/"),
|
||||
("FCSB1224W000 通信软件手册", "确认 SDK、运行库、支持控制器、开发语言和 API 能力。", "https://dl.mitsubishielectric.com/dl/fa/document/manual/cnc/ib1501209/ib1501209engh.pdf"),
|
||||
("MTConnect Data Collector", "确认支持的 CNC、适配器、采集器和系统要求。", "https://www.mitsubishielectric.com/fa/products/cnt/cnc/pmerit/iot/mtconnect_data_collector/"),
|
||||
("NC Machine Tool Connector", "确认 OPC UA/MTConnect 数据采集、连接对象和许可要求。", "https://dl.mitsubishielectric.com/dl/fa/document/manual/cnc/ib1501634/ib1501634engd.pdf"),
|
||||
("三菱 CNC 手册下载", "按实际 CNC 型号和版本下载规格、编程、操作、连接和设置手册。", "https://www.mitsubishielectric.com/fa/download/search.page?kisyu=%2Fcnc&mode=manual"),
|
||||
]
|
||||
source_table = doc.add_table(rows=1, cols=3)
|
||||
source_table.style = "Table Grid"
|
||||
set_table_fixed_width(source_table, [1.55, 2.55, 2.40])
|
||||
for i, text in enumerate(["官方入口", "用于确认", "网址"]):
|
||||
set_cell_shading(source_table.rows[0].cells[i], BLUE)
|
||||
set_cell_margins(source_table.rows[0].cells[i])
|
||||
p = source_table.rows[0].cells[i].paragraphs[0]
|
||||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
add_text(p, text, size=9.3, bold=True, color=WHITE)
|
||||
set_repeat_table_header(source_table.rows[0])
|
||||
for name, use, url in official_sources:
|
||||
row = source_table.add_row()
|
||||
keep_row_together(row)
|
||||
for cell in row.cells:
|
||||
set_cell_margins(cell)
|
||||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||||
add_text(row.cells[0].paragraphs[0], name, size=8.7, bold=True)
|
||||
add_text(row.cells[1].paragraphs[0], use, size=8.7)
|
||||
add_text(row.cells[2].paragraphs[0], url, size=7.8, color=BLUE)
|
||||
|
||||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
doc.save(OUT)
|
||||
print(OUT)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
build()
|
||||
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"request": "Regenerate the current compact guided-cylinder housing using only promoted generalized CAD experience while preserving the existing parameterized geometry and enlarged central bore.",
|
||||
"route": {
|
||||
"selected_backend": "build123d",
|
||||
"runner_skill": "cad",
|
||||
"project": "text-to-cad",
|
||||
"fallback_order": [
|
||||
"simplecadapi",
|
||||
"cadam"
|
||||
],
|
||||
"workflow_profiles": [
|
||||
"requirement_refinement",
|
||||
"visual_repair"
|
||||
]
|
||||
},
|
||||
"source": {
|
||||
"model_spec": "model-spec.json",
|
||||
"path": "compact_guided_cylinder.py",
|
||||
"format": "python",
|
||||
"backend": "build123d"
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"path": "compact_guided_cylinder.step",
|
||||
"role": "primary"
|
||||
},
|
||||
{
|
||||
"path": "compact_guided_cylinder.stl",
|
||||
"role": "secondary"
|
||||
}
|
||||
],
|
||||
"parameter_source": "model-spec.json",
|
||||
"experience": {
|
||||
"library": "../../../../cad-experience-library/library.json",
|
||||
"context_kind": "generalized_cad_experience_query",
|
||||
"query_scope": "global",
|
||||
"promoted_methods_found": 0,
|
||||
"methods_applied": [],
|
||||
"result": "No promoted generalized methods matched; candidate experiences were intentionally excluded from generation."
|
||||
},
|
||||
"assumptions": [
|
||||
"The image has no absolute dimensions; millimetre dimensions preserve visible proportions only.",
|
||||
"The product is represented as a three-body compound: main extrusion plus two end caps.",
|
||||
"The four top holes are modeled through the main body because the bottom view shows matching circles.",
|
||||
"The four large circles on each end are treated as protruding tie-rod ends.",
|
||||
"The central bore radius was doubled from 4.5 mm to 9 mm; the collar outer diameter was increased from 17 mm to 26 mm to retain a 4 mm radial wall.",
|
||||
"This is concept geometry, not a pressure-rated or manufacturing-certified pneumatic cylinder."
|
||||
],
|
||||
"validation": [
|
||||
{
|
||||
"check": "STEP generation",
|
||||
"result": "passed",
|
||||
"detail": "Generated a labeled three-body assembly and STL sidecar from the parameterized Python source."
|
||||
},
|
||||
{
|
||||
"check": "Bounding box",
|
||||
"result": "passed",
|
||||
"detail": "138 x 60 x 39 mm; dominant axis X."
|
||||
},
|
||||
{
|
||||
"check": "Topology",
|
||||
"result": "passed",
|
||||
"detail": "61 faces and 156 edges after the side-groove repair."
|
||||
},
|
||||
{
|
||||
"check": "Visual review",
|
||||
"result": "passed",
|
||||
"detail": "Reviewed regenerated isometric and top snapshots. Geometry remains visually consistent with the current parameterized source, including the enlarged central bore and four-hole pattern."
|
||||
},
|
||||
{
|
||||
"check": "Experience isolation",
|
||||
"result": "passed",
|
||||
"detail": "The router reported zero generalized methods. No candidate experience, parser input, parser output, private case JSON, source dimensions, or coordinates were used."
|
||||
},
|
||||
{
|
||||
"check": "JSON parameter rebuild",
|
||||
"result": "passed",
|
||||
"detail": "The STEP and STL were regenerated from the part-local model-spec.json through the native Python generator."
|
||||
}
|
||||
],
|
||||
"viewer_links": [
|
||||
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fcompact_guided_cylinder_v1&file=compact_guided_cylinder.step",
|
||||
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fcompact_guided_cylinder_v1&file=compact_guided_cylinder.stl"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
"""Image-inferred compact guided-cylinder housing.
|
||||
|
||||
The reference contains no dimensions, so the named dimensions below preserve
|
||||
its visible proportions rather than claiming a production drawing.
|
||||
|
||||
Coordinate convention:
|
||||
- X: long axis
|
||||
- Y: width
|
||||
- Z: height, with the lowest end-cap face at Z = 0
|
||||
"""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from build123d import Align, Box, Compound, Cylinder, Pos
|
||||
|
||||
|
||||
def _model_parameters():
|
||||
spec_path = Path(__file__).with_name("model-spec.json")
|
||||
payload = json.loads(spec_path.read_text(encoding="utf-8"))
|
||||
return {
|
||||
name: float(entry["value"])
|
||||
for name, entry in payload["parameters"].items()
|
||||
}
|
||||
|
||||
|
||||
PARAMETERS = _model_parameters()
|
||||
|
||||
|
||||
def _parameter(name: str) -> float:
|
||||
return PARAMETERS[name]
|
||||
|
||||
|
||||
# Overall proportions inferred from the reference image (millimetres).
|
||||
CORE_LENGTH = _parameter("core_length")
|
||||
CORE_WIDTH = _parameter("core_width")
|
||||
CORE_BOTTOM_Z = _parameter("core_bottom_z")
|
||||
CORE_HEIGHT = _parameter("core_height")
|
||||
END_CAP_THICKNESS = _parameter("end_cap_thickness")
|
||||
END_CAP_WIDTH = _parameter("end_cap_width")
|
||||
END_CAP_HEIGHT = _parameter("end_cap_height")
|
||||
|
||||
# Two shallow longitudinal relief grooves on both long side faces.
|
||||
SIDE_GROOVE_DEPTH = _parameter("side_groove_depth")
|
||||
SIDE_GROOVE_HEIGHT = _parameter("side_groove_height")
|
||||
SIDE_GROOVE_Z = (12.0, 24.0)
|
||||
|
||||
# Four inferred through mounting holes on the top/bottom pattern.
|
||||
MOUNT_HOLE_DIAMETER = _parameter("mount_hole_diameter")
|
||||
MOUNT_HOLE_X = _parameter("mount_hole_x")
|
||||
MOUNT_HOLE_Y = _parameter("mount_hole_y")
|
||||
|
||||
# Central top port and raised collar.
|
||||
PORT_BORE_DIAMETER = _parameter("port_bore_diameter")
|
||||
PORT_COLLAR_OUTER_DIAMETER = _parameter("port_collar_outer_diameter")
|
||||
PORT_COLLAR_HEIGHT = _parameter("port_collar_height")
|
||||
|
||||
# Four visible tie-rod ends on each end cap.
|
||||
TIE_ROD_END_DIAMETER = _parameter("tie_rod_end_diameter")
|
||||
TIE_ROD_END_PROTRUSION = _parameter("tie_rod_end_protrusion")
|
||||
TIE_ROD_Y = _parameter("tie_rod_y")
|
||||
TIE_ROD_Z = (7.0, 31.0)
|
||||
|
||||
# Small blind pilot/port visible on both end faces.
|
||||
END_FACE_HOLE_DIAMETER = _parameter("end_face_hole_diameter")
|
||||
END_FACE_HOLE_DEPTH = _parameter("end_face_hole_depth")
|
||||
END_FACE_HOLE_Z = _parameter("end_face_hole_z")
|
||||
|
||||
|
||||
END_CAP_CENTER_X = CORE_LENGTH / 2.0 + END_CAP_THICKNESS / 2.0
|
||||
TOTAL_LENGTH = CORE_LENGTH + 2.0 * END_CAP_THICKNESS
|
||||
VISIBLE_LENGTH = TOTAL_LENGTH + 2.0 * TIE_ROD_END_PROTRUSION
|
||||
TOTAL_HEIGHT = END_CAP_HEIGHT + PORT_COLLAR_HEIGHT
|
||||
|
||||
|
||||
def _x_cylinder(
|
||||
length: float,
|
||||
diameter: float,
|
||||
center_x: float,
|
||||
center_y: float,
|
||||
center_z: float,
|
||||
):
|
||||
"""Create an X-axis cylinder centered at the requested location."""
|
||||
return Pos(center_x, center_y, center_z) * Cylinder(
|
||||
diameter / 2.0,
|
||||
length,
|
||||
align=(Align.CENTER, Align.CENTER, Align.CENTER),
|
||||
rotation=(0.0, 90.0, 0.0),
|
||||
)
|
||||
|
||||
|
||||
def _make_core():
|
||||
"""Main extrusion with side grooves, top pattern, and central through port."""
|
||||
core = Pos(0.0, 0.0, CORE_BOTTOM_Z) * Box(
|
||||
CORE_LENGTH,
|
||||
CORE_WIDTH,
|
||||
CORE_HEIGHT,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
|
||||
# Cut two crisp grooves into each long side, stopping at the end-cap seams.
|
||||
for groove_z in SIDE_GROOVE_Z:
|
||||
groove_center_y = CORE_WIDTH / 2.0 - SIDE_GROOVE_DEPTH / 2.0 + 0.2
|
||||
front_tool = Pos(0.0, groove_center_y, groove_z) * Box(
|
||||
CORE_LENGTH + 1.0,
|
||||
SIDE_GROOVE_DEPTH + 0.4,
|
||||
SIDE_GROOVE_HEIGHT,
|
||||
align=(Align.CENTER, Align.CENTER, Align.CENTER),
|
||||
)
|
||||
back_tool = Pos(0.0, -groove_center_y, groove_z) * Box(
|
||||
CORE_LENGTH + 1.0,
|
||||
SIDE_GROOVE_DEPTH + 0.4,
|
||||
SIDE_GROOVE_HEIGHT,
|
||||
align=(Align.CENTER, Align.CENTER, Align.CENTER),
|
||||
)
|
||||
core = core - front_tool - back_tool
|
||||
|
||||
# Through mounting pattern visible from top and bottom in the reference.
|
||||
for x_pos in (-MOUNT_HOLE_X, MOUNT_HOLE_X):
|
||||
for y_pos in (-MOUNT_HOLE_Y, MOUNT_HOLE_Y):
|
||||
hole = Pos(x_pos, y_pos, -1.0) * Cylinder(
|
||||
MOUNT_HOLE_DIAMETER / 2.0,
|
||||
TOTAL_HEIGHT + 2.0,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
core = core - hole
|
||||
|
||||
port_hole = Pos(0.0, 0.0, -1.0) * Cylinder(
|
||||
PORT_BORE_DIAMETER / 2.0,
|
||||
TOTAL_HEIGHT + 2.0,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
core = core - port_hole
|
||||
|
||||
collar = Pos(0.0, 0.0, CORE_BOTTOM_Z + CORE_HEIGHT - 0.1) * Cylinder(
|
||||
PORT_COLLAR_OUTER_DIAMETER / 2.0,
|
||||
PORT_COLLAR_HEIGHT + 0.1,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
collar = collar - Pos(0.0, 0.0, CORE_BOTTOM_Z + CORE_HEIGHT - 1.0) * Cylinder(
|
||||
PORT_BORE_DIAMETER / 2.0,
|
||||
PORT_COLLAR_HEIGHT + 2.0,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
|
||||
core = core + collar
|
||||
core.label = "extruded_cylinder_body"
|
||||
return core
|
||||
|
||||
|
||||
def _make_end_cap(side: int):
|
||||
"""End plate with four protruding tie-rod ends and one blind face hole."""
|
||||
center_x = side * END_CAP_CENTER_X
|
||||
cap = Pos(center_x, 0.0, 0.0) * Box(
|
||||
END_CAP_THICKNESS,
|
||||
END_CAP_WIDTH,
|
||||
END_CAP_HEIGHT,
|
||||
align=(Align.CENTER, Align.CENTER, Align.MIN),
|
||||
)
|
||||
|
||||
boss_center_x = side * (
|
||||
TOTAL_LENGTH / 2.0 + TIE_ROD_END_PROTRUSION / 2.0 - 0.5
|
||||
)
|
||||
for y_pos in (-TIE_ROD_Y, TIE_ROD_Y):
|
||||
for z_pos in TIE_ROD_Z:
|
||||
cap = cap + _x_cylinder(
|
||||
TIE_ROD_END_PROTRUSION + 1.0,
|
||||
TIE_ROD_END_DIAMETER,
|
||||
boss_center_x,
|
||||
y_pos,
|
||||
z_pos,
|
||||
)
|
||||
|
||||
# Blind end-face pilot: overshoot the outside face but stop inside the cap.
|
||||
hole_center_x = side * (
|
||||
TOTAL_LENGTH / 2.0 - END_FACE_HOLE_DEPTH / 2.0 + 0.5
|
||||
)
|
||||
cap = cap - _x_cylinder(
|
||||
END_FACE_HOLE_DEPTH + 1.0,
|
||||
END_FACE_HOLE_DIAMETER,
|
||||
hole_center_x,
|
||||
0.0,
|
||||
END_FACE_HOLE_Z,
|
||||
)
|
||||
cap.label = "right_end_cap" if side > 0 else "left_end_cap"
|
||||
return cap
|
||||
|
||||
|
||||
def gen_step():
|
||||
"""Return a labeled three-body STEP-ready compound."""
|
||||
core = _make_core()
|
||||
left_cap = _make_end_cap(-1)
|
||||
right_cap = _make_end_cap(1)
|
||||
|
||||
model = Compound(children=[core, left_cap, right_cap])
|
||||
model.label = "compact_guided_cylinder_v1"
|
||||
return model
|
||||
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"model_spec_kind": "parametric_cad_model",
|
||||
"model_id": "compact_guided_cylinder",
|
||||
"units": "mm",
|
||||
"reconstruction": {
|
||||
"mode": "native_generator",
|
||||
"generator": {
|
||||
"path": "compact_guided_cylinder.py",
|
||||
"entrypoint": "gen_step"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"core_length": {
|
||||
"value": 116.0,
|
||||
"unit": "mm",
|
||||
"role": "main_body_length"
|
||||
},
|
||||
"core_width": {
|
||||
"value": 56.0,
|
||||
"unit": "mm",
|
||||
"role": "main_body_width"
|
||||
},
|
||||
"core_bottom_z": {
|
||||
"value": 2.0,
|
||||
"unit": "mm",
|
||||
"role": "main_body_base_offset"
|
||||
},
|
||||
"core_height": {
|
||||
"value": 34.0,
|
||||
"unit": "mm",
|
||||
"role": "main_body_height"
|
||||
},
|
||||
"end_cap_thickness": {
|
||||
"value": 8.0,
|
||||
"unit": "mm",
|
||||
"role": "end_cap_thickness"
|
||||
},
|
||||
"end_cap_width": {
|
||||
"value": 60.0,
|
||||
"unit": "mm",
|
||||
"role": "end_cap_width"
|
||||
},
|
||||
"end_cap_height": {
|
||||
"value": 38.0,
|
||||
"unit": "mm",
|
||||
"role": "end_cap_height"
|
||||
},
|
||||
"side_groove_depth": {
|
||||
"value": 1.8,
|
||||
"unit": "mm",
|
||||
"role": "longitudinal_groove_depth"
|
||||
},
|
||||
"side_groove_height": {
|
||||
"value": 2.0,
|
||||
"unit": "mm",
|
||||
"role": "longitudinal_groove_height"
|
||||
},
|
||||
"mount_hole_diameter": {
|
||||
"value": 4.5,
|
||||
"unit": "mm",
|
||||
"role": "mounting_hole_diameter"
|
||||
},
|
||||
"mount_hole_x": {
|
||||
"value": 32.0,
|
||||
"unit": "mm",
|
||||
"role": "mounting_pattern_half_spacing_x"
|
||||
},
|
||||
"mount_hole_y": {
|
||||
"value": 16.0,
|
||||
"unit": "mm",
|
||||
"role": "mounting_pattern_half_spacing_y"
|
||||
},
|
||||
"port_bore_diameter": {
|
||||
"value": 18.0,
|
||||
"unit": "mm",
|
||||
"role": "central_port_bore_diameter"
|
||||
},
|
||||
"port_collar_outer_diameter": {
|
||||
"value": 26.0,
|
||||
"unit": "mm",
|
||||
"role": "central_port_collar_outer_diameter"
|
||||
},
|
||||
"port_collar_height": {
|
||||
"value": 3.0,
|
||||
"unit": "mm",
|
||||
"role": "central_port_collar_height"
|
||||
},
|
||||
"tie_rod_end_diameter": {
|
||||
"value": 12.0,
|
||||
"unit": "mm",
|
||||
"role": "tie_rod_end_diameter"
|
||||
},
|
||||
"tie_rod_end_protrusion": {
|
||||
"value": 3.0,
|
||||
"unit": "mm",
|
||||
"role": "tie_rod_end_protrusion"
|
||||
},
|
||||
"tie_rod_y": {
|
||||
"value": 24.0,
|
||||
"unit": "mm",
|
||||
"role": "tie_rod_pattern_half_spacing_y"
|
||||
},
|
||||
"end_face_hole_diameter": {
|
||||
"value": 5.5,
|
||||
"unit": "mm",
|
||||
"role": "end_face_blind_hole_diameter"
|
||||
},
|
||||
"end_face_hole_depth": {
|
||||
"value": 6.0,
|
||||
"unit": "mm",
|
||||
"role": "end_face_blind_hole_depth"
|
||||
},
|
||||
"end_face_hole_z": {
|
||||
"value": 14.0,
|
||||
"unit": "mm",
|
||||
"role": "end_face_blind_hole_height"
|
||||
}
|
||||
},
|
||||
"feature_groups": [
|
||||
{
|
||||
"id": "main_body",
|
||||
"kind": "prismatic_body"
|
||||
},
|
||||
{
|
||||
"id": "mounting_pattern",
|
||||
"kind": "repeated_axial_hole_pattern",
|
||||
"parameters": [
|
||||
"mount_hole_diameter",
|
||||
"mount_hole_x",
|
||||
"mount_hole_y"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "central_port",
|
||||
"kind": "raised_collar_with_passage",
|
||||
"parameters": [
|
||||
"port_bore_diameter",
|
||||
"port_collar_outer_diameter",
|
||||
"port_collar_height"
|
||||
]
|
||||
}
|
||||
],
|
||||
"modifications": [],
|
||||
"outputs": {
|
||||
"step": "compact_guided_cylinder.step",
|
||||
"stl": "compact_guided_cylinder.stl"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 322 KiB |
|
After Width: | Height: | Size: 370 KiB |
|
After Width: | Height: | Size: 431 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 434 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"input": "compact_guided_cylinder.step",
|
||||
"mode": "view",
|
||||
"outputs": [
|
||||
{
|
||||
"path": "review/iso.png",
|
||||
"camera": "iso"
|
||||
},
|
||||
{
|
||||
"path": "review/iso_opposite.png",
|
||||
"camera": {
|
||||
"direction": [
|
||||
-1,
|
||||
1,
|
||||
-0.8
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "review/top.png",
|
||||
"camera": "top"
|
||||
},
|
||||
{
|
||||
"path": "review/front.png",
|
||||
"camera": "front"
|
||||
}
|
||||
],
|
||||
"display": {
|
||||
"mode": "solid",
|
||||
"projection": "orthographic"
|
||||
},
|
||||
"render": {
|
||||
"viewLabels": true,
|
||||
"padding": 0.12,
|
||||
"sizeProfile": "diagnostic"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"request": "把明确提供的 015240.step 转换成可修改的 model-spec.json,并保留原始几何作为精确基体",
|
||||
"route": {
|
||||
"selected_backend": "build123d",
|
||||
"runner_skill": "cad",
|
||||
"project": "text-to-cad",
|
||||
"fallback_order": [
|
||||
"simplecadapi",
|
||||
"cadam"
|
||||
],
|
||||
"workflow_profiles": [],
|
||||
"operation": "modify",
|
||||
"source_mode": "exact_step_base",
|
||||
"modification_mode": "model_spec_parameter_edit"
|
||||
},
|
||||
"source": {
|
||||
"path": "source.step",
|
||||
"format": "step",
|
||||
"backend": "build123d",
|
||||
"sha256": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4",
|
||||
"immutable_exact_base": true
|
||||
},
|
||||
"artifacts": [
|
||||
{
|
||||
"path": "model-spec.json",
|
||||
"role": "editable_geometry_contract"
|
||||
},
|
||||
{
|
||||
"path": "imported_015240.step",
|
||||
"role": "primary"
|
||||
},
|
||||
{
|
||||
"path": "imported_015240.stl",
|
||||
"role": "secondary"
|
||||
},
|
||||
{
|
||||
"path": "source.step",
|
||||
"role": "immutable_exact_base"
|
||||
}
|
||||
],
|
||||
"parameters": {},
|
||||
"experience": {
|
||||
"library": "../../../../cad-experience-library/library.json",
|
||||
"context_kind": "generalized_cad_experience_query",
|
||||
"methods_applied": []
|
||||
},
|
||||
"assumptions": [
|
||||
"单位按 STEP 几何和导入工具约定记录为 mm。",
|
||||
"原 STEP 不包含可可靠恢复的原生草图、约束或特征历史;后续修改通过 model-spec.json 的命名参数和修改特征表达。",
|
||||
"“选中面厚度增加”按直接建模语义解释为源面 #f22 沿其 +Z 法向外移 1.0 mm。",
|
||||
"“红圈内的孔缩小”按目标水平板通孔从约 Ø4.8 mm 缩小到 Ø3.6 mm 处理。"
|
||||
],
|
||||
"validation": [
|
||||
{
|
||||
"check": "exact_base_sha256",
|
||||
"passed": true,
|
||||
"value": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4"
|
||||
},
|
||||
{
|
||||
"check": "exact_base_geometry_signature",
|
||||
"passed": true,
|
||||
"scope": "immutable_source.step",
|
||||
"size_mm": [
|
||||
26.200001497,
|
||||
18.906424738,
|
||||
28.2
|
||||
],
|
||||
"center_mm": [
|
||||
13.100000748,
|
||||
7.453212369,
|
||||
0.0
|
||||
],
|
||||
"solid_count": 1,
|
||||
"face_count": 31,
|
||||
"edge_count": 87,
|
||||
"volume_mm3": 2400.466496
|
||||
},
|
||||
{
|
||||
"check": "model_spec_parameterized_rebuild",
|
||||
"passed": true,
|
||||
"modified_size_mm": [
|
||||
26.200001497,
|
||||
18.906424738,
|
||||
29.2
|
||||
],
|
||||
"modified_center_mm": [
|
||||
13.100000748,
|
||||
7.453212369,
|
||||
0.5
|
||||
],
|
||||
"solid_count": 1,
|
||||
"volume_mm3": 2476.533646
|
||||
},
|
||||
{
|
||||
"check": "selected_face_f22_extension",
|
||||
"passed": true,
|
||||
"source_plane_z_mm": 14.1,
|
||||
"result_plane_z_mm": 15.1,
|
||||
"extension_mm": 1.0,
|
||||
"result_face": "#f27"
|
||||
},
|
||||
{
|
||||
"check": "circled_hole_resize",
|
||||
"passed": true,
|
||||
"center_mm": [
|
||||
5.9,
|
||||
11.006425,
|
||||
0.0
|
||||
],
|
||||
"axis": "Z",
|
||||
"source_diameter_mm": 4.799996,
|
||||
"result_diameter_mm": 3.6,
|
||||
"result_face": "#f22"
|
||||
},
|
||||
{
|
||||
"check": "source_to_modified_cad_diff",
|
||||
"passed": true,
|
||||
"geometry_changed": true,
|
||||
"size_delta_mm": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"center_delta_mm": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"check": "cad_inspect_refs_facts_planes_positioning",
|
||||
"passed": true
|
||||
},
|
||||
{
|
||||
"check": "snapshot_review",
|
||||
"passed": true,
|
||||
"views": [
|
||||
"snapshots/iso_20260723T085707Z.png",
|
||||
"snapshots/iso_opposite_20260723T085707Z.png",
|
||||
"snapshots/top_ortho_20260723T085707Z.png",
|
||||
"snapshots/front_ortho_20260723T085707Z.png"
|
||||
]
|
||||
}
|
||||
],
|
||||
"viewer_links": [
|
||||
"http://127.0.0.1:4179/?dir=%2FUsers%2Fjerry%2Flinkhand%2FCadSet%2Ftext-to-cad%2Fmodels%2Fcad-router-v1%2Fimported_015240&file=imported_015240.step"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"model_spec_kind": "parametric_cad_model",
|
||||
"model_id": "imported_015240",
|
||||
"units": "mm",
|
||||
"reconstruction": {
|
||||
"mode": "exact_step_base",
|
||||
"source": {
|
||||
"path": "source.step",
|
||||
"sha256": "55aee6a0ad74435503a1e7f108b32c9f403ae9838933e2be6728fb5b2f5cfbd4",
|
||||
"geometry_signature": {
|
||||
"size_mm": [
|
||||
26.200001497,
|
||||
18.906424738,
|
||||
28.2
|
||||
],
|
||||
"center_mm": [
|
||||
13.100000748,
|
||||
7.453212369,
|
||||
-0.0
|
||||
],
|
||||
"solid_count": 1,
|
||||
"face_count": 31,
|
||||
"edge_count": 87,
|
||||
"volume_mm3": 2400.466496
|
||||
}
|
||||
},
|
||||
"history_recovery": {
|
||||
"status": "not_present_in_step",
|
||||
"contract": "Preserve the imported B-Rep exactly as the immutable base; represent later edits as named parametric modifications."
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"selected_face_extension": {
|
||||
"value": 1.0,
|
||||
"unit": "mm",
|
||||
"role": "outward_offset_of_source_face_f22",
|
||||
"source_face": "#f22",
|
||||
"direction": "+Z"
|
||||
},
|
||||
"target_hole_diameter": {
|
||||
"value": 3.6,
|
||||
"unit": "mm",
|
||||
"role": "diameter_of_circled_horizontal_plate_hole",
|
||||
"source_diameter_mm": 4.799996,
|
||||
"axis": "Z"
|
||||
}
|
||||
},
|
||||
"modifications": [
|
||||
{
|
||||
"id": "extend_selected_face_f22",
|
||||
"operation": "add_box",
|
||||
"source_face": "#f22",
|
||||
"direction": "+Z",
|
||||
"center": [
|
||||
13.1,
|
||||
-1.0,
|
||||
{
|
||||
"parameter": "selected_face_extension",
|
||||
"scale": 0.5,
|
||||
"offset": 14.1
|
||||
}
|
||||
],
|
||||
"size": [
|
||||
22.2,
|
||||
2.0,
|
||||
{
|
||||
"parameter": "selected_face_extension"
|
||||
}
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "fill_original_circled_hole",
|
||||
"operation": "add_cylinder",
|
||||
"axis": "z",
|
||||
"center": [
|
||||
5.9,
|
||||
11.006425,
|
||||
0.0
|
||||
],
|
||||
"diameter": 4.799996,
|
||||
"length": 4.0,
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"id": "cut_resized_circled_hole",
|
||||
"operation": "cut_cylinder",
|
||||
"axis": "z",
|
||||
"center": [
|
||||
5.9,
|
||||
11.006425,
|
||||
0.0
|
||||
],
|
||||
"diameter": {
|
||||
"parameter": "target_hole_diameter"
|
||||
},
|
||||
"length": 6.0,
|
||||
"enabled": true
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"step": "imported_015240.step",
|
||||
"stl": "imported_015240.stl"
|
||||
},
|
||||
"validation": {
|
||||
"exact_base_required": true,
|
||||
"geometry_signature_tolerance_mm": 1e-07
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"input": "imported_015240.step",
|
||||
"mode": "view",
|
||||
"outputs": [
|
||||
{
|
||||
"path": "snapshots/iso.png",
|
||||
"camera": "iso"
|
||||
},
|
||||
{
|
||||
"path": "snapshots/iso_opposite.png",
|
||||
"camera": {
|
||||
"direction": [-1, 1, -0.8]
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "snapshots/top_ortho.png",
|
||||
"camera": "top"
|
||||
},
|
||||
{
|
||||
"path": "snapshots/front_ortho.png",
|
||||
"camera": "front"
|
||||
}
|
||||
],
|
||||
"render": {
|
||||
"viewLabels": true,
|
||||
"padding": 0.12,
|
||||
"sizeProfile": "diagnostic"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 299 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 329 KiB |
|
After Width: | Height: | Size: 328 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 69 KiB |
@@ -0,0 +1,372 @@
|
||||
{
|
||||
"ok": true,
|
||||
"tokens": [
|
||||
{
|
||||
"line": 1,
|
||||
"token": "#",
|
||||
"cadPath": "targeted_015133",
|
||||
"stepPath": "targeted_015133.step",
|
||||
"stepHash": "97f9763fe46700d0a23fa052b08c119e7352275b4e850cb8ed8c77f85e29f4c3",
|
||||
"summary": {
|
||||
"kind": "part",
|
||||
"occurrenceCount": 1,
|
||||
"leafOccurrenceCount": 1,
|
||||
"shapeCount": 1,
|
||||
"faceCount": 199,
|
||||
"edgeCount": 508,
|
||||
"vertexCount": 0,
|
||||
"bounds": {
|
||||
"min": [
|
||||
-106.75,
|
||||
-0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
106.75,
|
||||
78.5,
|
||||
38.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"selections": [],
|
||||
"warnings": [],
|
||||
"entryFacts": {
|
||||
"size": [
|
||||
213.5,
|
||||
78.5,
|
||||
76.0
|
||||
],
|
||||
"extentAxis": "x",
|
||||
"center": [
|
||||
0.0,
|
||||
39.25,
|
||||
0.0
|
||||
],
|
||||
"diag": 239.8343178112757,
|
||||
"kind": "part"
|
||||
},
|
||||
"planes": [
|
||||
{
|
||||
"axis": "y",
|
||||
"coordinate": 0.0,
|
||||
"normalSign": -1,
|
||||
"faceCount": 1,
|
||||
"totalArea": 12646.876863,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
-0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
0.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f85"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "y",
|
||||
"coordinate": 76.0,
|
||||
"normalSign": 1,
|
||||
"faceCount": 1,
|
||||
"totalArea": 12168.786533,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
76.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f59"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "z",
|
||||
"coordinate": -38.00000000000001,
|
||||
"normalSign": -1,
|
||||
"faceCount": 3,
|
||||
"totalArea": 11242.8,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
-38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f110",
|
||||
"f86",
|
||||
"f98"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "z",
|
||||
"coordinate": 38.00000000000001,
|
||||
"normalSign": 1,
|
||||
"faceCount": 3,
|
||||
"totalArea": 11242.8,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
0.0,
|
||||
38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f60",
|
||||
"f72",
|
||||
"f84"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "x",
|
||||
"coordinate": -106.75000000000001,
|
||||
"normalSign": -1,
|
||||
"faceCount": 5,
|
||||
"totalArea": 5013.75858,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-106.75,
|
||||
0.35,
|
||||
-37.65
|
||||
],
|
||||
"max": [
|
||||
-106.75,
|
||||
75.65,
|
||||
37.65
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f140",
|
||||
"f166",
|
||||
"f168",
|
||||
"f170",
|
||||
"f172"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "x",
|
||||
"coordinate": 106.75000000000001,
|
||||
"normalSign": 1,
|
||||
"faceCount": 5,
|
||||
"totalArea": 5013.75858,
|
||||
"bbox": {
|
||||
"min": [
|
||||
106.75,
|
||||
0.35,
|
||||
-37.65
|
||||
],
|
||||
"max": [
|
||||
106.75,
|
||||
75.65,
|
||||
37.65
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f158",
|
||||
"f160",
|
||||
"f162",
|
||||
"f164",
|
||||
"f55"
|
||||
]
|
||||
}
|
||||
],
|
||||
"entryPositioning": {
|
||||
"kind": "part",
|
||||
"bbox": {
|
||||
"min": [
|
||||
-106.75,
|
||||
-0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
106.75,
|
||||
78.5,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"bboxFacts": {
|
||||
"size": [
|
||||
213.5,
|
||||
78.5,
|
||||
76.0
|
||||
],
|
||||
"extentAxis": "x",
|
||||
"center": [
|
||||
0.0,
|
||||
39.25,
|
||||
0.0
|
||||
],
|
||||
"diag": 239.8343178112757
|
||||
},
|
||||
"majorPlanes": [
|
||||
{
|
||||
"axis": "y",
|
||||
"coordinate": 0.0,
|
||||
"normalSign": -1,
|
||||
"faceCount": 1,
|
||||
"totalArea": 12646.876863,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
-0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
0.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f85"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "y",
|
||||
"coordinate": 76.0,
|
||||
"normalSign": 1,
|
||||
"faceCount": 1,
|
||||
"totalArea": 12168.786533,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
76.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f59"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "z",
|
||||
"coordinate": -38.00000000000001,
|
||||
"normalSign": -1,
|
||||
"faceCount": 3,
|
||||
"totalArea": 11242.8,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
0.0,
|
||||
-38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
-38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f110",
|
||||
"f86",
|
||||
"f98"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "z",
|
||||
"coordinate": 38.00000000000001,
|
||||
"normalSign": 1,
|
||||
"faceCount": 3,
|
||||
"totalArea": 11242.8,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-86.75,
|
||||
0.0,
|
||||
38.0
|
||||
],
|
||||
"max": [
|
||||
86.75,
|
||||
76.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f60",
|
||||
"f72",
|
||||
"f84"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "x",
|
||||
"coordinate": -106.75000000000001,
|
||||
"normalSign": -1,
|
||||
"faceCount": 5,
|
||||
"totalArea": 5013.75858,
|
||||
"bbox": {
|
||||
"min": [
|
||||
-106.75,
|
||||
0.35,
|
||||
-37.65
|
||||
],
|
||||
"max": [
|
||||
-106.75,
|
||||
75.65,
|
||||
37.65
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f140",
|
||||
"f166",
|
||||
"f168",
|
||||
"f170",
|
||||
"f172"
|
||||
]
|
||||
},
|
||||
{
|
||||
"axis": "x",
|
||||
"coordinate": 106.75000000000001,
|
||||
"normalSign": 1,
|
||||
"faceCount": 5,
|
||||
"totalArea": 5013.75858,
|
||||
"bbox": {
|
||||
"min": [
|
||||
106.75,
|
||||
0.35,
|
||||
-37.65
|
||||
],
|
||||
"max": [
|
||||
106.75,
|
||||
75.65,
|
||||
37.65
|
||||
]
|
||||
},
|
||||
"selectors": [
|
||||
"f158",
|
||||
"f160",
|
||||
"f162",
|
||||
"f164",
|
||||
"f55"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"errors": []
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"model_spec_kind": "parametric_cad_model",
|
||||
"model_id": "targeted_015133",
|
||||
"units": "mm",
|
||||
"reconstruction": {
|
||||
"mode": "exact_step_base",
|
||||
"source": {
|
||||
"path": "source.step",
|
||||
"sha256": "97f9763fe46700d0a23fa052b08c119e7352275b4e850cb8ed8c77f85e29f4c3",
|
||||
"geometry_signature": {
|
||||
"size_mm": [
|
||||
213.5,
|
||||
78.5,
|
||||
76.0
|
||||
],
|
||||
"center_mm": [
|
||||
0.0,
|
||||
39.25,
|
||||
0.0
|
||||
],
|
||||
"solid_count": 1,
|
||||
"face_count": 199,
|
||||
"edge_count": 488,
|
||||
"volume_mm3": 1174741.621833
|
||||
}
|
||||
},
|
||||
"history_recovery": {
|
||||
"status": "not_present_in_step",
|
||||
"contract": "Preserve the imported B-Rep exactly as the immutable base; represent later edits as named parametric modifications."
|
||||
}
|
||||
},
|
||||
"parameters": {},
|
||||
"modifications": [],
|
||||
"outputs": {
|
||||
"step": "targeted_015133.step",
|
||||
"stl": "targeted_015133.stl"
|
||||
},
|
||||
"validation": {
|
||||
"exact_base_required": true,
|
||||
"geometry_signature_tolerance_mm": 1e-07
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"input": "targeted_015133.step",
|
||||
"mode": "view",
|
||||
"outputs": [
|
||||
{
|
||||
"path": "snapshot_iso.png",
|
||||
"camera": "iso"
|
||||
},
|
||||
{
|
||||
"path": "snapshot_iso_opposite.png",
|
||||
"camera": {
|
||||
"direction": [-1, 1, -0.8]
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "snapshot_top.png",
|
||||
"camera": "top"
|
||||
},
|
||||
{
|
||||
"path": "snapshot_front.png",
|
||||
"camera": "front"
|
||||
}
|
||||
],
|
||||
"render": {
|
||||
"viewLabels": true,
|
||||
"padding": 0.12,
|
||||
"sizeProfile": "diagnostic"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 316 KiB |
|
After Width: | Height: | Size: 475 KiB |
|
After Width: | Height: | Size: 78 KiB |
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: cad-router
|
||||
description: Route natural-language CAD, mechanical-part, assembly, 3D-printing, and robot-description requests across text-to-cad, SimpleCADAPI, and CADAM, then enforce shared generation, validation, requirement refinement, visual repair, and CAD Viewer handoff. Use for new or modified STEP/STP, SCAD, STL, 3MF, DXF, URDF, SRDF, SDF, or G-code work when the agent must choose the best modeling backend and preserve editable sources.
|
||||
description: Route natural-language CAD, mechanical-part, assembly, 3D-printing, and robot-description requests across text-to-cad, SimpleCADAPI, and CADAM; create one editable model-spec.json per part; convert supplied STEP/STP files into exact-base JSON-controlled models; and enforce generation, modification, validation, visual repair, and CAD Viewer handoff. Use for new or modified STEP/STP, SCAD, STL, 3MF, DXF, URDF, SRDF, SDF, or G-code work when the agent must choose a backend, preserve editable sources, edit JSON model parameters, or parameterize an imported STEP.
|
||||
---
|
||||
|
||||
# CAD Router
|
||||
@@ -24,7 +24,11 @@ Choose a backend before modeling, keep its source as the editable authority, and
|
||||
Add explicit signals when known: `--output step`, `--output stl`, `--manufacturing machining|printing|laser-cutting`, `--edit-source path/to/source`, `--existing-model`, `--assembly`, or `--browser-controls`.
|
||||
The router auto-discovers the workspace `cad-experience-library/library.json`,
|
||||
infers an experience family and feature roles from the request, and emits a
|
||||
backend-neutral `design_plan`. Use `--experience-library`,
|
||||
backend-neutral `design_plan`. When promoted `reconstruction_grammar`
|
||||
records match, instantiate their symbolic parameter roles, datum roles,
|
||||
canonical stages, and validation roles in the new part's
|
||||
`model-spec.json`; fill values from explicit user dimensions first and
|
||||
dimensionless distributions second. Use `--experience-library`,
|
||||
`--experience-family`, or repeated `--experience-feature` only to override
|
||||
those defaults.
|
||||
3. Follow the selected route unless a hard environmental constraint makes it unavailable. If overriding it, record the reason in the task manifest.
|
||||
@@ -33,7 +37,13 @@ Choose a backend before modeling, keep its source as the editable authority, and
|
||||
- `simplecadapi`: use an installed SimpleCADAPI Skill/runtime; prefer it for gears, racks, ring gears, bearings, cycloidal parts, reducers, replayable graphs, semantic tags, and FreeCAD interchange.
|
||||
- `cadam`: generate editable `.scad`, then execute `node scripts/cadam_compile.mjs <source.scad> <output.stl|output.dxf>`. Set `CADAM_ROOT` or pass `--cadam-root` when the CADAM checkout is not beside text-to-cad. Prefer it for fast parametric printable models, repeated patterns, decorative CSG, and parameter controls.
|
||||
5. When the route reports `requirement_refinement` or `visual_repair`, refine the brief, generate, execute, inspect multiple views, and repair no more than three visual mismatch rounds before asking the user.
|
||||
6. Generate into a task-owned directory and start CAD Viewer with that task directory as `--dir`; do not expose the repository-wide fixture library for a task review. Never change backend during an edit unless conversion is explicitly requested; modify the recorded source of truth.
|
||||
6. Generate into a task-owned directory and start CAD Viewer with that task directory as `--dir`; do not expose the repository-wide fixture library for a task review. Write one `model-spec.json` per part and treat it as the editable geometry contract; keep `cad-task.json` as the execution record. Read `references/model-spec.md` before creating, importing, or modifying a model specification. Never change backend during an edit unless conversion is explicitly requested; modify the recorded source of truth.
|
||||
For a supplied STEP/STP without an editable generator, create an
|
||||
`exact_step_base` specification with `scripts/model_spec.py import-step`.
|
||||
Preserve the copied STEP as the immutable exact B-Rep base and express later
|
||||
edits as named JSON parameters and modification features. Do not claim that
|
||||
arbitrary STEP recovers its original sketches, constraints, or feature
|
||||
history.
|
||||
For modification, prefer a native Python/SCAD generator. With STEP only,
|
||||
inspect the explicitly supplied model and perform the smallest local feature
|
||||
rebuild possible. For major topology changes, reconstruct from that model
|
||||
@@ -43,7 +53,8 @@ Choose a backend before modeling, keep its source as the editable authority, and
|
||||
`--experience-feature`; do not guess across family-scoped experience.
|
||||
7. Validate against the brief. STEP-first routes must run geometry inspection and snapshot review. Mesh routes must check compilation, bounds, solidity/manifold facts when available, and snapshot review.
|
||||
For experience-informed generation, record which generalized methods were
|
||||
applied. Validate against the user's brief and held-out targets, never against
|
||||
applied, including the reconstruction grammar ID and instantiated symbolic
|
||||
roles. Validate against the user's brief and held-out targets, never against
|
||||
parameters exposed from the same source case.
|
||||
8. Write a `cad-task.json` manifest using the contract in `references/backend-contract.md`. Hand supported artifacts to `$cad-viewer` and return its live links.
|
||||
|
||||
@@ -67,4 +78,6 @@ Choose a backend before modeling, keep its source as the editable authority, and
|
||||
|
||||
- Read `references/capabilities.md` when comparing or invoking backends.
|
||||
- Read `references/backend-contract.md` before writing a task manifest or adding another backend.
|
||||
- Read `references/model-spec.md` when generating per-part parameter JSON,
|
||||
converting STEP to a JSON-controlled model, or modifying an existing part.
|
||||
- `scripts/capabilities.json` is the machine-readable V1 registry used by `scripts/route.py`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
interface:
|
||||
display_name: "CAD Router"
|
||||
short_description: "Route CAD work across four complementary projects."
|
||||
default_prompt: "Use $cad-router to select and run the best CAD backend, validate the result, and open only the task outputs in CAD Viewer."
|
||||
short_description: "Route CAD and manage per-part JSON model specs."
|
||||
default_prompt: "Use $cad-router to select the best CAD backend, create or modify the part through its model-spec.json, validate the result, and open it in CAD Viewer."
|
||||
|
||||
@@ -72,6 +72,14 @@ Write `cad-task.json` beside the task artifacts with this minimum shape:
|
||||
```
|
||||
|
||||
Paths are relative to the task directory unless an external input must remain absolute.
|
||||
Do not store authoritative model parameters in `cad-task.json`. Every part must
|
||||
have a sibling `model-spec.json` that owns its parameter values, reconstruction
|
||||
mode, parametric modification layer, and output paths. See `model-spec.md`.
|
||||
|
||||
For an imported STEP, use `exact_step_base`: copy the explicitly supplied file
|
||||
into the task directory, record its checksum and geometry signature, and keep
|
||||
the imported B-Rep unchanged when the modification list is empty. This is an
|
||||
exact geometric base contract, not recovery of the original CAD feature tree.
|
||||
|
||||
## Backend adapter requirements
|
||||
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Per-part model specification
|
||||
|
||||
Use one `model-spec.json` per generated or imported part. Treat it as the
|
||||
editable geometry contract. Keep `cad-task.json` separate for routing,
|
||||
experience use, validation results, and viewer links.
|
||||
|
||||
## Native generator
|
||||
|
||||
Use `native_generator` when the part has editable Python source:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "1.0",
|
||||
"model_spec_kind": "parametric_cad_model",
|
||||
"model_id": "example_part",
|
||||
"units": "mm",
|
||||
"reconstruction": {
|
||||
"mode": "native_generator",
|
||||
"generator": {
|
||||
"path": "example_part.py",
|
||||
"entrypoint": "gen_step"
|
||||
}
|
||||
},
|
||||
"parameters": {
|
||||
"width": {
|
||||
"value": 40,
|
||||
"unit": "mm",
|
||||
"role": "overall_width"
|
||||
}
|
||||
},
|
||||
"modifications": [],
|
||||
"outputs": {
|
||||
"step": "example_part.step",
|
||||
"stl": "example_part.stl"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The generator must read its sibling `model-spec.json`. Do not duplicate
|
||||
authoritative parameter values as editable constants in both files.
|
||||
|
||||
## Imported STEP
|
||||
|
||||
Convert an explicitly supplied STEP into a task-local exact-base specification:
|
||||
|
||||
```bash
|
||||
python scripts/model_spec.py import-step input.step \
|
||||
--task-dir models/cad-router-v1/imported_part \
|
||||
--model-id imported_part
|
||||
```
|
||||
|
||||
This copies the source to `source.step`, records its SHA-256 and geometry
|
||||
signature, writes `model-spec.json`, and exports regenerated STEP/STL files.
|
||||
With an empty modification list, `verify` requires the imported B-Rep to retain
|
||||
the same bounds, center, topology counts, solid count, and volume.
|
||||
|
||||
STEP normally contains final B-Rep geometry, not the original sketches,
|
||||
constraints, names, or feature history. Therefore:
|
||||
|
||||
- `exact_step_base` guarantees an unchanged imported base, not recovered native
|
||||
design history.
|
||||
- Add later edits as named parametric modifications.
|
||||
- Use semantic native reconstruction only when the feature family can be
|
||||
reliably recognized and validate it independently against the source.
|
||||
- Never claim arbitrary STEP-to-JSON conversion recovers the author’s original
|
||||
parameters or construction order.
|
||||
|
||||
## Parametric modification layer
|
||||
|
||||
Supported V1 operations are `add_box`, `cut_box`, `add_cylinder`, and
|
||||
`cut_cylinder`. Values may be literals, direct parameter references, or affine
|
||||
parameter expressions:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "central_bore",
|
||||
"operation": "cut_cylinder",
|
||||
"axis": "z",
|
||||
"center": [0, 0, 0],
|
||||
"diameter": {"parameter": "bore_diameter"},
|
||||
"length": {"parameter": "bore_length"},
|
||||
"enabled": true
|
||||
}
|
||||
```
|
||||
|
||||
Use `scale` and `offset` when a tool center must stay anchored while its size
|
||||
changes. For example, a positive-Z face at `z = 14.1` can remain the lower face
|
||||
of an added box while `extension` changes:
|
||||
|
||||
```json
|
||||
{
|
||||
"center": [
|
||||
13.1,
|
||||
-1.0,
|
||||
{"parameter": "extension", "scale": 0.5, "offset": 14.1}
|
||||
],
|
||||
"size": [22.2, 2.0, {"parameter": "extension"}]
|
||||
}
|
||||
```
|
||||
|
||||
Change a named value and rebuild:
|
||||
|
||||
```bash
|
||||
python scripts/model_spec.py set path/to/model-spec.json bore_diameter 18
|
||||
python scripts/model_spec.py build path/to/model-spec.json
|
||||
python scripts/model_spec.py verify path/to/model-spec.json
|
||||
```
|
||||
|
||||
Use `apply_patch` when adding or structurally editing parameters and features so
|
||||
the modification remains reviewable. After every visible rebuild, run the CAD
|
||||
inspection and snapshot workflow and update `cad-task.json`.
|
||||
@@ -0,0 +1,632 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Create and execute per-part JSON model specifications.
|
||||
|
||||
Two reconstruction modes are supported:
|
||||
|
||||
* ``native_generator`` loads a task-local Python generator. The generator reads
|
||||
the same model-spec.json, so named parameter edits remain the source of truth.
|
||||
* ``exact_step_base`` imports an immutable task-local STEP file and applies a
|
||||
parametric modification layer. With no modifications, the imported B-Rep is
|
||||
preserved geometrically even though the exported STEP text may differ.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import importlib.util
|
||||
import json
|
||||
import math
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from build123d import (
|
||||
Align,
|
||||
Box,
|
||||
Cylinder,
|
||||
Part,
|
||||
Pos,
|
||||
export_step,
|
||||
export_stl,
|
||||
import_step,
|
||||
)
|
||||
|
||||
|
||||
SCHEMA_VERSION = "1.0"
|
||||
MODEL_SPEC_KIND = "parametric_cad_model"
|
||||
MODEL_ID_RE = re.compile(r"[^a-zA-Z0-9._-]+")
|
||||
SUPPORTED_OPERATIONS = {
|
||||
"add_box",
|
||||
"cut_box",
|
||||
"add_cylinder",
|
||||
"cut_cylinder",
|
||||
}
|
||||
|
||||
|
||||
class ModelSpecError(ValueError):
|
||||
"""Raised when a model specification is invalid or unsafe to execute."""
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise ModelSpecError(f"Cannot read model spec {path}: {exc}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise ModelSpecError("Model spec must be a JSON object")
|
||||
return value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = path.with_suffix(path.suffix + ".tmp")
|
||||
temporary.write_text(
|
||||
json.dumps(value, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
temporary.replace(path)
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _safe_model_id(value: str) -> str:
|
||||
result = MODEL_ID_RE.sub("_", value.strip()).strip("._-")
|
||||
if not result:
|
||||
raise ModelSpecError("model_id must contain a letter or number")
|
||||
return result
|
||||
|
||||
|
||||
def _resolve_relative(spec_path: Path, value: str, field: str) -> Path:
|
||||
candidate = Path(value)
|
||||
if candidate.is_absolute():
|
||||
raise ModelSpecError(f"{field} must be task-relative, not absolute")
|
||||
return (spec_path.parent / candidate).resolve()
|
||||
|
||||
|
||||
def _parameter_value(spec: dict[str, Any], name: str) -> float:
|
||||
parameters = spec.get("parameters", {})
|
||||
entry = parameters.get(name) if isinstance(parameters, dict) else None
|
||||
if not isinstance(entry, dict) or "value" not in entry:
|
||||
raise ModelSpecError(f"Unknown parameter reference: {name}")
|
||||
value = entry["value"]
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
raise ModelSpecError(f"Parameter {name} must contain a numeric value")
|
||||
if not math.isfinite(float(value)):
|
||||
raise ModelSpecError(f"Parameter {name} must be finite")
|
||||
return float(value)
|
||||
|
||||
|
||||
def resolve_number(spec: dict[str, Any], value: Any, field: str) -> float:
|
||||
if isinstance(value, bool):
|
||||
raise ModelSpecError(f"{field} must be numeric")
|
||||
if isinstance(value, (int, float)):
|
||||
result = float(value)
|
||||
elif isinstance(value, dict) and "parameter" in value:
|
||||
unexpected = set(value) - {"parameter", "scale", "offset"}
|
||||
if unexpected:
|
||||
raise ModelSpecError(
|
||||
f"{field} parameter expression has unsupported keys: "
|
||||
+ ", ".join(sorted(unexpected))
|
||||
)
|
||||
scale = value.get("scale", 1.0)
|
||||
offset = value.get("offset", 0.0)
|
||||
if (
|
||||
isinstance(scale, bool)
|
||||
or not isinstance(scale, (int, float))
|
||||
or isinstance(offset, bool)
|
||||
or not isinstance(offset, (int, float))
|
||||
):
|
||||
raise ModelSpecError(f"{field} scale and offset must be numeric")
|
||||
result = (
|
||||
_parameter_value(spec, str(value["parameter"])) * float(scale)
|
||||
+ float(offset)
|
||||
)
|
||||
else:
|
||||
raise ModelSpecError(
|
||||
f"{field} must be a number or a parameter expression"
|
||||
)
|
||||
if not math.isfinite(result):
|
||||
raise ModelSpecError(f"{field} must be finite")
|
||||
return result
|
||||
|
||||
|
||||
def _vector3(spec: dict[str, Any], value: Any, field: str) -> tuple[float, float, float]:
|
||||
if not isinstance(value, list) or len(value) != 3:
|
||||
raise ModelSpecError(f"{field} must contain three values")
|
||||
return tuple(
|
||||
resolve_number(spec, item, f"{field}[{index}]")
|
||||
for index, item in enumerate(value)
|
||||
)
|
||||
|
||||
|
||||
def validate_model_spec(spec: dict[str, Any]) -> None:
|
||||
if spec.get("schema_version") != SCHEMA_VERSION:
|
||||
raise ModelSpecError(f"Unsupported model spec schema: {spec.get('schema_version')}")
|
||||
if spec.get("model_spec_kind") != MODEL_SPEC_KIND:
|
||||
raise ModelSpecError("Not a parametric CAD model specification")
|
||||
_safe_model_id(str(spec.get("model_id", "")))
|
||||
if spec.get("units") != "mm":
|
||||
raise ModelSpecError("V1 model specs use millimetres")
|
||||
|
||||
reconstruction = spec.get("reconstruction")
|
||||
if not isinstance(reconstruction, dict):
|
||||
raise ModelSpecError("reconstruction must be an object")
|
||||
mode = reconstruction.get("mode")
|
||||
if mode not in {"native_generator", "exact_step_base"}:
|
||||
raise ModelSpecError(f"Unsupported reconstruction mode: {mode}")
|
||||
if mode == "native_generator":
|
||||
generator = reconstruction.get("generator")
|
||||
if not isinstance(generator, dict) or not generator.get("path"):
|
||||
raise ModelSpecError("native_generator requires generator.path")
|
||||
else:
|
||||
source = reconstruction.get("source")
|
||||
if (
|
||||
not isinstance(source, dict)
|
||||
or not source.get("path")
|
||||
or not source.get("sha256")
|
||||
):
|
||||
raise ModelSpecError("exact_step_base requires source.path and source.sha256")
|
||||
|
||||
parameters = spec.get("parameters", {})
|
||||
if not isinstance(parameters, dict):
|
||||
raise ModelSpecError("parameters must be an object")
|
||||
for name, entry in parameters.items():
|
||||
if not isinstance(entry, dict) or "value" not in entry:
|
||||
raise ModelSpecError(f"Parameter {name} must be an object with value")
|
||||
_parameter_value(spec, name)
|
||||
|
||||
modifications = spec.get("modifications", [])
|
||||
if not isinstance(modifications, list):
|
||||
raise ModelSpecError("modifications must be an array")
|
||||
seen_ids: set[str] = set()
|
||||
for index, modification in enumerate(modifications):
|
||||
if not isinstance(modification, dict):
|
||||
raise ModelSpecError(f"modifications[{index}] must be an object")
|
||||
feature_id = str(modification.get("id", "")).strip()
|
||||
if not feature_id or feature_id in seen_ids:
|
||||
raise ModelSpecError("Every modification requires a unique id")
|
||||
seen_ids.add(feature_id)
|
||||
operation = modification.get("operation")
|
||||
if operation not in SUPPORTED_OPERATIONS:
|
||||
raise ModelSpecError(f"Unsupported modification operation: {operation}")
|
||||
|
||||
outputs = spec.get("outputs")
|
||||
if not isinstance(outputs, dict) or not outputs.get("step"):
|
||||
raise ModelSpecError("outputs.step is required")
|
||||
|
||||
|
||||
def geometry_facts(shape: Any) -> dict[str, Any]:
|
||||
box = shape.bounding_box()
|
||||
solids = list(shape.solids())
|
||||
volume = sum(float(solid.volume) for solid in solids)
|
||||
return {
|
||||
"size_mm": [
|
||||
round(float(box.size.X), 9),
|
||||
round(float(box.size.Y), 9),
|
||||
round(float(box.size.Z), 9),
|
||||
],
|
||||
"center_mm": [
|
||||
round(float(box.center().X), 9),
|
||||
round(float(box.center().Y), 9),
|
||||
round(float(box.center().Z), 9),
|
||||
],
|
||||
"solid_count": len(solids),
|
||||
"face_count": len(shape.faces()),
|
||||
"edge_count": len(shape.edges()),
|
||||
"volume_mm3": round(volume, 6),
|
||||
}
|
||||
|
||||
|
||||
def _rotation_for_axis(axis: str) -> tuple[float, float, float]:
|
||||
normalized = axis.lower()
|
||||
if normalized == "z":
|
||||
return (0.0, 0.0, 0.0)
|
||||
if normalized == "x":
|
||||
return (0.0, 90.0, 0.0)
|
||||
if normalized == "y":
|
||||
return (-90.0, 0.0, 0.0)
|
||||
raise ModelSpecError(f"Unsupported cylinder axis: {axis}")
|
||||
|
||||
|
||||
def _make_tool(spec: dict[str, Any], modification: dict[str, Any]) -> Any:
|
||||
operation = str(modification["operation"])
|
||||
center = _vector3(spec, modification.get("center", [0, 0, 0]), "center")
|
||||
if operation.endswith("_cylinder"):
|
||||
diameter = resolve_number(spec, modification.get("diameter"), "diameter")
|
||||
length = resolve_number(spec, modification.get("length"), "length")
|
||||
if diameter <= 0 or length <= 0:
|
||||
raise ModelSpecError("Cylinder diameter and length must be positive")
|
||||
tool = Cylinder(
|
||||
diameter / 2.0,
|
||||
length,
|
||||
align=(Align.CENTER, Align.CENTER, Align.CENTER),
|
||||
rotation=_rotation_for_axis(str(modification.get("axis", "z"))),
|
||||
)
|
||||
else:
|
||||
size = _vector3(spec, modification.get("size"), "size")
|
||||
if any(item <= 0 for item in size):
|
||||
raise ModelSpecError("Box dimensions must be positive")
|
||||
tool = Box(
|
||||
*size,
|
||||
align=(Align.CENTER, Align.CENTER, Align.CENTER),
|
||||
)
|
||||
return Pos(*center) * tool
|
||||
|
||||
|
||||
def _apply_modifications(shape: Any, spec: dict[str, Any]) -> Any:
|
||||
result = shape
|
||||
for modification in spec.get("modifications", []):
|
||||
if modification.get("enabled", True) is False:
|
||||
continue
|
||||
tool = _make_tool(spec, modification)
|
||||
operation = modification["operation"]
|
||||
result = result + tool if operation.startswith("add_") else result - tool
|
||||
return result
|
||||
|
||||
|
||||
def _load_generator(spec_path: Path, reconstruction: dict[str, Any]) -> Any:
|
||||
generator = reconstruction["generator"]
|
||||
source_path = _resolve_relative(spec_path, str(generator["path"]), "generator.path")
|
||||
if not source_path.is_file():
|
||||
raise ModelSpecError(f"Generator does not exist: {source_path}")
|
||||
module_spec = importlib.util.spec_from_file_location(
|
||||
f"cad_model_{hash(source_path)}",
|
||||
source_path,
|
||||
)
|
||||
if module_spec is None or module_spec.loader is None:
|
||||
raise ModelSpecError(f"Cannot load generator: {source_path}")
|
||||
module = importlib.util.module_from_spec(module_spec)
|
||||
module_spec.loader.exec_module(module)
|
||||
entrypoint = str(generator.get("entrypoint", "gen_step"))
|
||||
function = getattr(module, entrypoint, None)
|
||||
if not callable(function):
|
||||
raise ModelSpecError(f"Generator has no callable {entrypoint}: {source_path}")
|
||||
return function()
|
||||
|
||||
|
||||
def build_shape(spec_path: Path, spec: dict[str, Any] | None = None) -> Any:
|
||||
resolved_spec = spec_path.expanduser().resolve()
|
||||
payload = spec or _read_json(resolved_spec)
|
||||
validate_model_spec(payload)
|
||||
reconstruction = payload["reconstruction"]
|
||||
if reconstruction["mode"] == "native_generator":
|
||||
shape = _load_generator(resolved_spec, reconstruction)
|
||||
else:
|
||||
source = reconstruction["source"]
|
||||
source_path = _resolve_relative(resolved_spec, str(source["path"]), "source.path")
|
||||
if not source_path.is_file():
|
||||
raise ModelSpecError(f"Exact STEP base does not exist: {source_path}")
|
||||
actual_hash = _sha256(source_path)
|
||||
if actual_hash != source["sha256"]:
|
||||
raise ModelSpecError(
|
||||
"Exact STEP base checksum changed; import it again instead of "
|
||||
"silently rebuilding from a different source"
|
||||
)
|
||||
imported = import_step(source_path)
|
||||
# build123d's STEP importer returns a topology wrapper that is readable
|
||||
# and boolean-capable but is not always directly accepted by its STEP
|
||||
# exporter. Normalize it to a Part while preserving the wrapped B-Rep.
|
||||
shape = Part(imported.wrapped)
|
||||
return _apply_modifications(shape, payload)
|
||||
|
||||
|
||||
def build_model(spec_path: Path) -> dict[str, Any]:
|
||||
resolved_spec = spec_path.expanduser().resolve()
|
||||
payload = _read_json(resolved_spec)
|
||||
validate_model_spec(payload)
|
||||
outputs = payload["outputs"]
|
||||
step_path = _resolve_relative(resolved_spec, str(outputs["step"]), "outputs.step")
|
||||
step_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
reconstruction = payload["reconstruction"]
|
||||
stl_value = outputs.get("stl")
|
||||
stl_path = (
|
||||
_resolve_relative(resolved_spec, str(stl_value), "outputs.stl")
|
||||
if stl_value
|
||||
else None
|
||||
)
|
||||
|
||||
if reconstruction["mode"] == "native_generator":
|
||||
generator_path = _resolve_relative(
|
||||
resolved_spec,
|
||||
str(reconstruction["generator"]["path"]),
|
||||
"generator.path",
|
||||
)
|
||||
if generator_path.with_suffix(".step").resolve() != step_path:
|
||||
raise ModelSpecError(
|
||||
"V1 native generator output must use the generator basename"
|
||||
)
|
||||
cad_step = Path(__file__).resolve().parents[2] / "cad" / "scripts" / "step"
|
||||
command = [sys.executable, str(cad_step), str(generator_path), "--force"]
|
||||
if stl_path is not None:
|
||||
if stl_path.parent != generator_path.parent:
|
||||
raise ModelSpecError(
|
||||
"V1 native generator STL must remain in the task directory"
|
||||
)
|
||||
command.extend(["--stl", stl_path.name])
|
||||
completed = subprocess.run(
|
||||
command,
|
||||
cwd=resolved_spec.parent,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
)
|
||||
if completed.returncode != 0:
|
||||
raise ModelSpecError(
|
||||
"CAD generator failed: "
|
||||
+ (completed.stderr.strip() or completed.stdout.strip())
|
||||
)
|
||||
if not step_path.is_file():
|
||||
raise ModelSpecError(f"CAD generator did not write {step_path}")
|
||||
else:
|
||||
shape = build_shape(resolved_spec, payload)
|
||||
source_path = _resolve_relative(
|
||||
resolved_spec,
|
||||
str(reconstruction["source"]["path"]),
|
||||
"source.path",
|
||||
)
|
||||
if not payload.get("modifications"):
|
||||
# Empty exact-base rebuilds preserve the STEP byte-for-byte.
|
||||
if source_path != step_path:
|
||||
shutil.copy2(source_path, step_path)
|
||||
else:
|
||||
export_step(shape, step_path)
|
||||
if stl_path is not None:
|
||||
stl_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
export_stl(shape, stl_path)
|
||||
|
||||
exported_shape = Part(import_step(step_path).wrapped)
|
||||
result: dict[str, Any] = {
|
||||
"model_spec": str(resolved_spec),
|
||||
"step": str(step_path),
|
||||
"facts": geometry_facts(exported_shape),
|
||||
}
|
||||
if stl_path is not None:
|
||||
result["stl"] = str(stl_path)
|
||||
return result
|
||||
|
||||
|
||||
def import_step_model(
|
||||
source_step: Path,
|
||||
task_dir: Path,
|
||||
model_id: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
source = source_step.expanduser().resolve()
|
||||
if not source.is_file() or source.suffix.lower() not in {".step", ".stp"}:
|
||||
raise ModelSpecError(f"Input must be an existing STEP/STP file: {source}")
|
||||
target_dir = task_dir.expanduser().resolve()
|
||||
target_dir.mkdir(parents=True, exist_ok=True)
|
||||
normalized_id = _safe_model_id(model_id or source.stem)
|
||||
copied_source = target_dir / "source.step"
|
||||
if copied_source.resolve() != source:
|
||||
shutil.copy2(source, copied_source)
|
||||
source_shape = import_step(copied_source)
|
||||
source_facts = geometry_facts(source_shape)
|
||||
spec_path = target_dir / "model-spec.json"
|
||||
payload: dict[str, Any] = {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"model_spec_kind": MODEL_SPEC_KIND,
|
||||
"model_id": normalized_id,
|
||||
"units": "mm",
|
||||
"reconstruction": {
|
||||
"mode": "exact_step_base",
|
||||
"source": {
|
||||
"path": "source.step",
|
||||
"sha256": _sha256(copied_source),
|
||||
"geometry_signature": source_facts,
|
||||
},
|
||||
"history_recovery": {
|
||||
"status": "not_present_in_step",
|
||||
"contract": (
|
||||
"Preserve the imported B-Rep exactly as the immutable base; "
|
||||
"represent later edits as named parametric modifications."
|
||||
),
|
||||
},
|
||||
},
|
||||
"parameters": {},
|
||||
"modifications": [],
|
||||
"outputs": {
|
||||
"step": f"{normalized_id}.step",
|
||||
"stl": f"{normalized_id}.stl",
|
||||
},
|
||||
"validation": {
|
||||
"exact_base_required": True,
|
||||
"geometry_signature_tolerance_mm": 1e-7,
|
||||
},
|
||||
}
|
||||
_write_json(spec_path, payload)
|
||||
result = build_model(spec_path)
|
||||
result["source"] = str(copied_source)
|
||||
result["source_sha256"] = payload["reconstruction"]["source"]["sha256"]
|
||||
return result
|
||||
|
||||
|
||||
def _numbers_close(first: Any, second: Any, tolerance: float) -> bool:
|
||||
if isinstance(first, list) and isinstance(second, list):
|
||||
return len(first) == len(second) and all(
|
||||
_numbers_close(left, right, tolerance)
|
||||
for left, right in zip(first, second)
|
||||
)
|
||||
if isinstance(first, (int, float)) and isinstance(second, (int, float)):
|
||||
return math.isclose(
|
||||
float(first),
|
||||
float(second),
|
||||
rel_tol=tolerance,
|
||||
abs_tol=tolerance,
|
||||
)
|
||||
return first == second
|
||||
|
||||
|
||||
def verify_model(spec_path: Path) -> dict[str, Any]:
|
||||
resolved_spec = spec_path.expanduser().resolve()
|
||||
payload = _read_json(resolved_spec)
|
||||
shape = build_shape(resolved_spec, payload)
|
||||
facts = geometry_facts(shape)
|
||||
reconstruction = payload["reconstruction"]
|
||||
result: dict[str, Any] = {
|
||||
"model_spec": str(resolved_spec),
|
||||
"valid": True,
|
||||
"facts": facts,
|
||||
"checks": [],
|
||||
}
|
||||
if reconstruction["mode"] == "exact_step_base" and not payload.get("modifications"):
|
||||
expected = reconstruction["source"].get("geometry_signature", {})
|
||||
source_path = _resolve_relative(
|
||||
resolved_spec,
|
||||
str(reconstruction["source"]["path"]),
|
||||
"source.path",
|
||||
)
|
||||
output_path = _resolve_relative(
|
||||
resolved_spec,
|
||||
str(payload["outputs"]["step"]),
|
||||
"outputs.step",
|
||||
)
|
||||
output_shape = (
|
||||
Part(import_step(output_path).wrapped)
|
||||
if output_path.is_file()
|
||||
else None
|
||||
)
|
||||
output_facts = geometry_facts(output_shape) if output_shape is not None else {}
|
||||
tolerance = float(
|
||||
payload.get("validation", {}).get(
|
||||
"geometry_signature_tolerance_mm",
|
||||
1e-7,
|
||||
)
|
||||
)
|
||||
signature_exact = all(
|
||||
_numbers_close(expected.get(key), facts.get(key), tolerance)
|
||||
for key in (
|
||||
"size_mm",
|
||||
"center_mm",
|
||||
"solid_count",
|
||||
"face_count",
|
||||
"edge_count",
|
||||
"volume_mm3",
|
||||
)
|
||||
)
|
||||
exported_exact = (
|
||||
output_path.is_file()
|
||||
and _sha256(output_path) == _sha256(source_path)
|
||||
and all(
|
||||
_numbers_close(expected.get(key), output_facts.get(key), tolerance)
|
||||
for key in (
|
||||
"size_mm",
|
||||
"center_mm",
|
||||
"solid_count",
|
||||
"face_count",
|
||||
"edge_count",
|
||||
"volume_mm3",
|
||||
)
|
||||
)
|
||||
)
|
||||
exact = signature_exact and exported_exact
|
||||
result["checks"].append(
|
||||
{
|
||||
"check": "exact_base_geometry_signature",
|
||||
"passed": exact,
|
||||
"expected": expected,
|
||||
"actual": facts,
|
||||
"exported": output_facts,
|
||||
"byte_identical_step": (
|
||||
output_path.is_file()
|
||||
and _sha256(output_path) == _sha256(source_path)
|
||||
),
|
||||
}
|
||||
)
|
||||
result["valid"] = exact
|
||||
else:
|
||||
result["checks"].append(
|
||||
{
|
||||
"check": "model_spec_execution",
|
||||
"passed": True,
|
||||
"detail": "The parameterized model spec executed successfully.",
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def set_parameter(spec_path: Path, name: str, value: float) -> dict[str, Any]:
|
||||
resolved_spec = spec_path.expanduser().resolve()
|
||||
payload = _read_json(resolved_spec)
|
||||
validate_model_spec(payload)
|
||||
parameters = payload["parameters"]
|
||||
if name not in parameters:
|
||||
raise ModelSpecError(
|
||||
f"Unknown parameter {name}; add a named parameter and feature binding first"
|
||||
)
|
||||
parameters[name]["value"] = value
|
||||
validate_model_spec(payload)
|
||||
_write_json(resolved_spec, payload)
|
||||
return {
|
||||
"model_spec": str(resolved_spec),
|
||||
"parameter": name,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Create, build, modify, and verify per-part model-spec.json files."
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
import_parser = subparsers.add_parser(
|
||||
"import-step",
|
||||
help="Create an exact STEP-base model spec in a task-owned directory.",
|
||||
)
|
||||
import_parser.add_argument("source_step", type=Path)
|
||||
import_parser.add_argument("--task-dir", type=Path, required=True)
|
||||
import_parser.add_argument("--model-id")
|
||||
|
||||
build_command = subparsers.add_parser(
|
||||
"build",
|
||||
help="Regenerate STEP/STL from a model specification.",
|
||||
)
|
||||
build_command.add_argument("model_spec", type=Path)
|
||||
|
||||
verify_command = subparsers.add_parser(
|
||||
"verify",
|
||||
help="Validate a model specification and exact-base signature.",
|
||||
)
|
||||
verify_command.add_argument("model_spec", type=Path)
|
||||
|
||||
set_command = subparsers.add_parser(
|
||||
"set",
|
||||
help="Change one existing named parameter in a model specification.",
|
||||
)
|
||||
set_command.add_argument("model_spec", type=Path)
|
||||
set_command.add_argument("name")
|
||||
set_command.add_argument("value", type=float)
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
try:
|
||||
if args.command == "import-step":
|
||||
result = import_step_model(args.source_step, args.task_dir, args.model_id)
|
||||
elif args.command == "build":
|
||||
result = build_model(args.model_spec)
|
||||
elif args.command == "verify":
|
||||
result = verify_model(args.model_spec)
|
||||
else:
|
||||
result = set_parameter(args.model_spec, args.name, args.value)
|
||||
except ModelSpecError as exc:
|
||||
print(json.dumps({"error": str(exc)}, ensure_ascii=False, indent=2))
|
||||
return 2
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
if args.command == "verify" and result.get("valid") is False:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -76,6 +76,11 @@ EXPERIENCE_FAMILY_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("shaft", ("shaft", "轴类", "传动轴")),
|
||||
("flange", ("flange", "法兰")),
|
||||
("bushing", ("bushing", "sleeve", "轴套", "衬套")),
|
||||
("plate", ("plate", "mounting plate", "安装板", "平板")),
|
||||
("bracket", ("bracket", "mounting bracket", "支架", "托架")),
|
||||
("block", ("block", "rectangular body", "方块", "块体")),
|
||||
("manifold", ("manifold", "valve body", "housing", "歧管", "阀体", "壳体")),
|
||||
("rotational_part", ("roller", "pulley", "cylindrical body", "滚轮", "带轮")),
|
||||
)
|
||||
|
||||
EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
@@ -90,8 +95,90 @@ EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("conical_transition", ("cone", "taper", "锥", "斜面过渡")),
|
||||
("gear_teeth", ("gear", "teeth", "齿轮", "齿")),
|
||||
("coaxial_cylindrical_stack", ("stepped shaft", "阶梯轴", "同轴", "轴肩")),
|
||||
(
|
||||
"repeated_axial_hole_pattern",
|
||||
(
|
||||
"mounting holes",
|
||||
"corner holes",
|
||||
"four holes",
|
||||
"multiple holes",
|
||||
"bolt holes",
|
||||
"安装孔",
|
||||
"四个孔",
|
||||
"多个孔",
|
||||
),
|
||||
),
|
||||
("planar_dominant_body", ("plate", "bracket", "flat body", "板", "支架")),
|
||||
(
|
||||
"multi_level_planar_profile",
|
||||
("pocket", "recess", "stepped plate", "台阶面", "凹槽", "沉台"),
|
||||
),
|
||||
(
|
||||
"rotational_body",
|
||||
("cylindrical body", "roller", "pulley", "revolved", "圆柱体", "滚轮", "回转体"),
|
||||
),
|
||||
(
|
||||
"stepped_rotational_profile",
|
||||
("stepped shaft", "multiple diameters", "shoulder", "阶梯轴", "多段直径", "轴肩"),
|
||||
),
|
||||
("shaft_like_body", ("shaft", "axle", "rod", "轴", "长杆")),
|
||||
(
|
||||
"flange_like_rotational_body",
|
||||
("flange", "flanged", "法兰", "带法兰"),
|
||||
),
|
||||
(
|
||||
"cylindrical_feature_network",
|
||||
("bore", "passage", "drilled holes", "孔系", "流道", "钻孔"),
|
||||
),
|
||||
(
|
||||
"multi_axis_passage",
|
||||
("cross hole", "intersecting passage", "multi-axis", "交叉孔", "多轴孔"),
|
||||
),
|
||||
)
|
||||
|
||||
# The router vocabulary is request-facing (plate, bracket, flange), while the
|
||||
# distilled library vocabulary is geometry-facing (patterned_plate,
|
||||
# plate_or_bracket, flanged_rotational_part). Keep the query semantic by
|
||||
# expanding a request family into compatible generalized scopes; this never
|
||||
# uses held-out case geometry or instance parameters.
|
||||
EXPERIENCE_FAMILY_SCOPE_ALIASES: dict[str, set[str]] = {
|
||||
"flanged_hub_adapter": {
|
||||
"flanged_hub_adapter",
|
||||
"flanged_rotational_part",
|
||||
"hollow_or_stepped_shaft",
|
||||
"rotational_part",
|
||||
},
|
||||
"connecting_rod": {
|
||||
"connecting_rod",
|
||||
"patterned_prismatic_part",
|
||||
"plate_or_bracket",
|
||||
},
|
||||
"gear": {"gear", "rotational_part"},
|
||||
"shaft": {"shaft", "hollow_or_stepped_shaft", "rotational_part"},
|
||||
"flange": {"flange", "flanged_rotational_part", "rotational_part"},
|
||||
"bushing": {"bushing", "hollow_or_stepped_shaft", "rotational_part"},
|
||||
"plate": {"plate", "patterned_plate", "plate_or_bracket"},
|
||||
"bracket": {
|
||||
"bracket",
|
||||
"plate_or_bracket",
|
||||
"patterned_plate",
|
||||
"patterned_prismatic_part",
|
||||
},
|
||||
"block": {"block", "prismatic_block", "patterned_prismatic_part"},
|
||||
"manifold": {
|
||||
"manifold",
|
||||
"multi_axis_manifold",
|
||||
"prismatic_block",
|
||||
"patterned_prismatic_part",
|
||||
},
|
||||
"rotational_part": {
|
||||
"rotational_part",
|
||||
"shaft",
|
||||
"hollow_or_stepped_shaft",
|
||||
"flanged_rotational_part",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def contains_any(text: str, terms: tuple[str, ...]) -> bool:
|
||||
return any(term in text for term in terms)
|
||||
@@ -263,7 +350,10 @@ def classify_edit_context(
|
||||
suffix = source.suffix.lower() if source else ""
|
||||
text = request.lower()
|
||||
|
||||
if suffix in {".py", ".scad", ".js", ".mjs"}:
|
||||
if suffix == ".json" and source and source.name == "model-spec.json":
|
||||
mode = "model_spec_parameter_edit"
|
||||
reason = "A per-part model specification exists; update its named JSON parameters or modification features."
|
||||
elif suffix in {".py", ".scad", ".js", ".mjs"}:
|
||||
mode = "native_parameter_edit"
|
||||
reason = "An editable generator exists; update its named parameters or feature source."
|
||||
elif contains_any(text, MAJOR_EDIT_TERMS):
|
||||
@@ -304,7 +394,7 @@ def classify_edit_context(
|
||||
"preservation_contract": [
|
||||
"Preserve the source coordinate frame unless the request explicitly changes it.",
|
||||
"Preserve unmodified interfaces, datums, and feature relationships.",
|
||||
"Prefer the native generator over inferred STEP reconstruction when both exist.",
|
||||
"Prefer the per-part model-spec.json and native generator over inferred STEP reconstruction when they exist.",
|
||||
"Use generalized experience only as method guidance, never as replacement source geometry.",
|
||||
],
|
||||
}
|
||||
@@ -355,13 +445,20 @@ def load_experience_library(
|
||||
if item.strip()
|
||||
}
|
||||
normalized_family = family.strip().lower().replace(" ", "_") if family else None
|
||||
compatible_scopes = (
|
||||
EXPERIENCE_FAMILY_SCOPE_ALIASES.get(
|
||||
normalized_family, {normalized_family}
|
||||
)
|
||||
if normalized_family
|
||||
else set()
|
||||
)
|
||||
selected: list[object] = []
|
||||
for item in payload.get("experiences", []):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
scope = set(item.get("scope", []))
|
||||
if "global" not in scope:
|
||||
if not normalized_family or normalized_family not in scope:
|
||||
if not compatible_scopes or scope.isdisjoint(compatible_scopes):
|
||||
continue
|
||||
required = set(item.get("when", {}).get("features", []))
|
||||
if required and not required.issubset(requested_features):
|
||||
@@ -371,6 +468,7 @@ def load_experience_library(
|
||||
"schema_version": "2.0",
|
||||
"context_kind": "generalized_cad_experience_query",
|
||||
"family": normalized_family,
|
||||
"compatible_scopes": sorted(compatible_scopes),
|
||||
"requested_features": sorted(requested_features),
|
||||
"experiences": selected,
|
||||
"policy": {
|
||||
@@ -480,6 +578,12 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
|
||||
registry = json.loads(REGISTRY_PATH.read_text(encoding="utf-8"))
|
||||
backend_info = registry["backends"][selected.name]
|
||||
source_of_truth = backend_info["source_of_truth"]
|
||||
if (
|
||||
edit_context
|
||||
and edit_context.get("modification_mode") == "model_spec_parameter_edit"
|
||||
):
|
||||
source_of_truth = "per-part model-spec.json with a backend generator"
|
||||
gap = selected.score - max((scores[name].score for name in scores if name != selected.name), default=0)
|
||||
confidence = "high" if gap >= 40 else "medium" if gap >= 15 else "low"
|
||||
result: dict[str, object] = {
|
||||
@@ -498,7 +602,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
"workflow_profiles": profiles,
|
||||
"downstream_skills": downstream,
|
||||
"requested_outputs": outputs,
|
||||
"source_of_truth": backend_info["source_of_truth"],
|
||||
"source_of_truth": source_of_truth,
|
||||
"experience_context": experience_context,
|
||||
"design_plan": {
|
||||
"plan_kind": "backend_neutral_parametric_design_plan",
|
||||
@@ -506,7 +610,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
"part_family": experience_family or "unclassified",
|
||||
"requested_feature_roles": sorted(set(experience_features)),
|
||||
"selected_backend": selected.name,
|
||||
"source_of_truth": backend_info["source_of_truth"],
|
||||
"source_of_truth": source_of_truth,
|
||||
"generalized_methods": [
|
||||
{
|
||||
key: item[key]
|
||||
@@ -517,6 +621,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
"check",
|
||||
"repair",
|
||||
"confidence",
|
||||
"reconstruction_grammar",
|
||||
)
|
||||
if key in item
|
||||
}
|
||||
@@ -527,6 +632,17 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
)
|
||||
if isinstance(item, dict)
|
||||
],
|
||||
"reconstruction_grammars": [
|
||||
item["reconstruction_grammar"]
|
||||
for item in (
|
||||
experience_context.get("experiences", [])
|
||||
if experience_context
|
||||
else []
|
||||
)
|
||||
if isinstance(item, dict)
|
||||
and item.get("kind") == "reconstruction_grammar"
|
||||
and isinstance(item.get("reconstruction_grammar"), dict)
|
||||
],
|
||||
"experience_status": (
|
||||
"pending_source_classification"
|
||||
if needs_source_classification
|
||||
@@ -566,9 +682,10 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
if edit_context
|
||||
else [
|
||||
"Resolve explicit user dimensions and preserve them as authoritative parameters.",
|
||||
"Instantiate matched reconstruction grammars as symbolic model-spec.json parameters, datums, and canonical feature stages; never copy teacher dimensions.",
|
||||
"Translate requested feature roles and matched generalized methods into backend operations.",
|
||||
"Generate the editable source of truth and STEP-first artifact when requested.",
|
||||
"Validate topology, dimensions, feature relationships, and export integrity.",
|
||||
"Validate topology, dimensions, feature relationships, surface mix, and export integrity against the grammar validation roles.",
|
||||
]
|
||||
),
|
||||
},
|
||||
@@ -631,7 +748,7 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
parser.add_argument(
|
||||
"--edit-source",
|
||||
type=Path,
|
||||
help="Existing generator, STEP/STP, or private case JSON to modify.",
|
||||
help="Existing model-spec.json, generator, or explicit STEP/STP to modify; private case JSON is forbidden.",
|
||||
)
|
||||
parser.add_argument("--assembly", action="store_true", help="The requested result is an assembly.")
|
||||
parser.add_argument("--browser-controls", action="store_true", help="Interactive browser parameter controls are required.")
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from build123d import Box, Cylinder, Pos, export_step, import_step
|
||||
|
||||
|
||||
SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "model_spec.py"
|
||||
MODULE_SPEC = importlib.util.spec_from_file_location("cad_router_model_spec", SCRIPT_PATH)
|
||||
assert MODULE_SPEC is not None and MODULE_SPEC.loader is not None
|
||||
MODEL_SPEC = importlib.util.module_from_spec(MODULE_SPEC)
|
||||
MODULE_SPEC.loader.exec_module(MODEL_SPEC)
|
||||
|
||||
|
||||
class ModelSpecTests(unittest.TestCase):
|
||||
def test_affine_parameter_expression(self) -> None:
|
||||
payload = {
|
||||
"parameters": {
|
||||
"extension": {
|
||||
"value": 2.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
self.assertEqual(
|
||||
MODEL_SPEC.resolve_number(
|
||||
payload,
|
||||
{
|
||||
"parameter": "extension",
|
||||
"scale": 0.5,
|
||||
"offset": 14.1,
|
||||
},
|
||||
"center[2]",
|
||||
),
|
||||
15.1,
|
||||
)
|
||||
|
||||
def test_import_step_exact_base_round_trip(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
source = root / "teacher.step"
|
||||
export_step(Box(30, 20, 10) - Pos(0, 0, -1) * Cylinder(3, 12), source)
|
||||
|
||||
result = MODEL_SPEC.import_step_model(
|
||||
source,
|
||||
root / "task",
|
||||
"round_trip",
|
||||
)
|
||||
verification = MODEL_SPEC.verify_model(
|
||||
Path(result["model_spec"])
|
||||
)
|
||||
|
||||
self.assertTrue(verification["valid"])
|
||||
self.assertEqual(
|
||||
verification["checks"][0]["check"],
|
||||
"exact_base_geometry_signature",
|
||||
)
|
||||
self.assertTrue((root / "task" / "round_trip.step").is_file())
|
||||
self.assertTrue((root / "task" / "round_trip.stl").is_file())
|
||||
|
||||
def test_parameterized_cut_rebuilds_from_json(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
source = root / "source.step"
|
||||
export_step(Box(40, 30, 12), source)
|
||||
imported = MODEL_SPEC.import_step_model(
|
||||
source,
|
||||
root / "task",
|
||||
"parameterized_cut",
|
||||
)
|
||||
spec_path = Path(imported["model_spec"])
|
||||
payload = json.loads(spec_path.read_text(encoding="utf-8"))
|
||||
payload["parameters"] = {
|
||||
"bore_diameter": {
|
||||
"value": 6,
|
||||
"unit": "mm",
|
||||
"role": "central_bore_diameter",
|
||||
},
|
||||
"bore_length": {
|
||||
"value": 16,
|
||||
"unit": "mm",
|
||||
"role": "through_cut_length",
|
||||
},
|
||||
}
|
||||
payload["modifications"] = [
|
||||
{
|
||||
"id": "central_bore",
|
||||
"operation": "cut_cylinder",
|
||||
"axis": "z",
|
||||
"center": [0, 0, 0],
|
||||
"diameter": {"parameter": "bore_diameter"},
|
||||
"length": {"parameter": "bore_length"},
|
||||
"enabled": True,
|
||||
}
|
||||
]
|
||||
spec_path.write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
first = MODEL_SPEC.build_model(spec_path)
|
||||
first_volume = first["facts"]["volume_mm3"]
|
||||
MODEL_SPEC.set_parameter(spec_path, "bore_diameter", 12)
|
||||
second = MODEL_SPEC.build_model(spec_path)
|
||||
|
||||
self.assertLess(second["facts"]["volume_mm3"], first_volume)
|
||||
rebuilt = import_step(second["step"])
|
||||
self.assertGreater(len(rebuilt.faces()), 0)
|
||||
|
||||
def test_exact_base_rejects_changed_source(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
root = Path(temporary)
|
||||
source = root / "source.step"
|
||||
export_step(Box(10, 10, 10), source)
|
||||
imported = MODEL_SPEC.import_step_model(
|
||||
source,
|
||||
root / "task",
|
||||
"checksum_guard",
|
||||
)
|
||||
export_step(Box(12, 10, 10), root / "task" / "source.step")
|
||||
|
||||
with self.assertRaises(MODEL_SPEC.ModelSpecError):
|
||||
MODEL_SPEC.build_model(Path(imported["model_spec"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -227,6 +227,115 @@ class CadRouterTests(unittest.TestCase):
|
||||
plan["generalized_methods"][0]["id"],
|
||||
)
|
||||
|
||||
def test_design_plan_exposes_promoted_reconstruction_grammar(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"experiences": [
|
||||
{
|
||||
"id": "reconstruction.flanged_hub_adapter.primary",
|
||||
"kind": "reconstruction_grammar",
|
||||
"scope": ["flanged_hub_adapter"],
|
||||
"when": {"features": ["base_flange", "hollow_sleeve"]},
|
||||
"guidance": "Establish shared datums before composing the envelope.",
|
||||
"reconstruction_grammar": {
|
||||
"parameter_roles": ["overall_long_span"],
|
||||
"datum_roles": ["part_center", "primary_axis"],
|
||||
"feature_roles": ["base_flange", "hollow_sleeve"],
|
||||
"relation_roles": [],
|
||||
"canonical_stages": [
|
||||
{
|
||||
"id": "establish_reference_frame",
|
||||
"operation": "define_datums",
|
||||
"feature_roles": [],
|
||||
"reference_roles": ["part_center", "primary_axis"],
|
||||
}
|
||||
],
|
||||
"cardinality_classes": ["cylinder_dense"],
|
||||
"validation_roles": ["closed_solid"],
|
||||
},
|
||||
"support": 30,
|
||||
"confidence": 0.9,
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"生成一个带法兰轴套",
|
||||
"--output",
|
||||
"step",
|
||||
"--experience-library",
|
||||
str(library),
|
||||
)
|
||||
)
|
||||
grammars = result["design_plan"]["reconstruction_grammars"]
|
||||
self.assertEqual(1, len(grammars))
|
||||
self.assertEqual(["overall_long_span"], grammars[0]["parameter_roles"])
|
||||
|
||||
def test_request_family_matches_geometry_facing_distilled_scope(self) -> None:
|
||||
payload = {
|
||||
"schema_version": "2.0",
|
||||
"library_kind": "generalized_cad_experience",
|
||||
"policy": {
|
||||
"instance_parameters_allowed": False,
|
||||
"absolute_coordinates_allowed": False,
|
||||
"single_case_promotion_allowed": False,
|
||||
"llm_semantic_review_required": True,
|
||||
"draft_evidence_verified": True,
|
||||
"router_consumable": True,
|
||||
},
|
||||
"experiences": [
|
||||
{
|
||||
"id": "motif.patterned_plate.holes",
|
||||
"kind": "feature_motif",
|
||||
"scope": ["patterned_plate"],
|
||||
"when": {
|
||||
"features": [
|
||||
"planar_dominant_body",
|
||||
"repeated_axial_hole_pattern",
|
||||
]
|
||||
},
|
||||
"guidance": "Build the plate first, then place holes from symmetry.",
|
||||
"support": 40,
|
||||
"confidence": 0.94,
|
||||
}
|
||||
],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
library = Path(directory) / "library.json"
|
||||
library.write_text(json.dumps(payload), encoding="utf-8")
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
"Generate a mounting plate with four corner holes",
|
||||
"--output",
|
||||
"step",
|
||||
"--experience-library",
|
||||
str(library),
|
||||
)
|
||||
)
|
||||
context = result["experience_context"]
|
||||
self.assertEqual("plate", context["family"])
|
||||
self.assertIn("patterned_plate", context["compatible_scopes"])
|
||||
self.assertIn("planar_dominant_body", context["requested_features"])
|
||||
self.assertIn(
|
||||
"repeated_axial_hole_pattern", context["requested_features"]
|
||||
)
|
||||
self.assertEqual(
|
||||
["motif.patterned_plate.holes"],
|
||||
[item["id"] for item in context["experiences"]],
|
||||
)
|
||||
|
||||
def test_step_edit_uses_source_and_library_not_private_case(self) -> None:
|
||||
result = route_module.route(
|
||||
self.parse(
|
||||
|
||||