新增cdsl

This commit is contained in:
2026-08-24 14:52:08 +08:00
parent fd8c0c37ad
commit 9f8a55ff07
3892 changed files with 2872721 additions and 9169 deletions
+1
View File
@@ -33,6 +33,7 @@ build/
# Runtime data and generated local artifacts
backend/data/
json_to_cdsl/input/
# Logs
*.log
@@ -0,0 +1,33 @@
{
"built_count": 4,
"compiled_count": 4,
"complete": true,
"completed_count": 4,
"failure_category_counts": {
"geometry_mismatch": 4
},
"geometry_verified_count": 0,
"input_directory": "json_to_cdsl/output",
"part_count": 4,
"results": [
{
"part_id": "050123",
"report": "parts/050123.report.json"
},
{
"part_id": "068453",
"report": "parts/068453.report.json"
},
{
"part_id": "070825",
"report": "parts/070825.report.json"
},
{
"part_id": "115738",
"report": "parts/115738.report.json"
}
],
"runtime_eligible_count": 4,
"schema": "cdsl.engine.batch-rebuild.v1",
"semantic_valid_count": 4
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,998 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
FILE_NAME('Open CASCADE Shape Model','2026-08-24T10:12:48',('Author'),(
'Open CASCADE'),'Open CASCADE STEP processor 7.9','build123d',
'Unknown');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
ENDSEC;
DATA;
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2000,#2);
#2 = APPLICATION_CONTEXT(
'core data for automotive mechanical design processes');
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
#5 = PRODUCT_DEFINITION('design','',#6,#9);
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
#7 = PRODUCT('SOLID','SOLID','',(#8));
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#833);
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
#12 = CARTESIAN_POINT('',(0.,0.,0.));
#13 = DIRECTION('',(0.,0.,1.));
#14 = DIRECTION('',(1.,0.,-0.));
#15 = MANIFOLD_SOLID_BREP('',#16);
#16 = CLOSED_SHELL('',(#17,#147,#396,#467,#516,#570,#619,#668,#717,#766,
#819,#826));
#17 = ADVANCED_FACE('',(#18),#32,.F.);
#18 = FACE_BOUND('',#19,.F.);
#19 = EDGE_LOOP('',(#20,#55,#88,#116));
#20 = ORIENTED_EDGE('',*,*,#21,.T.);
#21 = EDGE_CURVE('',#22,#24,#26,.T.);
#22 = VERTEX_POINT('',#23);
#23 = CARTESIAN_POINT('',(-6.,6.5,0.));
#24 = VERTEX_POINT('',#25);
#25 = CARTESIAN_POINT('',(-6.,6.5,470.));
#26 = SURFACE_CURVE('',#27,(#31,#43),.PCURVE_S1.);
#27 = LINE('',#28,#29);
#28 = CARTESIAN_POINT('',(-6.,6.5,0.));
#29 = VECTOR('',#30,1.);
#30 = DIRECTION('',(0.,0.,1.));
#31 = PCURVE('',#32,#37);
#32 = CYLINDRICAL_SURFACE('',#33,1.25);
#33 = AXIS2_PLACEMENT_3D('',#34,#35,#36);
#34 = CARTESIAN_POINT('',(-6.,5.25,0.));
#35 = DIRECTION('',(0.,0.,-1.));
#36 = DIRECTION('',(0.,1.,0.));
#37 = DEFINITIONAL_REPRESENTATION('',(#38),#42);
#38 = LINE('',#39,#40);
#39 = CARTESIAN_POINT('',(0.,0.));
#40 = VECTOR('',#41,1.);
#41 = DIRECTION('',(0.,-1.));
#42 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#43 = PCURVE('',#44,#49);
#44 = PLANE('',#45);
#45 = AXIS2_PLACEMENT_3D('',#46,#47,#48);
#46 = CARTESIAN_POINT('',(-6.,7.5,0.));
#47 = DIRECTION('',(1.,0.,-0.));
#48 = DIRECTION('',(0.,-1.,0.));
#49 = DEFINITIONAL_REPRESENTATION('',(#50),#54);
#50 = LINE('',#51,#52);
#51 = CARTESIAN_POINT('',(1.,0.));
#52 = VECTOR('',#53,1.);
#53 = DIRECTION('',(0.,-1.));
#54 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#55 = ORIENTED_EDGE('',*,*,#56,.T.);
#56 = EDGE_CURVE('',#24,#57,#59,.T.);
#57 = VERTEX_POINT('',#58);
#58 = CARTESIAN_POINT('',(-6.,4.,470.));
#59 = SURFACE_CURVE('',#60,(#65,#72),.PCURVE_S1.);
#60 = CIRCLE('',#61,1.25);
#61 = AXIS2_PLACEMENT_3D('',#62,#63,#64);
#62 = CARTESIAN_POINT('',(-6.,5.25,470.));
#63 = DIRECTION('',(0.,0.,-1.));
#64 = DIRECTION('',(0.,1.,0.));
#65 = PCURVE('',#32,#66);
#66 = DEFINITIONAL_REPRESENTATION('',(#67),#71);
#67 = LINE('',#68,#69);
#68 = CARTESIAN_POINT('',(0.,-470.));
#69 = VECTOR('',#70,1.);
#70 = DIRECTION('',(1.,0.));
#71 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#72 = PCURVE('',#73,#78);
#73 = PLANE('',#74);
#74 = AXIS2_PLACEMENT_3D('',#75,#76,#77);
#75 = CARTESIAN_POINT('',(3.473329554175E-16,3.897876544724,470.));
#76 = DIRECTION('',(0.,0.,1.));
#77 = DIRECTION('',(1.,0.,-0.));
#78 = DEFINITIONAL_REPRESENTATION('',(#79),#87);
#79 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#80,#81,#82,#83,#84,#85,#86),
.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#80 = CARTESIAN_POINT('',(-6.,2.602123455276));
#81 = CARTESIAN_POINT('',(-3.834936490539,2.602123455276));
#82 = CARTESIAN_POINT('',(-4.917468245269,0.727123455276));
#83 = CARTESIAN_POINT('',(-6.,-1.147876544724));
#84 = CARTESIAN_POINT('',(-7.082531754731,0.727123455276));
#85 = CARTESIAN_POINT('',(-8.165063509461,2.602123455276));
#86 = CARTESIAN_POINT('',(-6.,2.602123455276));
#87 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#88 = ORIENTED_EDGE('',*,*,#89,.F.);
#89 = EDGE_CURVE('',#90,#57,#92,.T.);
#90 = VERTEX_POINT('',#91);
#91 = CARTESIAN_POINT('',(-6.,4.,0.));
#92 = SURFACE_CURVE('',#93,(#97,#104),.PCURVE_S1.);
#93 = LINE('',#94,#95);
#94 = CARTESIAN_POINT('',(-6.,4.,0.));
#95 = VECTOR('',#96,1.);
#96 = DIRECTION('',(0.,0.,1.));
#97 = PCURVE('',#32,#98);
#98 = DEFINITIONAL_REPRESENTATION('',(#99),#103);
#99 = LINE('',#100,#101);
#100 = CARTESIAN_POINT('',(3.14159265359,0.));
#101 = VECTOR('',#102,1.);
#102 = DIRECTION('',(0.,-1.));
#103 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#104 = PCURVE('',#105,#110);
#105 = PLANE('',#106);
#106 = AXIS2_PLACEMENT_3D('',#107,#108,#109);
#107 = CARTESIAN_POINT('',(-6.,4.,0.));
#108 = DIRECTION('',(1.,0.,-0.));
#109 = DIRECTION('',(0.,-1.,0.));
#110 = DEFINITIONAL_REPRESENTATION('',(#111),#115);
#111 = LINE('',#112,#113);
#112 = CARTESIAN_POINT('',(0.,0.));
#113 = VECTOR('',#114,1.);
#114 = DIRECTION('',(0.,-1.));
#115 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#116 = ORIENTED_EDGE('',*,*,#117,.F.);
#117 = EDGE_CURVE('',#22,#90,#118,.T.);
#118 = SURFACE_CURVE('',#119,(#124,#131),.PCURVE_S1.);
#119 = CIRCLE('',#120,1.25);
#120 = AXIS2_PLACEMENT_3D('',#121,#122,#123);
#121 = CARTESIAN_POINT('',(-6.,5.25,0.));
#122 = DIRECTION('',(0.,0.,-1.));
#123 = DIRECTION('',(0.,1.,0.));
#124 = PCURVE('',#32,#125);
#125 = DEFINITIONAL_REPRESENTATION('',(#126),#130);
#126 = LINE('',#127,#128);
#127 = CARTESIAN_POINT('',(0.,0.));
#128 = VECTOR('',#129,1.);
#129 = DIRECTION('',(1.,0.));
#130 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#131 = PCURVE('',#132,#137);
#132 = PLANE('',#133);
#133 = AXIS2_PLACEMENT_3D('',#134,#135,#136);
#134 = CARTESIAN_POINT('',(3.473329554175E-16,3.897876544724,0.));
#135 = DIRECTION('',(0.,0.,1.));
#136 = DIRECTION('',(1.,0.,-0.));
#137 = DEFINITIONAL_REPRESENTATION('',(#138),#146);
#138 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#139,#140,#141,#142,#143,#144
,#145),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#139 = CARTESIAN_POINT('',(-6.,2.602123455276));
#140 = CARTESIAN_POINT('',(-3.834936490539,2.602123455276));
#141 = CARTESIAN_POINT('',(-4.917468245269,0.727123455276));
#142 = CARTESIAN_POINT('',(-6.,-1.147876544724));
#143 = CARTESIAN_POINT('',(-7.082531754731,0.727123455276));
#144 = CARTESIAN_POINT('',(-8.165063509461,2.602123455276));
#145 = CARTESIAN_POINT('',(-6.,2.602123455276));
#146 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#147 = ADVANCED_FACE('',(#148),#73,.T.);
#148 = FACE_BOUND('',#149,.T.);
#149 = EDGE_LOOP('',(#150,#151,#174,#202,#230,#258,#286,#319,#347,#375)
);
#150 = ORIENTED_EDGE('',*,*,#56,.T.);
#151 = ORIENTED_EDGE('',*,*,#152,.T.);
#152 = EDGE_CURVE('',#57,#153,#155,.T.);
#153 = VERTEX_POINT('',#154);
#154 = CARTESIAN_POINT('',(-6.,0.5,470.));
#155 = SURFACE_CURVE('',#156,(#160,#167),.PCURVE_S1.);
#156 = LINE('',#157,#158);
#157 = CARTESIAN_POINT('',(-6.,4.,470.));
#158 = VECTOR('',#159,1.);
#159 = DIRECTION('',(0.,-1.,0.));
#160 = PCURVE('',#73,#161);
#161 = DEFINITIONAL_REPRESENTATION('',(#162),#166);
#162 = LINE('',#163,#164);
#163 = CARTESIAN_POINT('',(-6.,0.102123455276));
#164 = VECTOR('',#165,1.);
#165 = DIRECTION('',(0.,-1.));
#166 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#167 = PCURVE('',#105,#168);
#168 = DEFINITIONAL_REPRESENTATION('',(#169),#173);
#169 = LINE('',#170,#171);
#170 = CARTESIAN_POINT('',(0.,-470.));
#171 = VECTOR('',#172,1.);
#172 = DIRECTION('',(1.,0.));
#173 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#174 = ORIENTED_EDGE('',*,*,#175,.T.);
#175 = EDGE_CURVE('',#153,#176,#178,.T.);
#176 = VERTEX_POINT('',#177);
#177 = CARTESIAN_POINT('',(-5.5,0.,470.));
#178 = SURFACE_CURVE('',#179,(#183,#190),.PCURVE_S1.);
#179 = LINE('',#180,#181);
#180 = CARTESIAN_POINT('',(-6.,0.5,470.));
#181 = VECTOR('',#182,1.);
#182 = DIRECTION('',(0.707106781187,-0.707106781187,0.));
#183 = PCURVE('',#73,#184);
#184 = DEFINITIONAL_REPRESENTATION('',(#185),#189);
#185 = LINE('',#186,#187);
#186 = CARTESIAN_POINT('',(-6.,-3.397876544724));
#187 = VECTOR('',#188,1.);
#188 = DIRECTION('',(0.707106781187,-0.707106781187));
#189 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#190 = PCURVE('',#191,#196);
#191 = PLANE('',#192);
#192 = AXIS2_PLACEMENT_3D('',#193,#194,#195);
#193 = CARTESIAN_POINT('',(-6.,0.5,0.));
#194 = DIRECTION('',(0.707106781187,0.707106781187,-0.));
#195 = DIRECTION('',(0.707106781187,-0.707106781187,0.));
#196 = DEFINITIONAL_REPRESENTATION('',(#197),#201);
#197 = LINE('',#198,#199);
#198 = CARTESIAN_POINT('',(0.,-470.));
#199 = VECTOR('',#200,1.);
#200 = DIRECTION('',(1.,0.));
#201 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#202 = ORIENTED_EDGE('',*,*,#203,.T.);
#203 = EDGE_CURVE('',#176,#204,#206,.T.);
#204 = VERTEX_POINT('',#205);
#205 = CARTESIAN_POINT('',(5.5,0.,470.));
#206 = SURFACE_CURVE('',#207,(#211,#218),.PCURVE_S1.);
#207 = LINE('',#208,#209);
#208 = CARTESIAN_POINT('',(-5.5,0.,470.));
#209 = VECTOR('',#210,1.);
#210 = DIRECTION('',(1.,0.,0.));
#211 = PCURVE('',#73,#212);
#212 = DEFINITIONAL_REPRESENTATION('',(#213),#217);
#213 = LINE('',#214,#215);
#214 = CARTESIAN_POINT('',(-5.5,-3.897876544724));
#215 = VECTOR('',#216,1.);
#216 = DIRECTION('',(1.,0.));
#217 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#218 = PCURVE('',#219,#224);
#219 = PLANE('',#220);
#220 = AXIS2_PLACEMENT_3D('',#221,#222,#223);
#221 = CARTESIAN_POINT('',(-5.5,0.,0.));
#222 = DIRECTION('',(0.,1.,0.));
#223 = DIRECTION('',(1.,0.,0.));
#224 = DEFINITIONAL_REPRESENTATION('',(#225),#229);
#225 = LINE('',#226,#227);
#226 = CARTESIAN_POINT('',(0.,-470.));
#227 = VECTOR('',#228,1.);
#228 = DIRECTION('',(1.,0.));
#229 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#230 = ORIENTED_EDGE('',*,*,#231,.T.);
#231 = EDGE_CURVE('',#204,#232,#234,.T.);
#232 = VERTEX_POINT('',#233);
#233 = CARTESIAN_POINT('',(6.,0.5,470.));
#234 = SURFACE_CURVE('',#235,(#239,#246),.PCURVE_S1.);
#235 = LINE('',#236,#237);
#236 = CARTESIAN_POINT('',(5.5,0.,470.));
#237 = VECTOR('',#238,1.);
#238 = DIRECTION('',(0.707106781187,0.707106781187,0.));
#239 = PCURVE('',#73,#240);
#240 = DEFINITIONAL_REPRESENTATION('',(#241),#245);
#241 = LINE('',#242,#243);
#242 = CARTESIAN_POINT('',(5.5,-3.897876544724));
#243 = VECTOR('',#244,1.);
#244 = DIRECTION('',(0.707106781187,0.707106781187));
#245 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#246 = PCURVE('',#247,#252);
#247 = PLANE('',#248);
#248 = AXIS2_PLACEMENT_3D('',#249,#250,#251);
#249 = CARTESIAN_POINT('',(5.5,0.,0.));
#250 = DIRECTION('',(-0.707106781187,0.707106781187,0.));
#251 = DIRECTION('',(0.707106781187,0.707106781187,0.));
#252 = DEFINITIONAL_REPRESENTATION('',(#253),#257);
#253 = LINE('',#254,#255);
#254 = CARTESIAN_POINT('',(0.,-470.));
#255 = VECTOR('',#256,1.);
#256 = DIRECTION('',(1.,0.));
#257 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#258 = ORIENTED_EDGE('',*,*,#259,.T.);
#259 = EDGE_CURVE('',#232,#260,#262,.T.);
#260 = VERTEX_POINT('',#261);
#261 = CARTESIAN_POINT('',(6.,4.,470.));
#262 = SURFACE_CURVE('',#263,(#267,#274),.PCURVE_S1.);
#263 = LINE('',#264,#265);
#264 = CARTESIAN_POINT('',(6.,0.5,470.));
#265 = VECTOR('',#266,1.);
#266 = DIRECTION('',(0.,1.,0.));
#267 = PCURVE('',#73,#268);
#268 = DEFINITIONAL_REPRESENTATION('',(#269),#273);
#269 = LINE('',#270,#271);
#270 = CARTESIAN_POINT('',(6.,-3.397876544724));
#271 = VECTOR('',#272,1.);
#272 = DIRECTION('',(0.,1.));
#273 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#274 = PCURVE('',#275,#280);
#275 = PLANE('',#276);
#276 = AXIS2_PLACEMENT_3D('',#277,#278,#279);
#277 = CARTESIAN_POINT('',(6.,0.5,0.));
#278 = DIRECTION('',(-1.,0.,0.));
#279 = DIRECTION('',(0.,1.,0.));
#280 = DEFINITIONAL_REPRESENTATION('',(#281),#285);
#281 = LINE('',#282,#283);
#282 = CARTESIAN_POINT('',(0.,-470.));
#283 = VECTOR('',#284,1.);
#284 = DIRECTION('',(1.,0.));
#285 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#286 = ORIENTED_EDGE('',*,*,#287,.T.);
#287 = EDGE_CURVE('',#260,#288,#290,.T.);
#288 = VERTEX_POINT('',#289);
#289 = CARTESIAN_POINT('',(6.,6.5,470.));
#290 = SURFACE_CURVE('',#291,(#296,#307),.PCURVE_S1.);
#291 = CIRCLE('',#292,1.25);
#292 = AXIS2_PLACEMENT_3D('',#293,#294,#295);
#293 = CARTESIAN_POINT('',(6.,5.25,470.));
#294 = DIRECTION('',(0.,0.,-1.));
#295 = DIRECTION('',(0.,-1.,0.));
#296 = PCURVE('',#73,#297);
#297 = DEFINITIONAL_REPRESENTATION('',(#298),#306);
#298 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#299,#300,#301,#302,#303,#304
,#305),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#299 = CARTESIAN_POINT('',(6.,0.102123455276));
#300 = CARTESIAN_POINT('',(3.834936490539,0.102123455276));
#301 = CARTESIAN_POINT('',(4.917468245269,1.977123455276));
#302 = CARTESIAN_POINT('',(6.,3.852123455276));
#303 = CARTESIAN_POINT('',(7.082531754731,1.977123455276));
#304 = CARTESIAN_POINT('',(8.165063509461,0.102123455276));
#305 = CARTESIAN_POINT('',(6.,0.102123455276));
#306 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#307 = PCURVE('',#308,#313);
#308 = CYLINDRICAL_SURFACE('',#309,1.25);
#309 = AXIS2_PLACEMENT_3D('',#310,#311,#312);
#310 = CARTESIAN_POINT('',(6.,5.25,0.));
#311 = DIRECTION('',(0.,0.,-1.));
#312 = DIRECTION('',(0.,-1.,0.));
#313 = DEFINITIONAL_REPRESENTATION('',(#314),#318);
#314 = LINE('',#315,#316);
#315 = CARTESIAN_POINT('',(0.,-470.));
#316 = VECTOR('',#317,1.);
#317 = DIRECTION('',(1.,0.));
#318 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#319 = ORIENTED_EDGE('',*,*,#320,.T.);
#320 = EDGE_CURVE('',#288,#321,#323,.T.);
#321 = VERTEX_POINT('',#322);
#322 = CARTESIAN_POINT('',(6.,7.5,470.));
#323 = SURFACE_CURVE('',#324,(#328,#335),.PCURVE_S1.);
#324 = LINE('',#325,#326);
#325 = CARTESIAN_POINT('',(6.,6.5,470.));
#326 = VECTOR('',#327,1.);
#327 = DIRECTION('',(0.,1.,0.));
#328 = PCURVE('',#73,#329);
#329 = DEFINITIONAL_REPRESENTATION('',(#330),#334);
#330 = LINE('',#331,#332);
#331 = CARTESIAN_POINT('',(6.,2.602123455276));
#332 = VECTOR('',#333,1.);
#333 = DIRECTION('',(0.,1.));
#334 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#335 = PCURVE('',#336,#341);
#336 = PLANE('',#337);
#337 = AXIS2_PLACEMENT_3D('',#338,#339,#340);
#338 = CARTESIAN_POINT('',(6.,6.5,0.));
#339 = DIRECTION('',(-1.,0.,0.));
#340 = DIRECTION('',(0.,1.,0.));
#341 = DEFINITIONAL_REPRESENTATION('',(#342),#346);
#342 = LINE('',#343,#344);
#343 = CARTESIAN_POINT('',(0.,-470.));
#344 = VECTOR('',#345,1.);
#345 = DIRECTION('',(1.,0.));
#346 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#347 = ORIENTED_EDGE('',*,*,#348,.T.);
#348 = EDGE_CURVE('',#321,#349,#351,.T.);
#349 = VERTEX_POINT('',#350);
#350 = CARTESIAN_POINT('',(-6.,7.5,470.));
#351 = SURFACE_CURVE('',#352,(#356,#363),.PCURVE_S1.);
#352 = LINE('',#353,#354);
#353 = CARTESIAN_POINT('',(6.,7.5,470.));
#354 = VECTOR('',#355,1.);
#355 = DIRECTION('',(-1.,0.,0.));
#356 = PCURVE('',#73,#357);
#357 = DEFINITIONAL_REPRESENTATION('',(#358),#362);
#358 = LINE('',#359,#360);
#359 = CARTESIAN_POINT('',(6.,3.602123455276));
#360 = VECTOR('',#361,1.);
#361 = DIRECTION('',(-1.,0.));
#362 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#363 = PCURVE('',#364,#369);
#364 = PLANE('',#365);
#365 = AXIS2_PLACEMENT_3D('',#366,#367,#368);
#366 = CARTESIAN_POINT('',(6.,7.5,0.));
#367 = DIRECTION('',(0.,-1.,0.));
#368 = DIRECTION('',(-1.,0.,0.));
#369 = DEFINITIONAL_REPRESENTATION('',(#370),#374);
#370 = LINE('',#371,#372);
#371 = CARTESIAN_POINT('',(0.,-470.));
#372 = VECTOR('',#373,1.);
#373 = DIRECTION('',(1.,0.));
#374 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#375 = ORIENTED_EDGE('',*,*,#376,.T.);
#376 = EDGE_CURVE('',#349,#24,#377,.T.);
#377 = SURFACE_CURVE('',#378,(#382,#389),.PCURVE_S1.);
#378 = LINE('',#379,#380);
#379 = CARTESIAN_POINT('',(-6.,7.5,470.));
#380 = VECTOR('',#381,1.);
#381 = DIRECTION('',(0.,-1.,0.));
#382 = PCURVE('',#73,#383);
#383 = DEFINITIONAL_REPRESENTATION('',(#384),#388);
#384 = LINE('',#385,#386);
#385 = CARTESIAN_POINT('',(-6.,3.602123455276));
#386 = VECTOR('',#387,1.);
#387 = DIRECTION('',(0.,-1.));
#388 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#389 = PCURVE('',#44,#390);
#390 = DEFINITIONAL_REPRESENTATION('',(#391),#395);
#391 = LINE('',#392,#393);
#392 = CARTESIAN_POINT('',(0.,-470.));
#393 = VECTOR('',#394,1.);
#394 = DIRECTION('',(1.,0.));
#395 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#396 = ADVANCED_FACE('',(#397),#364,.F.);
#397 = FACE_BOUND('',#398,.F.);
#398 = EDGE_LOOP('',(#399,#422,#423,#446));
#399 = ORIENTED_EDGE('',*,*,#400,.T.);
#400 = EDGE_CURVE('',#401,#321,#403,.T.);
#401 = VERTEX_POINT('',#402);
#402 = CARTESIAN_POINT('',(6.,7.5,0.));
#403 = SURFACE_CURVE('',#404,(#408,#415),.PCURVE_S1.);
#404 = LINE('',#405,#406);
#405 = CARTESIAN_POINT('',(6.,7.5,0.));
#406 = VECTOR('',#407,1.);
#407 = DIRECTION('',(0.,0.,1.));
#408 = PCURVE('',#364,#409);
#409 = DEFINITIONAL_REPRESENTATION('',(#410),#414);
#410 = LINE('',#411,#412);
#411 = CARTESIAN_POINT('',(0.,-0.));
#412 = VECTOR('',#413,1.);
#413 = DIRECTION('',(0.,-1.));
#414 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#415 = PCURVE('',#336,#416);
#416 = DEFINITIONAL_REPRESENTATION('',(#417),#421);
#417 = LINE('',#418,#419);
#418 = CARTESIAN_POINT('',(1.,0.));
#419 = VECTOR('',#420,1.);
#420 = DIRECTION('',(0.,-1.));
#421 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#422 = ORIENTED_EDGE('',*,*,#348,.T.);
#423 = ORIENTED_EDGE('',*,*,#424,.F.);
#424 = EDGE_CURVE('',#425,#349,#427,.T.);
#425 = VERTEX_POINT('',#426);
#426 = CARTESIAN_POINT('',(-6.,7.5,0.));
#427 = SURFACE_CURVE('',#428,(#432,#439),.PCURVE_S1.);
#428 = LINE('',#429,#430);
#429 = CARTESIAN_POINT('',(-6.,7.5,0.));
#430 = VECTOR('',#431,1.);
#431 = DIRECTION('',(0.,0.,1.));
#432 = PCURVE('',#364,#433);
#433 = DEFINITIONAL_REPRESENTATION('',(#434),#438);
#434 = LINE('',#435,#436);
#435 = CARTESIAN_POINT('',(12.,0.));
#436 = VECTOR('',#437,1.);
#437 = DIRECTION('',(0.,-1.));
#438 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#439 = PCURVE('',#44,#440);
#440 = DEFINITIONAL_REPRESENTATION('',(#441),#445);
#441 = LINE('',#442,#443);
#442 = CARTESIAN_POINT('',(0.,0.));
#443 = VECTOR('',#444,1.);
#444 = DIRECTION('',(0.,-1.));
#445 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#446 = ORIENTED_EDGE('',*,*,#447,.F.);
#447 = EDGE_CURVE('',#401,#425,#448,.T.);
#448 = SURFACE_CURVE('',#449,(#453,#460),.PCURVE_S1.);
#449 = LINE('',#450,#451);
#450 = CARTESIAN_POINT('',(6.,7.5,0.));
#451 = VECTOR('',#452,1.);
#452 = DIRECTION('',(-1.,0.,0.));
#453 = PCURVE('',#364,#454);
#454 = DEFINITIONAL_REPRESENTATION('',(#455),#459);
#455 = LINE('',#456,#457);
#456 = CARTESIAN_POINT('',(0.,-0.));
#457 = VECTOR('',#458,1.);
#458 = DIRECTION('',(1.,0.));
#459 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#460 = PCURVE('',#132,#461);
#461 = DEFINITIONAL_REPRESENTATION('',(#462),#466);
#462 = LINE('',#463,#464);
#463 = CARTESIAN_POINT('',(6.,3.602123455276));
#464 = VECTOR('',#465,1.);
#465 = DIRECTION('',(-1.,0.));
#466 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#467 = ADVANCED_FACE('',(#468),#336,.F.);
#468 = FACE_BOUND('',#469,.F.);
#469 = EDGE_LOOP('',(#470,#493,#494,#495));
#470 = ORIENTED_EDGE('',*,*,#471,.T.);
#471 = EDGE_CURVE('',#472,#288,#474,.T.);
#472 = VERTEX_POINT('',#473);
#473 = CARTESIAN_POINT('',(6.,6.5,0.));
#474 = SURFACE_CURVE('',#475,(#479,#486),.PCURVE_S1.);
#475 = LINE('',#476,#477);
#476 = CARTESIAN_POINT('',(6.,6.5,0.));
#477 = VECTOR('',#478,1.);
#478 = DIRECTION('',(0.,0.,1.));
#479 = PCURVE('',#336,#480);
#480 = DEFINITIONAL_REPRESENTATION('',(#481),#485);
#481 = LINE('',#482,#483);
#482 = CARTESIAN_POINT('',(0.,0.));
#483 = VECTOR('',#484,1.);
#484 = DIRECTION('',(0.,-1.));
#485 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#486 = PCURVE('',#308,#487);
#487 = DEFINITIONAL_REPRESENTATION('',(#488),#492);
#488 = LINE('',#489,#490);
#489 = CARTESIAN_POINT('',(3.14159265359,0.));
#490 = VECTOR('',#491,1.);
#491 = DIRECTION('',(0.,-1.));
#492 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#493 = ORIENTED_EDGE('',*,*,#320,.T.);
#494 = ORIENTED_EDGE('',*,*,#400,.F.);
#495 = ORIENTED_EDGE('',*,*,#496,.F.);
#496 = EDGE_CURVE('',#472,#401,#497,.T.);
#497 = SURFACE_CURVE('',#498,(#502,#509),.PCURVE_S1.);
#498 = LINE('',#499,#500);
#499 = CARTESIAN_POINT('',(6.,6.5,0.));
#500 = VECTOR('',#501,1.);
#501 = DIRECTION('',(0.,1.,0.));
#502 = PCURVE('',#336,#503);
#503 = DEFINITIONAL_REPRESENTATION('',(#504),#508);
#504 = LINE('',#505,#506);
#505 = CARTESIAN_POINT('',(0.,0.));
#506 = VECTOR('',#507,1.);
#507 = DIRECTION('',(1.,0.));
#508 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#509 = PCURVE('',#132,#510);
#510 = DEFINITIONAL_REPRESENTATION('',(#511),#515);
#511 = LINE('',#512,#513);
#512 = CARTESIAN_POINT('',(6.,2.602123455276));
#513 = VECTOR('',#514,1.);
#514 = DIRECTION('',(0.,1.));
#515 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#516 = ADVANCED_FACE('',(#517),#308,.F.);
#517 = FACE_BOUND('',#518,.F.);
#518 = EDGE_LOOP('',(#519,#542,#543,#544));
#519 = ORIENTED_EDGE('',*,*,#520,.T.);
#520 = EDGE_CURVE('',#521,#260,#523,.T.);
#521 = VERTEX_POINT('',#522);
#522 = CARTESIAN_POINT('',(6.,4.,0.));
#523 = SURFACE_CURVE('',#524,(#528,#535),.PCURVE_S1.);
#524 = LINE('',#525,#526);
#525 = CARTESIAN_POINT('',(6.,4.,0.));
#526 = VECTOR('',#527,1.);
#527 = DIRECTION('',(0.,0.,1.));
#528 = PCURVE('',#308,#529);
#529 = DEFINITIONAL_REPRESENTATION('',(#530),#534);
#530 = LINE('',#531,#532);
#531 = CARTESIAN_POINT('',(0.,0.));
#532 = VECTOR('',#533,1.);
#533 = DIRECTION('',(0.,-1.));
#534 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#535 = PCURVE('',#275,#536);
#536 = DEFINITIONAL_REPRESENTATION('',(#537),#541);
#537 = LINE('',#538,#539);
#538 = CARTESIAN_POINT('',(3.5,0.));
#539 = VECTOR('',#540,1.);
#540 = DIRECTION('',(0.,-1.));
#541 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#542 = ORIENTED_EDGE('',*,*,#287,.T.);
#543 = ORIENTED_EDGE('',*,*,#471,.F.);
#544 = ORIENTED_EDGE('',*,*,#545,.F.);
#545 = EDGE_CURVE('',#521,#472,#546,.T.);
#546 = SURFACE_CURVE('',#547,(#552,#559),.PCURVE_S1.);
#547 = CIRCLE('',#548,1.25);
#548 = AXIS2_PLACEMENT_3D('',#549,#550,#551);
#549 = CARTESIAN_POINT('',(6.,5.25,0.));
#550 = DIRECTION('',(0.,0.,-1.));
#551 = DIRECTION('',(0.,-1.,0.));
#552 = PCURVE('',#308,#553);
#553 = DEFINITIONAL_REPRESENTATION('',(#554),#558);
#554 = LINE('',#555,#556);
#555 = CARTESIAN_POINT('',(0.,0.));
#556 = VECTOR('',#557,1.);
#557 = DIRECTION('',(1.,0.));
#558 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#559 = PCURVE('',#132,#560);
#560 = DEFINITIONAL_REPRESENTATION('',(#561),#569);
#561 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#562,#563,#564,#565,#566,#567
,#568),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#562 = CARTESIAN_POINT('',(6.,0.102123455276));
#563 = CARTESIAN_POINT('',(3.834936490539,0.102123455276));
#564 = CARTESIAN_POINT('',(4.917468245269,1.977123455276));
#565 = CARTESIAN_POINT('',(6.,3.852123455276));
#566 = CARTESIAN_POINT('',(7.082531754731,1.977123455276));
#567 = CARTESIAN_POINT('',(8.165063509461,0.102123455276));
#568 = CARTESIAN_POINT('',(6.,0.102123455276));
#569 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#570 = ADVANCED_FACE('',(#571),#275,.F.);
#571 = FACE_BOUND('',#572,.F.);
#572 = EDGE_LOOP('',(#573,#596,#597,#598));
#573 = ORIENTED_EDGE('',*,*,#574,.T.);
#574 = EDGE_CURVE('',#575,#232,#577,.T.);
#575 = VERTEX_POINT('',#576);
#576 = CARTESIAN_POINT('',(6.,0.5,0.));
#577 = SURFACE_CURVE('',#578,(#582,#589),.PCURVE_S1.);
#578 = LINE('',#579,#580);
#579 = CARTESIAN_POINT('',(6.,0.5,0.));
#580 = VECTOR('',#581,1.);
#581 = DIRECTION('',(0.,0.,1.));
#582 = PCURVE('',#275,#583);
#583 = DEFINITIONAL_REPRESENTATION('',(#584),#588);
#584 = LINE('',#585,#586);
#585 = CARTESIAN_POINT('',(0.,0.));
#586 = VECTOR('',#587,1.);
#587 = DIRECTION('',(0.,-1.));
#588 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#589 = PCURVE('',#247,#590);
#590 = DEFINITIONAL_REPRESENTATION('',(#591),#595);
#591 = LINE('',#592,#593);
#592 = CARTESIAN_POINT('',(0.707106781187,0.));
#593 = VECTOR('',#594,1.);
#594 = DIRECTION('',(0.,-1.));
#595 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#596 = ORIENTED_EDGE('',*,*,#259,.T.);
#597 = ORIENTED_EDGE('',*,*,#520,.F.);
#598 = ORIENTED_EDGE('',*,*,#599,.F.);
#599 = EDGE_CURVE('',#575,#521,#600,.T.);
#600 = SURFACE_CURVE('',#601,(#605,#612),.PCURVE_S1.);
#601 = LINE('',#602,#603);
#602 = CARTESIAN_POINT('',(6.,0.5,0.));
#603 = VECTOR('',#604,1.);
#604 = DIRECTION('',(0.,1.,0.));
#605 = PCURVE('',#275,#606);
#606 = DEFINITIONAL_REPRESENTATION('',(#607),#611);
#607 = LINE('',#608,#609);
#608 = CARTESIAN_POINT('',(0.,0.));
#609 = VECTOR('',#610,1.);
#610 = DIRECTION('',(1.,0.));
#611 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#612 = PCURVE('',#132,#613);
#613 = DEFINITIONAL_REPRESENTATION('',(#614),#618);
#614 = LINE('',#615,#616);
#615 = CARTESIAN_POINT('',(6.,-3.397876544724));
#616 = VECTOR('',#617,1.);
#617 = DIRECTION('',(0.,1.));
#618 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#619 = ADVANCED_FACE('',(#620),#247,.F.);
#620 = FACE_BOUND('',#621,.F.);
#621 = EDGE_LOOP('',(#622,#645,#646,#647));
#622 = ORIENTED_EDGE('',*,*,#623,.F.);
#623 = EDGE_CURVE('',#204,#624,#626,.T.);
#624 = VERTEX_POINT('',#625);
#625 = CARTESIAN_POINT('',(5.5,0.,0.));
#626 = SURFACE_CURVE('',#627,(#631,#638),.PCURVE_S1.);
#627 = LINE('',#628,#629);
#628 = CARTESIAN_POINT('',(5.5,0.,470.));
#629 = VECTOR('',#630,1.);
#630 = DIRECTION('',(0.,0.,-1.));
#631 = PCURVE('',#247,#632);
#632 = DEFINITIONAL_REPRESENTATION('',(#633),#637);
#633 = LINE('',#634,#635);
#634 = CARTESIAN_POINT('',(0.,-470.));
#635 = VECTOR('',#636,1.);
#636 = DIRECTION('',(0.,1.));
#637 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#638 = PCURVE('',#219,#639);
#639 = DEFINITIONAL_REPRESENTATION('',(#640),#644);
#640 = LINE('',#641,#642);
#641 = CARTESIAN_POINT('',(11.,-470.));
#642 = VECTOR('',#643,1.);
#643 = DIRECTION('',(0.,1.));
#644 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#645 = ORIENTED_EDGE('',*,*,#231,.T.);
#646 = ORIENTED_EDGE('',*,*,#574,.F.);
#647 = ORIENTED_EDGE('',*,*,#648,.F.);
#648 = EDGE_CURVE('',#624,#575,#649,.T.);
#649 = SURFACE_CURVE('',#650,(#654,#661),.PCURVE_S1.);
#650 = LINE('',#651,#652);
#651 = CARTESIAN_POINT('',(5.5,0.,0.));
#652 = VECTOR('',#653,1.);
#653 = DIRECTION('',(0.707106781187,0.707106781187,0.));
#654 = PCURVE('',#247,#655);
#655 = DEFINITIONAL_REPRESENTATION('',(#656),#660);
#656 = LINE('',#657,#658);
#657 = CARTESIAN_POINT('',(0.,0.));
#658 = VECTOR('',#659,1.);
#659 = DIRECTION('',(1.,0.));
#660 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#661 = PCURVE('',#132,#662);
#662 = DEFINITIONAL_REPRESENTATION('',(#663),#667);
#663 = LINE('',#664,#665);
#664 = CARTESIAN_POINT('',(5.5,-3.897876544724));
#665 = VECTOR('',#666,1.);
#666 = DIRECTION('',(0.707106781187,0.707106781187));
#667 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#668 = ADVANCED_FACE('',(#669),#219,.F.);
#669 = FACE_BOUND('',#670,.F.);
#670 = EDGE_LOOP('',(#671,#694,#715,#716));
#671 = ORIENTED_EDGE('',*,*,#672,.F.);
#672 = EDGE_CURVE('',#673,#624,#675,.T.);
#673 = VERTEX_POINT('',#674);
#674 = CARTESIAN_POINT('',(-5.5,0.,0.));
#675 = SURFACE_CURVE('',#676,(#680,#687),.PCURVE_S1.);
#676 = LINE('',#677,#678);
#677 = CARTESIAN_POINT('',(-5.5,0.,0.));
#678 = VECTOR('',#679,1.);
#679 = DIRECTION('',(1.,0.,0.));
#680 = PCURVE('',#219,#681);
#681 = DEFINITIONAL_REPRESENTATION('',(#682),#686);
#682 = LINE('',#683,#684);
#683 = CARTESIAN_POINT('',(0.,0.));
#684 = VECTOR('',#685,1.);
#685 = DIRECTION('',(1.,0.));
#686 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#687 = PCURVE('',#132,#688);
#688 = DEFINITIONAL_REPRESENTATION('',(#689),#693);
#689 = LINE('',#690,#691);
#690 = CARTESIAN_POINT('',(-5.5,-3.897876544724));
#691 = VECTOR('',#692,1.);
#692 = DIRECTION('',(1.,0.));
#693 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#694 = ORIENTED_EDGE('',*,*,#695,.F.);
#695 = EDGE_CURVE('',#176,#673,#696,.T.);
#696 = SURFACE_CURVE('',#697,(#701,#708),.PCURVE_S1.);
#697 = LINE('',#698,#699);
#698 = CARTESIAN_POINT('',(-5.5,0.,470.));
#699 = VECTOR('',#700,1.);
#700 = DIRECTION('',(0.,0.,-1.));
#701 = PCURVE('',#219,#702);
#702 = DEFINITIONAL_REPRESENTATION('',(#703),#707);
#703 = LINE('',#704,#705);
#704 = CARTESIAN_POINT('',(0.,-470.));
#705 = VECTOR('',#706,1.);
#706 = DIRECTION('',(0.,1.));
#707 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#708 = PCURVE('',#191,#709);
#709 = DEFINITIONAL_REPRESENTATION('',(#710),#714);
#710 = LINE('',#711,#712);
#711 = CARTESIAN_POINT('',(0.707106781187,-470.));
#712 = VECTOR('',#713,1.);
#713 = DIRECTION('',(0.,1.));
#714 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#715 = ORIENTED_EDGE('',*,*,#203,.T.);
#716 = ORIENTED_EDGE('',*,*,#623,.T.);
#717 = ADVANCED_FACE('',(#718),#191,.F.);
#718 = FACE_BOUND('',#719,.F.);
#719 = EDGE_LOOP('',(#720,#743,#744,#745));
#720 = ORIENTED_EDGE('',*,*,#721,.T.);
#721 = EDGE_CURVE('',#722,#153,#724,.T.);
#722 = VERTEX_POINT('',#723);
#723 = CARTESIAN_POINT('',(-6.,0.5,0.));
#724 = SURFACE_CURVE('',#725,(#729,#736),.PCURVE_S1.);
#725 = LINE('',#726,#727);
#726 = CARTESIAN_POINT('',(-6.,0.5,0.));
#727 = VECTOR('',#728,1.);
#728 = DIRECTION('',(0.,0.,1.));
#729 = PCURVE('',#191,#730);
#730 = DEFINITIONAL_REPRESENTATION('',(#731),#735);
#731 = LINE('',#732,#733);
#732 = CARTESIAN_POINT('',(0.,0.));
#733 = VECTOR('',#734,1.);
#734 = DIRECTION('',(0.,-1.));
#735 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#736 = PCURVE('',#105,#737);
#737 = DEFINITIONAL_REPRESENTATION('',(#738),#742);
#738 = LINE('',#739,#740);
#739 = CARTESIAN_POINT('',(3.5,0.));
#740 = VECTOR('',#741,1.);
#741 = DIRECTION('',(0.,-1.));
#742 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#743 = ORIENTED_EDGE('',*,*,#175,.T.);
#744 = ORIENTED_EDGE('',*,*,#695,.T.);
#745 = ORIENTED_EDGE('',*,*,#746,.F.);
#746 = EDGE_CURVE('',#722,#673,#747,.T.);
#747 = SURFACE_CURVE('',#748,(#752,#759),.PCURVE_S1.);
#748 = LINE('',#749,#750);
#749 = CARTESIAN_POINT('',(-6.,0.5,0.));
#750 = VECTOR('',#751,1.);
#751 = DIRECTION('',(0.707106781187,-0.707106781187,0.));
#752 = PCURVE('',#191,#753);
#753 = DEFINITIONAL_REPRESENTATION('',(#754),#758);
#754 = LINE('',#755,#756);
#755 = CARTESIAN_POINT('',(0.,0.));
#756 = VECTOR('',#757,1.);
#757 = DIRECTION('',(1.,0.));
#758 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#759 = PCURVE('',#132,#760);
#760 = DEFINITIONAL_REPRESENTATION('',(#761),#765);
#761 = LINE('',#762,#763);
#762 = CARTESIAN_POINT('',(-6.,-3.397876544724));
#763 = VECTOR('',#764,1.);
#764 = DIRECTION('',(0.707106781187,-0.707106781187));
#765 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#766 = ADVANCED_FACE('',(#767),#132,.F.);
#767 = FACE_BOUND('',#768,.F.);
#768 = EDGE_LOOP('',(#769,#770,#791,#792,#793,#794,#795,#796,#797,#798)
);
#769 = ORIENTED_EDGE('',*,*,#117,.T.);
#770 = ORIENTED_EDGE('',*,*,#771,.T.);
#771 = EDGE_CURVE('',#90,#722,#772,.T.);
#772 = SURFACE_CURVE('',#773,(#777,#784),.PCURVE_S1.);
#773 = LINE('',#774,#775);
#774 = CARTESIAN_POINT('',(-6.,4.,0.));
#775 = VECTOR('',#776,1.);
#776 = DIRECTION('',(0.,-1.,0.));
#777 = PCURVE('',#132,#778);
#778 = DEFINITIONAL_REPRESENTATION('',(#779),#783);
#779 = LINE('',#780,#781);
#780 = CARTESIAN_POINT('',(-6.,0.102123455276));
#781 = VECTOR('',#782,1.);
#782 = DIRECTION('',(0.,-1.));
#783 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#784 = PCURVE('',#105,#785);
#785 = DEFINITIONAL_REPRESENTATION('',(#786),#790);
#786 = LINE('',#787,#788);
#787 = CARTESIAN_POINT('',(0.,0.));
#788 = VECTOR('',#789,1.);
#789 = DIRECTION('',(1.,0.));
#790 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#791 = ORIENTED_EDGE('',*,*,#746,.T.);
#792 = ORIENTED_EDGE('',*,*,#672,.T.);
#793 = ORIENTED_EDGE('',*,*,#648,.T.);
#794 = ORIENTED_EDGE('',*,*,#599,.T.);
#795 = ORIENTED_EDGE('',*,*,#545,.T.);
#796 = ORIENTED_EDGE('',*,*,#496,.T.);
#797 = ORIENTED_EDGE('',*,*,#447,.T.);
#798 = ORIENTED_EDGE('',*,*,#799,.T.);
#799 = EDGE_CURVE('',#425,#22,#800,.T.);
#800 = SURFACE_CURVE('',#801,(#805,#812),.PCURVE_S1.);
#801 = LINE('',#802,#803);
#802 = CARTESIAN_POINT('',(-6.,7.5,0.));
#803 = VECTOR('',#804,1.);
#804 = DIRECTION('',(0.,-1.,0.));
#805 = PCURVE('',#132,#806);
#806 = DEFINITIONAL_REPRESENTATION('',(#807),#811);
#807 = LINE('',#808,#809);
#808 = CARTESIAN_POINT('',(-6.,3.602123455276));
#809 = VECTOR('',#810,1.);
#810 = DIRECTION('',(0.,-1.));
#811 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#812 = PCURVE('',#44,#813);
#813 = DEFINITIONAL_REPRESENTATION('',(#814),#818);
#814 = LINE('',#815,#816);
#815 = CARTESIAN_POINT('',(0.,0.));
#816 = VECTOR('',#817,1.);
#817 = DIRECTION('',(1.,0.));
#818 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#819 = ADVANCED_FACE('',(#820),#105,.F.);
#820 = FACE_BOUND('',#821,.F.);
#821 = EDGE_LOOP('',(#822,#823,#824,#825));
#822 = ORIENTED_EDGE('',*,*,#89,.T.);
#823 = ORIENTED_EDGE('',*,*,#152,.T.);
#824 = ORIENTED_EDGE('',*,*,#721,.F.);
#825 = ORIENTED_EDGE('',*,*,#771,.F.);
#826 = ADVANCED_FACE('',(#827),#44,.F.);
#827 = FACE_BOUND('',#828,.F.);
#828 = EDGE_LOOP('',(#829,#830,#831,#832));
#829 = ORIENTED_EDGE('',*,*,#424,.T.);
#830 = ORIENTED_EDGE('',*,*,#376,.T.);
#831 = ORIENTED_EDGE('',*,*,#21,.F.);
#832 = ORIENTED_EDGE('',*,*,#799,.F.);
#833 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#837)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#834,#835,#836)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#834 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#835 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#836 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#837 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#834,
'distance_accuracy_value','confusion accuracy');
#838 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
ENDSEC;
END-ISO-10303-21;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,9 @@
{
"extrude_add_blind": 2,
"extrude_add_two_sided": 1,
"extrude_cut_blind": 40,
"reference_axis": 210,
"reference_plane": 271,
"revolve_add": 4,
"revolve_cut": 24
}
@@ -0,0 +1 @@
{}
@@ -0,0 +1,33 @@
{
"built_count": 4,
"compiled_count": 4,
"complete": true,
"completed_count": 4,
"failure_category_counts": {
"geometry_verified": 4
},
"geometry_verified_count": 4,
"input_directory": "json_to_cdsl/output",
"part_count": 4,
"results": [
{
"part_id": "046112",
"report": "parts/046112.report.json"
},
{
"part_id": "053267",
"report": "parts/053267.report.json"
},
{
"part_id": "053393",
"report": "parts/053393.report.json"
},
{
"part_id": "197680",
"report": "parts/197680.report.json"
}
],
"runtime_eligible_count": 4,
"schema": "cdsl.engine.batch-rebuild.v1",
"semantic_valid_count": 4
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,316 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
FILE_NAME('Open CASCADE Shape Model','2026-08-24T10:01:44',('Author'),(
'Open CASCADE'),'Open CASCADE STEP processor 7.9','build123d',
'Unknown');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
ENDSEC;
DATA;
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2000,#2);
#2 = APPLICATION_CONTEXT(
'core data for automotive mechanical design processes');
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
#5 = PRODUCT_DEFINITION('design','',#6,#9);
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
#7 = PRODUCT('SOLID','SOLID','',(#8));
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#256);
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
#12 = CARTESIAN_POINT('',(0.,0.,0.));
#13 = DIRECTION('',(0.,0.,1.));
#14 = DIRECTION('',(1.,0.,-0.));
#15 = MANIFOLD_SOLID_BREP('',#16);
#16 = CLOSED_SHELL('',(#17,#109,#144,#199,#249));
#17 = ADVANCED_FACE('',(#18),#31,.T.);
#18 = FACE_BOUND('',#19,.F.);
#19 = EDGE_LOOP('',(#20,#54,#77,#108));
#20 = ORIENTED_EDGE('',*,*,#21,.F.);
#21 = EDGE_CURVE('',#22,#22,#24,.T.);
#22 = VERTEX_POINT('',#23);
#23 = CARTESIAN_POINT('',(0.,-4.,0.));
#24 = SURFACE_CURVE('',#25,(#30,#42),.PCURVE_S1.);
#25 = CIRCLE('',#26,4.);
#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29);
#27 = CARTESIAN_POINT('',(-1.430834047558E-15,1.430834047558E-15,0.));
#28 = DIRECTION('',(0.,0.,1.));
#29 = DIRECTION('',(3.577085118894E-16,-1.,0.));
#30 = PCURVE('',#31,#36);
#31 = CYLINDRICAL_SURFACE('',#32,4.);
#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35);
#33 = CARTESIAN_POINT('',(-1.430834047558E-15,-1.430834047558E-15,0.));
#34 = DIRECTION('',(-0.,-0.,-1.));
#35 = DIRECTION('',(1.,3.577085118894E-16,-0.));
#36 = DEFINITIONAL_REPRESENTATION('',(#37),#41);
#37 = LINE('',#38,#39);
#38 = CARTESIAN_POINT('',(1.570796326795,-0.));
#39 = VECTOR('',#40,1.);
#40 = DIRECTION('',(-1.,0.));
#41 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#42 = PCURVE('',#43,#48);
#43 = PLANE('',#44);
#44 = AXIS2_PLACEMENT_3D('',#45,#46,#47);
#45 = CARTESIAN_POINT('',(0.,-1.991521825606E-18,0.));
#46 = DIRECTION('',(0.,0.,1.));
#47 = DIRECTION('',(1.,0.,-0.));
#48 = DEFINITIONAL_REPRESENTATION('',(#49),#53);
#49 = CIRCLE('',#50,4.);
#50 = AXIS2_PLACEMENT_2D('',#51,#52);
#51 = CARTESIAN_POINT('',(-1.430834047558E-15,1.432825569383E-15));
#52 = DIRECTION('',(3.577085118894E-16,-1.));
#53 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#54 = ORIENTED_EDGE('',*,*,#55,.T.);
#55 = EDGE_CURVE('',#22,#56,#58,.T.);
#56 = VERTEX_POINT('',#57);
#57 = CARTESIAN_POINT('',(0.,-4.,2.));
#58 = SEAM_CURVE('',#59,(#63,#70),.PCURVE_S1.);
#59 = LINE('',#60,#61);
#60 = CARTESIAN_POINT('',(0.,-4.,0.));
#61 = VECTOR('',#62,1.);
#62 = DIRECTION('',(0.,0.,1.));
#63 = PCURVE('',#31,#64);
#64 = DEFINITIONAL_REPRESENTATION('',(#65),#69);
#65 = LINE('',#66,#67);
#66 = CARTESIAN_POINT('',(1.570796326795,-0.));
#67 = VECTOR('',#68,1.);
#68 = DIRECTION('',(-0.,-1.));
#69 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#70 = PCURVE('',#31,#71);
#71 = DEFINITIONAL_REPRESENTATION('',(#72),#76);
#72 = LINE('',#73,#74);
#73 = CARTESIAN_POINT('',(-4.712388980385,0.));
#74 = VECTOR('',#75,1.);
#75 = DIRECTION('',(-0.,-1.));
#76 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#77 = ORIENTED_EDGE('',*,*,#78,.F.);
#78 = EDGE_CURVE('',#56,#56,#79,.T.);
#79 = SURFACE_CURVE('',#80,(#85,#92),.PCURVE_S1.);
#80 = CIRCLE('',#81,4.);
#81 = AXIS2_PLACEMENT_3D('',#82,#83,#84);
#82 = CARTESIAN_POINT('',(5.677948281513E-17,6.523055238025E-17,2.));
#83 = DIRECTION('',(0.,0.,-1.));
#84 = DIRECTION('',(-2.614400165742E-17,-1.,-0.));
#85 = PCURVE('',#31,#86);
#86 = DEFINITIONAL_REPRESENTATION('',(#87),#91);
#87 = LINE('',#88,#89);
#88 = CARTESIAN_POINT('',(-4.712388980385,-2.));
#89 = VECTOR('',#90,1.);
#90 = DIRECTION('',(1.,-0.));
#91 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#92 = PCURVE('',#93,#98);
#93 = PLANE('',#94);
#94 = AXIS2_PLACEMENT_3D('',#95,#96,#97);
#95 = CARTESIAN_POINT('',(0.,-1.991521825606E-18,2.));
#96 = DIRECTION('',(0.,0.,1.));
#97 = DIRECTION('',(1.,0.,-0.));
#98 = DEFINITIONAL_REPRESENTATION('',(#99),#107);
#99 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#100,#101,#102,#103,#104,#105,
#106),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#100 = CARTESIAN_POINT('',(-4.779652381455E-17,-4.));
#101 = CARTESIAN_POINT('',(-6.928203230276,-4.));
#102 = CARTESIAN_POINT('',(-3.464101615138,2.));
#103 = CARTESIAN_POINT('',(-7.137859432434E-16,8.));
#104 = CARTESIAN_POINT('',(3.464101615138,2.));
#105 = CARTESIAN_POINT('',(6.928203230276,-4.));
#106 = CARTESIAN_POINT('',(-4.779652381455E-17,-4.));
#107 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#108 = ORIENTED_EDGE('',*,*,#55,.F.);
#109 = ADVANCED_FACE('',(#110,#113),#93,.T.);
#110 = FACE_BOUND('',#111,.T.);
#111 = EDGE_LOOP('',(#112));
#112 = ORIENTED_EDGE('',*,*,#78,.F.);
#113 = FACE_BOUND('',#114,.T.);
#114 = EDGE_LOOP('',(#115));
#115 = ORIENTED_EDGE('',*,*,#116,.F.);
#116 = EDGE_CURVE('',#117,#117,#119,.T.);
#117 = VERTEX_POINT('',#118);
#118 = CARTESIAN_POINT('',(1.7,-4.163799117101E-16,2.));
#119 = SURFACE_CURVE('',#120,(#125,#132),.PCURVE_S1.);
#120 = CIRCLE('',#121,1.7);
#121 = AXIS2_PLACEMENT_3D('',#122,#123,#124);
#122 = CARTESIAN_POINT('',(0.,0.,2.));
#123 = DIRECTION('',(0.,0.,1.));
#124 = DIRECTION('',(1.,0.,-0.));
#125 = PCURVE('',#93,#126);
#126 = DEFINITIONAL_REPRESENTATION('',(#127),#131);
#127 = CIRCLE('',#128,1.7);
#128 = AXIS2_PLACEMENT_2D('',#129,#130);
#129 = CARTESIAN_POINT('',(0.,1.991521825606E-18));
#130 = DIRECTION('',(1.,0.));
#131 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#132 = PCURVE('',#133,#138);
#133 = CYLINDRICAL_SURFACE('',#134,1.7);
#134 = AXIS2_PLACEMENT_3D('',#135,#136,#137);
#135 = CARTESIAN_POINT('',(0.,0.,0.));
#136 = DIRECTION('',(0.,0.,1.));
#137 = DIRECTION('',(1.,0.,-0.));
#138 = DEFINITIONAL_REPRESENTATION('',(#139),#143);
#139 = LINE('',#140,#141);
#140 = CARTESIAN_POINT('',(0.,2.));
#141 = VECTOR('',#142,1.);
#142 = DIRECTION('',(1.,0.));
#143 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#144 = ADVANCED_FACE('',(#145),#133,.F.);
#145 = FACE_BOUND('',#146,.F.);
#146 = EDGE_LOOP('',(#147,#176,#197,#198));
#147 = ORIENTED_EDGE('',*,*,#148,.T.);
#148 = EDGE_CURVE('',#149,#149,#151,.T.);
#149 = VERTEX_POINT('',#150);
#150 = CARTESIAN_POINT('',(1.7,-4.163799117101E-16,1.66));
#151 = SURFACE_CURVE('',#152,(#157,#164),.PCURVE_S1.);
#152 = CIRCLE('',#153,1.7);
#153 = AXIS2_PLACEMENT_3D('',#154,#155,#156);
#154 = CARTESIAN_POINT('',(0.,0.,1.66));
#155 = DIRECTION('',(0.,0.,1.));
#156 = DIRECTION('',(1.,0.,-0.));
#157 = PCURVE('',#133,#158);
#158 = DEFINITIONAL_REPRESENTATION('',(#159),#163);
#159 = LINE('',#160,#161);
#160 = CARTESIAN_POINT('',(0.,1.66));
#161 = VECTOR('',#162,1.);
#162 = DIRECTION('',(1.,0.));
#163 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#164 = PCURVE('',#165,#170);
#165 = CONICAL_SURFACE('',#166,3.36,0.785398163397);
#166 = AXIS2_PLACEMENT_3D('',#167,#168,#169);
#167 = CARTESIAN_POINT('',(0.,0.,0.));
#168 = DIRECTION('',(-0.,-0.,-1.));
#169 = DIRECTION('',(1.,0.,-0.));
#170 = DEFINITIONAL_REPRESENTATION('',(#171),#175);
#171 = LINE('',#172,#173);
#172 = CARTESIAN_POINT('',(-0.,-1.66));
#173 = VECTOR('',#174,1.);
#174 = DIRECTION('',(-1.,-0.));
#175 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#176 = ORIENTED_EDGE('',*,*,#177,.T.);
#177 = EDGE_CURVE('',#149,#117,#178,.T.);
#178 = SEAM_CURVE('',#179,(#183,#190),.PCURVE_S1.);
#179 = LINE('',#180,#181);
#180 = CARTESIAN_POINT('',(1.7,-4.163799117101E-16,0.));
#181 = VECTOR('',#182,1.);
#182 = DIRECTION('',(0.,0.,1.));
#183 = PCURVE('',#133,#184);
#184 = DEFINITIONAL_REPRESENTATION('',(#185),#189);
#185 = LINE('',#186,#187);
#186 = CARTESIAN_POINT('',(0.,-0.));
#187 = VECTOR('',#188,1.);
#188 = DIRECTION('',(0.,1.));
#189 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#190 = PCURVE('',#133,#191);
#191 = DEFINITIONAL_REPRESENTATION('',(#192),#196);
#192 = LINE('',#193,#194);
#193 = CARTESIAN_POINT('',(6.28318530718,-0.));
#194 = VECTOR('',#195,1.);
#195 = DIRECTION('',(0.,1.));
#196 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#197 = ORIENTED_EDGE('',*,*,#116,.F.);
#198 = ORIENTED_EDGE('',*,*,#177,.F.);
#199 = ADVANCED_FACE('',(#200),#165,.F.);
#200 = FACE_BOUND('',#201,.F.);
#201 = EDGE_LOOP('',(#202,#203,#226,#248));
#202 = ORIENTED_EDGE('',*,*,#148,.F.);
#203 = ORIENTED_EDGE('',*,*,#204,.F.);
#204 = EDGE_CURVE('',#205,#149,#207,.T.);
#205 = VERTEX_POINT('',#206);
#206 = CARTESIAN_POINT('',(3.36,-8.22962649027E-16,0.));
#207 = SEAM_CURVE('',#208,(#212,#219),.PCURVE_S1.);
#208 = LINE('',#209,#210);
#209 = CARTESIAN_POINT('',(3.36,-8.22962649027E-16,0.));
#210 = VECTOR('',#211,1.);
#211 = DIRECTION('',(-0.707106781187,1.731912112471E-16,0.707106781187)
);
#212 = PCURVE('',#165,#213);
#213 = DEFINITIONAL_REPRESENTATION('',(#214),#218);
#214 = LINE('',#215,#216);
#215 = CARTESIAN_POINT('',(-6.28318530718,0.));
#216 = VECTOR('',#217,1.);
#217 = DIRECTION('',(-0.,-1.));
#218 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#219 = PCURVE('',#165,#220);
#220 = DEFINITIONAL_REPRESENTATION('',(#221),#225);
#221 = LINE('',#222,#223);
#222 = CARTESIAN_POINT('',(0.,-0.));
#223 = VECTOR('',#224,1.);
#224 = DIRECTION('',(-0.,-1.));
#225 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#226 = ORIENTED_EDGE('',*,*,#227,.T.);
#227 = EDGE_CURVE('',#205,#205,#228,.T.);
#228 = SURFACE_CURVE('',#229,(#234,#241),.PCURVE_S1.);
#229 = CIRCLE('',#230,3.36);
#230 = AXIS2_PLACEMENT_3D('',#231,#232,#233);
#231 = CARTESIAN_POINT('',(0.,0.,0.));
#232 = DIRECTION('',(0.,0.,1.));
#233 = DIRECTION('',(1.,0.,-0.));
#234 = PCURVE('',#165,#235);
#235 = DEFINITIONAL_REPRESENTATION('',(#236),#240);
#236 = LINE('',#237,#238);
#237 = CARTESIAN_POINT('',(-0.,-0.));
#238 = VECTOR('',#239,1.);
#239 = DIRECTION('',(-1.,-0.));
#240 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#241 = PCURVE('',#43,#242);
#242 = DEFINITIONAL_REPRESENTATION('',(#243),#247);
#243 = CIRCLE('',#244,3.36);
#244 = AXIS2_PLACEMENT_2D('',#245,#246);
#245 = CARTESIAN_POINT('',(0.,1.991521825606E-18));
#246 = DIRECTION('',(1.,0.));
#247 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#248 = ORIENTED_EDGE('',*,*,#204,.T.);
#249 = ADVANCED_FACE('',(#250,#253),#43,.F.);
#250 = FACE_BOUND('',#251,.F.);
#251 = EDGE_LOOP('',(#252));
#252 = ORIENTED_EDGE('',*,*,#21,.T.);
#253 = FACE_BOUND('',#254,.F.);
#254 = EDGE_LOOP('',(#255));
#255 = ORIENTED_EDGE('',*,*,#227,.F.);
#256 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#260)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#257,#258,#259)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#257 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#258 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#259 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#260 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#257,
'distance_accuracy_value','confusion accuracy');
#261 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
ENDSEC;
END-ISO-10303-21;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,363 @@
ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Open CASCADE Model'),'2;1');
FILE_NAME('Open CASCADE Shape Model','2026-08-24T10:01:48',('Author'),(
'Open CASCADE'),'Open CASCADE STEP processor 7.9','build123d',
'Unknown');
FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));
ENDSEC;
DATA;
#1 = APPLICATION_PROTOCOL_DEFINITION('international standard',
'automotive_design',2000,#2);
#2 = APPLICATION_CONTEXT(
'core data for automotive mechanical design processes');
#3 = SHAPE_DEFINITION_REPRESENTATION(#4,#10);
#4 = PRODUCT_DEFINITION_SHAPE('','',#5);
#5 = PRODUCT_DEFINITION('design','',#6,#9);
#6 = PRODUCT_DEFINITION_FORMATION('','',#7);
#7 = PRODUCT('SOLID','SOLID','',(#8));
#8 = PRODUCT_CONTEXT('',#2,'mechanical');
#9 = PRODUCT_DEFINITION_CONTEXT('part definition',#2,'design');
#10 = ADVANCED_BREP_SHAPE_REPRESENTATION('',(#11,#15),#295);
#11 = AXIS2_PLACEMENT_3D('',#12,#13,#14);
#12 = CARTESIAN_POINT('',(0.,0.,0.));
#13 = DIRECTION('',(0.,0.,1.));
#14 = DIRECTION('',(1.,0.,-0.));
#15 = MANIFOLD_SOLID_BREP('',#16);
#16 = CLOSED_SHELL('',(#17,#89,#144,#203,#238,#268));
#17 = ADVANCED_FACE('',(#18,#54),#31,.T.);
#18 = FACE_BOUND('',#19,.F.);
#19 = EDGE_LOOP('',(#20));
#20 = ORIENTED_EDGE('',*,*,#21,.F.);
#21 = EDGE_CURVE('',#22,#22,#24,.T.);
#22 = VERTEX_POINT('',#23);
#23 = CARTESIAN_POINT('',(-5.,-0.,-0.));
#24 = SURFACE_CURVE('',#25,(#30,#42),.PCURVE_S1.);
#25 = CIRCLE('',#26,5.);
#26 = AXIS2_PLACEMENT_3D('',#27,#28,#29);
#27 = CARTESIAN_POINT('',(0.,0.,0.));
#28 = DIRECTION('',(0.,-1.,0.));
#29 = DIRECTION('',(-1.,-0.,0.));
#30 = PCURVE('',#31,#36);
#31 = PLANE('',#32);
#32 = AXIS2_PLACEMENT_3D('',#33,#34,#35);
#33 = CARTESIAN_POINT('',(0.,0.,0.));
#34 = DIRECTION('',(0.,-1.,0.));
#35 = DIRECTION('',(-1.,0.,0.));
#36 = DEFINITIONAL_REPRESENTATION('',(#37),#41);
#37 = CIRCLE('',#38,5.);
#38 = AXIS2_PLACEMENT_2D('',#39,#40);
#39 = CARTESIAN_POINT('',(0.,0.));
#40 = DIRECTION('',(1.,-0.));
#41 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#42 = PCURVE('',#43,#48);
#43 = CYLINDRICAL_SURFACE('',#44,5.);
#44 = AXIS2_PLACEMENT_3D('',#45,#46,#47);
#45 = CARTESIAN_POINT('',(0.,0.,0.));
#46 = DIRECTION('',(-0.,1.,-0.));
#47 = DIRECTION('',(-1.,0.,0.));
#48 = DEFINITIONAL_REPRESENTATION('',(#49),#53);
#49 = LINE('',#50,#51);
#50 = CARTESIAN_POINT('',(-0.,0.));
#51 = VECTOR('',#52,1.);
#52 = DIRECTION('',(-1.,0.));
#53 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#54 = FACE_BOUND('',#55,.F.);
#55 = EDGE_LOOP('',(#56));
#56 = ORIENTED_EDGE('',*,*,#57,.F.);
#57 = EDGE_CURVE('',#58,#58,#60,.T.);
#58 = VERTEX_POINT('',#59);
#59 = CARTESIAN_POINT('',(-5.510910596163E-16,0.,2.25));
#60 = SURFACE_CURVE('',#61,(#66,#77),.PCURVE_S1.);
#61 = CIRCLE('',#62,2.25);
#62 = AXIS2_PLACEMENT_3D('',#63,#64,#65);
#63 = CARTESIAN_POINT('',(0.,0.,0.));
#64 = DIRECTION('',(0.,1.,-0.));
#65 = DIRECTION('',(0.,0.,1.));
#66 = PCURVE('',#31,#67);
#67 = DEFINITIONAL_REPRESENTATION('',(#68),#76);
#68 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#69,#70,#71,#72,#73,#74,#75),
.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#69 = CARTESIAN_POINT('',(0.,-2.25));
#70 = CARTESIAN_POINT('',(-3.89711431703,-2.25));
#71 = CARTESIAN_POINT('',(-1.948557158515,1.125));
#72 = CARTESIAN_POINT('',(-5.510910596163E-16,4.5));
#73 = CARTESIAN_POINT('',(1.948557158515,1.125));
#74 = CARTESIAN_POINT('',(3.89711431703,-2.25));
#75 = CARTESIAN_POINT('',(0.,-2.25));
#76 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#77 = PCURVE('',#78,#83);
#78 = CYLINDRICAL_SURFACE('',#79,2.25);
#79 = AXIS2_PLACEMENT_3D('',#80,#81,#82);
#80 = CARTESIAN_POINT('',(0.,0.,0.));
#81 = DIRECTION('',(0.,1.,-0.));
#82 = DIRECTION('',(0.,0.,1.));
#83 = DEFINITIONAL_REPRESENTATION('',(#84),#88);
#84 = LINE('',#85,#86);
#85 = CARTESIAN_POINT('',(0.,0.));
#86 = VECTOR('',#87,1.);
#87 = DIRECTION('',(1.,0.));
#88 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#89 = ADVANCED_FACE('',(#90),#43,.T.);
#90 = FACE_BOUND('',#91,.F.);
#91 = EDGE_LOOP('',(#92,#93,#116,#143));
#92 = ORIENTED_EDGE('',*,*,#21,.T.);
#93 = ORIENTED_EDGE('',*,*,#94,.T.);
#94 = EDGE_CURVE('',#22,#95,#97,.T.);
#95 = VERTEX_POINT('',#96);
#96 = CARTESIAN_POINT('',(-5.,2.,0.));
#97 = SEAM_CURVE('',#98,(#102,#109),.PCURVE_S1.);
#98 = LINE('',#99,#100);
#99 = CARTESIAN_POINT('',(-5.,-0.,-0.));
#100 = VECTOR('',#101,1.);
#101 = DIRECTION('',(0.,1.,0.));
#102 = PCURVE('',#43,#103);
#103 = DEFINITIONAL_REPRESENTATION('',(#104),#108);
#104 = LINE('',#105,#106);
#105 = CARTESIAN_POINT('',(-0.,0.));
#106 = VECTOR('',#107,1.);
#107 = DIRECTION('',(-0.,1.));
#108 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#109 = PCURVE('',#43,#110);
#110 = DEFINITIONAL_REPRESENTATION('',(#111),#115);
#111 = LINE('',#112,#113);
#112 = CARTESIAN_POINT('',(-6.28318530718,0.));
#113 = VECTOR('',#114,1.);
#114 = DIRECTION('',(-0.,1.));
#115 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#116 = ORIENTED_EDGE('',*,*,#117,.F.);
#117 = EDGE_CURVE('',#95,#95,#118,.T.);
#118 = SURFACE_CURVE('',#119,(#124,#131),.PCURVE_S1.);
#119 = CIRCLE('',#120,5.);
#120 = AXIS2_PLACEMENT_3D('',#121,#122,#123);
#121 = CARTESIAN_POINT('',(0.,2.,0.));
#122 = DIRECTION('',(0.,-1.,0.));
#123 = DIRECTION('',(-1.,0.,0.));
#124 = PCURVE('',#43,#125);
#125 = DEFINITIONAL_REPRESENTATION('',(#126),#130);
#126 = LINE('',#127,#128);
#127 = CARTESIAN_POINT('',(-0.,2.));
#128 = VECTOR('',#129,1.);
#129 = DIRECTION('',(-1.,0.));
#130 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#131 = PCURVE('',#132,#137);
#132 = PLANE('',#133);
#133 = AXIS2_PLACEMENT_3D('',#134,#135,#136);
#134 = CARTESIAN_POINT('',(0.,2.,0.));
#135 = DIRECTION('',(0.,-1.,0.));
#136 = DIRECTION('',(-1.,0.,0.));
#137 = DEFINITIONAL_REPRESENTATION('',(#138),#142);
#138 = CIRCLE('',#139,5.);
#139 = AXIS2_PLACEMENT_2D('',#140,#141);
#140 = CARTESIAN_POINT('',(0.,0.));
#141 = DIRECTION('',(1.,0.));
#142 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#143 = ORIENTED_EDGE('',*,*,#94,.F.);
#144 = ADVANCED_FACE('',(#145),#78,.F.);
#145 = FACE_BOUND('',#146,.F.);
#146 = EDGE_LOOP('',(#147,#180,#201,#202));
#147 = ORIENTED_EDGE('',*,*,#148,.F.);
#148 = EDGE_CURVE('',#149,#149,#151,.T.);
#149 = VERTEX_POINT('',#150);
#150 = CARTESIAN_POINT('',(-5.510910596163E-16,10.2,2.25));
#151 = SURFACE_CURVE('',#152,(#157,#164),.PCURVE_S1.);
#152 = CIRCLE('',#153,2.25);
#153 = AXIS2_PLACEMENT_3D('',#154,#155,#156);
#154 = CARTESIAN_POINT('',(0.,10.2,0.));
#155 = DIRECTION('',(0.,1.,-0.));
#156 = DIRECTION('',(0.,0.,1.));
#157 = PCURVE('',#78,#158);
#158 = DEFINITIONAL_REPRESENTATION('',(#159),#163);
#159 = LINE('',#160,#161);
#160 = CARTESIAN_POINT('',(0.,10.2));
#161 = VECTOR('',#162,1.);
#162 = DIRECTION('',(1.,0.));
#163 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#164 = PCURVE('',#165,#170);
#165 = PLANE('',#166);
#166 = AXIS2_PLACEMENT_3D('',#167,#168,#169);
#167 = CARTESIAN_POINT('',(0.,10.2,0.));
#168 = DIRECTION('',(0.,-1.,0.));
#169 = DIRECTION('',(-1.,0.,0.));
#170 = DEFINITIONAL_REPRESENTATION('',(#171),#179);
#171 = ( BOUNDED_CURVE() B_SPLINE_CURVE(2,(#172,#173,#174,#175,#176,#177
,#178),.UNSPECIFIED.,.T.,.F.) B_SPLINE_CURVE_WITH_KNOTS((1,2,2,2,2,1),(
-2.094395102393,0.,2.094395102393,4.188790204786,6.28318530718,
8.377580409573),.UNSPECIFIED.) CURVE() GEOMETRIC_REPRESENTATION_ITEM()
RATIONAL_B_SPLINE_CURVE((1.,0.5,1.,0.5,1.,0.5,1.)) REPRESENTATION_ITEM(
'') );
#172 = CARTESIAN_POINT('',(0.,-2.25));
#173 = CARTESIAN_POINT('',(-3.89711431703,-2.25));
#174 = CARTESIAN_POINT('',(-1.948557158515,1.125));
#175 = CARTESIAN_POINT('',(-5.510910596163E-16,4.5));
#176 = CARTESIAN_POINT('',(1.948557158515,1.125));
#177 = CARTESIAN_POINT('',(3.89711431703,-2.25));
#178 = CARTESIAN_POINT('',(0.,-2.25));
#179 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#180 = ORIENTED_EDGE('',*,*,#181,.F.);
#181 = EDGE_CURVE('',#58,#149,#182,.T.);
#182 = SEAM_CURVE('',#183,(#187,#194),.PCURVE_S1.);
#183 = LINE('',#184,#185);
#184 = CARTESIAN_POINT('',(-5.510910596163E-16,0.,2.25));
#185 = VECTOR('',#186,1.);
#186 = DIRECTION('',(0.,1.,0.));
#187 = PCURVE('',#78,#188);
#188 = DEFINITIONAL_REPRESENTATION('',(#189),#193);
#189 = LINE('',#190,#191);
#190 = CARTESIAN_POINT('',(6.28318530718,-0.));
#191 = VECTOR('',#192,1.);
#192 = DIRECTION('',(0.,1.));
#193 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#194 = PCURVE('',#78,#195);
#195 = DEFINITIONAL_REPRESENTATION('',(#196),#200);
#196 = LINE('',#197,#198);
#197 = CARTESIAN_POINT('',(0.,-0.));
#198 = VECTOR('',#199,1.);
#199 = DIRECTION('',(0.,1.));
#200 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#201 = ORIENTED_EDGE('',*,*,#57,.T.);
#202 = ORIENTED_EDGE('',*,*,#181,.T.);
#203 = ADVANCED_FACE('',(#204,#207),#132,.F.);
#204 = FACE_BOUND('',#205,.F.);
#205 = EDGE_LOOP('',(#206));
#206 = ORIENTED_EDGE('',*,*,#117,.T.);
#207 = FACE_BOUND('',#208,.F.);
#208 = EDGE_LOOP('',(#209));
#209 = ORIENTED_EDGE('',*,*,#210,.F.);
#210 = EDGE_CURVE('',#211,#211,#213,.T.);
#211 = VERTEX_POINT('',#212);
#212 = CARTESIAN_POINT('',(-4.,2.,0.));
#213 = SURFACE_CURVE('',#214,(#219,#226),.PCURVE_S1.);
#214 = CIRCLE('',#215,4.);
#215 = AXIS2_PLACEMENT_3D('',#216,#217,#218);
#216 = CARTESIAN_POINT('',(0.,2.,0.));
#217 = DIRECTION('',(0.,-1.,0.));
#218 = DIRECTION('',(-1.,0.,0.));
#219 = PCURVE('',#132,#220);
#220 = DEFINITIONAL_REPRESENTATION('',(#221),#225);
#221 = CIRCLE('',#222,4.);
#222 = AXIS2_PLACEMENT_2D('',#223,#224);
#223 = CARTESIAN_POINT('',(0.,0.));
#224 = DIRECTION('',(1.,0.));
#225 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#226 = PCURVE('',#227,#232);
#227 = CYLINDRICAL_SURFACE('',#228,4.);
#228 = AXIS2_PLACEMENT_3D('',#229,#230,#231);
#229 = CARTESIAN_POINT('',(0.,2.,0.));
#230 = DIRECTION('',(-0.,1.,-0.));
#231 = DIRECTION('',(-1.,0.,0.));
#232 = DEFINITIONAL_REPRESENTATION('',(#233),#237);
#233 = LINE('',#234,#235);
#234 = CARTESIAN_POINT('',(-0.,0.));
#235 = VECTOR('',#236,1.);
#236 = DIRECTION('',(-1.,0.));
#237 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#238 = ADVANCED_FACE('',(#239,#265),#165,.F.);
#239 = FACE_BOUND('',#240,.F.);
#240 = EDGE_LOOP('',(#241));
#241 = ORIENTED_EDGE('',*,*,#242,.T.);
#242 = EDGE_CURVE('',#243,#243,#245,.T.);
#243 = VERTEX_POINT('',#244);
#244 = CARTESIAN_POINT('',(-4.,10.2,0.));
#245 = SURFACE_CURVE('',#246,(#251,#258),.PCURVE_S1.);
#246 = CIRCLE('',#247,4.);
#247 = AXIS2_PLACEMENT_3D('',#248,#249,#250);
#248 = CARTESIAN_POINT('',(0.,10.2,0.));
#249 = DIRECTION('',(0.,-1.,0.));
#250 = DIRECTION('',(-1.,0.,0.));
#251 = PCURVE('',#165,#252);
#252 = DEFINITIONAL_REPRESENTATION('',(#253),#257);
#253 = CIRCLE('',#254,4.);
#254 = AXIS2_PLACEMENT_2D('',#255,#256);
#255 = CARTESIAN_POINT('',(0.,0.));
#256 = DIRECTION('',(1.,0.));
#257 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#258 = PCURVE('',#227,#259);
#259 = DEFINITIONAL_REPRESENTATION('',(#260),#264);
#260 = LINE('',#261,#262);
#261 = CARTESIAN_POINT('',(-0.,8.2));
#262 = VECTOR('',#263,1.);
#263 = DIRECTION('',(-1.,0.));
#264 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#265 = FACE_BOUND('',#266,.F.);
#266 = EDGE_LOOP('',(#267));
#267 = ORIENTED_EDGE('',*,*,#148,.T.);
#268 = ADVANCED_FACE('',(#269),#227,.T.);
#269 = FACE_BOUND('',#270,.F.);
#270 = EDGE_LOOP('',(#271,#272,#293,#294));
#271 = ORIENTED_EDGE('',*,*,#210,.T.);
#272 = ORIENTED_EDGE('',*,*,#273,.T.);
#273 = EDGE_CURVE('',#211,#243,#274,.T.);
#274 = SEAM_CURVE('',#275,(#279,#286),.PCURVE_S1.);
#275 = LINE('',#276,#277);
#276 = CARTESIAN_POINT('',(-4.,2.,0.));
#277 = VECTOR('',#278,1.);
#278 = DIRECTION('',(0.,1.,0.));
#279 = PCURVE('',#227,#280);
#280 = DEFINITIONAL_REPRESENTATION('',(#281),#285);
#281 = LINE('',#282,#283);
#282 = CARTESIAN_POINT('',(-0.,0.));
#283 = VECTOR('',#284,1.);
#284 = DIRECTION('',(-0.,1.));
#285 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#286 = PCURVE('',#227,#287);
#287 = DEFINITIONAL_REPRESENTATION('',(#288),#292);
#288 = LINE('',#289,#290);
#289 = CARTESIAN_POINT('',(-6.28318530718,0.));
#290 = VECTOR('',#291,1.);
#291 = DIRECTION('',(-0.,1.));
#292 = ( GEOMETRIC_REPRESENTATION_CONTEXT(2)
PARAMETRIC_REPRESENTATION_CONTEXT() REPRESENTATION_CONTEXT('2D SPACE',''
) );
#293 = ORIENTED_EDGE('',*,*,#242,.F.);
#294 = ORIENTED_EDGE('',*,*,#273,.F.);
#295 = ( GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#299)) GLOBAL_UNIT_ASSIGNED_CONTEXT
((#296,#297,#298)) REPRESENTATION_CONTEXT('Context #1',
'3D Context with UNIT and UNCERTAINTY') );
#296 = ( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) );
#297 = ( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) );
#298 = ( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() );
#299 = UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(1.E-07),#296,
'distance_accuracy_value','confusion accuracy');
#300 = PRODUCT_RELATED_PRODUCT_CATEGORY('part',$,(#7));
ENDSEC;
END-ISO-10303-21;
@@ -0,0 +1,9 @@
{
"extrude_add_blind": 2,
"extrude_add_two_sided": 1,
"hole_wizard": 2,
"reference_axis": 9,
"reference_plane": 23,
"revolve_add": 2,
"revolve_cut": 1
}
@@ -0,0 +1 @@
{}
+32 -9
View File
@@ -1,3 +1,8 @@
---
name: cad-engine
description: Generate and revise executable CDSL CAD models through the local schema, runtime, library references, and injected part-family planning bridges.
---
# CAD Engine Skill
## Purpose
@@ -6,15 +11,33 @@ Help an AI agent generate CAD models through the repository's CDSL engine.
## Required workflow
1. Read the engine README, `profile_schema.json`, `cdsl_schema.json`, and the
relevant engine modules. `cdsl_schema.json` is the executable CDSL input
contract; its nested object and array shapes are mandatory.
2. Search the official CDSL library for similar parts, profiles, and feature
sequences.
3. Produce or revise parameterized CDSL.
4. Validate that the CDSL can run through the `cdsl_only` path.
5. Compile the CDSL and generate a STEP file.
6. Return the generated artifact, validation result, and library references.
1. Use any injected CDSL part-skill bridge to establish the part structure,
parameter roles, and feature order. It is planning guidance only.
2. Submit a complete DesignIntent before reading CDSL references or generating
CDSL. It contains semantic structures and mappings, never sketch coordinates
or raw CAD code. The backend records canonical selected part skills.
3. Read the engine README, `profile_schema.json`, and `cdsl_schema.json`, then
search the official CDSL library for schema-valid profiles and feature
expressions. `cdsl_schema.json` is the executable input contract.
4. Produce parameterized CDSL in feature dependency order, using the accepted
DesignIntent's feature IDs and mappings plus the part
plan for intent and CDSL references for concrete schema expressions.
5. Call the generation tool only when every chosen feature is executable by
the current schema and runtime. Validate through the `cdsl_only` path,
then generate STEP and GLB artifacts.
## Part-skill boundary
- User requirements and the CDSL schema/runtime override bridge guidance;
CDSL examples are lower-priority expression references.
- Do not expose upstream source skills directly or treat them as code. Use
only the injected CDSL bridge content.
- Never emit build123d source or invent an atomic, profile, selector, or
capability that the local contract does not support.
- Express circular bolt layouts as explicit circles when supported; preserve
thread intent as a cylindrical-bore approximation; omit fillets/chamfers
without a unique selector. These decisions must be recorded in the build
audit rather than hidden in generated geometry.
## Hard constraints
@@ -0,0 +1,40 @@
# DesignIntent Planning Recipe
## Requirement analysis
1. Treat the backend-injected part-family skill as the selected family before
identifying structures. It provides planning guidance only.
2. Decompose the requested part into structures with roles: `base`,
`reference`, `additive`, `subtractive`, `dressup`, or `pattern`.
3. Every structure must state a purpose, structural dependencies, parameter
roles, selector roles, an executable CDSL atomic ID, and a named profile
type when its atomic needs a sketch.
4. Put every structure exactly once in `feature_order`, after its dependencies.
Its `cdsl_feature_id` is the immutable ID that the later CDSL feature must use.
5. Do not put coordinates, raw CDSL, Build123d, or low-level sketch data in a
DesignIntent.
6. Missing essential dimensions are blocking `open_questions`. Unsupported
geometry is a `capability_gaps` item. A blocking item requires
`status: needs_clarification`; do not approximate silently.
## Part family boundary
- Use only the backend-injected primary and support skills. Never submit
`part_skill_ids`; the backend records the canonical selection for audit.
- Explicit user requirements override part skills. The executable CDSL
schema/runtime overrides both part skills and library examples.
- The current primary family is inherited during revisions unless the user
explicitly asks to replace the whole part. A family conflict needs a concise
clarification before a ready plan can be submitted.
- CDSL library examples show only expression patterns. They never add or remove
a requested DesignIntent structure.
## CDSL generation
- Submit `propose_design_intent` before any library or CDSL-generation call.
- After its ready result includes `intent_id`, inspect references and generate
complete CDSL. Use every planned `cdsl_feature_id` once, in `feature_order`.
- Do not add undeclared CDSL features, delete planned features, change planned
atomics/profiles, or omit declared selector evidence.
- Keep `cdsl_only` as the only build path. Never use `compiler_context` or a
legacy fallback.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 earthtojake
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,36 @@
# Part Skill Provenance
The files under `source/` are copied from the local `cadSet` checkout:
`/Users/lk/Projects/cadSet/text-to-cad/skills/cad/references/cad-skillx/`
The source planning, functional, and atomic documents are MIT licensed by
earthtojake. The files under `bridge/` are local CDSL-specific adaptations.
They preserve the structural guidance while documenting the current CDSL
schema/runtime boundary. Keep the source copies unchanged when possible and
review future upstream updates manually.
Each local `source/` path maps to the same relative path below the upstream
root above. The copied source documents are:
- `planning/mounting-plate.planning.md`
- `planning/mounting-bracket.planning.md`
- `planning/flange.planning.md`
- `planning/simple-shaft-or-cylindrical-rod.planning.md`
- `planning/bearing-housing-or-seat.planning.md`
- `planning/hexagonal-nut.planning.md`
- `functional/axisymmetric-revolve-strategy.functional.md`
- `functional/bearing-bore-seat.functional.md`
- `functional/flange-bolt-circle.functional.md`
- `functional/mounting-plate-hole-layout.functional.md`
- `functional/slotted-adjustment-feature.functional.md`
- `functional/standard-hole-wizard.functional.md`
- `functional/symmetric-feature-layout.functional.md`
- `atomic/coaxial-bore-rule.atomic.md`
- `atomic/counterbored-hole-creation.atomic.md`
- `atomic/extrude-base-profile.atomic.md`
- `atomic/fillet-chamfer-last.atomic.md`
- `atomic/pattern-holes-from-datum.atomic.md`
- `atomic/revolve-profile-around-axis.atomic.md`
- `atomic/threaded-hole-creation.atomic.md`
- `atomic/through-hole-cut.atomic.md`
@@ -0,0 +1,5 @@
# CDSL Bridge: Coaxial Bore
- Share one explicit axis or workplane origin across bore, seat, hub, sleeve, and clearance features.
- Keep each diameter role distinct. Use sequential cuts for a through clearance plus a deeper/larger seat.
- Prefer `reference_axis`, explicit frames, `revolve_cut`, or circular cut profiles; do not infer a new axis from arbitrary topology.
@@ -0,0 +1,6 @@
# CDSL Bridge: Counterbored Hole
- A counterbore is a smaller functional hole plus a larger, shallow coaxial recess.
- Use `hole_counterbore` or `hole_wizard` with `counterbore` fields when host placement is resolvable.
- Preserve main diameter, depth, counterbore diameter, and counterbore depth separately.
- Do not use a plain blind hole when the screw-head seat is part of the request.
@@ -0,0 +1,5 @@
# CDSL Bridge: Extrude Base
- Start with a closed named profile and a workplane. Use `extrude_add_blind` for a plate, block, annulus, hex body, or constant cylinder.
- Keep the base simple; add holes, slots, bosses, and finishing as dependent features.
- Use one coherent body unless separate solids are explicitly requested.
@@ -0,0 +1,5 @@
# CDSL Bridge: Fillet And Chamfer Last
- Apply `fillet` and `chamfer` only after primary adds, bores, holes, pockets, and patterns.
- Both operations require selectors that resolve uniquely on the active body.
- If no stable selector is available, omit the finishing operation and record `omitted: selector_unavailable`; never apply it to every edge.
@@ -0,0 +1,5 @@
# CDSL Bridge: Pattern Holes From Datum
- Build a valid seed hole first and preserve its host frame and functional subtype.
- Use `pattern_linear` for one/two-dimensional rows and `pattern_mirror` for symmetry.
- For circular layouts, use an explicit `circles` profile expansion and record the audit status; do not invent a circular-pattern atomic.
@@ -0,0 +1,5 @@
# CDSL Bridge: Revolve Profile
- Use a closed side profile and explicit axis with `revolve_add` or `revolve_cut`.
- Use 360 degrees unless a partial sector is requested. Keep the profile from crossing the axis invalidly.
- Add flats, slots, keyways, and bolt holes only after the rotational body is valid.
@@ -0,0 +1,5 @@
# CDSL Bridge: Threaded Hole
- Preserve thread size/depth as user intent and use the supported hole contract for the bore.
- Current CDSL runtime does not generate helical thread topology. Do not claim that it does.
- Record `approximated: thread_geometry_omitted` in the revision audit and include the bore dimensions used.
@@ -0,0 +1,5 @@
# CDSL Bridge: Through Hole
- Use a host face/frame and an explicit diameter. Prefer `hole_wizard` with a through end condition when its selector contract is complete.
- Otherwise use a circular sketch and a cut depth that is explicitly sufficient for the body, preserving the through-hole intent in the feature name/assumptions.
- Keep central bores on the main axis and repeated holes in a pattern or explicit circle group.
@@ -0,0 +1,6 @@
# CDSL Bridge: Axisymmetric Revolve
- Choose a closed half-profile and an explicit `axis` for `revolve_add` or `revolve_cut`.
- Use 360 degrees for a complete body. Keep the profile on one valid side of the axis.
- Prefer revolve for steps, shoulders, tapers, grooves, sleeves, and turned parts; use circle extrusion for a constant cylinder.
- Validate one coherent solid and shared axis before adding non-axisymmetric cuts.
@@ -0,0 +1,6 @@
# CDSL Bridge: Bearing Bore Seat
- Build enough support material first, then cut the smaller shaft clearance and larger bearing seat on the same frame.
- Use a defined seat depth and shoulder; never collapse both diameters into one hole.
- Express coaxial cuts with explicit frames or a stable reference axis/plane. Mounting holes follow the seat relationship.
- If a selector cannot identify the seat face uniquely, stop the dependent feature and report the blocker.
@@ -0,0 +1,6 @@
# CDSL Bridge: Flange Bolt Circle
- Keep bolt-circle diameter, count, start angle, and hole diameter explicit.
- Since the runtime has no circular-pattern atomic, expand the layout into explicit circle centers in a `circles` profile and cut them together.
- This is a semantic expansion, not an arbitrary approximation; report `expanded: circular_pattern_to_explicit_circles`.
- Do not use a rectangular grid for a circular flange.
@@ -0,0 +1,6 @@
# CDSL Bridge: Mounting Plate Hole Layout
- Establish plate edge/center datums before hole placement.
- Use one seed hole or explicit hole group, then `pattern_linear` or `pattern_mirror` for rows and symmetry.
- Select clearance, threaded, counterbored, or countersunk intent from the request; do not reduce every hole to a generic cylinder.
- Preserve margins and pitch. Finish after all functional holes and slots.
@@ -0,0 +1,6 @@
# CDSL Bridge: Slotted Adjustment
- A slot is an obround cut with equal end radii, dimensioned by width, length, and a datum frame.
- Use an `obround` sketch and `extrude_cut_blind`/through-depth after the base exists.
- A slot is not a shaft keyway or revolved groove. Pattern or mirror only after the seed cut is valid.
- Leave material at slot ends and finish slot edges last when selectors are available.
@@ -0,0 +1,6 @@
# CDSL Bridge: Standard Hole Wizard
- Choose the hole subtype from function: clearance, blind, through, counterbore, countersink, or threaded intent.
- Use `hole_wizard` only with its exact schema fields and a resolvable host face, or use a self-contained circular cut when the frame is explicit.
- Positions are objects such as `{"mm": [u, v, w]}`; never emit bare coordinate arrays.
- Thread metadata is retained as intent but current geometry is cylindrical; report the approximation.
@@ -0,0 +1,6 @@
# CDSL Bridge: Symmetric Feature Layout
- Define a centerline, plane, or datum before placing symmetric features.
- Use `pattern_mirror` for a mirrored source feature and `pattern_linear` for regular rows.
- The source feature must execute first and its transformed selectors/frames must remain uniquely resolvable.
- Never create symmetry by independently guessed coordinates when a pattern contract is available.
@@ -0,0 +1,7 @@
# CDSL Bridge: Bearing Housing
- Use for a bearing seat, bearing support, pillow-block-like body, or bushing seat, not a rolling-bearing assembly.
- Invariants: one bearing axis, distinct bearing seat and shaft-clearance diameters, a locating shoulder/depth, and a mounting base or flange.
- Plan: base/support add -> coaxial shaft bore -> larger bearing pocket/counterbore -> mounting holes -> ribs/bosses -> late finishing.
- Keep bearing outer diameter, seat depth, shaft clearance, center height, wall thickness, and mounting pattern separate.
- Ribs must connect base and support; never add floating decorative ribs.
@@ -0,0 +1,7 @@
# CDSL Bridge: Flange
- Use for a circular flange, flanged sleeve, or coupling flange with a shared central axis.
- Invariants: coaxial outside diameter, bore/hub, sealing land, and fastener holes on a pitch circle.
- Plan: `revolve_add` for stepped axisymmetric bodies or circular `extrude_add_blind` -> coaxial bore -> one bolt-hole definition -> explicit circle set or supported repetition -> late finishing.
- No circular-pattern atomic exists. Expand a bolt circle into explicit circles in one cut profile and record `expanded` in the audit.
- Keep bolt-circle diameter, count, start angle, bore, and hub dimensions distinct.
@@ -0,0 +1,7 @@
# CDSL Bridge: Hexagonal Nut
- Use for a hex body with a centered internal threaded-bore intent.
- Invariants: across-flats, thickness, centered bore, and functional lead-in chamfers.
- Plan: polygon/hex `extrude_add_blind` -> centered bore or `hole_wizard` -> thread audit -> finishing when selectors are valid.
- Preserve across-flats separately from thread/bore diameter. Do not create a mating bolt.
- Current runtime does not create helical threads; represent the bore and record `approximated: thread_geometry_omitted`.
@@ -0,0 +1,7 @@
# CDSL Bridge: Mounting Bracket
- Use for an L/U/T bracket or a support with a base plus web, lug, tab, or boss.
- Invariants: one coherent body, a load path from base to web/boss, datum-driven holes, and no invented fastener assembly.
- Plan: base `extrude_add_blind` -> web/tab/boss add features -> clearance/slot cuts -> holes -> mirror/linear repetition -> late edge treatment.
- Use named planar profiles and explicit workplanes. Keep holes after the support geometry so they do not float.
- Do not flatten a 3D bracket into a plate or add fillets without a valid selector.
@@ -0,0 +1,7 @@
# CDSL Bridge: Mounting Plate
- Use for a mostly uniform plate, base, adapter, fixture, or panel.
- Invariants: a named rectangular/rounded profile, thickness, datum-based holes, and functional cutouts.
- Plan: `extrude_add_blind` base -> `hole_wizard` or cut-hole features -> `pattern_linear`/`pattern_mirror` -> slots and pockets -> late `fillet`/`chamfer` only with selectors.
- Prefer `rectangle`, `rectangle_with_fillets`, or `obround` profiles. Keep edge margins and pitch as named numeric parameters.
- Rectangular hole groups use linear/mirror patterns; never use circular-pattern semantics for a plate.
@@ -0,0 +1,7 @@
# CDSL Bridge: Simple Shaft
- Use for a single cylindrical rod, pin, spacer, or stepped shaft, not an assembly.
- Invariants: one main axis, one coherent solid, axial segment lengths/diameters, and secondary features referenced to the axis.
- Use a circle plus `extrude_add_blind` for a uniform rod; use a closed side profile plus `revolve_add` for steps, shoulders, tapers, or grooves.
- Add end holes, flats, slots, or keyway-like cuts only after the primary body. Finish last.
- Do not invent bearings, gears, couplings, or real thread geometry when not requested.
@@ -0,0 +1,96 @@
{
"schema_version": "1.0",
"max_planning": 1,
"max_support": 3,
"skills": [
{
"id": "planning/mounting-plate",
"kind": "planning",
"title": "Mounting Plate",
"priority": 100,
"triggers": ["mounting plate", "base plate", "adapter plate", "fixture plate", "安装底板", "底板", "转接板", "连接板", "面板"],
"exclude": ["flange", "法兰", "3d bracket", "mounting bracket", "轴承座"],
"bridge": "bridge/planning-mounting-plate.md",
"source": "source/planning/mounting-plate.planning.md",
"capability_translation_rules": ["exact: schema_valid_feature_sequence"],
"related": ["functional/mounting-plate-hole-layout", "functional/slotted-adjustment-feature", "functional/standard-hole-wizard", "functional/symmetric-feature-layout", "atomic/extrude-base-profile", "atomic/pattern-holes-from-datum", "atomic/fillet-chamfer-last"]
},
{
"id": "planning/mounting-bracket",
"kind": "planning",
"title": "Mounting Bracket",
"priority": 99,
"triggers": ["mounting bracket", "support bracket", "l bracket", "u bracket", "t bracket", "bracket with holes", "安装支架", "支撑架", "L型支架", "U型支架"],
"exclude": ["flat mounting plate only", "仅底板", "sheet metal"],
"bridge": "bridge/planning-mounting-bracket.md",
"source": "source/planning/mounting-bracket.planning.md",
"capability_translation_rules": ["exact: schema_valid_feature_sequence"],
"related": ["functional/slotted-adjustment-feature", "functional/standard-hole-wizard", "functional/symmetric-feature-layout", "atomic/extrude-base-profile", "atomic/pattern-holes-from-datum", "atomic/fillet-chamfer-last"]
},
{
"id": "planning/flange",
"kind": "planning",
"title": "Flange",
"priority": 98,
"triggers": ["flange", "circular flange", "pipe flange", "coupling flange", "flanged sleeve", "法兰", "法兰盘", "管法兰", "联轴器法兰"],
"exclude": ["flange nut", "法兰螺母", "side tab"],
"bridge": "bridge/planning-flange.md",
"source": "source/planning/flange.planning.md",
"capability_translation_rules": ["expanded: circular_pattern_to_explicit_circles"],
"related": ["functional/flange-bolt-circle", "functional/axisymmetric-revolve-strategy", "functional/standard-hole-wizard", "atomic/coaxial-bore-rule", "atomic/revolve-profile-around-axis", "atomic/pattern-holes-from-datum", "atomic/fillet-chamfer-last"]
},
{
"id": "planning/simple-shaft",
"kind": "planning",
"title": "Simple Shaft Or Cylindrical Rod",
"priority": 97,
"triggers": ["simple shaft", "cylindrical rod", "stepped shaft", "shaft", "rod", "pin", "standoff", "轴", "阶梯轴", "圆柱杆", "销", "支柱"],
"exclude": ["spindle assembly", "bearing assembly", "gearbox shaft assembly", "主轴组件", "轴承组件"],
"bridge": "bridge/planning-simple-shaft.md",
"source": "source/planning/simple-shaft-or-cylindrical-rod.planning.md",
"capability_translation_rules": ["exact: revolve_or_extrude_supported"],
"related": ["functional/axisymmetric-revolve-strategy", "atomic/coaxial-bore-rule", "atomic/revolve-profile-around-axis", "atomic/through-hole-cut", "atomic/fillet-chamfer-last"]
},
{
"id": "planning/bearing-housing",
"kind": "planning",
"title": "Bearing Housing Or Seat",
"priority": 96,
"triggers": ["bearing housing", "bearing seat", "bearing support", "bearing block", "bushing seat", "pillow block", "轴承座", "轴承支架", "轴承块", "衬套座", "带座轴承"],
"exclude": ["complete rolling bearing", "bearing assembly", "完整轴承", "轴承组件"],
"bridge": "bridge/planning-bearing-housing.md",
"source": "source/planning/bearing-housing-or-seat.planning.md",
"capability_translation_rules": ["blocked: selector_required_for_dependent_features"],
"related": ["functional/bearing-bore-seat", "functional/standard-hole-wizard", "atomic/coaxial-bore-rule", "atomic/extrude-base-profile", "atomic/revolve-profile-around-axis", "atomic/counterbored-hole-creation", "atomic/fillet-chamfer-last"]
},
{
"id": "planning/hexagonal-nut",
"kind": "planning",
"title": "Hexagonal Nut",
"priority": 95,
"triggers": ["hex nut", "hexagonal nut", "threaded nut", "hexagonal threaded insert", "六角螺母", "六角螺帽", "螺纹螺母"],
"exclude": ["bolt", "screw", "wing nut", "cap nut", "flange nut", "螺栓", "螺钉", "蝶形螺母", "法兰螺母"],
"bridge": "bridge/planning-hexagonal-nut.md",
"source": "source/planning/hexagonal-nut.planning.md",
"capability_translation_rules": ["approximated: thread_geometry_omitted"],
"related": ["functional/axisymmetric-revolve-strategy", "functional/standard-hole-wizard", "atomic/extrude-base-profile", "atomic/coaxial-bore-rule", "atomic/threaded-hole-creation", "atomic/fillet-chamfer-last"]
},
{"id": "functional/axisymmetric-revolve-strategy", "kind": "functional", "title": "Axisymmetric Revolve", "priority": 80, "triggers": ["axisymmetric", "revolve", "revolved body", "rotational part", "turned part", "回转体", "轴对称", "车削件"], "exclude": ["simple extruded cylinder", "rectangular plate"], "bridge": "bridge/functional-axisymmetric-revolve.md", "source": "source/functional/axisymmetric-revolve-strategy.functional.md", "capability_translation_rules": ["exact: explicit_revolve_axis"], "related": []},
{"id": "functional/bearing-bore-seat", "kind": "functional", "title": "Bearing Bore Seat", "priority": 79, "triggers": ["bearing bore", "bearing pocket", "bearing seat", "bushing bore", "shaft clearance bore", "轴承孔", "轴承座孔", "轴承腔", "轴间隙孔"], "exclude": [], "bridge": "bridge/functional-bearing-bore-seat.md", "source": "source/functional/bearing-bore-seat.functional.md", "capability_translation_rules": ["blocked: selector_required_for_dependent_features"], "related": ["atomic/coaxial-bore-rule", "atomic/counterbored-hole-creation"]},
{"id": "functional/flange-bolt-circle", "kind": "functional", "title": "Flange Bolt Circle", "priority": 78, "triggers": ["flange bolt circle", "bolt circle", "pitch circle", "pcd", "circular bolt pattern", "螺栓圆", "节圆", "圆周孔阵列"], "exclude": ["rectangular hole grid", "矩形孔阵列"], "bridge": "bridge/functional-flange-bolt-circle.md", "source": "source/functional/flange-bolt-circle.functional.md", "capability_translation_rules": ["expanded: circular_pattern_to_explicit_circles", "blocked: explicit_circle_layout_required"], "related": ["atomic/pattern-holes-from-datum"]},
{"id": "functional/mounting-plate-hole-layout", "kind": "functional", "title": "Mounting Plate Hole Layout", "priority": 77, "triggers": ["mounting plate hole layout", "base plate hole pattern", "rectangular hole grid", "symmetric mounting holes", "底板孔布局", "矩形孔阵列", "安装孔阵列"], "exclude": ["flange bolt circle", "bearing bore"], "bridge": "bridge/functional-mounting-plate-hole-layout.md", "source": "source/functional/mounting-plate-hole-layout.functional.md", "capability_translation_rules": ["exact: linear_or_mirror_pattern"], "related": ["atomic/pattern-holes-from-datum", "functional/symmetric-feature-layout"]},
{"id": "functional/slotted-adjustment-feature", "kind": "functional", "title": "Slotted Adjustment Feature", "priority": 76, "triggers": ["slot", "slotted hole", "adjustment slot", "elongated hole", "guide slot", "槽", "长圆槽", "调节槽", "导向槽"], "exclude": ["shaft keyway", "revolved groove", "轴键槽", "回转槽"], "bridge": "bridge/functional-slotted-adjustment.md", "source": "source/functional/slotted-adjustment-feature.functional.md", "capability_translation_rules": ["exact: obround_extrude_cut"], "related": []},
{"id": "functional/standard-hole-wizard", "kind": "functional", "title": "Standard Hole Wizard", "priority": 75, "triggers": ["hole wizard", "standard holes", "threaded holes", "counterbored holes", "countersunk holes", "tapped holes", "孔向导", "标准孔", "螺纹孔", "沉孔", "沉头孔"], "exclude": [], "bridge": "bridge/functional-standard-hole-wizard.md", "source": "source/functional/standard-hole-wizard.functional.md", "capability_translation_rules": ["approximated: thread_geometry_omitted", "exact: supported_hole_subtype"], "related": ["atomic/counterbored-hole-creation", "atomic/threaded-hole-creation", "atomic/through-hole-cut"]},
{"id": "functional/symmetric-feature-layout", "kind": "functional", "title": "Symmetric Feature Layout", "priority": 74, "triggers": ["symmetric holes", "mirror pattern", "symmetry", "mirrored features", "centerline layout", "对称孔", "镜像阵列", "对称布局", "中心线布局"], "exclude": [], "bridge": "bridge/functional-symmetric-layout.md", "source": "source/functional/symmetric-feature-layout.functional.md", "capability_translation_rules": ["exact: pattern_mirror_or_pattern_linear"], "related": []},
{"id": "atomic/coaxial-bore-rule", "kind": "atomic", "title": "Coaxial Bore", "priority": 60, "triggers": ["coaxial bore", "central bore", "concentric", "shaft clearance", "同轴孔", "中心孔", "同心", "轴间隙"], "exclude": [], "bridge": "bridge/atomic-coaxial-bore.md", "source": "source/atomic/coaxial-bore-rule.atomic.md", "capability_translation_rules": ["exact: explicit_axis_or_frame"], "related": []},
{"id": "atomic/counterbored-hole-creation", "kind": "atomic", "title": "Counterbored Hole", "priority": 59, "triggers": ["counterbored hole", "counterbore", "socket head screw", "cap screw", "recessed screw head", "沉孔", "内六角螺钉", "螺钉头沉孔"], "exclude": [], "bridge": "bridge/atomic-counterbore.md", "source": "source/atomic/counterbored-hole-creation.atomic.md", "capability_translation_rules": ["exact: hole_counterbore"], "related": []},
{"id": "atomic/extrude-base-profile", "kind": "atomic", "title": "Extrude Base Profile", "priority": 58, "triggers": ["extrude base profile", "extrude rectangle", "extrude circle", "base extrusion", "simple cylinder", "rectangular block", "基础拉伸", "矩形拉伸", "圆柱拉伸"], "exclude": [], "bridge": "bridge/atomic-extrude-base.md", "source": "source/atomic/extrude-base-profile.atomic.md", "capability_translation_rules": ["exact: extrude_add_blind"], "related": []},
{"id": "atomic/fillet-chamfer-last", "kind": "atomic", "title": "Fillet And Chamfer Last", "priority": 57, "triggers": ["fillet chamfer last", "edge treatment", "chamfered edges", "filleted edges", "最后倒角", "最后圆角", "边缘处理"], "exclude": [], "bridge": "bridge/atomic-fillet-chamfer-last.md", "source": "source/atomic/fillet-chamfer-last.atomic.md", "capability_translation_rules": ["omitted: selector_unavailable", "exact: unique_selector"], "related": []},
{"id": "atomic/pattern-holes-from-datum", "kind": "atomic", "title": "Pattern Holes From Datum", "priority": 56, "triggers": ["hole pattern", "pattern holes from datum", "repeated holes", "symmetric holes", "孔阵列", "重复孔", "基准孔阵列", "对称孔"], "exclude": [], "bridge": "bridge/atomic-pattern-holes.md", "source": "source/atomic/pattern-holes-from-datum.atomic.md", "capability_translation_rules": ["expanded: explicit_circle_layout", "exact: pattern_linear_or_pattern_mirror"], "related": []},
{"id": "atomic/revolve-profile-around-axis", "kind": "atomic", "title": "Revolve Profile Around Axis", "priority": 55, "triggers": ["revolve profile", "revolve around axis", "revolved cut", "axisymmetric profile", "截面回转", "绕轴回转", "回转切除"], "exclude": [], "bridge": "bridge/atomic-revolve-profile.md", "source": "source/atomic/revolve-profile-around-axis.atomic.md", "capability_translation_rules": ["exact: revolve_add_or_revolve_cut"], "related": []},
{"id": "atomic/threaded-hole-creation", "kind": "atomic", "title": "Threaded Hole", "priority": 54, "triggers": ["threaded hole", "tapped hole", "internal thread", "m3", "m4", "m5", "m6", "螺纹孔", "攻丝孔", "内螺纹"], "exclude": [], "bridge": "bridge/atomic-threaded-hole.md", "source": "source/atomic/threaded-hole-creation.atomic.md", "capability_translation_rules": ["approximated: thread_geometry_omitted"], "related": []},
{"id": "atomic/through-hole-cut", "kind": "atomic", "title": "Through Hole Cut", "priority": 53, "triggers": ["through hole", "clearance hole", "central hole", "circular opening", "cut through", "bore through", "通孔", "间隙孔", "贯穿孔", "切穿"], "exclude": [], "bridge": "bridge/atomic-through-hole.md", "source": "source/atomic/through-hole-cut.atomic.md", "capability_translation_rules": ["exact: through_hole_or_explicit_cut"], "related": []}
]
}
@@ -0,0 +1,83 @@
# Coaxial Bore Atomic Skill
## When to use
- A model contains nested cylindrical features that must share one axis: bore, pocket, hub, sleeve, shaft clearance, bearing seat, pilot diameter, or counterbore.
- The request mentions concentric, coaxial, central bore, shaft hole, bearing pocket, or flanged cylindrical geometry.
## Do not use when
- Circular holes are independent mounting holes on a plate face.
- The circular features intentionally use different centers.
- The shape is decorative and has no functional axis.
## Recognition features
- Central bore or shaft clearance through a rotational or support body.
- Concentric circles in sketch evidence.
- Cylindrical faces nested around one axis.
- Bearing seat, flange hub, sleeve, bushing, washer, spacer, or shaft-like part.
## Core invariants
- Use one explicit construction axis or shared sketch origin for all central cylindrical features.
- Distinguish each diameter by role: clearance, seat, counterbore, outer diameter, pilot, groove.
- Cut through holes through all relevant material; cut seats and counterbores only to their functional depth.
- Do not create separate sketches whose centers can drift.
## Parameter roles
- `axis` is the shared centerline.
- `bore_diameter` controls the through opening.
- `seat_diameter` controls bearing/pilot/counterbore fit.
- `outer_diameter` controls surrounding material.
- `depth` controls blind pocket, counterbore, or groove depth.
- `lead_in_chamfer` controls assembly entry.
## Construction sequence
1. Define the shared axis using the sketch origin, construction line, or selected cylindrical face.
2. Sketch concentric circles or a revolved profile from that axis.
3. Cut the smallest through bore first when it establishes shaft clearance.
4. Cut larger seats, pockets, or counterbores to controlled depth.
5. Add grooves, chamfers, and fillets after the primary coaxial cuts are stable.
## Common failures
- Off-axis bore because each circle was sketched from a different center.
- Confusing through bore diameter with bearing seat diameter.
- Cutting the bearing pocket through all and deleting the shoulder.
- Treating unrelated bolt holes as coaxial features.
## Evidence summary
- Current library evidence: 50 reviewed SCAD58 flange and bearing-seat models.
- Latest run evidence: accepted insights for central bore alignment, concentric circles, bearing seats, flanges, washers, bushings, and axisymmetric bodies.
- Frequent operations in the latest run: Cut, RevCut, Revolution, Extrusion of concentric circles, Chamfer.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `coaxial bore`
- `central bore`
- `concentric`
- `shaft clearance`
Secondary triggers:
- `bearing seat`
- `pilot bore`
- `counterbore`
- `hub`
- `sleeve`
Operation triggers:
- `concentric sketch`
- `cut through bore`
- `counterbore to depth`
- `revolved cut`
Exclusions:
- `random face holes`
- `rectangular hole grid`
- `offset holes`
@@ -0,0 +1,89 @@
# Counterbored Hole Creation Atomic Skill
## When to use
- A fastener hole needs a cylindrical recess so a socket head, cap screw head, or flat seating surface sits flush or below the top face.
- The request mentions counterbore, counterbored hole, socket head screw, cap screw, recessed screw head, or flat-bottom screw seat.
## Do not use when
- The screw head requires a conical countersink rather than a cylindrical counterbore.
- The feature is a bearing pocket, large cylindrical recess, decorative ring, or simple through hole.
- The part has no fastener-seat function.
## Recognition features
- Through hole plus larger coaxial shallow cylindrical recess.
- HoleWzd evidence for counterbored screw holes.
- Descriptions mention recessed screw heads, socket head screws, counterbore depth, or flush mounting.
- Often appears on mounting plates, brackets, flanges, and covers.
## Core invariants
- Counterbore axis and through-hole axis are identical.
- Counterbore diameter must be larger than through-hole diameter.
- Counterbore depth must be enough for the screw head but not cut through the part unless requested.
- Create the hole as one standard feature when possible to preserve fastener intent.
- Pattern the complete counterbored hole feature when repeated.
## Parameter roles
- `through_hole_diameter` defines screw clearance.
- `counterbore_diameter` defines head clearance.
- `counterbore_depth` defines head seating depth.
- `fastener_size` defines standard screw selection.
- `placement_point` defines hole center.
- `end_condition` defines through, blind, or up-to-next behavior.
## Construction sequence
1. Select the mounting face where the screw head sits.
2. Place the hole center from datums or pattern references.
3. Create a counterbored hole feature with through diameter, counterbore diameter, and counterbore depth.
4. Confirm the counterbore opens on the correct face.
5. Pattern or mirror the full counterbored hole feature if repeated.
6. Add small entrance chamfers only after the counterbore is correct.
## Common failures
- Modeling only a shallow large circle without the through hole.
- Reversing the counterbore to the wrong side of the part.
- Making counterbore and through-hole diameters equal.
- Using countersink geometry for a socket head screw.
- Patterning only the through hole and forgetting the recess.
## Evidence summary
- Latest run evidence: 49 accepted atomic insights matched counterbored hole creation.
- Strong repeated names include `create_counterbored_hole`, `use_hole_wizard_for_counterbored_holes`, and `create_counterbore_hole_for_socket_head_screw`.
- Frequent operations in the latest run: HoleWzd, Cut, LinearPattern, MirrorPattern, Chamfer.
- Source model examples: 020555, 028735, 031527, 079425, 080632, 086114, 096301, 144289, 146696, 174675, 202122, 211326, 217613, 225627, 227232.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `counterbored hole`
- `counterbore`
- `socket head screw`
- `cap screw`
- `recessed screw head`
Secondary triggers:
- `flush screw`
- `counterbore depth`
- `counterbore diameter`
- `mounting hole`
- `fastener seat`
Operation triggers:
- `hole wizard counterbore`
- `cut counterbore`
- `pattern counterbored hole`
- `chamfer hole edge`
Exclusions:
- `countersink`
- `bearing pocket`
- `decorative ring`
- `plain through hole`
@@ -0,0 +1,87 @@
# Extrude Base Profile Atomic Skill
## When to use
- The first solid body is a simple prismatic shape made from a closed 2D profile: rectangle, rounded rectangle, circle, annulus, hexagon, or simple contour.
- The part is a plate, block, simple cylinder, bracket base, washer blank, spacer blank, or nut body made by extrusion.
## Do not use when
- The primary body has multiple coaxial diameter steps better made by revolve.
- The shape is freeform, lofted, swept, bent sheet metal, or surface-based.
- The extrusion profile is open or ambiguous.
## Recognition features
- SolidWorks evidence such as Extrusion/Boss-Extrude after a single sketch.
- STEP evidence dominated by planar faces for prismatic parts or cylindrical surfaces for simple cylinders.
- Descriptions mention rectangular block, plate, simple cylinder, extruded profile, or uniform thickness.
## Core invariants
- Start from a fully closed, constrained sketch.
- Extrude normal to the sketch plane by the intended thickness or length.
- Use symmetric or mid-plane extrusion when the part should stay centered on a datum.
- Keep the base body simple; add holes, pockets, bosses, slots, and edge treatments as later features.
- Use concentric circles for annular extrusions such as washers or tubes.
## Parameter roles
- `profile_type` selects rectangle, circle, annulus, hexagon, or custom closed contour.
- `length`, `width`, `radius`, `outer_radius`, `inner_radius`, and `across_flats` define the sketch.
- `extrude_depth` or `thickness` defines the solid depth.
- `extrude_direction` and `midplane` define placement relative to datums.
## Construction sequence
1. Select the reference plane or face for the base profile.
2. Draw and constrain the closed profile.
3. Apply dimensional constraints for width, length, radius, or diameter.
4. Extrude the profile to the required depth.
5. Keep the resulting body merged as the main solid unless separate bodies are explicitly requested.
6. Add secondary cuts and edge treatments afterward.
## Common failures
- Using an open sketch for solid extrusion.
- Combining holes and detailed cutouts into the first sketch when separate features would be clearer.
- Using multiple separate extrusions for what should be one base body.
- Extruding a stepped shaft when revolve would preserve axial intent better.
## Evidence summary
- Latest run evidence: 70 accepted atomic insights matched base extrusion from rectangular/circular profiles.
- Strong repeated names include `extrude_rectangle`, `extrude_circle`, `extrude_rectangle_from_sketch`, `extrude_rectangular_profile`, and `extrude_circular_profile`.
- Frequent operations in the latest run: Extrusion, Sketch, Cut, Chamfer.
- Source model examples: 000166, 026303, 050972, 053267, 088851, 114800, 148444, 157630, 162045, 188147, 209994, 220726, 237160, 238926.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `extrude base profile`
- `extrude rectangle`
- `extrude circle`
- `base extrusion`
- `simple cylinder`
- `rectangular block`
Secondary triggers:
- `plate blank`
- `bracket base`
- `washer blank`
- `annular extrusion`
- `uniform thickness`
Operation triggers:
- `sketch closed profile`
- `boss extrude`
- `extrude to thickness`
- `midplane extrusion`
Exclusions:
- `stepped shaft`
- `revolve profile`
- `loft`
- `sweep`
- `sheet metal bend`
@@ -0,0 +1,82 @@
# Fillet Chamfer Last Atomic Skill
## When to use
- A machined part needs edge breaks, deburring chamfers, assembly lead-ins, stress-relief fillets, or small finishing rounds.
- The edge treatment is secondary to the main body, holes, pockets, slots, and bores.
## Do not use when
- The rounded or chamfered profile is the primary defining shape, such as an O-ring groove, cam surface, ergonomic shell, or turbine blade.
- A sharp edge is explicitly required for sealing, locating, scraping, or mating.
- The chamfer/fillet would remove critical wall thickness around a hole, shoulder, or thin rib.
## Recognition features
- SolidWorks feature evidence such as Chamfer, Fillet, radius values, or angle-distance chamfers.
- STEP evidence with conical or toroidal surfaces near final edges.
- Descriptions mentioning chamfered edges, rounded corners, smooth transitions, edge relief, assembly ease, or stress reduction.
## Core invariants
- Apply edge treatments after primary solids, holes, bores, pockets, slots, and patterns are stable.
- Keep edge treatment small relative to nearby feature size and wall thickness.
- Use chamfer for lead-in/deburring and fillet for stress relief or smooth load transition.
- Select specific edges; do not blindly fillet or chamfer every edge.
- Preserve functional shoulders, datum faces, seal lands, and fastener seats.
## Parameter roles
- `chamfer_distance` defines bevel size.
- `chamfer_angle` defines bevel angle, often 45 degrees when not otherwise specified.
- `fillet_radius` defines round size.
- `edge_set` defines which edges receive treatment.
- `minimum_wall_after_edge_treatment` protects small holes, ribs, and pockets.
## Construction sequence
1. Complete the main body and all functional cuts.
2. Identify functional edges that need lead-in, deburring, stress relief, or handling safety.
3. Apply chamfers to hole entrances, exposed outer edges, or insertion edges.
4. Apply fillets to internal corners, web transitions, and load-bearing transitions.
5. Verify that holes, slots, shoulders, and bearing seats remain intact.
## Common failures
- Adding large fillets early and causing later cuts or patterns to fail.
- Chamfering a bearing shoulder, seal land, or datum face that should remain crisp.
- Using one global fillet radius across tiny holes and large outer edges.
- Treating decorative edge rounding as more important than functional geometry.
## Evidence summary
- Current library evidence: 58 reviewed SCAD58 machined parts with final chamfer/fillet operations.
- Latest run evidence: high-frequency accepted insights for `chamfer_edges`, `chamfer_edges_for_safety`, `apply_chamfer_to_edges`, `apply_fillet_to_edges`, and stress-relief fillets.
- Frequent operations in the latest run: Chamfer and Fillet after Extrusion, Cut, HoleWzd, Pattern, and Revolution.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `fillet chamfer last`
- `edge treatment`
- `chamfered edges`
- `filleted edges`
Secondary triggers:
- `deburr`
- `assembly lead in`
- `stress relief`
- `smooth transition`
- `edge break`
Operation triggers:
- `chamfer`
- `fillet`
- `apply edge treatment after cuts`
Exclusions:
- `primary rounded profile`
- `sharp datum edge`
- `seal land`
- `cam surface`
@@ -0,0 +1,86 @@
# Pattern Holes From Datum Atomic Skill
## When to use
- A model has repeated holes or repeated cut features that should be controlled by pitch, angle, symmetry, or a construction datum.
- The design intent is a hole row, hole grid, mirrored pair, circular bolt circle, or repeated mounting pattern.
## Do not use when
- There is only one custom non-repeated hole.
- Holes are intentionally irregular or individually dimensioned.
- The feature is decorative and not constrained by functional layout.
## Recognition features
- Multiple holes with equal spacing, equal angular spacing, or mirrored positions.
- SolidWorks evidence such as LinearPattern, CircularPattern, MirrorPattern, or repeated HoleWzd features.
- Datum clues such as centerlines, edge offsets, pitch dimensions, central axis, or symmetry planes.
## Core invariants
- Build one correct seed hole or seed cut feature first.
- Constrain the seed from a datum: edge offset, centerline, pitch circle, or axis.
- Pattern the feature/cut, not only sketch circles.
- Choose pattern type from geometry: linear for rows/grids, circular for flange bolt circles, mirror for symmetric pairs.
- Keep the pattern editable by count, pitch, angle, and axis rather than hard-coded coordinates.
## Parameter roles
- `seed_position` defines the first hole from the datum.
- `pitch` defines linear spacing.
- `count` defines number of repeated instances.
- `pattern_direction` defines row/grid direction.
- `pattern_axis` defines circular pattern axis.
- `total_angle` is usually 360 degrees for full bolt circles.
- `mirror_plane` defines symmetric duplication.
## Construction sequence
1. Define the datum: edge offset, centerline, symmetry plane, or central axis.
2. Create and validate one seed hole or cut feature.
3. Select linear, circular, or mirror pattern based on the part family and layout.
4. Pattern the feature with explicit count and spacing/angle.
5. Add shared counterbore/countersink/thread detail before patterning when all instances match.
6. Verify clearance to edges, central bores, slots, and neighboring holes.
## Common failures
- Duplicating sketch circles without actual cuts.
- Using linear pattern for a flange bolt circle.
- Using circular pattern for rectangular plate holes.
- Patterning counterbore circles without patterning the underlying through hole.
- Missing the datum so later size changes break alignment.
## Evidence summary
- Current library evidence: 58 reviewed SCAD58 models with repeated hole patterns.
- Latest run evidence: accepted insights for linear repeated holes, mirror symmetry, symmetric hole placement, circular bolt patterns, and datum-driven construction geometry.
- Frequent operations in the latest run: HoleWzd, Cut, LinearPattern, MirrorPattern, CircularPattern.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `hole pattern`
- `pattern holes from datum`
- `repeated holes`
- `symmetric holes`
Secondary triggers:
- `linear pattern`
- `circular pattern`
- `mirror pattern`
- `bolt holes`
- `hole grid`
Operation triggers:
- `pattern cut feature`
- `linear pattern`
- `circular pattern`
- `mirror`
Exclusions:
- `single custom hole`
- `decorative circles`
- `irregular holes`
@@ -0,0 +1,87 @@
# Revolve Profile Around Axis Atomic Skill
## When to use
- A single feature is created by revolving a 2D profile around an axis: stepped cylinder, shaft shoulder, sleeve, bushing, washer groove, nut blank, knob, or circular recess.
- The feature has a rotational cross-section that is easier to define in side view.
## Do not use when
- A plain constant-diameter cylinder can be created more simply by extruding a circle.
- The profile is open, crosses the axis incorrectly, or represents non-axisymmetric geometry.
- The feature is a rectangular plate, bracket wall, or arbitrary cutout.
## Recognition features
- Revolution/Revolve/RevCut feature evidence.
- Sketch contains an axis line or construction centerline.
- Multiple coaxial cylindrical, conical, or toroidal surfaces are expected.
- Profile controls axial steps, shoulders, tapers, grooves, or rounded transitions.
## Core invariants
- The revolve axis must be explicit and stable.
- The profile must be closed for material-adding revolve.
- Keep the profile on the correct side of the axis to avoid self-intersection.
- Use 360 degrees for complete rotational solids unless a partial sector is requested.
- Use revolved cut, not boss revolve, when removing a groove or internal recess.
## Parameter roles
- `axis` defines the rotation center.
- `profile` defines the closed side-view outline.
- `revolve_angle` defines full or partial rotation.
- `diameter_steps`, `axial_lengths`, `groove_depth`, and `taper_angle` define shape.
- `operation_type` selects add or cut.
## Construction sequence
1. Create a sketch on a plane that contains the desired axis.
2. Draw a construction axis or select a stable centerline.
3. Draw the closed profile for added material, or the removal profile for a revolved cut.
4. Revolve the profile around the axis, usually 360 degrees.
5. Validate that the result is one solid and all intended circular features are coaxial.
6. Add later holes, flats, slots, patterns, chamfers, and fillets as separate features.
## Common failures
- Revolving around the wrong edge or temporary line.
- Letting the sketch cross the axis and causing invalid geometry.
- Making several extruded cylinders instead of a single coherent turned profile.
- Using revolve for non-axisymmetric bracket or plate features.
## Evidence summary
- Latest run evidence: 81 accepted atomic insights matched revolve, revolved profile, or revolved cut operations.
- Strong repeated names include `revolve_profile`, `create_revolved_cut`, `revolve_profile_around_axis`, and `revolved_cut_for_internal_thread`.
- Frequent operations in the latest run: Revolution, RevCut, Sketch, Cut, Chamfer, Fillet.
- Source model examples: 009934, 010137, 015133, 026624, 053895, 079633, 086237, 094412, 139073, 146478, 161210, 162241, 222550, 238044.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `revolve profile`
- `revolve around axis`
- `revolved cut`
- `axisymmetric profile`
Secondary triggers:
- `shaft shoulder`
- `turned groove`
- `bushing`
- `washer`
- `nut blank`
- `stepped diameter`
Operation triggers:
- `draw construction axis`
- `revolve 360 degrees`
- `revolved cut`
- `sketch half profile`
Exclusions:
- `plain extruded cylinder`
- `rectangular plate`
- `bracket wall`
- `loft`
@@ -0,0 +1,93 @@
# Threaded Hole Creation Atomic Skill
## When to use
- A part needs an internal thread, tapped hole, threaded mounting hole, or screw hole with a specified metric/imperial size.
- The request mentions M3, M4, M5, M6, M8, M10, tapped hole, threaded hole, or thread depth.
## Do not use when
- The feature is an external thread on a bolt, screw, or shaft.
- The hole is only a smooth clearance hole.
- The model should avoid thread geometry and only show a simple bore unless the prompt asks for thread detail.
## Recognition features
- SolidWorks evidence such as HoleWzd threaded hole or thread parameters.
- Descriptions mention fastening, tapped holes, internal thread, or screw mounting.
- Hole is located on a planar face, boss face, or shaft end face.
## Core invariants
- Use a standard threaded hole feature or thread callout when available.
- Keep the threaded hole axis normal to the placement face unless angled threading is requested.
- Separate tap drill diameter, nominal thread size, hole depth, and thread depth.
- Keep enough wall material around the threaded hole.
- Add lead-in chamfer after the thread/hole is placed.
## Parameter roles
- `thread_size` defines nominal size such as M3/M4/M5/M6/M8/M10.
- `thread_pitch` defines fine/coarse pitch when specified.
- `thread_depth` defines threaded length.
- `hole_depth` defines blind hole depth if not through.
- `placement_point` and `face_normal` define location and direction.
- `lead_in_chamfer` defines assembly entry.
## Construction sequence
1. Select the planar face or end face for the threaded hole.
2. Place the hole center from datums, centerlines, or axis references.
3. Create the hole using a threaded HoleWzd/hole feature with size and depth.
4. If the backend cannot model threads, create the correct pilot bore and preserve thread intent in naming/comments.
5. Pattern or mirror the threaded hole feature when repeated.
6. Add a small lead-in chamfer after the threaded feature is stable.
## Common failures
- Modeling a threaded hole as a plain through cylinder.
- Creating external thread geometry on an internal hole.
- Placing threaded holes too close to edges, slots, or pockets.
- Forgetting blind depth or thread depth.
- Adding visual helical threads that make the model heavy or fragile when a callout is enough.
## Evidence summary
- Latest run evidence: 87 accepted atomic insights matched threaded/tapped hole creation.
- Strong repeated names include `create_threaded_hole_with_hole_wizard`, `create_threaded_hole`, `threaded_hole_placement`, and `use_hole_wizard_for_threaded_holes`.
- Frequent operations in the latest run: HoleWzd, Cut, RevCut, Chamfer, Pattern.
- Source model examples: 000924, 011524, 017151, 047238, 077661, 080683, 095520, 105855, 145389, 160434, 174675, 200790, 206894, 211326, 225767.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `threaded hole`
- `tapped hole`
- `internal thread`
- `M3`
- `M4`
- `M5`
- `M6`
- `M8`
- `M10`
Secondary triggers:
- `thread depth`
- `tap drill`
- `screw mounting`
- `threaded boss`
- `end threaded hole`
Operation triggers:
- `hole wizard threaded hole`
- `create tapped hole`
- `add thread callout`
- `lead in chamfer`
Exclusions:
- `external thread`
- `bolt`
- `screw`
- `clearance hole only`
- `decorative helix`
@@ -0,0 +1,89 @@
# Through Hole Cut Atomic Skill
## When to use
- A part needs a clean circular hole or bore that passes fully through a plate, block, bracket wall, flange, washer, spacer, or boss.
- The request mentions through hole, clearance hole, central hole, circular opening, bore through, or cut through all.
## Do not use when
- The hole is threaded, counterbored, countersunk, or a bearing seat requiring special geometry.
- The circular feature is a blind pocket, groove, decorative recess, or non-through relief.
- The hole is part of a patterned bolt circle; combine with the pattern skill for repeated holes.
## Recognition features
- Circular sketch on a planar face followed by Cut-Extrude or HoleWzd through all.
- Cylindrical internal face runs through the full thickness.
- Used for clearance, alignment, lightening, shaft passage, or central opening.
## Core invariants
- Hole axis should be normal to the face or coaxial with the main part axis.
- Use through-all end condition when the hole must pass through the entire body.
- Keep hole diameter and placement explicit.
- For central holes, use the part axis or centerline as the placement reference.
- For repeated holes, make one seed through hole and pattern it.
## Parameter roles
- `hole_diameter` defines opening size.
- `placement_point` defines center on the start face.
- `axis` defines coaxial central placement when needed.
- `end_condition` is usually through all.
- `face` defines the start plane.
## Construction sequence
1. Select the face or reference plane where the hole starts.
2. Place a circle center from datums, centerlines, or the main axis.
3. Sketch the circle with the required diameter.
4. Cut through all material in the intended direction.
5. Pattern or mirror the through-hole cut if repeated.
6. Add hole edge chamfers only after the cut exists.
## Common failures
- Creating a shallow blind pocket when a through hole is required.
- Leaving a sketch circle without cutting material.
- Placing a central hole off-axis.
- Treating threaded/counterbored/bearing holes as plain through holes.
- Cutting through unintended bodies in an assembly-like model.
## Evidence summary
- Latest run evidence: 49 accepted atomic insights matched through-hole and circular-cut creation.
- Strong repeated names include `create_through_hole`, `cut_circular_hole`, `cut_circle`, `cut_central_bore_through_plate`, and `create_through_holes_with_cut`.
- Frequent operations in the latest run: Cut, HoleWzd, CircularPattern, LinearPattern, Chamfer.
- Source model examples: 001563, 001942, 046907, 054633, 059866, 080145, 086131, 096474, 142955, 148675, 153729, 160077, 222192, 230562, 241179.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `through hole`
- `clearance hole`
- `central hole`
- `circular opening`
- `cut through`
- `bore through`
Secondary triggers:
- `shaft passage`
- `alignment hole`
- `lightening hole`
- `hole through plate`
- `hole through boss`
Operation triggers:
- `cut extrude through all`
- `cut circle`
- `hole wizard through hole`
- `pattern through hole`
Exclusions:
- `threaded hole`
- `counterbored hole`
- `countersink`
- `bearing seat`
- `blind pocket`
@@ -0,0 +1,91 @@
# Axisymmetric Revolve Strategy Functional Skill
## When to use
- A part or major feature is rotationally symmetric around an axis: shaft, stepped shaft, flange hub, bushing, washer, spacer, sleeve, pulley-like blank, turned knob, or nut blank.
- The profile contains steps, shoulders, grooves, tapers, spherical/rounded transitions, or coaxial bores that are easier to define in a half-section.
## Do not use when
- The body is a simple constant-diameter cylinder that can be created by one circle extrusion.
- The part is primarily prismatic with only small cylindrical holes.
- The geometry is freeform, non-axisymmetric, or has asymmetric lugs as the dominant feature.
## Recognition features
- Dominant central axis and circular cross-sections.
- SolidWorks features such as Revolution, Revolve, RevCut, or revolved thread/groove cuts.
- STEP evidence with multiple cylindrical, conical, or toroidal surfaces sharing an axis.
- Functional roles include shafting, spacing, sealing, rotation, alignment, or fastening.
## Core invariants
- Sketch the axial cross-section on a plane that contains the rotation axis.
- Keep the revolved profile on one side of the axis unless the CAD kernel expects a centerline profile.
- Use one 360-degree revolve for the main axisymmetric body where possible.
- Use revolved cuts for coaxial grooves, internal thread reliefs, and circular recesses.
- Add asymmetric holes, flats, keyways, or bolt patterns after the revolved body is complete.
## Parameter roles
- `axis` defines the rotation centerline.
- `profile_points` define the half-section outline.
- `diameters` and `axial_lengths` define steps and shoulders.
- `revolve_angle` is usually 360 degrees for a complete solid.
- `groove_width`, `groove_depth`, `taper_angle`, and `fillet_radius` define turned details.
## Construction sequence
1. Define the central axis and an axial sketch plane.
2. Draw the half-section profile with all major diameters, steps, shoulders, and bore boundaries.
3. Revolve the closed profile 360 degrees to create the main body.
4. Use revolved cuts for grooves, recesses, or thread reliefs that are also axisymmetric.
5. Add non-axisymmetric secondary features such as flats, slots, keyways, or bolt holes.
6. Apply chamfers and fillets at ends and shoulders last.
## Common failures
- Extruding several cylinders separately and leaving seams or separate bodies.
- Revolving a profile that crosses the axis and creates invalid self-intersections.
- Forgetting the construction axis or using an arbitrary edge as axis.
- Adding asymmetric features before the revolve and making the base profile ambiguous.
- Using revolve for a simple plain cylinder when extrusion is clearer.
## Evidence summary
- Latest run evidence: 105 accepted functional insights matched axisymmetric revolve and revolved-cut strategies.
- Strong repeated names include `use_revolve_for_axisymmetric_parts`, `use_revolve_for_rotational_parts`, `use_revolve_for_axisymmetric_body`, and `use_revolution_for_axisymmetric_features`.
- Frequent operations in the latest run: Revolution, RevCut, Cut, Chamfer, Fillet, HoleWzd.
- Source model examples: 001563, 001957, 009934, 010137, 025245, 053895, 080683, 086232, 094412, 135604, 144887, 162241, 173716, 218803, 225038.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `axisymmetric`
- `revolve`
- `revolved body`
- `rotational part`
- `turned part`
Secondary triggers:
- `shaft`
- `bushing`
- `washer`
- `sleeve`
- `flange hub`
- `stepped diameter`
- `groove`
Operation triggers:
- `sketch half profile`
- `revolve profile`
- `revolved cut`
- `define construction axis`
- `chamfer shoulders`
Exclusions:
- `simple extruded cylinder`
- `rectangular plate`
- `mounting bracket`
- `freeform surface`
@@ -0,0 +1,90 @@
# Bearing Bore Seat Functional Skill
## When to use
- A part must locate a bearing, bushing, sleeve, or shaft using a coaxial bore, counterbore, pocket, shoulder, or retaining seat.
- The requested model is a housing/seat component, bracketed bearing support, bushing support, or bearing retainer.
## Do not use when
- The part only has generic decorative holes or a simple washer hole.
- The task is to create a full bearing assembly with rolling elements.
- The central hole is only a screw clearance hole and not a shaft/bearing datum.
## Recognition features
- Through bore for shaft clearance.
- Larger coaxial pocket for bearing outside diameter.
- Shoulder or depth stop for axial location.
- Optional retaining groove, cover recess, set-screw hole, lubrication hole, or mounting base.
- Bore entry chamfers for assembly.
## Core invariants
- Shaft clearance bore and bearing seat remain coaxial.
- Seat diameter is larger than shaft clearance diameter when a bearing pocket is present.
- Seat depth must be represented; a bearing seat is not just a surface ring.
- Shoulder geometry should remain after the pocket cut.
- Mounting holes must not break into the bearing seat unless explicitly designed.
## Parameter roles
- `axis` defines the bore centerline.
- `shaft_clearance_diameter` defines the through hole.
- `bearing_outer_diameter` defines the pocket/seat diameter.
- `seat_depth` defines bearing insertion depth.
- `shoulder_diameter` and `shoulder_height` define axial stop.
- `retaining_groove_width` and `retaining_groove_depth` define optional retention.
## Construction sequence
1. Create or select the main housing body.
2. Establish a construction axis through the intended shaft centerline.
3. Cut the shaft clearance bore through all relevant material.
4. Cut the bearing seat/counterbore to controlled depth from the assembly side.
5. Preserve or create the shoulder that stops the bearing.
6. Add retaining grooves, set-screw holes, lubrication holes, and mounting holes only after the bore/seat is correct.
7. Apply bore lead-in chamfers and small external fillets last.
## Common failures
- Making the seat blind depth ambiguous.
- Losing the shoulder by cutting the same diameter through the whole body.
- Off-axis bearing pocket relative to shaft bore.
- Treating bearing housing as complete bearing assembly.
- Adding mounting holes before the seat and accidentally breaking the pocket wall.
## Evidence summary
- Current library evidence: 24 reviewed SCAD58 bearing housing/seat models.
- Latest run evidence: accepted insights for central bore alignment, coaxial cylindrical features, counterbore/seat construction, and bore chamfering.
- Frequent operations in the latest run: Cut, RevCut, Revolution, HoleWzd, Chamfer, Fillet.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `bearing bore seat`
- `bearing pocket`
- `bearing seat`
- `bushing bore`
- `shaft clearance bore`
Secondary triggers:
- `coaxial bore`
- `counterbore`
- `shoulder`
- `retaining groove`
- `bearing housing`
Operation triggers:
- `cut through bore`
- `counterbore to depth`
- `preserve shoulder`
- `chamfer bore`
Exclusions:
- `flange only`
- `plain washer`
- `decorative circular hole`
- `complete rolling bearing`
@@ -0,0 +1,86 @@
# Flange Bolt Circle Functional Skill
## When to use
- A flange, circular cover, annular ring, coupling, or flanged sleeve needs fastener holes evenly spaced around a central bore.
- The request mentions bolt circle, pitch circle, PCD, evenly spaced flange holes, or circular pattern of screws.
## Do not use when
- Holes are in a rectangular grid, linear row, or mirrored pair on a plate or bracket.
- The part has no central axis or central bore.
- The holes are decorative circular cutouts rather than fastening holes.
## Recognition features
- Central bore or hub defines the rotation axis.
- Bolt holes lie on a pitch circle concentric with the bore.
- Angular spacing is equal: `360 / bolt_count`.
- Optional counterbores or countersinks remain concentric with each bolt hole.
## Core invariants
- Bolt-circle center, central bore, hub, and outer diameter share the same axis.
- Create one correct seed hole with the correct diameter and seat type.
- Circular-pattern the hole feature, not just sketch circles.
- Keep bolt count, pitch-circle diameter, and start angle explicit.
- Add counterbore/countersink to the seed hole before patterning when all holes share the same fastener type.
## Parameter roles
- `bolt_count` controls number of fastener holes.
- `bolt_circle_diameter` or `pitch_circle_diameter` controls radial placement.
- `bolt_hole_diameter` defines clearance or pilot size.
- `start_angle` controls angular orientation relative to a datum.
- `counterbore_diameter`, `counterbore_depth`, and `countersink_angle` define fastener seating.
## Construction sequence
1. Define the central axis from the flange bore or construction geometry.
2. Place one seed hole center at radius `bolt_circle_diameter / 2`.
3. Create the seed hole as a cut, HoleWzd hole, counterbore, or countersink according to the fastener.
4. Use circular pattern around the central axis for `bolt_count` instances over 360 degrees.
5. Verify that every patterned hole cuts through the flange and remains inside the outer diameter.
6. Add final chamfers and fillets after the bolt pattern is complete.
## Common failures
- Manually placing each hole and producing uneven angular spacing.
- Patterning a sketch without cutting material.
- Using a linear array on a circular flange.
- Choosing pitch circle too close to the bore or outer edge.
- Forgetting counterbore/countersink depth for flush fasteners.
## Evidence summary
- Current library evidence: 45 reviewed SCAD58 flange-like models.
- Latest run evidence: accepted atomic and functional insights for circular pattern bolt holes, central bore alignment, and axisymmetric flange bodies.
- Frequent operations in the latest run: CircularPattern, HoleWzd, Cut, Revolution, Chamfer.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `flange bolt circle`
- `bolt circle`
- `pitch circle`
- `PCD`
- `circular bolt pattern`
Secondary triggers:
- `flange holes`
- `evenly spaced holes`
- `central bore`
- `counterbored flange holes`
Operation triggers:
- `circular pattern`
- `hole wizard`
- `pattern cut feature`
- `cut bolt holes`
Exclusions:
- `linear hole grid`
- `mounting plate row`
- `random holes`
- `side tab holes`
@@ -0,0 +1,88 @@
# Mounting Plate Hole Layout Functional Skill
## When to use
- A plate or base block needs holes, slots, counterbores, countersinks, or threaded holes positioned from datum edges or centerlines.
- The layout is rectangular, row-based, symmetric, mirrored, or grid-like rather than circular around a flange axis.
- The hole layout controls mounting, alignment, adjustment, or fastening.
## Do not use when
- Holes are evenly spaced around a circular bolt circle on a flange.
- The holes are decorative, random, or not functionally related.
- The main operation is a bearing bore or central shaft seat rather than a mounting-hole layout.
## Recognition features
- Flat planar top face or side face used as the hole placement plane.
- Edge margins, centerline offsets, pitch values, or symmetric pairs.
- Repeated hole groups, slots, threaded holes, clearance holes, counterbores, countersinks, or dowel holes.
- SolidWorks evidence often appears as HoleWzd, Cut, LPattern, MirrorPattern, and Chamfer.
## Core invariants
- Define plate datums before hole placement.
- Keep hole centers constrained to margins, pitch, and symmetry instead of free coordinates.
- Select the hole type from fastening function, not from appearance.
- Pattern the cut or hole feature after the seed feature is correct.
- Preserve minimum material around holes and between holes.
## Parameter roles
- `datum_edge_x`, `datum_edge_y`, and `centerline` define references.
- `edge_margin_x`, `edge_margin_y`, `pitch_x`, `pitch_y`, `row_count`, and `column_count` define repeated layouts.
- `hole_diameter`, `thread_size`, `fit`, and `end_condition` define the hole itself.
- `counterbore_diameter`, `counterbore_depth`, `countersink_angle`, and `slot_length` define fastener seating or adjustability.
## Construction sequence
1. Select the plate face and define construction centerlines or edge-offset dimensions.
2. Create one seed hole or one seed hole group using HoleWzd or a through cut.
3. Use linear pattern for rows/grids and mirror for symmetric pairs.
4. Use slots when the design requires adjustment rather than fixed bolt location.
5. Add counterbore, countersink, or thread data to the seed hole before patterning where possible.
6. Add chamfers and deburring features after the functional holes are complete.
## Common failures
- Converting counterbored/threaded/countersunk holes into plain cylinders.
- Patterning sketch circles instead of the actual cut or hole feature.
- Mixing flange bolt-circle logic into rectangular plate layouts.
- Missing datum margins, causing holes to float.
- Placing slots too close to edges or other holes.
## Evidence summary
- Current library evidence: 207 reviewed SCAD58 plate-like models.
- Latest run evidence: high-frequency accepted insights for hole wizard use, standard hole types, symmetric hole placement, linear hole patterns, mirror patterns, slots, and edge chamfers.
- Frequent operations in the latest run: HoleWzd, Cut, LinearPattern, MirrorPattern, Chamfer.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `mounting plate hole layout`
- `base plate hole pattern`
- `rectangular hole grid`
- `symmetric mounting holes`
Secondary triggers:
- `edge margin`
- `linear pitch`
- `counterbored holes`
- `threaded holes`
- `slots`
- `dowel holes`
Operation triggers:
- `hole wizard`
- `linear pattern`
- `mirror holes`
- `cut through holes`
- `counterbore`
- `countersink`
Exclusions:
- `flange bolt circle`
- `bearing bore`
- `random decorative holes`
@@ -0,0 +1,87 @@
# Slotted Adjustment Feature Functional Skill
## When to use
- A plate, bracket, clamp, guide, or mounting part needs an elongated slot for positional adjustment, clearance, sliding, or alignment.
- The request mentions slot, slotted hole, adjustment slot, elongated hole, guide slot, keyway-like slot, or obround cutout.
## Do not use when
- The feature is a simple round hole with no adjustment function.
- The slot is actually a shaft keyway or turned groove; use a shaft/keyway-specific rule if available.
- The slot is decorative and not tied to fastening, travel, or clearance.
## Recognition features
- Elongated hole with two semicircular ends and straight sides.
- Slot located on a flat plate/bracket face or through a wall.
- Often paired, mirrored, or patterned.
- Can coexist with counterbored holes, threaded holes, ribs, or edge chamfers.
## Core invariants
- Slot width should match the fastener/shaft clearance role.
- Slot length defines adjustment travel and must be longer than the fastener diameter.
- Keep end radii equal to half the slot width for manufacturable milled/slot features.
- Dimension slots from datums and centerlines.
- Cut slots after the base body exists and before final chamfers/fillets.
## Parameter roles
- `slot_width` defines cutter or fastener clearance.
- `slot_length` defines adjustment range.
- `end_radius` is usually `slot_width / 2`.
- `slot_center`, `slot_angle`, and `edge_margin` locate the slot.
- `slot_depth` or `through_all` defines end condition.
## Construction sequence
1. Select the planar face or reference plane where the slot starts.
2. Sketch an obround slot from centerline, length, and width.
3. Cut through all or to the required depth.
4. Pattern or mirror the slot if repeated.
5. Add small chamfers to slot edges after the cut is complete.
## Common failures
- Modeling a slot as a rectangle with sharp internal corners.
- Making the slot length equal to the hole diameter, removing adjustment function.
- Placing slots without enough wall material at the ends.
- Confusing a bracket adjustment slot with a shaft keyway or revolved groove.
## Evidence summary
- Latest run evidence: 38 accepted functional insights matched slot, slotted adjustment, guide slot, or slot/cutout behavior.
- Strong repeated names include `use_slots_for_adjustment`, `use_cut_extrude_for_holes_and_slots`, `central_slot_for_alignment`, and `cut_slots_for_adjustment`.
- Frequent operations in the latest run: Cut, Extrusion, LinearPattern, MirrorPattern, Chamfer.
- Source model examples: 004764, 005640, 006514, 011853, 026940, 043134, 058682, 096676, 106216, 156844, 163197, 185756, 199739, 213507, 240416.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `slot`
- `slotted hole`
- `adjustment slot`
- `elongated hole`
- `guide slot`
Secondary triggers:
- `obround`
- `clearance slot`
- `mounting adjustment`
- `sliding`
- `alignment slot`
Operation triggers:
- `sketch slot`
- `cut extrude slot`
- `mirror slot`
- `linear pattern slot`
- `chamfer slot edge`
Exclusions:
- `round hole only`
- `shaft keyway`
- `revolved groove`
- `decorative cutout`
@@ -0,0 +1,92 @@
# Standard Hole Wizard Functional Skill
## When to use
- A part needs standard clearance holes, threaded/tapped holes, counterbored holes, countersunk holes, dowel holes, or screw-seat holes.
- The request mentions screw size, metric thread, socket head screw, flat head screw, fastener clearance, tapping, counterbore, countersink, or Hole Wizard-like behavior.
## Do not use when
- The circular feature is a bearing bore, shaft bore, decorative opening, large window, or non-standard milled pocket.
- The hole is part of a freeform surface where a standard hole feature cannot be cleanly placed.
- The user asks for detailed thread geometry and the CAD backend cannot represent it reliably.
## Recognition features
- Feature evidence such as HoleWzd, threaded hole, counterbored hole, countersunk hole, through hole, or standard metric sizes like M3, M4, M5, M6, M8, M10.
- Holes are used for fastening, alignment, or assembly rather than decoration.
- Hole positions are points on a planar face, often patterned or mirrored.
## Core invariants
- Select hole type before modeling: clearance, threaded, counterbore, countersink, or dowel.
- Keep hole axis normal to the placement face unless an angled hole is explicitly requested.
- Specify end condition: through all, blind depth, up to next, or thread depth.
- Preserve fastener seating geometry: counterbore/countersink is not a decorative ring.
- Pattern or mirror the completed hole feature when identical holes repeat.
## Parameter roles
- `hole_type` selects clearance, threaded, counterbore, countersink, dowel, or simple through hole.
- `standard` and `thread_size` define metric/imperial thread family when available.
- `hole_diameter`, `tap_drill_diameter`, `thread_depth`, and `hole_depth` define cut size.
- `counterbore_diameter`, `counterbore_depth`, and `countersink_angle` define screw head seating.
- `placement_points`, `edge_offsets`, and `pattern_count` define layout.
## Construction sequence
1. Identify the fastening or alignment function for each hole group.
2. Choose the matching standard hole type and size.
3. Place hole center points on the correct face using datums, centerlines, or coordinates.
4. Create the hole feature with diameter, depth, thread, and seating data.
5. Pattern or mirror the completed hole feature when the group repeats.
6. Add entrance chamfers only after the hole geometry is correct.
## Common failures
- Modeling all holes as plain cylinders and losing thread/counterbore/countersink meaning.
- Adding visual thread helixes where a thread callout or standard threaded hole is enough.
- Forgetting counterbore depth for socket head screws.
- Patterning hole center sketches without patterning the hole cut.
- Using non-standard sizes when the prompt specifies standard fasteners.
## Evidence summary
- Latest run evidence: 149 accepted functional insights matched standard hole creation and fastener-hole logic.
- Strong repeated names include `use_hole_wizard_for_standard_holes`, `use_counterbored_holes_for_flat_head_screws`, `use_hole_wizard_for_threaded_holes`, and `use_hole_wizard_for_counterbore_holes`.
- Frequent operations in the latest run: HoleWzd, Cut, LinearPattern, MirrorPattern, Chamfer.
- Source model examples: 001082, 001607, 003846, 011721, 014040, 029487, 041650, 061936, 074693, 094929, 102841, 145389, 160434, 211326, 227986.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `hole wizard`
- `standard holes`
- `threaded holes`
- `counterbored holes`
- `countersunk holes`
- `tapped holes`
Secondary triggers:
- `M3`
- `M4`
- `M5`
- `M6`
- `M8`
- `socket head screw`
- `flat head screw`
Operation triggers:
- `create hole wizard feature`
- `create threaded hole`
- `create counterbore`
- `create countersink`
- `pattern hole feature`
Exclusions:
- `bearing bore`
- `shaft bore`
- `decorative hole`
- `large cutout`
- `freeform pocket`
@@ -0,0 +1,85 @@
# Symmetric Feature Layout Functional Skill
## When to use
- A part has holes, cutouts, bosses, tabs, ribs, or slots arranged symmetrically about a centerline, midplane, or axis.
- The prompt mentions symmetric holes, mirrored features, balanced mounting, centered layout, equal offsets, or left/right duplicates.
## Do not use when
- Features are intentionally asymmetric for clearance, indexing, handedness, or mating orientation.
- The only symmetry is the natural circular symmetry of a plain shaft or washer with no repeated secondary features.
- The model requires unique hand-placed features with different sizes.
## Recognition features
- Hole pairs, mirrored slots, duplicated bosses, matching tabs, or opposite-side cutouts.
- SolidWorks features such as MirrorPattern or sketches with construction centerlines.
- Descriptions mention symmetry, balanced fastening, alignment, centered holes, or mirrored geometry.
## Core invariants
- Define the symmetry plane, centerline, or axis before placing the seed feature.
- Model one side or one quadrant carefully, then mirror/pattern it.
- Keep symmetric features tied to the same dimensions so edits preserve balance.
- Do not mirror features that are explicitly handed or direction-specific.
- Combine with hole-type rules when mirrored features are fastener holes.
## Parameter roles
- `symmetry_plane` or `centerline` defines the mirror reference.
- `seed_feature` defines the original hole, slot, boss, or cutout.
- `offset_from_center`, `edge_margin`, and `pitch` define placement.
- `mirror_count`, `pattern_count`, and `quadrant_count` define repetition intent.
## Construction sequence
1. Identify the natural datum: plate centerline, bracket midplane, flange axis, or body midplane.
2. Create construction geometry for the symmetry reference.
3. Build the seed feature on one side with full dimensions.
4. Mirror or pattern the completed feature across the reference.
5. Verify that all mirrored features remain inside material and keep correct hole type/depth.
6. Apply common edge treatments after mirrored features are complete.
## Common failures
- Manually placing mirrored holes and getting unequal offsets.
- Mirroring asymmetric features such as countersinks that should face a specific side.
- Losing thread/counterbore data during mirror or pattern.
- Creating a visually symmetric model with unconstrained feature positions.
## Evidence summary
- Latest run evidence: 228 accepted functional insights contained symmetry, mirror, centerline, or pattern alignment signals.
- Strong repeated names include `use_mirror_pattern_for_symmetry`, `position_holes_symmetrically`, `symmetrical_hole_placement`, and `use_mirror_for_symmetry`.
- Frequent operations in the latest run: MirrorPattern, LinearPattern, HoleWzd, Cut, construction lines, Chamfer.
- Source model examples: 000124, 002620, 008006, 012598, 029447, 032081, 059866, 097101, 125932, 134012, 145543, 154893, 166339, 173306, 202020.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `symmetric holes`
- `mirror pattern`
- `symmetry`
- `mirrored features`
- `centerline layout`
Secondary triggers:
- `balanced mounting`
- `equal offsets`
- `left right holes`
- `construction line`
- `midplane`
Operation triggers:
- `create construction centerline`
- `mirror feature`
- `pattern symmetric holes`
- `use equal constraints`
Exclusions:
- `asymmetric clearance`
- `handed part`
- `unique holes`
- `plain cylinder only`
@@ -0,0 +1,98 @@
# Bearing Housing Or Seat Planning Skill
## When to use
- The request describes a bearing housing, bearing seat, bearing support, bearing block, pillow-block-like support, bushing seat, shaft support, or retainer body.
- The central functional feature is a precise coaxial bore, pocket, sleeve, shoulder, or counterbore that locates a bearing or bushing.
- The part is a single machined component that supports a shaft or bearing, not the rolling bearing assembly itself.
## Do not use when
- The user asks for a complete bearing with balls, rollers, cage, inner race, and outer race.
- The bearing word is only speculative and there is no bore, seat, sleeve, pocket, or retaining geometry.
- The part is mainly a flange, washer, simple tube, or rectangular plate with unrelated holes.
- The task requires an assembly of shaft, bearing, seals, fasteners, and housing together.
## Recognition features
- Coaxial shaft clearance bore through the body.
- Larger bearing pocket or counterbore with controlled depth.
- Shoulder, step, retaining lip, snap-ring groove, or cover seat to locate the bearing axially.
- Mounting base, side lugs, flange, bolt holes, ribs, or bosses can appear, but they support the bearing axis rather than define the part family.
- Chamfers at bore entrances and seat edges guide assembly.
## Core invariants
- Bearing seat diameter, shaft clearance diameter, housing outside diameter, and cover features share one axis.
- Separate the bearing outer-diameter seat from the shaft clearance bore; they are not the same feature.
- Leave a shoulder or defined depth when the bearing must stop axially.
- Build enough housing material before cutting the bearing pocket.
- Add mounting holes and ribs after the main bore/seat relationship is established.
- Do not model balls or rollers unless the prompt explicitly asks for a bearing assembly.
## Parameter roles
- `bearing_outer_diameter` defines the pocket or seat diameter.
- `shaft_clearance_diameter` defines the through bore.
- `seat_depth` defines how far the bearing is inserted.
- `shoulder_height` or `shoulder_diameter` defines axial retention.
- `housing_wall_thickness` controls material around the bearing pocket.
- `mounting_hole_pattern`, `base_length`, `base_width`, and `base_thickness` define attachment to the frame.
- `rib_thickness` and `rib_height` define optional support ribs.
## Construction sequence
1. Define the bearing axis and choose the housing form: block, flanged seat, sleeve, pedestal, or bracketed support.
2. Create the main housing/body with enough wall thickness around the bearing axis.
3. Cut the through shaft clearance bore on the main axis.
4. Cut the bearing pocket/counterbore to its seat depth, leaving a clear shoulder where needed.
5. Add retaining grooves, cover recesses, set-screw holes, oil holes, or alignment features only if requested.
6. Add base mounting holes, bolt pads, ribs, and bosses using datum-driven positions.
7. Finish with bore lead-in chamfers, outside chamfers, and small fillets.
## Common failures
- Generating a bearing assembly instead of a housing or seat part.
- Making the bearing seat and shaft clearance the same diameter.
- Missing the shoulder/depth that locates the bearing.
- Off-axis mounting of bore, pocket, or outer sleeve.
- Turning every bearing support into a flange even when the evidence suggests a block or pedestal.
- Adding decorative ribs or fasteners that obscure the bearing pocket.
## Evidence summary
- Current library evidence: 24 previously reviewed SCAD58 bearing housing/seat descriptions.
- Latest run evidence: accepted insights for central bore alignment, bearing/bushing-like cylindrical seats, coaxial features, counterbores, mounting holes, and chamfered bore entries.
- Frequent operations in the latest run: Extrusion, Revolution, Cut, RevCut, HoleWzd, Chamfer, Fillet.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `bearing housing`
- `bearing seat`
- `bearing support`
- `bearing block`
- `bushing seat`
Secondary triggers:
- `bearing bore`
- `bearing pocket`
- `shaft clearance`
- `retaining shoulder`
- `coaxial seat`
- `pillow block`
Operation triggers:
- `cut coaxial bore`
- `counterbore bearing seat`
- `create shoulder`
- `add mounting base holes`
- `chamfer bore entry`
Exclusions:
- `complete rolling bearing`
- `plain washer`
- `flange only`
- `simple tube`
- `shaft only`
@@ -0,0 +1,93 @@
# Flange Planning Skill
## When to use
- The request describes a circular flange, coupling flange, pipe flange, gasket-like flange, flanged sleeve, or flanged cylindrical adapter.
- The part has a central axis, central bore, and fastener holes arranged around a pitch circle.
- The flange connects, spaces, seals, or mounts coaxial parts such as shafts, pipes, bearings, covers, or couplings.
## Do not use when
- The word flange only means a side tab, flat ear, or rectangular mounting plate extension.
- The hole layout is a rectangular grid, edge row, or arbitrary plate pattern.
- The model is primarily a plain shaft, washer, pulley, bearing assembly, or bracket without a true circular flange body.
## Recognition features
- Axisymmetric disc, annular ring, raised hub, sleeve, stepped boss, or cylindrical landing surface.
- Central through bore, pilot bore, counterbore, recess, seal land, or shaft clearance on the main axis.
- Bolt holes are evenly spaced by angle around the same center as the bore.
- Optional recesses, gasket grooves, chamfers, fillets, or counterbores are secondary to the coaxial flange body.
## Core invariants
- The outer diameter, bore, hub, sleeve, recesses, and bolt circle share one central construction axis.
- Build the rotational body before cutting the bolt holes.
- Define bolt-circle diameter separately from outer diameter and bore diameter.
- Create one correct bolt hole, then circular-pattern the cut feature over 360 degrees.
- Do not replace the bolt circle with a rectangular hole grid.
- Keep fillets, chamfers, and cosmetic edge breaks late in the sequence.
## Parameter roles
- `outer_diameter` defines the flange envelope.
- `thickness` defines the disc or flange plate depth.
- `bore_diameter` defines shaft or pipe clearance.
- `hub_diameter`, `hub_height`, and `pilot_diameter` define raised coaxial features.
- `bolt_circle_diameter`, `bolt_count`, `bolt_hole_diameter`, and `start_angle` define the fastener pattern.
- `counterbore_diameter`, `counterbore_depth`, `gasket_groove_width`, and `gasket_groove_depth` define secondary seats.
## Construction sequence
1. Establish the central axis and primary reference plane.
2. Create the flange disc and any coaxial hub or sleeve using revolve for stepped profiles, or extrude circles for simple flat discs.
3. Cut the central bore through the flange and hub on the same axis.
4. Add one bolt hole at the pitch radius using the correct fastener hole type.
5. Circular-pattern that cut around the central axis with equal angular spacing.
6. Add recesses, gasket grooves, counterbores, chamfers, and fillets after the bore and bolt pattern are stable.
## Common failures
- Off-axis bore relative to hub or outer diameter.
- Manual bolt hole placement that produces unequal angular spacing.
- Confusing a rectangular mounting plate with a flange because both have multiple holes.
- Forgetting the central bore and creating only a circular plate with holes.
- Applying large fillets before the bolt pattern and causing feature failure.
## Evidence summary
- Current library evidence: 45 previously reviewed SCAD58 flange-like models.
- Latest run evidence: accepted insights for circular flange bolt holes, circular pattern around a central bore, revolved axisymmetric bodies, and coaxial cylindrical features.
- Frequent operations in the latest run: Revolution, Extrusion, Cut, HoleWzd, CircularPattern, Chamfer, Fillet.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `flange`
- `circular flange`
- `pipe flange`
- `coupling flange`
- `flanged sleeve`
Secondary triggers:
- `bolt circle`
- `pitch circle`
- `central bore`
- `raised hub`
- `gasket groove`
- `pilot diameter`
Operation triggers:
- `revolve profile`
- `extrude circular disc`
- `cut central bore`
- `circular pattern`
- `counterbore bolt holes`
Exclusions:
- `mounting plate`
- `rectangular hole grid`
- `side tab`
- `plain shaft`
- `complete bearing`
@@ -0,0 +1,91 @@
# Hexagonal Nut Planning Skill
## When to use
- The request describes a hex nut, hexagonal nut, machined nut, nut-like fastener, or hexagonal threaded insert.
- The key features are wrench flats, a central threaded bore, and chamfered faces/edges.
## Do not use when
- The part is a bolt, screw, socket head cap screw, threaded rod, wing nut, cap nut, or flange nut unless the prompt explicitly asks for that variant.
- The hexagon is only a decorative grip on another part.
- The threaded feature is external rather than an internal bore.
## Recognition features
- Six flat wrench faces or hexagonal prism envelope.
- Central internal threaded hole coaxial with the nut thickness.
- Chamfers on top and bottom openings and on hex edges.
- Optional counterbore, lead-in chamfer, or relieved thread entry.
## Core invariants
- Keep the threaded bore centered through the hex body.
- Preserve the hex across-flats size separately from thread size and thickness.
- Model chamfers as functional lead-ins and edge breaks, not as random bevels.
- Represent internal thread intent using a threaded hole, thread callout, or helical/revolved cut only when required by output format.
- Do not generate the mating bolt unless requested.
## Parameter roles
- `across_flats` defines wrench size.
- `thickness` defines nut height.
- `thread_size`, `thread_diameter`, and `thread_depth` define the central bore.
- `bore_diameter` defines the pre-thread or clearance hole if thread geometry is abstracted.
- `face_chamfer` and `edge_chamfer` define lead-in and deburring.
## Construction sequence
1. Create the hexagonal body as a hex extrusion or by cutting flats from a revolved/chamfered blank.
2. Define the central axis through the nut.
3. Create the central bore or threaded hole through the body.
4. Add thread representation or thread callout if the CAD output supports it.
5. Add top/bottom lead-in chamfers and small edge chamfers.
6. Verify the hex flats remain planar and the bore remains coaxial.
## Common failures
- Producing a bolt or screw instead of a nut.
- Forgetting the central threaded bore.
- Placing the bore off-center relative to the hex body.
- Making the chamfers so large that wrench flats disappear.
- Treating a flange nut, cap nut, or wing nut as a plain hex nut.
## Evidence summary
- Latest run evidence: 25 accepted planning insights matched hex nut or nut-like threaded fastener descriptions.
- Strong repeated names include `plan_hex_nut`, `revolved_hex_nut_with_internal_thread`, `hex_nut_construction`, and `plan_hexagonal_nut_with_threaded_bore`.
- Frequent operations in the latest run: Revolution, Extrusion, Cut, RevCut, HoleWzd, Chamfer.
- Source model examples: 009934, 010137, 052867, 066148, 067249, 135604, 138372, 150622, 200147, 204805, 222550, 225767, 231397, 238840.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `hex nut`
- `hexagonal nut`
- `nut`
- `threaded nut`
- `hexagonal threaded insert`
Secondary triggers:
- `internal thread`
- `central threaded bore`
- `wrench flats`
- `across flats`
- `chamfered nut`
Operation triggers:
- `extrude hexagon`
- `cut central bore`
- `hole wizard threaded hole`
- `revolved cut thread`
- `chamfer faces`
Exclusions:
- `bolt`
- `screw`
- `threaded shaft`
- `wing nut`
- `cap nut`
- `flange nut`
@@ -0,0 +1,97 @@
# Mounting Bracket Planning Skill
## When to use
- The request describes a mounting bracket, L bracket, U bracket, T bracket, support bracket, clamp bracket, lugged bracket, or machined support with holes.
- The part has a load-bearing base plus one or more raised webs, tabs, arms, bosses, lugs, pockets, slots, or cutouts.
- The model is a single machined bracket component, not an assembly with screws or attached hardware.
## Do not use when
- The part is only a flat plate with holes and no bracket-like 3D support structure.
- The part is mainly a shaft, flange, bearing housing, complete hinge assembly, or sheet-metal bent bracket.
- The prompt asks for a decorative stand or enclosure rather than a functional machined support.
## Recognition features
- Base block or plate that mounts to another part.
- Vertical web, side wall, lug, tab, raised boss, or protruding arm.
- Mounting holes, threaded holes, counterbores, slots, or cutouts.
- Reinforcing ribs or fillets at transitions may appear when strength matters.
- Hole placement is usually datum-driven and often symmetric or mirrored.
## Core invariants
- Build the main load path first: base, web/tab/lug, and boss geometry before small holes.
- Keep bracket features as one coherent body unless the prompt explicitly asks for assembled parts.
- Place holes from functional datums, not visual guesses.
- Use standard hole features for threaded, counterbored, countersunk, and clearance holes.
- Add cutouts/slots for clearance or adjustment only where they make functional sense.
- Apply fillets/chamfers late, especially at web-to-base transitions and exposed edges.
## Parameter roles
- `base_length`, `base_width`, and `base_thickness` define the mounting base.
- `web_height`, `web_thickness`, `tab_width`, `lug_radius`, and `boss_diameter` define support geometry.
- `hole_diameter`, `thread_size`, `counterbore_diameter`, and `slot_length` define fastening and adjustment.
- `edge_margin`, `hole_pitch`, `symmetry_plane`, and `boss_offset` define layout.
- `fillet_radius` controls stress relief at transitions.
## Construction sequence
1. Create the base extrusion or main bracket profile.
2. Add vertical webs, tabs, lugs, bosses, or arms as secondary extrusions from clear datum faces.
3. Cut clearance pockets, U/L/T-shaped openings, slots, or lightening cutouts.
4. Add standard holes using HoleWzd or cut features, positioned from datums and symmetry references.
5. Pattern or mirror repeated holes/features when the design is symmetric.
6. Add structural fillets at web/base intersections, then finish with small chamfers on exposed edges.
## Common failures
- Collapsing a bracket into a simple flat plate and losing the vertical/web structure.
- Creating an assembly of screws, pins, or attached parts when only the bracket is requested.
- Adding holes before the base/web geometry and producing floating or misaligned holes.
- Mixing several unrelated bracket variants into one overloaded model.
- Using decorative cutouts that weaken the load path or overlap holes.
## Evidence summary
- Latest run evidence: 247 accepted planning insights matched bracket-like descriptions and operations.
- Strong repeated names include `plan_l_bracket_with_holes`, `bracket_with_holes_and_cutouts`, `bracket_with_holes_planning`, `mounting_bracket_with_holes`, and `plan_bracket_with_holes`.
- Frequent operations in the latest run: Extrusion, Cut, HoleWzd, LinearPattern, MirrorPattern, Chamfer, Fillet, Boss.
- Source model examples: 000780, 001607, 002620, 004421, 009414, 028735, 032081, 036242, 041396, 047048, 054507, 079425, 096301, 134801, 187546, 195869, 207737, 225658.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `mounting bracket`
- `support bracket`
- `L bracket`
- `U bracket`
- `T bracket`
- `bracket with holes`
Secondary triggers:
- `web`
- `lug`
- `tab`
- `boss`
- `slot`
- `cutout`
- `threaded holes`
Operation triggers:
- `extrude base`
- `extrude tab`
- `cut slot`
- `hole wizard`
- `mirror pattern`
- `fillet web transition`
Exclusions:
- `flat mounting plate only`
- `flange`
- `shaft`
- `bearing assembly`
- `sheet metal bend`
@@ -0,0 +1,97 @@
# Mounting Plate Planning Skill
## When to use
- The request describes a flat mounting plate, base plate, adapter plate, panel, fixture plate, or spacer plate.
- The dominant body is a prismatic plate with functional holes, slots, counterbores, countersinks, threaded holes, pockets, or a central clearance cutout.
- The plate locates, fastens, spaces, or supports other components from planar datum faces.
## Do not use when
- The primary geometry is an axisymmetric flange with a circular bolt pitch pattern.
- The plate is only a secondary base under a bearing housing, bracket, motor body, or cylindrical module.
- The part is a true 3D bracket with vertical webs, lugs, arms, or side walls that control the design.
- The request is for sheet-metal bends, formed panels, or decorative flat art rather than a machined plate.
## Recognition features
- Rectangular, square, rounded-rectangle, or simple profile plate with mostly uniform thickness.
- Holes are located from datum edges, centerlines, or symmetric offsets instead of arbitrary visual placement.
- Common hole types include clearance holes, threaded holes, counterbored holes, countersunk holes, dowel holes, and through holes.
- Cutouts can be central circular holes, rectangular windows, slots, pockets, or weight-reduction openings.
- Edge chamfers and small fillets are usually finishing operations, not the defining shape.
## Core invariants
- Establish the base plate envelope, thickness, top face, bottom face, and edge datum references before adding holes.
- Keep mounting holes fully constrained by edge margins, centerlines, pitch, symmetry, or functional datums.
- Model holes as real cuts or standard hole features; do not leave sketch circles on the face.
- Use one seed hole or hole group plus pattern/mirror when the layout is regular.
- Choose hole type from function: clearance for bolts, threaded/tapped for fastening into the plate, counterbore/countersink for flush screw heads, slot for adjustment.
- Apply chamfers and fillets after functional holes and cutouts so edge treatment does not distort hole geometry.
## Parameter roles
- `length`, `width`, and `thickness` define the blank.
- `corner_radius` or `corner_chamfer` controls perimeter treatment when requested.
- `edge_margin_x`, `edge_margin_y`, `pitch_x`, and `pitch_y` locate rectangular hole groups.
- `central_cutout_width`, `central_cutout_length`, or `central_bore_diameter` define clearance openings.
- `hole_diameter`, `thread_size`, `counterbore_diameter`, `counterbore_depth`, `countersink_angle`, and `slot_length` define fastener features.
## Construction sequence
1. Create a constrained base sketch for the plate outline and extrude it to thickness.
2. Define datum centerlines, edge offsets, and symmetry references on the main face.
3. Add the central clearance hole, rectangular cutout, or pocket if it controls component clearance.
4. Add the first functional hole using the correct hole type and end condition.
5. Replicate regular hole groups with linear pattern, circular pattern, or mirror according to layout intent.
6. Add slots, secondary pockets, bosses, or local relief cuts only after primary mounting references are stable.
7. Finish with perimeter chamfers, small fillets, and deburring details.
## Common failures
- Treating every round mark as the same generic hole and losing counterbore/thread/countersink meaning.
- Placing holes by eyeballing coordinates instead of datum margins and symmetry.
- Using a flange bolt-circle pattern for a rectangular plate grid.
- Adding chamfers before holes, causing holes or counterbores to be clipped.
- Over-modeling decorative bevels while missing the functional fastening layout.
- Combining unrelated bracket walls or cylindrical housings into the mounting plate body.
## Evidence summary
- Current library evidence: 207 previously reviewed SCAD58 mounting/base plate descriptions.
- Latest run evidence: repeated accepted insights for `mounting_plate_with_holes`, `plan_mounting_plate_with_holes`, `extrude_rectangular_plate`, `base_plate_with_mounting_holes`, central cutouts, threaded holes, counterbored holes, linear patterns, and edge chamfers.
- Frequent operations in the latest run: Extrusion, HoleWzd, Cut, LinearPattern, MirrorPattern, Chamfer, Fillet.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `mounting plate`
- `base plate`
- `adapter plate`
- `fixture plate`
- `flat plate with holes`
Secondary triggers:
- `hole grid`
- `edge margins`
- `counterbored holes`
- `threaded holes`
- `central cutout`
- `slots`
Operation triggers:
- `extrude base plate`
- `hole wizard`
- `linear pattern`
- `mirror holes`
- `cut central opening`
- `chamfer edges last`
Exclusions:
- `flange bolt circle`
- `bearing housing`
- `shaft`
- `3d bracket`
- `sheet metal bend`
@@ -0,0 +1,95 @@
# Simple Shaft Or Cylindrical Rod Planning Skill
## When to use
- The request describes a single cylindrical rod, simple shaft, spacer rod, cylindrical pin, standoff-like cylinder, or shaft-like machined part.
- The body is primarily a cylinder along one axis, optionally with end holes, chamfers, simple steps, flats, slots, or threaded holes.
- The task is to generate one part, not a spindle cartridge or shaft assembly.
## Do not use when
- The request describes a spindle module, motor rotor assembly, gearbox shaft assembly, bearings, seals, pulleys, keys, and fasteners together.
- The part is mainly a flange, washer, bushing, screw, bolt, or complex housing.
- The body is a hollow tube or bearing sleeve where inner/outer diameter relationship is the main design intent.
## Recognition features
- Long cylindrical body with uniform or stepped diameters.
- Central axis is the dominant datum.
- Optional end chamfers, end fillets, centered threaded holes, transverse holes, flats, slots, grooves, or keyway-like cuts.
- Turning and cylindrical surfaces dominate; milling may appear for flats, slots, or keyways.
## Core invariants
- Establish one main shaft axis and overall length first.
- Use extrude for a uniform simple cylinder; use revolve for stepped diameters, grooves, shoulders, tapers, or turned profiles.
- Keep all coaxial diameter changes, end holes, and grooves aligned to the shaft axis.
- Treat holes, slots, flats, keyways, and threads as secondary features.
- Do not invent bearings, couplings, gears, or housings unless requested.
## Parameter roles
- `length` defines the axial span.
- `diameter` defines a simple rod.
- `step_diameters`, `step_lengths`, `shoulder_positions`, and `groove_width` define turned features.
- `end_hole_diameter`, `thread_size`, and `thread_depth` define centered end holes.
- `flat_width`, `slot_width`, `slot_depth`, and `keyway_length` define milled secondary features.
- `chamfer_distance` and `fillet_radius` define edge finishing.
## Construction sequence
1. Define the shaft axis and overall length.
2. For a simple rod, sketch a circle and extrude along the axis.
3. For stepped or shouldered shafts, sketch the half-profile and revolve it around the axis.
4. Add centered end holes, threaded holes, grooves, flats, slots, or keyways as secondary cuts.
5. Keep secondary features referenced to the shaft axis or end-face datums.
6. Finish with end chamfers, shoulder fillets, and small edge breaks.
## Common failures
- Generating a spindle or bearing assembly when only a shaft/rod part was requested.
- Creating off-axis end holes.
- Using multiple separate bodies for diameter steps instead of one coherent solid.
- Treating a threaded shaft, bolt, or screw as the same as a plain shaft without preserving thread/head differences.
- Adding arbitrary decorative grooves that were not requested.
## Evidence summary
- Latest run evidence: 107 accepted planning insights matched shaft, rod, cylinder, and stepped-shaft construction.
- Strong repeated names include `plan_stepped_shaft`, `simple_cylinder_extrusion`, `stepped_shaft_construction`, `cylindrical_rod_planning`, and `plan_revolve_shaft_profile`.
- Frequent operations in the latest run: Extrusion, Revolution, Cut, RevCut, HoleWzd, Chamfer, Fillet.
- Source model examples: 000166, 001957, 040325, 069352, 080683, 100698, 112997, 144199, 148300, 160726, 172872, 210853, 218803, 225038, 232675, 239358.
- Source: CAD-SkillX offline review of SCAD58 descriptions, STEP summaries, and SolidWorks feature evidence.
## Retrieval metadata
Triggers:
- `cylindrical rod`
- `simple shaft`
- `shaft`
- `rod`
- `pin`
- `standoff`
Secondary triggers:
- `stepped shaft`
- `end hole`
- `threaded end`
- `shaft groove`
- `keyway`
- `chamfered ends`
Operation triggers:
- `extrude circle`
- `revolve shaft profile`
- `cut end hole`
- `cut slot`
- `chamfer ends`
Exclusions:
- `spindle assembly`
- `bearing assembly`
- `flange`
- `washer`
- `bolt head`
- `gear`
+33
View File
@@ -127,6 +127,30 @@ async def read_task(task_id: str) -> JSONResponse:
return JSONResponse(task)
@app.get("/v1/tasks/{task_id}/design-intent")
async def read_current_design_intent(task_id: str) -> JSONResponse:
try:
safe_id = safe_task_id(task_id)
result = store.read_design_intent(safe_id)
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error)) from error
if result is None:
raise HTTPException(status_code=404, detail="No DesignIntent exists for this task")
return JSONResponse({"task_id": safe_id, "intent_id": result["record"]["intent_id"], **result})
@app.get("/v1/tasks/{task_id}/design-intents/{intent_id}")
async def read_design_intent(task_id: str, intent_id: str) -> JSONResponse:
try:
safe_id = safe_task_id(task_id)
result = store.read_design_intent(safe_id, intent_id)
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error)) from error
if result is None:
raise HTTPException(status_code=404, detail="DesignIntent not found")
return JSONResponse({"task_id": safe_id, "intent_id": result["record"]["intent_id"], **result})
@app.get("/v1/tasks/{task_id}/artifacts/{artifact_path:path}")
async def read_artifact(task_id: str, artifact_path: str) -> StreamingResponse:
from fastapi.responses import FileResponse
@@ -165,6 +189,10 @@ async def update_parameters(task_id: str, payload: ParameterUpdate) -> JSONRespo
safe_id = safe_task_id(task_id)
task = store.read_task(safe_id)
current_revision_id = str((task or {}).get("current_revision") or "")
current_revision = next(
(item for item in (task or {}).get("revisions", []) if item.get("revision_id") == current_revision_id),
{},
)
current_path = store.current_cdsl_path(safe_id)
if not task or not current_path or not current_revision_id:
raise ValueError("Task has no successful CDSL revision")
@@ -179,6 +207,11 @@ async def update_parameters(task_id: str, payload: ParameterUpdate) -> JSONRespo
summary="Updated CDSL parameters",
parent_revision_id=current_revision_id,
operation={"type": "parameter_update", "values": payload.values},
part_skills=None,
generation_assumptions=[],
design_intent_id=str(current_revision.get("design_intent_id") or ""),
design_intent_path=str(current_revision.get("design_intent_path") or ""),
design_intent_status="accepted" if current_revision.get("design_intent_id") else "",
)
return JSONResponse(result)
except ValueError as error:
+2
View File
@@ -57,6 +57,8 @@ class CadResult(BaseModel):
parameters_path: str | None = None
selector_path: str | None = None
edges_path: str | None = None
design_intent_id: str | None = None
design_intent_path: str | None = None
summary: str
reference_ids: list[str] = Field(default_factory=list)
engine: str = "cdsl_only"
+293 -33
View File
@@ -15,6 +15,7 @@ import httpx
from app.models.contracts import ChatMessage
from app.services.engine_service import build_revision, load_engine, validate_cdsl
from app.services.library import CdslLibrary
from app.services.part_skills import PartSkillLibrary
from app.services.sse import event
from app.services.storage import WorkspaceStore, now_iso
from app.settings import ProviderConfig, ProviderModel, Settings
@@ -141,12 +142,25 @@ def invalid_cdsl_result(error: ValueError) -> dict[str, Any]:
}
def invalid_design_intent_result(error: Exception) -> dict[str, Any]:
code = str(getattr(error, "code", "INVALID_DESIGN_INTENT"))
return {
"ok": False,
"code": code,
"message": f"The DesignIntent plan was rejected: {error}. Correct the complete plan before generating CDSL.",
}
def user_visible_tool_message(result: dict[str, Any], user_text: str) -> str:
code = str(result.get("code") or "")
if code == "INVALID_CDSL":
if any("\u4e00" <= char <= "\u9fff" for char in str(user_text or "")):
return "CDSL 不符合 engine 的模型契约,正在请求模型按 schema 修正后重新生成。"
return "The CDSL model does not match the engine contract. Asking the model to correct it and retry."
if code in {"INVALID_DESIGN_INTENT", "INTENT_CDSL_MISMATCH", "DESIGN_INTENT_REQUIRED", "DESIGN_INTENT_BLOCKED"}:
if any("\u4e00" <= char <= "\u9fff" for char in str(user_text or "")):
return "设计意图尚未通过校验,未进入 CAD 构建。"
return "The design intent has not passed validation, so CAD construction has not started."
return str(result.get("message") or result.get("summary") or "")
@@ -187,6 +201,22 @@ def _cdsl_tool_schema() -> dict[str, Any]:
CDSL_TOOL_SCHEMA = _cdsl_tool_schema()
def _design_intent_tool_schema() -> dict[str, Any]:
engine_dir = Path(__file__).resolve().parents[2] / "engine" / "cdsl_engine"
try:
schema = json.loads((engine_dir / "design_intent_schema.json").read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise RuntimeError("Local DesignIntent JSON Schema is unavailable or invalid") from error
# The model cannot forge storage/audit fields. They are added only after
# backend validation by WorkspaceStore.create_design_intent().
for field in ("intent_id", "created_at", "part_skill_ids", "part_skill_selection"):
schema.get("properties", {}).pop(field, None)
return schema
DESIGN_INTENT_TOOL_SCHEMA = _design_intent_tool_schema()
TOOL_SCHEMAS: list[dict[str, Any]] = [
{
"type": "function",
@@ -197,6 +227,7 @@ TOOL_SCHEMAS: list[dict[str, Any]] = [
"type": "object",
"properties": {"query": {"type": "string"}, "limit": {"type": "integer", "minimum": 1, "maximum": 8}},
"required": ["query"],
"additionalProperties": False,
},
},
},
@@ -205,7 +236,24 @@ TOOL_SCHEMAS: list[dict[str, Any]] = [
"function": {
"name": "read_cdsl_reference",
"description": "Read one official CDSL sample by part_id. Use this before creating geometry based on a reference.",
"parameters": {"type": "object", "properties": {"part_id": {"type": "string"}}, "required": ["part_id"]},
"parameters": {"type": "object", "properties": {"part_id": {"type": "string"}}, "required": ["part_id"], "additionalProperties": False},
},
},
{
"type": "function",
"function": {
"name": "propose_design_intent",
"description": "Submit a complete semantic DesignIntent plan before searching CDSL references or generating CDSL.",
"parameters": {
"type": "object",
"properties": {
"intent": DESIGN_INTENT_TOOL_SCHEMA,
"summary": {"type": "string", "minLength": 1},
"assumptions": {"type": "array", "items": {"type": "string"}},
},
"required": ["intent", "summary", "assumptions"],
"additionalProperties": False,
},
},
},
{
@@ -224,11 +272,12 @@ TOOL_SCHEMAS: list[dict[str, Any]] = [
"parameters": {
"type": "object",
"properties": {
"design_intent_id": {"type": "string", "pattern": "^intent_[a-z0-9]{12}$"},
"cdsl": CDSL_TOOL_SCHEMA,
"summary": {"type": "string", "minLength": 1},
"assumptions": {"type": "array", "items": {"type": "string"}},
},
"required": ["cdsl", "summary"],
"required": ["design_intent_id", "cdsl", "summary", "assumptions"],
"additionalProperties": False,
},
},
@@ -242,13 +291,11 @@ def tools_for_model(model: ProviderModel) -> list[dict[str, Any]]:
if not model.strict_tool_schema:
return tools
generate_tool = next(
tool for tool in tools
if tool.get("function", {}).get("name") == "generate_cdsl_model"
)
# This flag constrains function arguments only. It has no effect on normal
# assistant text, the user's prompt, or the natural-language summary.
generate_tool["function"]["strict"] = True
for tool in tools:
if tool.get("function", {}).get("name") in {"propose_design_intent", "generate_cdsl_model"}:
# This flag constrains function arguments only. It has no effect on
# normal assistant text, the user's prompt, or the summary.
tool["function"]["strict"] = True
return tools
@@ -352,9 +399,12 @@ def system_prompt(
user_text: str,
viewer_context: list[dict[str, Any]] | None = None,
task_id: str = "",
part_skill_context: str = "",
) -> str:
skill_path = settings.engine_root.parent.parent / "agent" / "skills" / "cad-engine" / "SKILL.md"
skill = skill_path.read_text(encoding="utf-8") if skill_path.is_file() else ""
planning_recipe_path = skill_path.with_name("planning-recipe.md")
planning_recipe = planning_recipe_path.read_text(encoding="utf-8") if planning_recipe_path.is_file() else ""
readme_path = settings.engine_root / "README.md"
engine_readme = readme_path.read_text(encoding="utf-8") if readme_path.is_file() else ""
profile_schema_path = settings.engine_root / "profile_schema.json"
@@ -379,6 +429,12 @@ Tool call contract:
- Do not append prose, Markdown code fences, comments, or a second JSON value.
- For generate_cdsl_model, pass the complete CDSL as the cdsl object directly,
not as Markdown and not as a concatenated JSON string.
- Call propose_design_intent first. Its `intent` is the complete semantic
planning JSON, without sketch coordinates, raw CAD code, storage IDs, or
part-skill IDs. The backend chooses and persists part skills itself.
- Do not call search_cdsl_library, read_cdsl_reference, or
generate_cdsl_model until propose_design_intent returns an accepted
`intent_id`. Pass that exact ID to generate_cdsl_model.
- The generate_cdsl_model `cdsl` parameter is the complete machine-enforced
schema. Satisfy its nested object and array types exactly; do not substitute
a shorthand array for an object. For example, every hole position is
@@ -399,12 +455,34 @@ Workflow limits:
{response_language_instruction(user_text)}
You generate parameterized CDSL, never raw CAD source code. For new CAD requests:
1. Search the local official CDSL library.
2. Read at least one relevant reference when a match exists.
3. Call generate_cdsl_model only when the request is sufficiently specified.
4. Never claim success unless the tool returns a successful CDSL-only STEP and GLB artifact.
1. Use the injected part-skill guidance, when present, only to establish the
structural plan, feature dependency order, and parameter roles.
2. Call propose_design_intent. A blocking question or capability gap must make
the plan `needs_clarification`; then ask one concise user-facing question
and do not call CDSL tools.
3. Only after an accepted ready intent, search the local official CDSL library.
Read at least one relevant reference when a match exists; samples provide
schema-valid expressions, not higher-priority part intent.
4. Generate a complete CDSL whose feature IDs, atomics, dependencies, profiles,
and selector evidence exactly realize the accepted DesignIntent, then call
generate_cdsl_model with its intent ID.
5. Never claim success unless the tool returns a successful CDSL-only STEP and GLB artifact.
For a revision, call read_current_cdsl first and preserve unrelated features.
Precedence is strict: explicit user request, then CDSL schema/runtime, then
part-skill guidance, then CDSL-library examples. Part skills never authorize
build123d source, an unknown atomic/profile, an invented selector, or a free-
coordinate substitute for a capability the runtime cannot express. For an
unsupported requested structure, ask one concise clarification question or
state the blocker rather than fabricating geometry. If a part-family conflict
is injected, preserve the current part unless the user explicitly requests a
whole-part replacement. A primary-family conflict is a hard clarification
stop: ask one concise question and do not call generate_cdsl_model until the
user resolves it.
For a revision, call read_current_cdsl first and preserve unrelated features,
then propose a revise DesignIntent with base_revision_id set to the current
successful revision. Do not search references or generate CDSL before that plan
is accepted.
Do not output compiler_context, unknown_shape, complex_arc_shape, entities,
contour_edges_mm, or contour_regions_mm. Use only self-contained named profiles
and feature atomic IDs defined in the engine schema below. Read the engine
@@ -415,6 +493,9 @@ dimensions or intent are missing. Ordinary explanations must not create CAD.
Local skill:
{skill}
DesignIntent planning recipe:
{planning_recipe}
Local engine guide:
{engine_readme}
@@ -423,15 +504,29 @@ Authoritative engine schema:
Supported named profile types:
{supported_profiles}
Injected part-skill context:
{part_skill_context or "No part-family skill guidance was selected for this request."}
{viewer_selection_prompt(viewer_context, task_id)}
"""
def part_skill_root(settings: Settings) -> Path:
return settings.engine_root.parent.parent / "agent" / "skills" / "cad-engine" / "references" / "part-skills"
class AgentService:
def __init__(self, settings: Settings, store: WorkspaceStore, library: CdslLibrary) -> None:
def __init__(
self,
settings: Settings,
store: WorkspaceStore,
library: CdslLibrary,
part_skill_library: PartSkillLibrary | None = None,
) -> None:
self.settings = settings
self.store = store
self.library = library
self.part_skill_library = part_skill_library or PartSkillLibrary(part_skill_root(settings))
async def stream(
self,
@@ -489,7 +584,29 @@ class AgentService:
yield event("progress", {"step": "analyze_request", "label": "分析需求", "status": "running", "message": "正在整理当前会话和 CAD 需求。"})
references: list[str] = []
library_searches = 0
model_messages: list[dict[str, Any]] = [{"role": "system", "content": system_prompt(self.settings, user_text, viewer_context, task_id)}]
current_task = self.store.read_task(task_id) if task_id else None
inherited_skill_ids = self.part_skill_library.inherited_from_task(current_task)
part_skill_selection = self.part_skill_library.select(user_text, inherited_skill_ids)
intent_state: dict[str, Any] = {
"phase": "WAITING_FOR_INTENT",
"design_intent_id": "",
}
yield event("progress", {
"step": "select_part_skill",
"label": "识别零件族",
"status": "success",
"message": "已完成零件族与辅助建模规则识别。",
})
model_messages: list[dict[str, Any]] = [{
"role": "system",
"content": system_prompt(
self.settings,
user_text,
viewer_context,
task_id,
self.part_skill_library.render_context(part_skill_selection),
),
}]
model_messages.extend(messages_for_model(messages))
if attachment_message:
model_messages.append({"role": "user", "content": attachment_message})
@@ -591,14 +708,30 @@ class AgentService:
"message": "Agent 正在调用本地 CAD 工具。",
})
try:
result, generated = await self._run_tool(name, arguments, task_id, user_text, references)
result, generated = await self._run_tool(
name,
arguments,
task_id,
user_text,
references,
part_skill_selection=part_skill_selection,
intent_state=intent_state,
)
except (ValueError, RuntimeError) as error:
if name == "generate_cdsl_model":
code = str(getattr(error, "code", ""))
if code in {"INVALID_DESIGN_INTENT", "INTENT_CDSL_MISMATCH", "DESIGN_INTENT_REQUIRED", "DESIGN_INTENT_BLOCKED"}:
result = invalid_design_intent_result(error)
generated = None
if name in {"propose_design_intent", "generate_cdsl_model"} and code != "DESIGN_INTENT_BLOCKED":
required_tool_name = name
elif name == "generate_cdsl_model":
result = invalid_cdsl_result(error)
generated = None
required_tool_name = name
else:
raise
if result.get("task_id"):
task_id = str(result["task_id"])
if generated:
task_id = generated["task_id"]
model_messages.append({
@@ -612,7 +745,14 @@ class AgentService:
"status": "success" if result.get("ok", True) else "error",
"message": user_visible_tool_message(result, user_text),
})
if name == "generate_cdsl_model" and result.get("ok"):
if name == "propose_design_intent" and result.get("ok"):
yield event("progress", {
"step": "validate_design_intent",
"label": "校验设计意图",
"status": "success",
"message": "设计意图已通过结构、依赖和能力边界校验。",
})
if name in {"propose_design_intent", "generate_cdsl_model"} and result.get("ok"):
required_tool_name = None
if generated:
result_payload = {
@@ -625,6 +765,8 @@ class AgentService:
"parametersPath": generated.get("parameters_path"),
"selectorPath": generated.get("selector_path"),
"edgesPath": generated.get("edges_path"),
"designIntentId": generated.get("design_intent_id"),
"designIntentPath": generated.get("design_intent_path"),
"summary": generated["summary"],
"referenceIds": generated["reference_ids"],
"engine": generated["engine"],
@@ -632,6 +774,12 @@ class AgentService:
successful_result = result_payload
assistant_parts.append({"type": "data-cad-result", "data": result_payload})
yield event("cad_result", result_payload)
yield event("progress", {
"step": "build_cad",
"label": "构建 CAD",
"status": "success",
"message": "已通过 cdsl_only runtime 构建 STEP 和 GLB。",
})
if iteration == 7:
error_payload = {"stage": "agent", "message": "Agent tool loop reached its safety limit."}
assistant_parts.append({"type": "data-cad-error", "data": error_payload})
@@ -749,15 +897,87 @@ class AgentService:
raise RuntimeError(f"LLM request failed ({response.status_code}): {response.text[:800]}")
return response.json()
async def _run_tool(self, name: str, arguments: dict[str, Any], task_id: str, request: str, references: list[str]) -> tuple[dict[str, Any], dict[str, Any] | None]:
async def _run_tool(
self,
name: str,
arguments: dict[str, Any],
task_id: str,
request: str,
references: list[str],
*,
part_skill_selection: dict[str, Any] | None = None,
intent_state: dict[str, Any] | None = None,
) -> tuple[dict[str, Any], dict[str, Any] | None]:
state = intent_state if intent_state is not None else {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
phase = str(state.get("phase") or "WAITING_FOR_INTENT")
if name == "propose_design_intent":
intent = arguments.get("intent")
if not isinstance(intent, dict):
raise ValueError("propose_design_intent requires an intent JSON object")
if any(field in intent for field in ("intent_id", "created_at", "part_skill_ids", "part_skill_selection")):
raise ValueError("DesignIntent audit fields are assigned only by the backend")
summary = str(arguments.get("summary") or "").strip()
assumptions = arguments.get("assumptions")
if not summary or not isinstance(assumptions, list) or not all(isinstance(item, str) for item in assumptions):
raise ValueError("propose_design_intent requires a summary and an array of string assumptions")
selection = part_skill_selection or self.part_skill_library.select(request)
if selection.get("conflict"):
return {
"ok": False,
"code": "DESIGN_INTENT_BLOCKED",
"message": str(selection["conflict"].get("message") or "The current part family must be clarified before planning."),
}, None
engine = load_engine(self.settings)
current_task = self.store.read_task(task_id) if task_id else None
current_revision_id = str((current_task or {}).get("current_revision") or "")
if current_revision_id and intent.get("mode") != "revise":
raise engine.DesignIntentError("INVALID_DESIGN_INTENT", "A task with a successful revision requires a revise DesignIntent")
if intent.get("mode") == "revise" and not current_revision_id:
raise engine.DesignIntentError("INVALID_DESIGN_INTENT", "A revise DesignIntent requires a current successful revision")
normalized = deepcopy(intent)
if assumptions:
normalized["assumptions"] = list(dict.fromkeys([
*normalized.get("assumptions", []),
*(item.strip() for item in assumptions if item.strip()),
]))
normalized = engine.validate_design_intent(normalized, engine, current_revision_id=current_revision_id)
persisted = self.store.create_design_intent(task_id or None, request, normalized, selection)
state["design_intent_id"] = persisted["intent_id"]
if normalized["status"] != "ready":
state["phase"] = "WAITING_FOR_INTENT"
return {
"ok": False,
"code": "DESIGN_INTENT_BLOCKED",
"task_id": persisted["task_id"],
"design_intent_id": persisted["intent_id"],
"status": normalized["status"],
"intent": persisted["intent"],
"message": "The DesignIntent is saved but blocked. Ask the user only about its blocking question or capability gap.",
}, None
state["phase"] = "INTENT_ACCEPTED"
return {
"ok": True,
"task_id": persisted["task_id"],
"design_intent_id": persisted["intent_id"],
"design_intent_path": persisted["path"],
"status": "accepted",
"intent": persisted["intent"],
"summary": summary,
}, None
if name == "search_cdsl_library":
if phase not in {"INTENT_ACCEPTED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
return {"ok": False, "code": "DESIGN_INTENT_REQUIRED", "message": "Submit and receive an accepted DesignIntent before searching CDSL references."}, None
results = self.library.search(str(arguments.get("query") or request), int(arguments.get("limit") or 5))
state["phase"] = "LIBRARY_REFERENCE"
return {"ok": True, "results": results}, None
if name == "read_cdsl_reference":
if phase not in {"INTENT_ACCEPTED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
return {"ok": False, "code": "DESIGN_INTENT_REQUIRED", "message": "Submit and receive an accepted DesignIntent before reading CDSL references."}, None
part_id = str(arguments.get("part_id") or "")
sample = self.library.read_sample(part_id)
if part_id not in references:
references.append(part_id)
state["phase"] = "WAITING_FOR_CDSL"
return {"ok": True, "part_id": part_id, "cdsl": sample}, None
if name == "read_current_cdsl":
if not task_id:
@@ -767,6 +987,17 @@ class AgentService:
return {"ok": False, "message": "The current task has no successful CDSL revision."}, None
return {"ok": True, "task_id": task_id, "cdsl": json.loads(path.read_text(encoding="utf-8"))}, None
if name == "generate_cdsl_model":
if phase not in {"INTENT_ACCEPTED", "LIBRARY_REFERENCE", "WAITING_FOR_CDSL"}:
return {"ok": False, "code": "DESIGN_INTENT_REQUIRED", "message": "Submit and receive an accepted DesignIntent before generating CDSL."}, None
design_intent_id = str(arguments.get("design_intent_id") or "")
if not design_intent_id or design_intent_id != str(state.get("design_intent_id") or "") or not task_id:
return {"ok": False, "code": "DESIGN_INTENT_REQUIRED", "message": "generate_cdsl_model must use the accepted DesignIntent ID for this task."}, None
intent_record = self.store.read_design_intent(task_id, design_intent_id)
if not intent_record or intent_record["record"].get("status") != "accepted":
return {"ok": False, "code": "DESIGN_INTENT_REQUIRED", "message": "The requested DesignIntent is not accepted for this task."}, None
intent = intent_record["intent"]
if intent.get("status") != "ready":
return {"ok": False, "code": "DESIGN_INTENT_BLOCKED", "message": "The DesignIntent is blocked and cannot be built."}, None
cdsl = arguments.get("cdsl")
if isinstance(cdsl, str):
cdsl = json.loads(cdsl)
@@ -775,19 +1006,47 @@ class AgentService:
# Reject malformed model output before build_revision allocates a task
# directory or revision. build_revision will assign the real task ID.
preflight_cdsl = {**cdsl, "part_id": str(cdsl.get("part_id") or "agent_preflight")}
validate_cdsl(preflight_cdsl, load_engine(self.settings))
engine = load_engine(self.settings)
current_path = self.store.current_cdsl_path(task_id)
current_cdsl = json.loads(current_path.read_text(encoding="utf-8")) if current_path else None
engine.validate_intent_cdsl(intent, preflight_cdsl, engine, current_cdsl=current_cdsl)
validate_cdsl(preflight_cdsl, engine)
summary = str(arguments.get("summary") or "CDSL CAD model")
yieldable = await asyncio.to_thread(
build_revision,
settings=self.settings,
store=self.store,
task_id=task_id or None,
request=request,
cdsl=cdsl,
reference_ids=list(references),
summary=summary,
)
return {"ok": True, "summary": summary, "task_id": yieldable["task_id"], "revision_id": yieldable["revision_id"]}, yieldable
raw_assumptions = arguments.get("assumptions") or []
if not isinstance(raw_assumptions, list) or not all(isinstance(item, str) for item in raw_assumptions):
raise ValueError("generate_cdsl_model assumptions must be an array of strings")
assumptions = [item.strip() for item in raw_assumptions if item.strip()]
selection = part_skill_selection or self.part_skill_library.select(request)
part_skill_audit = self.part_skill_library.audit(selection, cdsl, assumptions)
state["phase"] = "BUILDING"
try:
yieldable = await asyncio.to_thread(
build_revision,
settings=self.settings,
store=self.store,
task_id=task_id or None,
request=request,
cdsl=cdsl,
reference_ids=list(references),
summary=summary,
part_skills=part_skill_audit,
generation_assumptions=assumptions,
design_intent=intent,
design_intent_path=str(intent_record["record"].get("path") or ""),
)
except Exception:
# The accepted plan stays current so the model can submit a
# corrected implementation without silently replanning.
state["phase"] = "INTENT_ACCEPTED"
raise
state["phase"] = "COMPLETED"
return {
"ok": True,
"summary": summary,
"task_id": yieldable["task_id"],
"revision_id": yieldable["revision_id"],
"design_intent_id": design_intent_id,
}, yieldable
raise ValueError(f"Unknown agent tool: {name}")
@staticmethod
@@ -800,7 +1059,8 @@ class AgentService:
"search_cdsl_library": "检索 CDSL 模型库",
"read_cdsl_reference": "读取 CDSL 参考模型",
"read_current_cdsl": "读取当前 CDSL",
"generate_cdsl_model": "生成 CDSL CAD 模型",
"propose_design_intent": "生成设计意图",
"generate_cdsl_model": "生成 CDSL",
}.get(name, "调用 CAD 工具")
def _attachment_message(self, conversation: dict[str, Any], model: ProviderModel) -> list[dict[str, Any]] | str:
+9
View File
@@ -147,6 +147,10 @@ def apply_direct_edit(
revision_id = str((task or {}).get("current_revision") or "")
if source is None or not revision_id:
raise ValueError("Task has no successful CDSL revision")
current_revision = next(
(item for item in (task or {}).get("revisions", []) if item.get("revision_id") == revision_id),
{},
)
frame = _selection_frame(selection)
cdsl = copy.deepcopy(json.loads(source.read_text(encoding="utf-8")))
features = cdsl.setdefault("features", [])
@@ -184,4 +188,9 @@ def apply_direct_edit(
summary=f"Applied {operation}",
parent_revision_id=revision_id,
operation={"type": operation, "selection": selection, "parameters": parameters},
part_skills=None,
generation_assumptions=[],
design_intent_id=str(current_revision.get("design_intent_id") or ""),
design_intent_path=str(current_revision.get("design_intent_path") or ""),
design_intent_status="accepted" if current_revision.get("design_intent_id") else "",
)
+148 -46
View File
@@ -319,6 +319,68 @@ def apply_parameter_updates(cdsl: dict[str, Any], values: dict[str, float]) -> t
return updated, parameter_contract(updated)
def _part_skill_audit(
part_skills: dict[str, Any] | None,
request: str,
generation_assumptions: list[str] | None,
) -> dict[str, Any]:
"""Normalize the planning audit persisted beside a product revision."""
audit = copy.deepcopy(part_skills) if isinstance(part_skills, dict) else {}
skills = [item for item in audit.get("skills") or [] if isinstance(item, dict)]
skill_ids = [str(item) for item in audit.get("skill_ids") or [] if str(item)]
if not skill_ids:
skill_ids = [str(item.get("id")) for item in skills if item.get("id")]
evidence = audit.get("evidence") if isinstance(audit.get("evidence"), dict) else {}
audit.update({
"schema_version": str(audit.get("schema_version") or "1.0"),
"request": str(audit.get("request") or request),
"structural_intent": str(audit.get("structural_intent") or request),
"skill_ids": skill_ids,
"skills": skills,
"inherited_skill_ids": [str(item) for item in audit.get("inherited_skill_ids") or [] if str(item)],
"assumptions": [str(item) for item in generation_assumptions or []],
"evidence": evidence,
"capability_translations": [
item for item in audit.get("capability_translations") or [] if isinstance(item, dict)
],
})
return audit
def _generation_context(
reference_ids: list[str],
part_skill_audit: dict[str, Any],
design_intent: dict[str, Any] | None = None,
design_intent_path: str = "",
design_intent_id: str = "",
) -> dict[str, Any]:
skills = [item for item in part_skill_audit.get("skills") or [] if isinstance(item, dict)]
intent = design_intent if isinstance(design_intent, dict) else {}
return {
"design_intent_id": str(intent.get("intent_id") or design_intent_id or ""),
"design_intent_path": design_intent_path,
"design_intent_structures": [
str(item.get("id") or "")
for item in intent.get("structures") or []
if isinstance(item, dict) and item.get("id")
],
"cdsl_reference_ids": list(reference_ids),
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
"part_skill_paths": [
{
"id": str(item.get("id") or ""),
"bridge": str(item.get("bridge") or ""),
"source": str(item.get("source") or ""),
}
for item in skills
],
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
"design_intent_assumptions": list(part_skill_audit.get("design_intent_assumptions") or []),
"design_intent_capability_gaps": list(part_skill_audit.get("design_intent_capability_gaps") or []),
"capability_translations": list(part_skill_audit.get("capability_translations") or []),
}
def build_revision(
*,
settings: Settings,
@@ -331,18 +393,16 @@ def build_revision(
parent_revision_id: str | None = None,
operation: dict[str, Any] | None = None,
attachments: list[dict[str, Any]] | None = None,
part_skills: dict[str, Any] | None = None,
generation_assumptions: list[str] | None = None,
design_intent: dict[str, Any] | None = None,
design_intent_path: str = "",
design_intent_id: str = "",
design_intent_status: str = "",
) -> dict[str, Any]:
engine = load_engine(settings)
task = store.ensure_task(task_id, request)
revision_id, revision_dir = store.next_revision(task["task_id"])
cdsl_copy = copy.deepcopy(cdsl)
cdsl_copy["part_id"] = task["task_id"]
meta = cdsl_copy.setdefault("meta", {})
if not isinstance(meta, dict):
raise ValueError("CDSL meta must be an object when present")
if not isinstance(meta.get("editable_parameters"), list) or not meta["editable_parameters"]:
meta["editable_parameters"] = _derived_parameters(cdsl_copy)
validate_cdsl(cdsl_copy, engine)
cdsl_path = revision_dir / "model.cdsl.json"
step_path = revision_dir / "model.step"
glb_path = revision_dir / "model.glb"
@@ -352,13 +412,76 @@ def build_revision(
parameters_path = revision_dir / "parameters.json"
selector_path = revision_dir / "model.selector.json"
edges_path = revision_dir / "model.edges.json"
part_skills_path = revision_dir / "part-skills.json"
intent = copy.deepcopy(design_intent) if isinstance(design_intent, dict) else {}
intent_assumptions = [str(item) for item in intent.get("assumptions") or [] if str(item)]
merged_assumptions = list(dict.fromkeys([
*intent_assumptions,
*(str(item) for item in generation_assumptions or [] if str(item)),
]))
part_skill_audit = _part_skill_audit(part_skills, request, merged_assumptions)
intent_id = str(intent.get("intent_id") or design_intent_id or "")
if design_intent_path:
design_intent_path = str(design_intent_path)
part_skill_audit.update({
"design_intent_id": intent_id,
"design_intent_path": design_intent_path,
"intent_status": str(intent.get("status") or design_intent_status or ""),
"design_intent_assumptions": intent_assumptions,
"design_intent_capability_gaps": [
item for item in intent.get("capability_gaps") or [] if isinstance(item, dict)
],
})
generation_context = _generation_context(reference_ids, part_skill_audit, intent, design_intent_path, intent_id)
write_json(request_path, {"request": request, "created_at": now_iso()})
write_json(references_path, {"reference_ids": reference_ids})
write_json(cdsl_path, cdsl_copy)
contract = parameter_contract(cdsl_copy)
write_json(parameters_path, contract)
write_json(part_skills_path, part_skill_audit)
def revision_record(status: str, *, error: str = "", engine_name: str = "") -> dict[str, Any]:
record = {
"revision_id": revision_id,
"status": status,
"created_at": now_iso(),
"request_path": request_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"cdsl_path": cdsl_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"report_path": report_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"parameters_path": parameters_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"part_skills_path": part_skills_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"design_intent_id": intent_id,
"design_intent_path": design_intent_path,
"part_skill_ids": list(part_skill_audit.get("skill_ids") or []),
"generation_assumptions": list(part_skill_audit.get("assumptions") or []),
"reference_ids": reference_ids,
"summary": summary,
"parent_revision_id": parent_revision_id or "",
"operation": operation or {},
"attachments": attachments or [],
}
if status == "success":
record.update({
"step_path": step_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"glb_path": glb_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"selector_path": selector_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"edges_path": edges_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"engine": engine_name,
})
else:
record["error"] = error
return record
try:
cdsl_copy = copy.deepcopy(cdsl)
if not isinstance(cdsl_copy, dict):
raise ValueError("CDSL must be a JSON object")
cdsl_copy["part_id"] = task["task_id"]
meta = cdsl_copy.setdefault("meta", {})
if not isinstance(meta, dict):
raise ValueError("CDSL meta must be an object when present")
if not isinstance(meta.get("editable_parameters"), list) or not meta["editable_parameters"]:
meta["editable_parameters"] = _derived_parameters(cdsl_copy)
write_json(cdsl_path, cdsl_copy)
write_json(parameters_path, parameter_contract(cdsl_copy))
validate_cdsl(cdsl_copy, engine)
# Product revisions are semantic CDSL artifacts. Do not route them
# through the legacy rebuild entry point, which is allowed to use
# compiler_context/translator compatibility fallbacks.
@@ -369,44 +492,23 @@ def build_revision(
selector, edges = topology_sidecars(engine_result, preview)
write_json(selector_path, selector)
write_json(edges_path, edges)
report = {"engine_result": engine_result, "preview": preview, "validated_at": now_iso()}
report = {
"engine_result": engine_result,
"preview": preview,
"generation_context": generation_context,
"validated_at": now_iso(),
}
write_json(report_path, report)
revision = {
"revision_id": revision_id,
"status": "success",
"created_at": now_iso(),
"request_path": request_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"cdsl_path": cdsl_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"step_path": step_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"glb_path": glb_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"report_path": report_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"parameters_path": parameters_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"selector_path": selector_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"edges_path": edges_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"reference_ids": reference_ids,
"summary": summary,
"engine": engine_result["engine"],
"parent_revision_id": parent_revision_id or "",
"operation": operation or {},
"attachments": attachments or [],
}
revision = revision_record("success", engine_name=str(engine_result["engine"]))
except Exception as error:
write_json(report_path, {"error": str(error), "validated_at": now_iso()})
revision = {
"revision_id": revision_id,
"status": "failed",
"created_at": now_iso(),
"request_path": request_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"cdsl_path": cdsl_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"report_path": report_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"parameters_path": parameters_path.relative_to(store.task_dir(task["task_id"])).as_posix(),
"reference_ids": reference_ids,
"summary": summary,
if not cdsl_path.is_file() and isinstance(cdsl, dict):
write_json(cdsl_path, copy.deepcopy(cdsl))
write_json(report_path, {
"error": str(error),
"parent_revision_id": parent_revision_id or "",
"operation": operation or {},
"attachments": attachments or [],
}
"generation_context": generation_context,
"validated_at": now_iso(),
})
revision = revision_record("failed", error=str(error))
store.update_task(task["task_id"], revision)
raise
store.update_task(task["task_id"], revision)
+303
View File
@@ -0,0 +1,303 @@
from __future__ import annotations
import json
import re
import unicodedata
from dataclasses import dataclass
from pathlib import Path
from typing import Any
_WORD = re.compile(r"[a-z0-9]+(?:[-'][a-z0-9]+)*")
_REPLACEMENT_INTENT = (
"replace the whole part",
"replace entire part",
"replace the part",
"replace this part",
"start over as",
"replace with a",
"替换整个零件",
"替换整个部件",
"替换零件",
"重新生成一个",
"改成一个新的",
)
def _normalize(value: str) -> str:
return unicodedata.normalize("NFKC", str(value or "")).casefold().strip()
def _matches(text: str, phrase: str) -> bool:
normalized_text = _normalize(text)
normalized_phrase = _normalize(phrase)
if not normalized_phrase:
return False
if any("\u4e00" <= char <= "\u9fff" for char in normalized_phrase):
return normalized_phrase in normalized_text
words = _WORD.findall(normalized_phrase)
if not words:
return normalized_phrase in normalized_text
return bool(re.search(r"(?<![a-z0-9])" + r"\s+".join(map(re.escape, words)) + r"(?![a-z0-9])", normalized_text))
def _specificity(phrase: str) -> int:
normalized = _normalize(phrase)
chinese = [char for char in normalized if "\u4e00" <= char <= "\u9fff"]
return len(chinese) if chinese else len(_WORD.findall(normalized))
@dataclass(frozen=True)
class PartSkill:
id: str
kind: str
title: str
priority: int
triggers: tuple[str, ...]
exclude: tuple[str, ...]
bridge: str
source: str
related: tuple[str, ...]
capability_translation_rules: tuple[str, ...]
@classmethod
def from_mapping(cls, value: dict[str, Any]) -> "PartSkill":
return cls(
id=str(value["id"]),
kind=str(value["kind"]),
title=str(value.get("title") or value["id"]),
priority=int(value.get("priority") or 0),
triggers=tuple(str(item) for item in value.get("triggers") or []),
exclude=tuple(str(item) for item in value.get("exclude") or []),
bridge=str(value["bridge"]),
source=str(value["source"]),
related=tuple(str(item) for item in value.get("related") or []),
capability_translation_rules=tuple(
str(item) for item in value.get("capability_translation_rules") or []
),
)
class PartSkillLibrary:
"""Deterministic local selector for CDSL-specific part planning guidance."""
def __init__(self, root: Path) -> None:
self.root = Path(root)
payload = json.loads((self.root / "catalog.json").read_text(encoding="utf-8"))
self.max_planning = int(payload.get("max_planning") or 1)
self.max_support = int(payload.get("max_support") or 3)
self.skills = tuple(PartSkill.from_mapping(item) for item in payload.get("skills") or [])
self.by_id = {skill.id: skill for skill in self.skills}
if len(self.by_id) != len(self.skills):
raise ValueError("Part skill catalog contains duplicate ids")
for skill in self.skills:
for relative in (skill.bridge, skill.source):
if not (self.root / relative).is_file():
raise ValueError(f"Part skill {skill.id} references missing file: {relative}")
def _matched(self, skill: PartSkill, request: str) -> tuple[int, tuple[str, ...]]:
if any(_matches(request, phrase) for phrase in skill.exclude):
return 0, ()
triggers = tuple(trigger for trigger in skill.triggers if _matches(request, trigger))
if not triggers:
return 0, ()
# Longer phrases are more specific than generic words such as "shaft"
# or "hole". Priority breaks ties between equally specific skills.
score = sum(max(1, _specificity(trigger)) * 10 for trigger in triggers)
return score + skill.priority, triggers
def _record(self, skill: PartSkill, *, selection: str, matched: tuple[str, ...] = ()) -> dict[str, Any]:
return {
"id": skill.id,
"kind": skill.kind,
"category": skill.kind,
"title": skill.title,
"source": skill.source,
"bridge": skill.bridge,
"capability_translation_rules": list(skill.capability_translation_rules),
"selection": selection,
"matched_triggers": list(matched),
}
def select(self, request: str, inherited_ids: list[str] | tuple[str, ...] = ()) -> dict[str, Any]:
inherited = [self.by_id[item] for item in inherited_ids if item in self.by_id]
scores = [(self._matched(skill, request), skill) for skill in self.skills]
planning_matches = sorted(
((score, triggers, skill) for (score, triggers), skill in scores if skill.kind == "planning" and score),
key=lambda item: (-item[0], -item[2].priority, item[2].id),
)
inherited_planning = [skill for skill in inherited if skill.kind == "planning"]
planning: list[PartSkill] = inherited_planning[: self.max_planning]
conflict: dict[str, Any] | None = None
replacement_requested = any(_matches(request, phrase) for phrase in _REPLACEMENT_INTENT)
if not planning and planning_matches:
planning = [planning_matches[0][2]]
elif planning and planning_matches and planning_matches[0][2].id not in {skill.id for skill in planning}:
if replacement_requested:
planning = [planning_matches[0][2]]
else:
conflict = {
"current": planning[0].id,
"matched": planning_matches[0][2].id,
"message": "A different primary part family matched this revision request; preserve the current family unless replacement is explicit.",
}
selected_ids = {skill.id for skill in planning}
inherited_planning_ids = {skill.id for skill in inherited_planning}
records = [
self._record(
skill,
selection="inherited" if skill.id in inherited_planning_ids else "selected",
matched=next((triggers for score, triggers, candidate in planning_matches if candidate.id == skill.id), ()),
)
for skill in planning
]
support: list[tuple[int, int, int, str, PartSkill, tuple[str, ...]]] = []
related_ids = {related for skill in planning for related in skill.related}
direct_support: list[tuple[int, PartSkill, tuple[str, ...]]] = []
for (score, triggers), skill in scores:
if skill.kind == "planning" or not score:
continue
direct_support.append((score, skill, triggers))
bonus = 100 if skill.id in related_ids else 0
# Direct request matches always win over inherited context, even
# when the current planning family's catalog does not name them.
support.append((3, bonus + score, skill.priority, skill.id, skill, triggers))
direct_related_ids = {
related
for _, skill, _ in direct_support
for related in skill.related
if related in self.by_id and self.by_id[related].kind != "planning"
}
known_support_ids = {item[3] for item in support}
for related_id in sorted(direct_related_ids - known_support_ids):
skill = self.by_id[related_id]
planning_bonus = 25 if skill.id in related_ids else 0
support.append((2, 50 + planning_bonus, skill.priority, skill.id, skill, ()))
known_support_ids.add(skill.id)
for skill in inherited:
if skill.kind != "planning" and skill.id not in known_support_ids:
# Keep prior guidance only after direct request matches and
# their catalog-declared supporting rules.
support.append((1, 0, skill.priority, skill.id, skill, ()))
known_support_ids.add(skill.id)
support.sort(key=lambda item: (-item[0], -item[1], -item[2], item[3]))
for _, _, _, _, skill, triggers in support:
if len(records) - len(planning) >= self.max_support or skill.id in selected_ids:
continue
selected_ids.add(skill.id)
inherited_marker = "inherited" if skill in inherited else "selected"
records.append(self._record(skill, selection=inherited_marker, matched=triggers))
return {
"schema_version": "1.0",
"request": str(request or ""),
"skills": records,
"skill_ids": [record["id"] for record in records],
"planning_ids": [record["id"] for record in records if record["kind"] == "planning"],
"support_ids": [record["id"] for record in records if record["kind"] != "planning"],
"inherited_skill_ids": [skill.id for skill in inherited],
"replacement_requested": replacement_requested,
"conflict": conflict,
}
def inherited_from_task(self, task: dict[str, Any] | None) -> list[str]:
revision_id = str((task or {}).get("current_revision") or "")
revisions = (task or {}).get("revisions") or []
revision = next((item for item in revisions if item.get("revision_id") == revision_id), None)
revision_ids = [str(item) for item in (revision or {}).get("part_skill_ids") or [] if str(item) in self.by_id]
if revision_ids:
return revision_ids
# A valid DesignIntent creates a task before the first runtime build.
# Its canonical backend selection must therefore be inheritable too.
intent_id = str((task or {}).get("current_design_intent_id") or "")
intent = next(
(item for item in (task or {}).get("design_intents") or [] if str(item.get("intent_id") or "") == intent_id),
None,
)
return [str(item) for item in (intent or {}).get("part_skill_ids") or [] if str(item) in self.by_id]
def render_context(self, selection: dict[str, Any]) -> str:
records = selection.get("skills") or []
if not records:
return "No part-family skill matched this request. Use the CDSL schema and library only."
sections = [
"Selected CDSL part-skill guidance (planning context only):",
"Part skill guidance never overrides the user request or the authoritative CDSL schema/runtime.",
"Translate the guidance into valid CDSL; do not emit build123d source or invent atomics.",
]
for record in records:
path = self.root / str(record["bridge"])
sections.append(f"\n[{record['id']}] ({record['selection']})\n{path.read_text(encoding='utf-8').strip()}")
if selection.get("conflict"):
sections.append("\nPrimary-family conflict: preserve the current task family. Ask one concise clarification question and do not generate until the user explicitly requests whole-part replacement or confirms the current family.")
return "\n".join(sections)
def audit(self, selection: dict[str, Any], cdsl: dict[str, Any], assumptions: list[str] | None = None) -> dict[str, Any]:
features = [item for item in cdsl.get("features") or [] if isinstance(item, dict)]
feature_ids = [str(item.get("id")) for item in features if item.get("id")]
atomics = [str(item.get("atomic_id")) for item in features]
sketches = [item for item in (cdsl.get("geometry") or {}).get("sketches") or [] if isinstance(item, dict)]
profiles = [str((item.get("profile") or {}).get("type")) for item in sketches]
evidence = {
"feature_ids": feature_ids,
"atomic_ids": sorted(set(atomics)),
"profile_types": sorted(set(profiles)),
"features": [
{
"id": str(item.get("id") or ""),
"atomic_id": str(item.get("atomic_id") or ""),
"sketch_id": str(item.get("sketch_id") or ""),
"params": item.get("params") if isinstance(item.get("params"), dict) else {},
}
for item in features
],
"profiles": [
{
"id": str(item.get("id") or ""),
"type": str((item.get("profile") or {}).get("type") or ""),
"profile": item.get("profile") if isinstance(item.get("profile"), dict) else {},
}
for item in sketches
],
}
translations: list[dict[str, Any]] = []
for record in selection.get("skills") or []:
skill_id = str(record.get("id"))
status = "exact"
reason = "The selected structural guidance is represented by the submitted CDSL plan."
if skill_id == "functional/flange-bolt-circle":
if "circles" in profiles and "extrude_cut_blind" in atomics:
status, reason = "expanded", "Circular bolt layout is represented by explicit circle geometry because no circular-pattern atomic exists."
else:
status, reason = "blocked", "No explicit circular bolt layout evidence was found in the CDSL."
elif skill_id == "atomic/threaded-hole-creation":
status, reason = "approximated", "The current runtime preserves a cylindrical bore but does not generate helical thread topology."
elif skill_id == "atomic/fillet-chamfer-last":
finishing = [item for item in features if item.get("atomic_id") in {"fillet", "chamfer"}]
if not finishing:
status, reason = "omitted", "No stable finishing selector was submitted; edge treatment was omitted."
elif skill_id == "atomic/pattern-holes-from-datum":
if "circles" in profiles or "circle_grid" in profiles:
status, reason = "expanded", "The layout is represented by explicit profile circles."
elif not any(item in {"pattern_linear", "pattern_mirror"} for item in atomics):
status, reason = "blocked", "No supported pattern or explicit circle layout was found."
translation: dict[str, Any] = {"skill_id": skill_id, "status": status, "reason": reason, "evidence": evidence}
if skill_id == "functional/flange-bolt-circle" and status == "expanded":
translation["translation"] = "circular_pattern_to_explicit_circles"
elif skill_id == "atomic/threaded-hole-creation":
translation["translation"] = "thread_geometry_omitted"
elif skill_id == "atomic/fillet-chamfer-last" and status == "omitted":
translation["translation"] = "selector_unavailable"
translations.append(translation)
return {
"schema_version": "1.0",
"request": selection.get("request", ""),
"structural_intent": selection.get("request", ""),
"skill_ids": list(selection.get("skill_ids") or []),
"skills": list(selection.get("skills") or []),
"inherited_skill_ids": list(selection.get("inherited_skill_ids") or []),
"conflict": selection.get("conflict"),
"assumptions": [str(item) for item in assumptions or []],
"evidence": evidence,
"capability_translations": translations,
}
+87 -2
View File
@@ -12,6 +12,7 @@ from app.settings import Settings
TASK_ID = re.compile(r"^cad_[a-z0-9]{12}$")
CONVERSATION_ID = re.compile(r"^conv_[a-z0-9]{12}$")
DESIGN_INTENT_ID = re.compile(r"^intent_[a-z0-9]{12}$")
def now_iso() -> str:
@@ -36,6 +37,13 @@ def safe_conversation_id(conversation_id: str) -> str:
return value
def safe_design_intent_id(intent_id: str) -> str:
value = str(intent_id or "").strip()
if not DESIGN_INTENT_ID.fullmatch(value):
raise ValueError("Invalid design intent id")
return value
def safe_relative_path(value: str) -> str:
path = Path(str(value or ""))
if not value or path.is_absolute() or ".." in path.parts:
@@ -102,7 +110,7 @@ class WorkspaceStore:
write_json(path, current)
return current
record = {
"schema_version": "1.0",
"schema_version": "1.1",
"conversation_id": cid,
"created_at": now_iso(),
"updated_at": now_iso(),
@@ -144,12 +152,14 @@ class WorkspaceStore:
task_dir = self.task_dir(tid)
(task_dir / "revisions").mkdir(parents=True, exist_ok=True)
record = {
"schema_version": "1.0",
"schema_version": "1.1",
"task_id": tid,
"request": request,
"created_at": now_iso(),
"updated_at": now_iso(),
"current_revision": "",
"current_design_intent_id": "",
"design_intents": [],
"revisions": [],
}
write_json(path, record)
@@ -171,6 +181,81 @@ class WorkspaceStore:
write_json(self.task_path(task_id), task)
return task
def create_design_intent(
self,
task_id: str | None,
request: str,
intent: dict[str, Any],
part_skill_selection: dict[str, Any],
) -> dict[str, Any]:
"""Persist a validated plan and atomically make it the current plan."""
task = self.ensure_task(task_id, request)
task.setdefault("design_intents", [])
task.setdefault("current_design_intent_id", "")
task["schema_version"] = "1.1"
previous_id = str(task.get("current_design_intent_id") or "")
if previous_id:
for record in task["design_intents"]:
if str(record.get("intent_id") or "") == previous_id and record.get("status") != "superseded":
record["status"] = "superseded"
intent_id = new_id("intent")
created_at = now_iso()
relative = Path("planning") / f"design-intent-{intent_id}.json"
skill_ids = [str(item) for item in part_skill_selection.get("skill_ids") or [] if str(item)]
persisted = dict(intent)
persisted.update({
"intent_id": intent_id,
"created_at": created_at,
"part_skill_ids": skill_ids,
"part_skill_selection": part_skill_selection,
})
write_json(self.artifact_path(task["task_id"], relative.as_posix()), persisted)
record = {
"intent_id": intent_id,
"status": "accepted" if persisted.get("status") == "ready" else "pending",
"created_at": created_at,
"path": relative.as_posix(),
"part_skill_ids": skill_ids,
"base_revision_id": str(persisted.get("base_revision_id") or ""),
}
task["design_intents"].append(record)
task["current_design_intent_id"] = intent_id
task["updated_at"] = now_iso()
write_json(self.task_path(task["task_id"]), task)
return {"task_id": task["task_id"], **record, "intent": persisted}
def read_design_intent(self, task_id: str, intent_id: str | None = None) -> dict[str, Any] | None:
task = self.read_task(task_id)
if not task:
return None
chosen = safe_design_intent_id(intent_id) if intent_id else str(task.get("current_design_intent_id") or "")
if not chosen:
return None
record = next(
(item for item in task.get("design_intents") or [] if str(item.get("intent_id") or "") == chosen),
None,
)
if not isinstance(record, dict):
return None
path = self.artifact_path(task_id, str(record.get("path") or ""))
intent = read_json(path)
if not isinstance(intent, dict):
return None
return {"task_id": task_id, "record": record, "intent": intent}
def update_design_intent_status(self, task_id: str, intent_id: str, status: str) -> dict[str, Any]:
safe_id = safe_design_intent_id(intent_id)
task = self.ensure_task(task_id, "")
records = task.setdefault("design_intents", [])
record = next((item for item in records if str(item.get("intent_id") or "") == safe_id), None)
if not isinstance(record, dict):
raise ValueError("Design intent not found")
record["status"] = str(status)
task["updated_at"] = now_iso()
write_json(self.task_path(task_id), task)
return record
def read_task(self, task_id: str) -> dict[str, Any] | None:
return read_json(self.task_path(task_id))
+5
View File
@@ -13,6 +13,7 @@ from .rebuild import compare_with_gold, compile_cdsl_to_pack, run_cdsl_only, run
from .semantic_validation import validate_semantic_cdsl
from .sketch_solver import SHAPE_GENERATORS, resolve_all_sketches, resolve_required_sketches
from .runtime import ALL_ATOMIC_IDS, EXECUTORS, RuntimeExecutionError, analyze_cdsl, rebuild_cdsl
from .design_intent import DesignIntentError, design_intent_from_cdsl, validate_design_intent, validate_intent_cdsl
# The package-level runtime contract is the session executor registry. The
# older llm_engine dispatcher remains available only for legacy engine packs.
@@ -38,6 +39,10 @@ __all__ = [
"rebuild_cdsl",
"analyze_cdsl",
"RuntimeExecutionError",
"DesignIntentError",
"validate_design_intent",
"validate_intent_cdsl",
"design_intent_from_cdsl",
]
__version__ = "1.0.0"
+331
View File
@@ -0,0 +1,331 @@
"""Validation for the semantic planning artifact between a request and CDSL."""
from __future__ import annotations
import json
from copy import deepcopy
from pathlib import Path
from typing import Any
from jsonschema import Draft202012Validator
from jsonschema.exceptions import SchemaError
class DesignIntentError(ValueError):
"""A plan-contract violation with a stable error code for the Agent."""
def __init__(self, code: str, message: str) -> None:
super().__init__(message)
self.code = code
def _schema_path(engine: Any) -> Path:
return Path(str(engine.__file__)).with_name("design_intent_schema.json")
def load_design_intent_schema(engine: Any) -> dict[str, Any]:
path = _schema_path(engine)
try:
schema = json.loads(path.read_text(encoding="utf-8"))
Draft202012Validator.check_schema(schema)
except (OSError, json.JSONDecodeError, SchemaError) as error:
raise RuntimeError("The local DesignIntent JSON Schema is unavailable or invalid") from error
return schema
def _location(error: Any) -> str:
return "$" + "".join(
f"[{item}]" if isinstance(item, int) else f".{item}"
for item in error.absolute_path
)
def _schema_validate(intent: dict[str, Any], engine: Any) -> None:
errors = sorted(
Draft202012Validator(load_design_intent_schema(engine)).iter_errors(intent),
key=lambda error: (list(error.absolute_path), error.message),
)
if errors:
error = errors[0]
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"DesignIntent schema violation at {_location(error)}: {error.message}",
)
def _runtime_capabilities(engine: Any) -> tuple[set[str], set[str], dict[str, dict[str, Any]]]:
try:
profile_schema = json.loads(Path(str(engine.__file__)).with_name("profile_schema.json").read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise RuntimeError("The local CDSL profile schema is unavailable or invalid") from error
atomic_ids = set(profile_schema.get("runtime_supported_atomic_ids") or getattr(engine, "SUPPORTED_ATOMIC_IDS", ()))
profiles = {
name
for name, contract in (profile_schema.get("profiles") or {}).items()
if isinstance(contract, dict) and contract.get("agent_allowed") is True
}
atomic_contracts = {
str(atomic_id): contract
for atomic_id, contract in (profile_schema.get("feature_atomic_ids") or {}).items()
if isinstance(contract, dict)
}
return {str(item) for item in atomic_ids}, {str(item) for item in profiles}, atomic_contracts
def _blocking(intent: dict[str, Any]) -> bool:
questions = intent.get("open_questions") or []
gaps = intent.get("capability_gaps") or []
return any(isinstance(item, dict) and item.get("blocking") is True for item in [*questions, *gaps])
def validate_design_intent(
intent: dict[str, Any],
engine: Any,
*,
current_revision_id: str = "",
) -> dict[str, Any]:
"""Validate DesignIntent syntax and semantic ordering against runtime capability."""
if not isinstance(intent, dict):
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent must be a JSON object")
_schema_validate(intent, engine)
normalized = deepcopy(intent)
mode = str(normalized["mode"])
base_revision_id = str(normalized["base_revision_id"] or "")
if mode == "create" and base_revision_id:
raise DesignIntentError("INVALID_DESIGN_INTENT", "create DesignIntent must not set base_revision_id")
if mode == "revise":
if not base_revision_id:
raise DesignIntentError("INVALID_DESIGN_INTENT", "revise DesignIntent requires base_revision_id")
if current_revision_id and base_revision_id != current_revision_id:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
"revise DesignIntent base_revision_id must be the current successful revision",
)
structures = normalized["structures"]
ids = [str(item["id"]) for item in structures]
feature_ids = [str(item["cdsl_feature_id"]) for item in structures]
if len(set(ids)) != len(ids):
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent structure ids must be unique")
if len(set(feature_ids)) != len(feature_ids):
raise DesignIntentError("INVALID_DESIGN_INTENT", "DesignIntent cdsl_feature_id values must be unique")
feature_order = [str(item) for item in normalized["feature_order"]]
if len(set(feature_order)) != len(feature_order) or set(feature_order) != set(ids):
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
"feature_order must list every DesignIntent structure exactly once",
)
order = {structure_id: index for index, structure_id in enumerate(feature_order)}
atomic_ids, profile_types, atomic_contracts = _runtime_capabilities(engine)
for structure in structures:
structure_id = str(structure["id"])
for dependency in structure["depends_on"]:
dependency_id = str(dependency)
if dependency_id not in order:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Structure {structure_id} depends on an unknown structure {dependency_id}",
)
if order[dependency_id] >= order[structure_id]:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Structure {structure_id} must depend only on earlier feature_order entries",
)
strategy = structure["cdsl_strategy"]
atomic_id = str(strategy["atomic_id"])
if atomic_id not in atomic_ids:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Structure {structure_id} uses unsupported atomic_id {atomic_id}",
)
profile_type = str(strategy.get("profile_type") or "")
if atomic_contracts.get(atomic_id, {}).get("requires_sketch") and not profile_type:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Structure {structure_id} uses {atomic_id} and requires a profile_type",
)
if profile_type and profile_type not in profile_types:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Structure {structure_id} uses unsupported or agent-disallowed profile_type {profile_type}",
)
if normalized["status"] == "ready" and _blocking(normalized):
raise DesignIntentError(
"DESIGN_INTENT_BLOCKED",
"A DesignIntent with blocking questions or capability gaps must need clarification",
)
if normalized["status"] == "needs_clarification" and not _blocking(normalized):
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
"needs_clarification requires a blocking question or capability gap",
)
if normalized["status"] == "ready" and not structures:
raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready DesignIntent needs at least one structure")
if normalized["status"] == "ready" and mode == "create" and not any(item["role"] == "base" for item in structures):
raise DesignIntentError("INVALID_DESIGN_INTENT", "A ready create DesignIntent needs a base structure")
nonblocking_gaps = [item for item in normalized["capability_gaps"] if not item["blocking"]]
if nonblocking_gaps and not normalized["assumptions"]:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
"Non-blocking capability gaps require an explicit approximation assumption",
)
for gap in normalized["capability_gaps"]:
if str(gap["structure_id"]) not in set(ids):
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"Capability gap references an unknown structure {gap['structure_id']}",
)
valid_expectation_ids = set(ids) | set(feature_ids)
for expectation in normalized["verification_expectations"]:
if expectation["type"] == "feature_count" and str(expectation["feature_id"]) not in valid_expectation_ids:
raise DesignIntentError(
"INVALID_DESIGN_INTENT",
f"feature_count expectation references an unknown structure or CDSL feature {expectation['feature_id']}",
)
return normalized
def _has_selector_evidence(value: Any, role: str) -> bool:
if isinstance(value, dict):
for key, child in value.items():
if role in {"selector", "selectors"} and key == "selectors" and isinstance(child, list) and child:
return True
if key == role and child not in (None, "", [], {}):
if not (isinstance(child, dict) and set(child) == {"unresolved"}):
return True
if _has_selector_evidence(child, role):
return True
elif isinstance(value, list):
return any(_has_selector_evidence(item, role) for item in value)
return False
def validate_intent_cdsl(
intent: dict[str, Any],
cdsl: dict[str, Any],
engine: Any,
*,
current_cdsl: dict[str, Any] | None = None,
) -> None:
"""Ensure an executable CDSL document faithfully realizes one accepted plan."""
normalized = validate_design_intent(intent, engine)
if normalized["status"] != "ready":
raise DesignIntentError("DESIGN_INTENT_BLOCKED", "CDSL generation is blocked until the DesignIntent is ready")
if not isinstance(cdsl, dict):
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must be a JSON object")
features = cdsl.get("features")
sketches = (cdsl.get("geometry") or {}).get("sketches")
if not isinstance(features, list) or not isinstance(sketches, list):
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL must contain features and sketches")
feature_by_id = {str(feature.get("id") or ""): feature for feature in features if isinstance(feature, dict)}
if len(feature_by_id) != len(features):
raise DesignIntentError("INTENT_CDSL_MISMATCH", "CDSL features must have unique ids")
structure_by_id = {str(item["id"]): item for item in normalized["structures"]}
expected_feature_ids = [str(structure_by_id[item]["cdsl_feature_id"]) for item in normalized["feature_order"]]
if normalized["mode"] == "revise" and isinstance(current_cdsl, dict):
base_feature_ids = {
str(feature.get("id") or "")
for feature in current_cdsl.get("features") or []
if isinstance(feature, dict) and feature.get("id")
}
if not base_feature_ids.issubset(set(expected_feature_ids)):
missing = ", ".join(sorted(base_feature_ids - set(expected_feature_ids)))
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
f"revise DesignIntent must explicitly preserve current features: {missing}",
)
actual_feature_ids = [str(feature.get("id") or "") for feature in features]
if actual_feature_ids != expected_feature_ids:
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
"CDSL feature ids and order must exactly match DesignIntent feature_order",
)
sketch_by_id = {str(sketch.get("id") or ""): sketch for sketch in sketches if isinstance(sketch, dict)}
feature_for_structure = {
str(structure["id"]): feature_by_id[str(structure["cdsl_feature_id"])]
for structure in normalized["structures"]
}
for structure in normalized["structures"]:
structure_id = str(structure["id"])
feature = feature_for_structure[structure_id]
strategy = structure["cdsl_strategy"]
if feature.get("atomic_id") != strategy["atomic_id"]:
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
f"CDSL feature {feature.get('id')} atomic_id does not match structure {structure_id}",
)
expected_dependencies = {
str(structure_by_id[dependency]["cdsl_feature_id"])
for dependency in structure["depends_on"]
}
actual_dependencies = {str(item) for item in feature.get("depends_on") or []}
if not expected_dependencies.issubset(actual_dependencies):
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
f"CDSL feature {feature.get('id')} is missing planned dependencies",
)
profile_type = str(strategy.get("profile_type") or "")
if profile_type:
sketch_id = str(feature.get("sketch_id") or "")
sketch = sketch_by_id.get(sketch_id)
actual_profile_type = str(((sketch or {}).get("profile") or {}).get("type") or "")
if actual_profile_type != profile_type:
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
f"CDSL feature {feature.get('id')} must use profile_type {profile_type}",
)
for selector_role in strategy["selector_roles"]:
if not _has_selector_evidence(feature, str(selector_role)):
raise DesignIntentError(
"INTENT_CDSL_MISMATCH",
f"CDSL feature {feature.get('id')} is missing selector evidence for {selector_role}",
)
def design_intent_from_cdsl(cdsl: dict[str, Any], *, request: str, mode: str = "create", base_revision_id: str = "") -> dict[str, Any]:
"""Create a conservative audit-only plan for legacy revisions when needed."""
sketches = (cdsl.get("geometry") or {}).get("sketches") or []
profile_by_sketch_id = {
str(sketch.get("id") or ""): str((sketch.get("profile") or {}).get("type") or "")
for sketch in sketches
if isinstance(sketch, dict)
}
structures: list[dict[str, Any]] = []
for index, feature in enumerate(cdsl.get("features") or []):
if not isinstance(feature, dict):
continue
feature_id = str(feature.get("id") or f"feature_{index + 1}")
atomic_id = str(feature.get("atomic_id") or "")
profile_type = profile_by_sketch_id.get(str(feature.get("sketch_id") or ""), "")
strategy: dict[str, Any] = {
"atomic_id": atomic_id,
"parameter_roles": sorted(str(key) for key in (feature.get("params") or {}).keys()),
"selector_roles": [],
}
if profile_type:
strategy["profile_type"] = profile_type
structures.append({
"id": feature_id,
"cdsl_feature_id": feature_id,
"role": "base" if index == 0 else "subtractive" if "cut" in atomic_id or atomic_id.startswith("hole") else "additive",
"purpose": str(feature.get("name") or feature_id),
"depends_on": [str(item) for item in feature.get("depends_on") or []],
"cdsl_strategy": strategy,
})
return {
"schema": "cad.cdsl.design-intent.v1",
"schema_version": "1.0",
"mode": mode,
"request": request,
"base_revision_id": base_revision_id,
"structures": structures,
"feature_order": [item["id"] for item in structures],
"assumptions": ["Synthesized from a legacy CDSL revision."],
"open_questions": [],
"capability_gaps": [],
"verification_expectations": [],
"status": "ready",
}
@@ -0,0 +1,124 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://cdsl.local/schema/cad.cdsl.design-intent.v1",
"title": "CDSL design intent",
"type": "object",
"properties": {
"schema": {"const": "cad.cdsl.design-intent.v1"},
"schema_version": {"const": "1.0"},
"mode": {"enum": ["create", "revise"]},
"request": {"type": "string", "minLength": 1},
"base_revision_id": {"type": "string"},
"structures": {"type": "array", "items": {"$ref": "#/$defs/structure"}},
"feature_order": {"type": "array", "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"}},
"assumptions": {"type": "array", "items": {"type": "string", "minLength": 1}},
"open_questions": {"type": "array", "items": {"$ref": "#/$defs/openQuestion"}},
"capability_gaps": {"type": "array", "items": {"$ref": "#/$defs/capabilityGap"}},
"verification_expectations": {"type": "array", "items": {"$ref": "#/$defs/verificationExpectation"}},
"status": {"enum": ["ready", "needs_clarification"]},
"intent_id": {"type": "string", "pattern": "^intent_[a-z0-9]{12}$"},
"created_at": {"type": "string", "minLength": 1},
"part_skill_ids": {"type": "array", "items": {"type": "string", "minLength": 1}},
"part_skill_selection": {"type": "object"}
},
"required": [
"schema",
"schema_version",
"mode",
"request",
"base_revision_id",
"structures",
"feature_order",
"assumptions",
"open_questions",
"capability_gaps",
"verification_expectations",
"status"
],
"additionalProperties": false,
"$defs": {
"structure": {
"type": "object",
"properties": {
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
"cdsl_feature_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
"role": {"enum": ["base", "reference", "additive", "subtractive", "dressup", "pattern"]},
"purpose": {"type": "string", "minLength": 1},
"depends_on": {"type": "array", "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"}},
"cdsl_strategy": {"$ref": "#/$defs/cdslStrategy"}
},
"required": ["id", "cdsl_feature_id", "role", "purpose", "depends_on", "cdsl_strategy"],
"additionalProperties": false
},
"cdslStrategy": {
"type": "object",
"properties": {
"atomic_id": {"type": "string", "minLength": 1},
"profile_type": {"type": "string", "minLength": 1},
"parameter_roles": {"type": "array", "items": {"type": "string", "minLength": 1}},
"selector_roles": {"type": "array", "items": {"type": "string", "minLength": 1}}
},
"required": ["atomic_id", "parameter_roles", "selector_roles"],
"additionalProperties": false
},
"openQuestion": {
"type": "object",
"properties": {
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
"question": {"type": "string", "minLength": 1},
"blocking": {"type": "boolean"}
},
"required": ["id", "question", "blocking"],
"additionalProperties": false
},
"capabilityGap": {
"type": "object",
"properties": {
"code": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,100}$"},
"structure_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
"message": {"type": "string", "minLength": 1},
"blocking": {"type": "boolean"}
},
"required": ["code", "structure_id", "message", "blocking"],
"additionalProperties": false
},
"verificationExpectation": {
"oneOf": [
{
"type": "object",
"properties": {
"type": {"const": "feature_count"},
"feature_id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
"expected": {"type": "integer", "minimum": 0}
},
"required": ["type", "feature_id", "expected"],
"additionalProperties": false
},
{
"type": "object",
"properties": {"type": {"const": "symmetry"}, "axis": {"enum": ["x", "y", "z"]}},
"required": ["type", "axis"],
"additionalProperties": false
},
{
"type": "object",
"properties": {
"type": {"const": "bbox"},
"expected_mm": {
"type": "object",
"properties": {
"x": {"type": "number", "exclusiveMinimum": 0},
"y": {"type": "number", "exclusiveMinimum": 0},
"z": {"type": "number", "exclusiveMinimum": 0}
},
"required": ["x", "y", "z"],
"additionalProperties": false
}
},
"required": ["type", "expected_mm"],
"additionalProperties": false
}
]
}
}
}
+5 -5
View File
@@ -62,11 +62,11 @@ class ParseToolArgumentsTests(unittest.TestCase):
self.assertNotIn("extrude", cdsl["$defs"]["feature_atomic_ids"]["enum"])
self.assertEqual(cdsl, CDSL_TOOL_SCHEMA)
def test_strict_tool_schema_is_limited_to_cdsl_generation_arguments(self) -> None:
def test_strict_tool_schema_covers_design_intent_and_cdsl_generation_arguments(self) -> None:
tools = tools_for_model(ProviderModel("strict-model", strict_tool_schema=True))
strict_tools = [tool["function"]["name"] for tool in tools if tool["function"].get("strict")]
self.assertEqual(strict_tools, ["generate_cdsl_model"])
self.assertEqual(strict_tools, ["propose_design_intent", "generate_cdsl_model"])
generate_tool = next(tool for tool in tools if tool["function"]["name"] == "generate_cdsl_model")
self.assertEqual(generate_tool["function"]["parameters"]["properties"]["summary"], {"type": "string", "minLength": 1})
self.assertEqual(generate_tool["function"]["parameters"]["properties"]["cdsl"], CDSL_TOOL_SCHEMA)
@@ -243,7 +243,7 @@ class ToolArgumentsRetryTests(unittest.TestCase):
self.assertTrue(any("已修正工具参数" in str(event.get("text", "")) for event in events))
self.assertEqual(list(settings.task_root.glob("cad_*")), [])
def test_incomplete_cdsl_is_returned_to_the_model_without_creating_a_task(self) -> None:
def test_direct_cdsl_generation_is_rejected_without_creating_a_task(self) -> None:
class RetryAgent(AgentService):
def __init__(self, *args: object, **kwargs: object) -> None:
super().__init__(*args, **kwargs)
@@ -328,8 +328,8 @@ class ToolArgumentsRetryTests(unittest.TestCase):
tool_result = agent.seen_messages[1][-1]
self.assertEqual(tool_result["role"], "tool")
self.assertEqual(json.loads(str(tool_result["content"]))["code"], "INVALID_CDSL")
self.assertEqual(agent.required_tools, [None, "generate_cdsl_model", None])
self.assertEqual(json.loads(str(tool_result["content"]))["code"], "DESIGN_INTENT_REQUIRED")
self.assertEqual(agent.required_tools, [None, None, None])
self.assertEqual(list(settings.task_root.glob("cad_*")), [])
+202
View File
@@ -0,0 +1,202 @@
from __future__ import annotations
import copy
import sys
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))
def workplane(z: float = 0.0) -> dict[str, list[float]]:
return {
"origin_mm": [0.0, 0.0, z],
"x_dir": [1.0, 0.0, 0.0],
"y_dir": [0.0, 1.0, 0.0],
"normal": [0.0, 0.0, 1.0],
}
def mounting_plate_intent() -> dict:
return {
"schema": "cad.cdsl.design-intent.v1",
"schema_version": "1.0",
"mode": "create",
"request": "Create a mounting plate with four holes and a center slot",
"base_revision_id": "",
"structures": [
{
"id": "base_plate",
"cdsl_feature_id": "base_add",
"role": "base",
"purpose": "Rectangular mounting plate",
"depends_on": [],
"cdsl_strategy": {
"atomic_id": "extrude_add_blind",
"profile_type": "rectangle",
"parameter_roles": ["width_mm", "height_mm", "thickness_mm"],
"selector_roles": [],
},
},
{
"id": "mounting_holes",
"cdsl_feature_id": "hole_cut",
"role": "subtractive",
"purpose": "Four mounting holes",
"depends_on": ["base_plate"],
"cdsl_strategy": {
"atomic_id": "extrude_cut_blind",
"profile_type": "circle_grid",
"parameter_roles": ["diameter_mm", "count_x", "count_y"],
"selector_roles": [],
},
},
{
"id": "adjustment_slot",
"cdsl_feature_id": "slot_cut",
"role": "subtractive",
"purpose": "Center adjustment slot",
"depends_on": ["mounting_holes"],
"cdsl_strategy": {
"atomic_id": "extrude_cut_blind",
"profile_type": "obround",
"parameter_roles": ["length_mm", "width_mm", "depth_mm"],
"selector_roles": [],
},
},
],
"feature_order": ["base_plate", "mounting_holes", "adjustment_slot"],
"assumptions": [],
"open_questions": [],
"capability_gaps": [],
"verification_expectations": [
{"type": "feature_count", "feature_id": "mounting_holes", "expected": 4},
{"type": "symmetry", "axis": "x"},
{"type": "bbox", "expected_mm": {"x": 100, "y": 60, "z": 10}},
],
"status": "ready",
}
def mounting_plate_cdsl() -> dict:
return {
"schema": "cad.cdsl.llm.v1",
"schema_version": "1.0",
"kind": "part",
"part_id": "mounting-plate",
"geometry": {
"sketches": [
{"id": "base_sketch", "workplane": workplane(), "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 100, "height_mm": 60}},
{"id": "holes_sketch", "workplane": workplane(10), "profile": {"type": "circle_grid", "radius_mm": 3, "count_x": 2, "count_y": 2, "spacing_x_mm": 80, "spacing_y_mm": 40, "center_mm": [0, 0]}},
{"id": "slot_sketch", "workplane": workplane(10), "profile": {"type": "obround", "center": [0, 0], "length_mm": 32, "width_mm": 10}},
],
},
"features": [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "base_sketch", "params": {"distance_mm": 10}},
{"id": "hole_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "sketch_id": "holes_sketch", "params": {"distance_mm": 10, "reverse": True}},
{"id": "slot_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["hole_cut"], "sketch_id": "slot_sketch", "params": {"distance_mm": 10, "reverse": True}},
],
}
class DesignIntentValidationTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
sys.path.insert(0, str(ROOT / "backend" / "engine"))
import cdsl_engine
cls.engine = cdsl_engine
def assert_plan_error(self, intent: dict, code: str = "INVALID_DESIGN_INTENT") -> None:
with self.assertRaises(self.engine.DesignIntentError) as context:
self.engine.validate_design_intent(intent, self.engine)
self.assertEqual(context.exception.code, code)
def test_valid_mounting_plate_plan_and_cdsl_match(self) -> None:
intent = mounting_plate_intent()
validated = self.engine.validate_design_intent(intent, self.engine)
self.assertEqual(validated["feature_order"], ["base_plate", "mounting_holes", "adjustment_slot"])
self.engine.validate_intent_cdsl(validated, mounting_plate_cdsl(), self.engine)
def test_revise_requires_current_base_revision(self) -> None:
intent = mounting_plate_intent()
intent["mode"] = "revise"
intent["base_revision_id"] = ""
self.assert_plan_error(intent)
intent["base_revision_id"] = "rev_001"
self.engine.validate_design_intent(intent, self.engine, current_revision_id="rev_001")
with self.assertRaises(self.engine.DesignIntentError) as context:
self.engine.validate_design_intent(intent, self.engine, current_revision_id="rev_002")
self.assertEqual(context.exception.code, "INVALID_DESIGN_INTENT")
def test_rejects_duplicate_ids_bad_order_and_unregistered_capabilities(self) -> None:
duplicate = mounting_plate_intent()
duplicate["structures"][1]["id"] = "base_plate"
self.assert_plan_error(duplicate)
cyclic = mounting_plate_intent()
cyclic["structures"][0]["depends_on"] = ["mounting_holes"]
self.assert_plan_error(cyclic)
unsupported = mounting_plate_intent()
unsupported["structures"][0]["cdsl_strategy"]["atomic_id"] = "thread_add"
self.assert_plan_error(unsupported)
incomplete = mounting_plate_intent()
incomplete["feature_order"].pop()
self.assert_plan_error(incomplete)
no_profile = mounting_plate_intent()
no_profile["structures"][0]["cdsl_strategy"].pop("profile_type")
self.assert_plan_error(no_profile)
unknown_expectation = mounting_plate_intent()
unknown_expectation["verification_expectations"][0]["feature_id"] = "missing"
self.assert_plan_error(unknown_expectation)
def test_blockers_and_nonblocking_approximations_are_explicit(self) -> None:
clarification = mounting_plate_intent()
clarification["open_questions"] = [{"id": "thickness", "question": "What thickness is required?", "blocking": True}]
clarification["status"] = "needs_clarification"
self.engine.validate_design_intent(clarification, self.engine)
with self.assertRaises(self.engine.DesignIntentError) as context:
self.engine.validate_intent_cdsl(clarification, mounting_plate_cdsl(), self.engine)
self.assertEqual(context.exception.code, "DESIGN_INTENT_BLOCKED")
approximation = mounting_plate_intent()
approximation["capability_gaps"] = [{
"code": "unsupported_thread_geometry",
"structure_id": "mounting_holes",
"message": "Only a cylindrical bore is available.",
"blocking": False,
}]
self.assert_plan_error(approximation)
approximation["assumptions"] = ["Thread geometry is approximated by cylindrical bores."]
self.engine.validate_design_intent(approximation, self.engine)
def test_rejects_cdsl_feature_atomic_profile_and_selector_mismatches(self) -> None:
missing_feature = mounting_plate_cdsl()
missing_feature["features"].pop()
with self.assertRaises(self.engine.DesignIntentError) as context:
self.engine.validate_intent_cdsl(mounting_plate_intent(), missing_feature, self.engine)
self.assertEqual(context.exception.code, "INTENT_CDSL_MISMATCH")
atomic = mounting_plate_cdsl()
atomic["features"][1]["atomic_id"] = "extrude_add_blind"
with self.assertRaises(self.engine.DesignIntentError):
self.engine.validate_intent_cdsl(mounting_plate_intent(), atomic, self.engine)
profile = mounting_plate_cdsl()
profile["geometry"]["sketches"][1]["profile"]["type"] = "circles"
with self.assertRaises(self.engine.DesignIntentError):
self.engine.validate_intent_cdsl(mounting_plate_intent(), profile, self.engine)
selector_plan = copy.deepcopy(mounting_plate_intent())
selector_plan["structures"][2]["cdsl_strategy"]["selector_roles"] = ["host_face"]
with self.assertRaises(self.engine.DesignIntentError):
self.engine.validate_intent_cdsl(selector_plan, mounting_plate_cdsl(), self.engine)
+124
View File
@@ -0,0 +1,124 @@
from __future__ import annotations
import asyncio
import json
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))
from app.services.agent_service import AgentService # noqa: E402
from app.services.engine_service import load_engine # noqa: E402
from app.services.library import CdslLibrary # noqa: E402
from app.services.part_skills import PartSkillLibrary # noqa: E402
from app.services.storage import WorkspaceStore # noqa: E402
from app.settings import ProviderConfig, ProviderModel, Settings # noqa: E402
from tests.test_design_intent import mounting_plate_cdsl, mounting_plate_intent # noqa: E402
BACKEND = ROOT / "backend"
PART_SKILL_ROOT = BACKEND / "agent" / "skills" / "cad-engine" / "references" / "part-skills"
class DesignIntentFlowTests(unittest.TestCase):
def settings(self, root: Path) -> Settings:
provider = ProviderConfig("test", "Test", "https://example.invalid/v1", "test-key", (ProviderModel("test-model"),))
return Settings(
task_root=root / "tasks",
conversation_root=root / "conversations",
library_root=BACKEND / "cdsl_library",
engine_root=BACKEND / "engine" / "cdsl_engine",
llm_base_url=provider.base_url,
llm_api_key=provider.api_key,
llm_model="test-model",
llm_timeout_s=1,
default_provider_id="test",
providers=(provider,),
)
def test_design_intent_is_required_before_library_or_cdsl(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
agent = AgentService(settings, store, CdslLibrary(settings), PartSkillLibrary(PART_SKILL_ROOT))
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
searched, _ = asyncio.run(agent._run_tool("search_cdsl_library", {"query": "mounting plate"}, "", "mounting plate", [], intent_state=state))
generated, _ = asyncio.run(agent._run_tool("generate_cdsl_model", {"design_intent_id": "intent_aaaaaaaaaaaa", "cdsl": {}, "summary": "x", "assumptions": []}, "", "mounting plate", [], intent_state=state))
self.assertEqual(searched["code"], "DESIGN_INTENT_REQUIRED")
self.assertEqual(generated["code"], "DESIGN_INTENT_REQUIRED")
self.assertEqual(list(settings.task_root.glob("cad_*")), [])
def test_ready_plan_persists_before_build_and_links_success_revision(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
skills = PartSkillLibrary(PART_SKILL_ROOT)
agent = AgentService(settings, store, CdslLibrary(settings), skills)
request = "Create a mounting plate with four holes and a center slot"
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
planned, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": mounting_plate_intent(), "summary": "plate plan", "assumptions": []},
"", request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(planned["ok"])
task = store.read_task(planned["task_id"])
self.assertEqual(task["current_revision"], "")
planning_path = store.artifact_path(planned["task_id"], planned["design_intent_path"])
self.assertTrue(planning_path.is_file())
saved = json.loads(planning_path.read_text(encoding="utf-8"))
self.assertEqual(saved["part_skill_ids"], skills.select(request)["skill_ids"])
searched, _ = asyncio.run(agent._run_tool("search_cdsl_library", {"query": "plate"}, planned["task_id"], request, [], intent_state=state))
self.assertTrue(searched["ok"])
result, built = asyncio.run(agent._run_tool(
"generate_cdsl_model",
{"design_intent_id": planned["design_intent_id"], "cdsl": mounting_plate_cdsl(), "summary": "plate", "assumptions": []},
planned["task_id"], request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(result["ok"])
self.assertIsNotNone(built)
revision = store.read_task(planned["task_id"])["revisions"][-1]
self.assertEqual(revision["design_intent_id"], planned["design_intent_id"])
self.assertEqual(revision["design_intent_path"], planned["design_intent_path"])
audit = json.loads(store.artifact_path(planned["task_id"], revision["part_skills_path"]).read_text(encoding="utf-8"))
self.assertEqual(audit["design_intent_id"], planned["design_intent_id"])
self.assertEqual(audit["design_intent_assumptions"], [])
def test_blocked_plan_is_persisted_without_a_revision_and_new_plan_supersedes_it(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self.settings(Path(directory))
store = WorkspaceStore(settings)
skills = PartSkillLibrary(PART_SKILL_ROOT)
agent = AgentService(settings, store, CdslLibrary(settings), skills)
request = "Create a mounting plate"
state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
blocked_intent = mounting_plate_intent()
blocked_intent["open_questions"] = [{"id": "thickness", "question": "Thickness?", "blocking": True}]
blocked_intent["status"] = "needs_clarification"
blocked, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": blocked_intent, "summary": "blocked", "assumptions": []},
"", request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertEqual(blocked["code"], "DESIGN_INTENT_BLOCKED")
task = store.read_task(blocked["task_id"])
self.assertEqual(task["revisions"], [])
self.assertEqual(task["design_intents"][0]["status"], "pending")
ready, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": mounting_plate_intent(), "summary": "ready", "assumptions": []},
blocked["task_id"], request, [], part_skill_selection=skills.select(request), intent_state=state,
))
self.assertTrue(ready["ok"])
task = store.read_task(blocked["task_id"])
self.assertEqual(task["design_intents"][0]["status"], "superseded")
self.assertEqual(task["design_intents"][-1]["status"], "accepted")
+39 -1
View File
@@ -1,6 +1,8 @@
from __future__ import annotations
import sys
import json
import tempfile
import unittest
from copy import deepcopy
from pathlib import Path
@@ -9,7 +11,7 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "json_to_cdsl"))
from evidence_v2_to_cdsl import StepInspector, convert_evidence
from evidence_v2_to_cdsl import StepInspector, batch_convert, convert_evidence
def _identity(stable_id: str, *, kind: str = "feature", geometry: dict | None = None) -> dict:
@@ -59,6 +61,42 @@ FIXTURE = {
class EvidenceV2ToCdslTests(unittest.TestCase):
def test_batch_conversion_recurses_and_records_relative_source_path(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
evidence_dir = root / "evidence"
source = evidence_dir / "category" / "part.solidworks_evidence_v2.json"
source.parent.mkdir(parents=True)
source.write_text(json.dumps(FIXTURE), encoding="utf-8")
output_dir = root / "output"
manifest = batch_convert(evidence_dir, output_dir)
self.assertEqual(manifest["input_count"], 1)
self.assertEqual(manifest["converted_count"], 1)
self.assertEqual(manifest["results"][0]["source"], "category/part.solidworks_evidence_v2.json")
diagnostic = json.loads((output_dir / "part.diagnostic.json").read_text(encoding="utf-8"))
self.assertEqual(diagnostic["source"], "category/part.solidworks_evidence_v2.json")
def test_batch_conversion_uses_full_filename_and_disambiguates_normalized_ids(self) -> None:
with tempfile.TemporaryDirectory() as temporary_directory:
root = Path(temporary_directory)
evidence_dir = root / "evidence"
sources = [
evidence_dir / "first" / "14.Rod Nut.solidworks_evidence_v2.json",
evidence_dir / "second" / "14 Rod Nut.solidworks_evidence_v2.json",
]
for source in sources:
source.parent.mkdir(parents=True, exist_ok=True)
source.write_text(json.dumps(FIXTURE), encoding="utf-8")
manifest = batch_convert(evidence_dir, root / "output")
self.assertEqual(manifest["converted_count"], 2)
part_ids = {item["part_id"] for item in manifest["results"]}
self.assertEqual(len(part_ids), 2)
self.assertTrue(all(part_id.startswith("14-Rod-Nut-") for part_id in part_ids))
def test_converts_fixed_evidence_v2_fixture_to_semantic_cdsl(self) -> None:
cdsl, diagnostic = convert_evidence(FIXTURE, source_name="fixture.solidworks_evidence_v2.json")
atoms = {feature["atomic_id"] for feature in cdsl["features"]}
+345
View File
@@ -0,0 +1,345 @@
from __future__ import annotations
import asyncio
import copy
import json
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "backend"))
from app.models.contracts import ChatMessage, MessagePart # noqa: E402
from app.services.agent_service import AgentService, TOOL_SCHEMAS, system_prompt # noqa: E402
from app.services.engine_service import build_revision, load_engine, validate_cdsl # noqa: E402
from app.services.library import CdslLibrary # noqa: E402
from app.services.part_skills import PartSkillLibrary # noqa: E402
from app.services.storage import WorkspaceStore # noqa: E402
from app.settings import ProviderConfig, ProviderModel, Settings # noqa: E402
BACKEND = ROOT / "backend"
PART_SKILL_ROOT = BACKEND / "agent" / "skills" / "cad-engine" / "references" / "part-skills"
def workplane(z: float = 0.0) -> dict[str, list[float]]:
return {
"origin_mm": [0.0, 0.0, z],
"x_dir": [1.0, 0.0, 0.0],
"y_dir": [0.0, 1.0, 0.0],
"normal": [0.0, 0.0, 1.0],
}
def document(part_id: str, sketches: list[dict], features: list[dict]) -> dict:
return {
"schema": "cad.cdsl.llm.v1",
"schema_version": "1.1.0",
"kind": "part",
"part_id": part_id,
"meta": {"unit": "mm"},
"geometry": {"sketches": sketches},
"features": features,
}
def mounting_plate_fixture() -> dict:
return document("golden-mounting-plate", [
{"id": "base", "workplane": workplane(), "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 80, "height_mm": 60}},
{"id": "grid", "workplane": workplane(10), "profile": {"type": "circle_grid", "radius_mm": 3, "count_x": 2, "count_y": 2, "spacing_x_mm": 50, "spacing_y_mm": 30, "center_mm": [0, 0]}},
], [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 10}, "sketch_id": "base"},
{"id": "mount_pattern", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "params": {"distance_mm": 10, "reverse": True}, "sketch_id": "grid"},
{"id": "counterbores", "atomic_id": "hole_counterbore", "depends_on": ["mount_pattern"], "params": {"diameter_mm": 6, "depth_mm": 10, "counterbore_diameter_mm": 12, "counterbore_depth_mm": 4, "positions": [{"mm": [-15, 0, 0]}, {"mm": [15, 0, 0]}], "host_face": {"frame": workplane(10)}}, "sketch_id": "base"},
])
def mounting_bracket_fixture() -> dict:
web_plane = {"origin_mm": [0, -20, 0], "x_dir": [1, 0, 0], "y_dir": [0, 0, -1], "normal": [0, 1, 0]}
return document("golden-mounting-bracket", [
{"id": "base", "workplane": workplane(), "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 80, "height_mm": 40}},
{"id": "web", "workplane": web_plane, "profile": {"type": "rectangle", "center": [0, -15], "width_mm": 50, "height_mm": 30}},
{"id": "slot", "workplane": workplane(6), "profile": {"type": "obround", "center": [0, 0], "length_mm": 24, "width_mm": 8}},
{"id": "symmetric_holes", "workplane": workplane(6), "profile": {"type": "circles", "items": [{"center": [-25, 0], "radius_mm": 3}, {"center": [25, 0], "radius_mm": 3}]}},
], [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 6}, "sketch_id": "base"},
{"id": "web_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"], "params": {"distance_mm": 6}, "sketch_id": "web"},
{"id": "slot_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["web_add"], "params": {"distance_mm": 6, "reverse": True}, "sketch_id": "slot"},
{"id": "symmetric_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["slot_cut"], "params": {"distance_mm": 6, "reverse": True}, "sketch_id": "symmetric_holes"},
])
def flange_fixture() -> dict:
return document("golden-flange", [
{"id": "base", "workplane": workplane(), "profile": {"type": "annulus", "inner_radius_mm": 10, "outer_radius_mm": 40}},
{"id": "bolt_circle", "workplane": workplane(12), "profile": {"type": "circles", "items": [{"center": [25, 0], "radius_mm": 3}, {"center": [0, 25], "radius_mm": 3}, {"center": [-25, 0], "radius_mm": 3}, {"center": [0, -25], "radius_mm": 3}]}},
], [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 12}, "sketch_id": "base"},
{"id": "bolt_holes", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "params": {"distance_mm": 12, "reverse": True}, "sketch_id": "bolt_circle"},
])
def shaft_fixture() -> dict:
end_plane = {"origin_mm": [0, 35, 0], "x_dir": [1, 0, 0], "y_dir": [0, 0, -1], "normal": [0, 1, 0]}
return document("golden-stepped-shaft", [
{"id": "shaft_profile", "workplane": workplane(), "profile": {"type": "polygon", "vertices": [[0, 0], [10, 0], [10, 15], [7, 15], [7, 35], [0, 35]]}},
], [
{"id": "shaft_add", "atomic_id": "revolve_add", "depends_on": [], "params": {"angle_deg": 360, "axis": {"origin_mm": [0, 0, 0], "direction": [0, 1, 0]}}, "sketch_id": "shaft_profile"},
{"id": "coaxial_bore", "atomic_id": "hole_blind", "depends_on": ["shaft_add"], "params": {"diameter_mm": 4, "depth_mm": 35, "positions": [{"mm": [0, 0, 0]}], "host_face": {"frame": end_plane}}, "sketch_id": "shaft_profile"},
])
def bearing_housing_fixture() -> dict:
return document("golden-bearing-housing", [
{"id": "base", "workplane": workplane(), "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 100, "height_mm": 60}},
{"id": "housing", "workplane": workplane(8), "profile": {"type": "circle", "radius_mm": 28}},
{"id": "base_holes", "workplane": workplane(8), "profile": {"type": "circle_grid", "radius_mm": 4, "count_x": 2, "count_y": 2, "spacing_x_mm": 80, "spacing_y_mm": 40, "center_mm": [0, 0]}},
], [
{"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 8}, "sketch_id": "base"},
{"id": "housing_add", "atomic_id": "extrude_add_blind", "depends_on": ["base_add"], "params": {"distance_mm": 25}, "sketch_id": "housing"},
{"id": "bearing_seat", "atomic_id": "hole_counterbore", "depends_on": ["housing_add"], "params": {"diameter_mm": 12, "depth_mm": 25, "counterbore_diameter_mm": 30, "counterbore_depth_mm": 10, "positions": [{"mm": [0, 0, 0]}], "host_face": {"frame": workplane(33)}}, "sketch_id": "housing"},
{"id": "base_mount_holes", "atomic_id": "extrude_cut_blind", "depends_on": ["bearing_seat"], "params": {"distance_mm": 8, "reverse": True}, "sketch_id": "base_holes"},
])
def hex_nut_fixture() -> dict:
return document("golden-hex-nut", [
{"id": "hex", "workplane": workplane(), "profile": {"type": "polygon", "vertices": [[10, 0], [5, 8.660254], [-5, 8.660254], [-10, 0], [-5, -8.660254], [5, -8.660254]]}},
{"id": "thread_bore", "workplane": workplane(10), "profile": {"type": "circle", "radius_mm": 3}},
], [
{"id": "nut_add", "atomic_id": "extrude_add_blind", "depends_on": [], "params": {"distance_mm": 10}, "sketch_id": "hex"},
{"id": "m6_bore", "atomic_id": "extrude_cut_blind", "depends_on": ["nut_add"], "params": {"distance_mm": 10, "reverse": True}, "sketch_id": "thread_bore"},
])
GOLDEN_CASES = (
("带沉孔和孔阵列的安装底板", mounting_plate_fixture, {"planning/mounting-plate", "atomic/counterbored-hole-creation", "atomic/pattern-holes-from-datum"}, {"atomic/pattern-holes-from-datum": "expanded"}),
("带槽和对称孔的安装支架", mounting_bracket_fixture, {"planning/mounting-bracket", "functional/slotted-adjustment-feature", "functional/symmetric-feature-layout"}, {"atomic/pattern-holes-from-datum": "expanded"}),
("带螺栓圆的法兰", flange_fixture, {"planning/flange", "functional/flange-bolt-circle"}, {"functional/flange-bolt-circle": "expanded"}),
("带回转体同轴孔的阶梯轴", shaft_fixture, {"planning/simple-shaft", "functional/axisymmetric-revolve-strategy", "atomic/coaxial-bore-rule"}, {}),
("带轴承座孔和底座孔的轴承座", bearing_housing_fixture, {"planning/bearing-housing", "functional/bearing-bore-seat"}, {}),
("M6 六角螺母", hex_nut_fixture, {"planning/hexagonal-nut", "atomic/threaded-hole-creation"}, {"atomic/threaded-hole-creation": "approximated"}),
)
class PartSkillLibraryTests(unittest.TestCase):
def setUp(self) -> None:
self.library = PartSkillLibrary(PART_SKILL_ROOT)
def test_english_and_chinese_triggers(self) -> None:
english = self.library.select("Create a mounting plate with counterbored hole pattern")
chinese = self.library.select("带沉孔和孔阵列的安装底板")
self.assertIn("planning/mounting-plate", english["skill_ids"])
self.assertIn("planning/mounting-plate", chinese["skill_ids"])
self.assertIn("atomic/counterbored-hole-creation", chinese["skill_ids"])
def test_exclusions_and_selection_limits(self) -> None:
selection = self.library.select("Create a flange nut with an M6 threaded hole")
self.assertNotIn("planning/flange", selection["skill_ids"])
self.assertLessEqual(len(selection["planning_ids"]), 1)
self.assertLessEqual(len(selection["support_ids"]), 3)
self.assertEqual(len(selection["skill_ids"]), len(set(selection["skill_ids"])))
def test_inheritance_and_primary_family_conflict(self) -> None:
inherited = ["planning/mounting-plate", "atomic/counterbored-hole-creation"]
addition = self.library.select("Add one M6 threaded hole", inherited)
conflict = self.library.select("Add a flange bolt circle", inherited)
replacement = self.library.select("Replace the whole part with a flange bolt circle", inherited)
self.assertIn("planning/mounting-plate", addition["skill_ids"])
self.assertIn("atomic/threaded-hole-creation", addition["skill_ids"])
self.assertEqual(conflict["conflict"]["current"], "planning/mounting-plate")
self.assertEqual(conflict["conflict"]["matched"], "planning/flange")
self.assertEqual(replacement["planning_ids"], ["planning/flange"])
self.assertIsNone(replacement["conflict"])
def test_catalog_and_bridge_files_are_complete(self) -> None:
payload = json.loads((PART_SKILL_ROOT / "catalog.json").read_text(encoding="utf-8"))
categories = {kind: 0 for kind in ("planning", "functional", "atomic")}
for skill in payload["skills"]:
categories[skill["kind"]] += 1
self.assertTrue((PART_SKILL_ROOT / skill["bridge"]).is_file())
self.assertTrue((PART_SKILL_ROOT / skill["source"]).is_file())
self.assertTrue(skill["triggers"])
self.assertTrue(skill["capability_translation_rules"])
self.assertEqual(categories, {"planning": 6, "functional": 7, "atomic": 8})
self.assertTrue((PART_SKILL_ROOT / "LICENSE").is_file())
self.assertTrue((PART_SKILL_ROOT / "PROVENANCE.md").is_file())
def test_audit_records_exact_omitted_and_blocked_capability_states(self) -> None:
fixture = mounting_plate_fixture()
exact = self.library.audit(self.library.select("安装底板"), fixture)
finishing = self.library.audit(self.library.select("edge treatment"), fixture)
blocked = self.library.audit(self.library.select("flange bolt circle"), mounting_plate_fixture())
exact_statuses = {item["skill_id"]: item["status"] for item in exact["capability_translations"]}
finishing_statuses = {item["skill_id"]: item for item in finishing["capability_translations"]}
blocked_statuses = {item["skill_id"]: item["status"] for item in blocked["capability_translations"]}
self.assertEqual(exact_statuses["planning/mounting-plate"], "exact")
self.assertEqual(finishing_statuses["atomic/fillet-chamfer-last"]["status"], "omitted")
self.assertEqual(finishing_statuses["atomic/fillet-chamfer-last"]["translation"], "selector_unavailable")
self.assertEqual(blocked_statuses["functional/flange-bolt-circle"], "blocked")
class AgentPartSkillTests(unittest.TestCase):
def test_agent_stream_selects_and_injects_part_skill_before_first_model_call(self) -> None:
class CaptureAgent(AgentService):
def __init__(self, *args: object, **kwargs: object) -> None:
super().__init__(*args, **kwargs)
self.first_messages: list[dict[str, object]] = []
async def _complete(self, messages: list[dict[str, object]], *args: object, **kwargs: object) -> dict[str, object]:
self.first_messages = [dict(message) for message in messages]
return {"choices": [{"message": {"role": "assistant", "content": "已分析", "tool_calls": []}}]}
with tempfile.TemporaryDirectory() as directory:
settings = self._settings(Path(directory))
library = PartSkillLibrary(PART_SKILL_ROOT)
agent = CaptureAgent(settings, WorkspaceStore(settings), CdslLibrary(settings), library)
message = ChatMessage(id="user_1", role="user", parts=[MessagePart(type="text", text="生成一个法兰螺栓圆")])
async def consume() -> None:
async for _ in agent.stream([message], None, None):
pass
asyncio.run(consume())
self.assertIn("[planning/flange]", str(agent.first_messages[0]["content"]))
self.assertIn("[functional/flange-bolt-circle]", str(agent.first_messages[0]["content"]))
def test_prompt_contains_bridge_without_adding_tools(self) -> None:
library = PartSkillLibrary(PART_SKILL_ROOT)
selection = library.select("Create a flange with a bolt circle")
with tempfile.TemporaryDirectory() as directory:
settings = self._settings(Path(directory))
prompt = system_prompt(settings, "Create a flange with a bolt circle", part_skill_context=library.render_context(selection))
self.assertIn("[planning/flange]", prompt)
self.assertIn("circular-pattern atomic", prompt)
self.assertEqual([tool["function"]["name"] for tool in TOOL_SCHEMAS], ["search_cdsl_library", "read_cdsl_reference", "propose_design_intent", "read_current_cdsl", "generate_cdsl_model"])
def test_generation_persists_assumptions_and_selected_skills(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = self._settings(Path(directory))
store = WorkspaceStore(settings)
library = PartSkillLibrary(PART_SKILL_ROOT)
agent = AgentService(settings, store, CdslLibrary(settings), library)
request = "M6 六角螺母"
selection = library.select(request)
engine = load_engine(settings)
intent = engine.design_intent_from_cdsl(hex_nut_fixture(), request=request)
intent_state = {"phase": "WAITING_FOR_INTENT", "design_intent_id": ""}
planned, _ = asyncio.run(agent._run_tool(
"propose_design_intent",
{"intent": intent, "summary": "M6 nut plan", "assumptions": []},
"",
request,
[],
part_skill_selection=selection,
intent_state=intent_state,
))
self.assertTrue(planned["ok"])
result, generated = asyncio.run(agent._run_tool(
"generate_cdsl_model",
{"design_intent_id": planned["design_intent_id"], "cdsl": hex_nut_fixture(), "summary": "M6 nut", "assumptions": ["M6 thread is represented as a cylindrical bore"]},
planned["task_id"],
request,
["reference-fixture"],
part_skill_selection=selection,
intent_state=intent_state,
))
self.assertTrue(result["ok"])
self.assertIsNotNone(generated)
task = store.read_task(str(generated["task_id"]))
revision = task["revisions"][0]
audit = json.loads(store.artifact_path(task["task_id"], revision["part_skills_path"]).read_text(encoding="utf-8"))
report = json.loads(store.artifact_path(task["task_id"], revision["report_path"]).read_text(encoding="utf-8"))
self.assertIn("M6 thread is represented as a cylindrical bore", audit["assumptions"])
self.assertIn("atomic/threaded-hole-creation", revision["part_skill_ids"])
self.assertEqual(report["generation_context"]["cdsl_reference_ids"], ["reference-fixture"])
@staticmethod
def _settings(root: Path) -> Settings:
provider = ProviderConfig("test", "Test", "https://example.invalid/v1", "test-key", (ProviderModel("test-model"),))
return Settings(
task_root=root / "tasks",
conversation_root=root / "conversations",
library_root=BACKEND / "cdsl_library",
engine_root=BACKEND / "engine" / "cdsl_engine",
llm_base_url=provider.base_url,
llm_api_key=provider.api_key,
llm_model="test-model",
llm_timeout_s=1,
default_provider_id="test",
providers=(provider,),
)
class PartSkillBuildAndGoldenTests(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.settings = AgentPartSkillTests._settings(Path(tempfile.mkdtemp()))
cls.engine = load_engine(cls.settings)
cls.library = PartSkillLibrary(PART_SKILL_ROOT)
def test_success_and_failure_revisions_always_write_part_skill_audit(self) -> None:
with tempfile.TemporaryDirectory() as directory:
settings = AgentPartSkillTests._settings(Path(directory))
store = WorkspaceStore(settings)
selection = self.library.select("带螺栓圆的法兰")
audit = self.library.audit(selection, flange_fixture(), ["Explicit circle expansion"])
success = build_revision(
settings=settings, store=store, task_id=None, request="带螺栓圆的法兰", cdsl=flange_fixture(),
reference_ids=["flange-reference"], summary="flange", part_skills=audit,
generation_assumptions=["Explicit circle expansion"],
)
success_audit = json.loads(store.artifact_path(success["task_id"], success["part_skills_path"]).read_text(encoding="utf-8"))
success_report = json.loads(store.artifact_path(success["task_id"], success["report_path"]).read_text(encoding="utf-8"))
self.assertEqual(success_audit["skill_ids"], audit["skill_ids"])
self.assertIn("generation_context", success_report)
invalid = copy.deepcopy(flange_fixture())
invalid["features"][0]["atomic_id"] = "not_supported"
failed_audit = self.library.audit(selection, invalid, [])
with self.assertRaisesRegex(ValueError, "CDSL schema violation"):
build_revision(
settings=settings, store=store, task_id=success["task_id"], request="bad flange", cdsl=invalid,
reference_ids=[], summary="bad", part_skills=failed_audit, generation_assumptions=[],
)
task = store.read_task(success["task_id"])
failed = task["revisions"][-1]
self.assertEqual(failed["status"], "failed")
self.assertTrue(store.artifact_path(task["task_id"], failed["part_skills_path"]).is_file())
report = json.loads(store.artifact_path(task["task_id"], failed["report_path"]).read_text(encoding="utf-8"))
self.assertEqual(report["generation_context"]["part_skill_ids"], audit["skill_ids"])
legacy_report = {"engine_result": {"engine": "cdsl_only"}}
self.assertEqual(legacy_report.get("generation_context", {}), {})
def test_golden_part_families_validate_preflight_rebuild_and_audit(self) -> None:
for request, factory, expected_skills, expected_statuses in GOLDEN_CASES:
with self.subTest(request=request):
cdsl = factory()
selection = self.library.select(request)
self.assertTrue(expected_skills.issubset(selection["skill_ids"]))
validate_cdsl(cdsl, self.engine)
with tempfile.TemporaryDirectory() as directory:
step_path = Path(directory) / "model.step"
result = self.engine.run_cdsl_only(copy.deepcopy(cdsl), step_path)
self.assertEqual(result["engine"], "cdsl_only")
self.assertTrue(step_path.is_file())
self.assertGreater(step_path.stat().st_size, 0)
audit = self.library.audit(selection, cdsl, ["golden fixture"])
statuses = {item["skill_id"]: item["status"] for item in audit["capability_translations"]}
for skill_id, status in expected_statuses.items():
self.assertEqual(statuses[skill_id], status)
self.assertTrue(audit["evidence"]["features"])
self.assertTrue(audit["evidence"]["profiles"])
if __name__ == "__main__":
unittest.main()
@@ -25,12 +25,13 @@ export function CadProgressPart({ data }: { data: CadProgress }) {
}
export function CadResultPart({ data }: { data: CadResult }) {
const downloads = [
const downloads: Array<[string, string]> = [
["STEP", data.stepPath],
["CDSL", data.cdslPath],
["GLB", data.glbPath],
["REPORT", data.reportPath],
] as const;
];
if (data.designIntentPath) downloads.push(["DESIGN INTENT", data.designIntentPath]);
return (
<div className="cad-card cad-result-card">
<div className="cad-card-icon success">
@@ -51,6 +51,8 @@ function resultFromBackend(payload: Record<string, unknown>): CadResult {
parametersPath: typeof payload.parameters_path === "string" ? payload.parameters_path : undefined,
selectorPath: typeof payload.selector_path === "string" ? payload.selector_path : undefined,
edgesPath: typeof payload.edges_path === "string" ? payload.edges_path : undefined,
designIntentId: typeof payload.design_intent_id === "string" ? payload.design_intent_id : undefined,
designIntentPath: typeof payload.design_intent_path === "string" ? payload.design_intent_path : undefined,
summary: String(payload.summary || "Updated CDSL model"),
referenceIds: Array.isArray(payload.reference_ids) ? payload.reference_ids.map(String) : [],
engine: String(payload.engine || "cdsl_only"),
+2
View File
@@ -31,6 +31,8 @@ export function latestSuccessfulResult(task: TaskRecord | null): CadResult | nul
parametersPath: current.parameters_path,
selectorPath: current.selector_path,
edgesPath: current.edges_path,
designIntentId: current.design_intent_id,
designIntentPath: current.design_intent_path,
summary: current.summary || "CDSL CAD model",
referenceIds: current.reference_ids || [],
engine: current.engine || "cdsl_only",
+5
View File
@@ -17,6 +17,8 @@ export type CadResult = {
parametersPath?: string;
selectorPath?: string;
edgesPath?: string;
designIntentId?: string;
designIntentPath?: string;
summary: string;
referenceIds: string[];
engine: string;
@@ -64,6 +66,8 @@ export type TaskRevision = {
parameters_path?: string;
selector_path?: string;
edges_path?: string;
design_intent_id?: string;
design_intent_path?: string;
summary?: string;
reference_ids?: string[];
engine?: string;
@@ -73,6 +77,7 @@ export type TaskRevision = {
export type TaskRecord = {
task_id: string;
current_revision: string;
current_design_intent_id?: string;
revisions: TaskRevision[];
};
+8 -2
View File
@@ -24,8 +24,8 @@ implementation are emitted with `execution_status: "deferred"` instead.
```bash
PYTHONPATH=backend/engine python json_to_cdsl/evidence_v2_to_cdsl.py \
/path/to/evidence_v2 \
--truth-dir /path/to/evidence_v2/truth \
json_to_cdsl/input/evidence_v2 \
--truth-dir json_to_cdsl/input/evidence_v2/truth \
--out json_to_cdsl/output \
--workers 1
```
@@ -41,3 +41,9 @@ For every `*.solidworks_evidence_v2.json` input it writes:
The optional STEP directory is used only to infer a host face when exactly one
candidate is compatible with a Hole Wizard's captured locations. Ambiguous
geometry stays in `unresolved`; the converter never invents a selector.
Source records are discovered recursively. Output remains flat and is named by
the normalized full source filename. Files that normalize to the same part ID
receive a deterministic relative-path hash suffix, so every input record maps
to a distinct output. When a matching STEP file exists, it is resolved through
the same relative subdirectory as its source record.
+46 -13
View File
@@ -32,6 +32,7 @@ from cdsl_engine.semantic_validation import validate_semantic_cdsl # noqa: E402
METERS_TO_MM = 1000.0
EPSILON_MM = 1e-4
EVIDENCE_V2_SUFFIX = ".solidworks_evidence_v2.json"
SUPPORTED_ATOMS = {
"extrude_add_blind",
"extrude_add_two_sided",
@@ -870,7 +871,17 @@ def _topologically_order_features(features: list[dict[str, Any]]) -> list[dict[s
return ordered
def convert_evidence(evidence: dict[str, Any], *, source_name: str, step_path: Path | None = None) -> tuple[dict[str, Any], dict[str, Any]]:
def _part_id_from_source_name(source_name: str) -> str:
"""Build a schema-valid ID from the entire evidence file name."""
stem = Path(source_name).name.removesuffix(EVIDENCE_V2_SUFFIX)
part_id = re.sub(r"[^A-Za-z0-9_-]+", "-", stem).strip("-")
if len(part_id) < 3:
part_id = f"part-{part_id}".rstrip("-")
return part_id[:80]
def convert_evidence(evidence: dict[str, Any], *, source_name: str, step_path: Path | None = None,
part_id: str | None = None) -> tuple[dict[str, Any], dict[str, Any]]:
if evidence.get("schema") != "solidworks.cad_evidence.v2":
raise ValueError("Expected schema solidworks.cad_evidence.v2")
features = sorted((item for item in evidence.get("features") or [] if isinstance(item, dict)), key=lambda item: int(_number(item.get("sequence"))))
@@ -1105,7 +1116,7 @@ def convert_evidence(evidence: dict[str, Any], *, source_name: str, step_path: P
})
cdsl_features = _topologically_order_features(cdsl_features)
part_id = re.sub(r"[^A-Za-z0-9_-]+", "-", Path(source_name).name.split(".")[0]).strip("-") or "part"
part_id = part_id or _part_id_from_source_name(source_name)
truth = evidence.get("document_truth") or {}
step_summary = step.summary(truth)
cdsl = {
@@ -1133,34 +1144,56 @@ def _write_json(path: Path, value: Any) -> None:
path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
def _convert_path(source: Path, output_dir: Path, truth_dir: Path | None, overwrite: bool) -> dict[str, Any]:
part_id = source.name.split(".")[0]
def _convert_path(source: Path, output_dir: Path, truth_dir: Path | None, overwrite: bool,
source_name: str, truth_relative_path: Path, part_id: str) -> dict[str, Any]:
cdsl_path = output_dir / f"{part_id}.cdsl.json"
diagnostic_path = output_dir / f"{part_id}.diagnostic.json"
if not overwrite and cdsl_path.exists() and diagnostic_path.exists():
return {"part_id": part_id, "status": "skipped", "cdsl": cdsl_path.name, "diagnostic": diagnostic_path.name}
return {
"part_id": part_id, "source": source_name, "status": "skipped",
"cdsl": cdsl_path.name, "diagnostic": diagnostic_path.name,
}
try:
evidence = json.loads(source.read_text(encoding="utf-8"))
step_path = (truth_dir / f"{part_id}.step") if truth_dir else None
cdsl, diagnostic = convert_evidence(evidence, source_name=source.name, step_path=step_path)
step_path = (truth_dir / truth_relative_path) if truth_dir else None
cdsl, diagnostic = convert_evidence(evidence, source_name=source_name, step_path=step_path, part_id=part_id)
_write_json(cdsl_path, cdsl)
_write_json(diagnostic_path, diagnostic)
return {"part_id": part_id, "status": "converted", "cdsl": cdsl_path.name, "diagnostic": diagnostic_path.name,
return {"part_id": part_id, "source": source_name, "status": "converted", "cdsl": cdsl_path.name, "diagnostic": diagnostic_path.name,
"future_rebuild_ready": diagnostic["semantic_validation"]["future_rebuild_ready"],
"deferred_feature_count": len(diagnostic["semantic_validation"]["deferred_feature_ids"]),
"unresolved_feature_count": len(diagnostic["semantic_validation"]["unresolved"])}
except Exception as error:
_write_json(diagnostic_path, {"source": source.name, "part_id": part_id, "error": str(error)})
return {"part_id": part_id, "status": "failed", "diagnostic": diagnostic_path.name, "error": str(error)}
_write_json(diagnostic_path, {"source": source_name, "part_id": part_id, "error": str(error)})
return {"part_id": part_id, "source": source_name, "status": "failed", "diagnostic": diagnostic_path.name, "error": str(error)}
def batch_convert(evidence_dir: Path, output_dir: Path, *, truth_dir: Path | None = None, workers: int = 1,
fail_fast: bool = False, overwrite: bool = False) -> dict[str, Any]:
sources = sorted(evidence_dir.glob("*.solidworks_evidence_v2.json"))
sources = sorted(evidence_dir.rglob(f"*{EVIDENCE_V2_SUFFIX}"))
if not sources:
raise ValueError(f"No *.solidworks_evidence_v2.json files found in {evidence_dir}")
raise ValueError(f"No *{EVIDENCE_V2_SUFFIX} files found in {evidence_dir}")
sources_by_base_id: dict[str, list[Path]] = {}
for source in sources:
base_id = _part_id_from_source_name(source.name)
sources_by_base_id.setdefault(base_id, []).append(source)
part_id_by_source: dict[Path, str] = {}
for base_id, matching_sources in sources_by_base_id.items():
if len(matching_sources) == 1:
part_id_by_source[matching_sources[0]] = base_id
continue
for source in matching_sources:
relative_name = source.relative_to(evidence_dir).as_posix()
digest = hashlib.sha256(relative_name.encode("utf-8")).hexdigest()[:10]
part_id_by_source[source] = f"{base_id[:69]}-{digest}"
if len(set(part_id_by_source.values())) != len(part_id_by_source):
raise ValueError("Unable to create unique output part IDs from evidence source paths")
output_dir.mkdir(parents=True, exist_ok=True)
convert = lambda source: _convert_path(source, output_dir, truth_dir, overwrite)
def convert(source: Path) -> dict[str, Any]:
relative = source.relative_to(evidence_dir)
step_relative = Path(str(relative).removesuffix(EVIDENCE_V2_SUFFIX) + ".step")
return _convert_path(source, output_dir, truth_dir, overwrite, relative.as_posix(), step_relative,
part_id_by_source[source])
results: list[dict[str, Any]] = []
if workers == 1:
for source in sources:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,395 @@
{
"source": "02_传动件/0202_轴/020299_通用轴/0.750in Shaft.solidworks_evidence_v2.json",
"part_id": "0-750in-Shaft",
"source_status": "hybrid_semantic_surfaceir_pending",
"source_self_validation": {
"schema": "solidworks.cad_evidence.self_validation.v1",
"passed": true,
"meaning": "complete reusable extraction; not a claim of backend feature support or 1:1 rebuild",
"counts": {
"features": 43,
"opaque_exact_geometry_features": 0,
"typed_body_features": 13,
"direct_semantic_features": 5,
"typed_features_requiring_history": 8,
"complete_history_features": 8,
"spline_segments": 0,
"incomplete_splines": 0,
"exact_bcurves": 0,
"incomplete_bcurves": 0,
"exact_bsurfaces": 0,
"incomplete_bsurfaces": 0,
"exact_trimmed_faces": 0,
"incomplete_trimmed_faces": 0,
"missing_exact_geometry_catalog_references": 0,
"incomplete_topology_records": 0,
"skipped_getter_signatures": 2
},
"feature_contracts": [
{
"feature": "Boss-Extrude1",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude2",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude1",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Mirror1",
"family": "mirror",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude2",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude3",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude3",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude4",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Mirror2",
"family": "mirror",
"passed": true,
"blockers": []
},
{
"feature": "Chamfer1",
"family": "chamfer",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude4",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude5",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "CirPattern1",
"family": "pattern",
"passed": true,
"blockers": []
}
],
"blockers": []
},
"step_inference": {
"available": true,
"face_count": 98,
"surface_counts": {
"cone": 2,
"cylinder": 15,
"plane": 81
},
"axis_count": 17,
"repeat_spacings_mm": [],
"metrics": {
"bounding_box_mm": [
-21.6,
-11.525,
-11.525,
168.2,
11.525,
11.525
],
"volume_mm3": 47900.228207498,
"surface_area_mm2": 13550.918128974,
"solid_count": 1,
"face_count": 98,
"edge_count": 267,
"vertex_count": 176
},
"error": null,
"truth_comparison": {
"expected_from_evidence": {
"bounding_box_mm": [
-21.6,
-11.525,
-11.525,
168.2,
11.525,
11.525
],
"volume_mm3": 47900.228207498,
"surface_area_mm2": 13550.918128974,
"solid_count": 1,
"face_count": 90,
"edge_count": 235,
"vertex_count": 148
},
"comparisons": {
"volume_mm3": {
"expected": 47900.228207498,
"actual": 47900.228207498,
"absolute_delta": 0.0,
"within_tolerance": true
},
"surface_area_mm2": {
"expected": 13550.918128974,
"actual": 13550.918128974,
"absolute_delta": 0.0,
"within_tolerance": true
},
"bounding_box_mm": {
"expected": [
-21.6,
-11.525,
-11.525,
168.2,
11.525,
11.525
],
"actual": [
-21.6,
-11.525,
-11.525,
168.2,
11.525,
11.525
],
"max_absolute_delta": 0.0,
"within_tolerance": true
},
"solid_count": {
"expected": 1,
"actual": 1,
"matches": true
},
"face_count": {
"expected": 90,
"actual": 98,
"matches": false
},
"edge_count": {
"expected": 235,
"actual": 267,
"matches": false
},
"vertex_count": {
"expected": 148,
"actual": 176,
"matches": false
}
},
"numeric_geometry_match": true,
"topology_counts_match": false
}
},
"features": [
{
"feature_id": "f_001",
"source_name": "Front Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_002",
"source_name": "Top Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_003",
"source_name": "Right Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_004",
"source_name": "Boss-Extrude1",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_005",
"source_name": "Plane1",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_006",
"source_name": "Boss-Extrude2",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_007",
"source_name": "Cut-Extrude1",
"atomic_id": "extrude_cut_blind",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_008",
"source_name": "Mirror1",
"atomic_id": "pattern_mirror",
"execution_status": "deferred",
"unresolved": [
"mirror pattern source features or plane were not captured"
],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_009",
"source_name": "Plane2",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_010",
"source_name": "Cut-Extrude2",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_011",
"source_name": "Boss-Extrude3",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_012",
"source_name": "Cut-Extrude3",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_013",
"source_name": "Cut-Extrude4",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_014",
"source_name": "Plane3",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_015",
"source_name": "Mirror2",
"atomic_id": "pattern_mirror",
"execution_status": "deferred",
"unresolved": [
"mirror pattern source features or plane were not captured"
],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_016",
"source_name": "Chamfer1",
"atomic_id": "chamfer",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_017",
"source_name": "Boss-Extrude4",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_018",
"source_name": "Boss-Extrude5",
"atomic_id": "extrude_add_blind",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
}
],
"semantic_validation": {
"schema_version": "1.1.0",
"feature_count": 18,
"sketch_count": 11,
"deferred_feature_ids": [
"f_001",
"f_002",
"f_003",
"f_005",
"f_014",
"f_007",
"f_009",
"f_015",
"f_018",
"f_008",
"f_016"
],
"unresolved": [
{
"feature_id": "f_015",
"reasons": [
"mirror pattern source features or plane were not captured"
]
},
{
"feature_id": "f_008",
"reasons": [
"mirror pattern source features or plane were not captured"
]
}
],
"future_rebuild_ready": false
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,430 @@
{
"source": "02_传动件/0202_轴/020299_通用轴/0.875in Shaft.solidworks_evidence_v2.json",
"part_id": "0-875in-Shaft",
"source_status": "hybrid_semantic_surfaceir_pending",
"source_self_validation": {
"schema": "solidworks.cad_evidence.self_validation.v1",
"passed": true,
"meaning": "complete reusable extraction; not a claim of backend feature support or 1:1 rebuild",
"counts": {
"features": 43,
"opaque_exact_geometry_features": 0,
"typed_body_features": 13,
"direct_semantic_features": 6,
"typed_features_requiring_history": 7,
"complete_history_features": 7,
"spline_segments": 0,
"incomplete_splines": 0,
"exact_bcurves": 0,
"incomplete_bcurves": 0,
"exact_bsurfaces": 0,
"incomplete_bsurfaces": 0,
"exact_trimmed_faces": 0,
"incomplete_trimmed_faces": 0,
"missing_exact_geometry_catalog_references": 0,
"incomplete_topology_records": 0,
"skipped_getter_signatures": 1
},
"feature_contracts": [
{
"feature": "Boss-Extrude1",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude2",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude1",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude2",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Mirror1",
"family": "mirror",
"passed": true,
"blockers": []
},
{
"feature": "Mirror2",
"family": "mirror",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude3",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude4",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude3",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Boss-Extrude4",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Cut-Extrude5",
"family": "extrude",
"passed": true,
"blockers": []
},
{
"feature": "Mirror3",
"family": "mirror",
"passed": true,
"blockers": []
},
{
"feature": "Chamfer1",
"family": "chamfer",
"passed": true,
"blockers": []
}
],
"blockers": []
},
"step_inference": {
"available": true,
"face_count": 51,
"surface_counts": {
"cone": 4,
"cylinder": 19,
"plane": 28
},
"axis_count": 23,
"repeat_spacings_mm": [
{
"spacing_mm": 17.46,
"pair_count": 2
},
{
"spacing_mm": 69.74,
"pair_count": 2
}
],
"metrics": {
"bounding_box_mm": [
-46.6,
-13.1125,
-13.1125,
41.6,
13.1125,
13.1125
],
"volume_mm3": 32968.880004057,
"surface_area_mm2": 7995.933985328,
"solid_count": 1,
"face_count": 51,
"edge_count": 117,
"vertex_count": 74
},
"error": null,
"truth_comparison": {
"expected_from_evidence": {
"bounding_box_mm": [
-46.6,
-13.1125,
-13.1125,
41.6,
13.1125,
13.1125
],
"volume_mm3": 32968.880012805,
"surface_area_mm2": 7995.933988027,
"solid_count": 1,
"face_count": 41,
"edge_count": 78,
"vertex_count": 40
},
"comparisons": {
"volume_mm3": {
"expected": 32968.880012805,
"actual": 32968.880004057,
"absolute_delta": 8.74800025485456e-06,
"within_tolerance": true
},
"surface_area_mm2": {
"expected": 7995.933988027,
"actual": 7995.933985328,
"absolute_delta": 2.69900010607671e-06,
"within_tolerance": true
},
"bounding_box_mm": {
"expected": [
-46.6,
-13.1125,
-13.1125,
41.6,
13.1125,
13.1125
],
"actual": [
-46.6,
-13.1125,
-13.1125,
41.6,
13.1125,
13.1125
],
"max_absolute_delta": 0.0,
"within_tolerance": true
},
"solid_count": {
"expected": 1,
"actual": 1,
"matches": true
},
"face_count": {
"expected": 41,
"actual": 51,
"matches": false
},
"edge_count": {
"expected": 78,
"actual": 117,
"matches": false
},
"vertex_count": {
"expected": 40,
"actual": 74,
"matches": false
}
},
"numeric_geometry_match": true,
"topology_counts_match": false
}
},
"features": [
{
"feature_id": "f_001",
"source_name": "Front Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_002",
"source_name": "Top Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_003",
"source_name": "Right Plane",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_004",
"source_name": "Boss-Extrude1",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_005",
"source_name": "Plane1",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_006",
"source_name": "Boss-Extrude2",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_007",
"source_name": "Cut-Extrude1",
"atomic_id": "extrude_cut_blind",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_008",
"source_name": "Cut-Extrude2",
"atomic_id": "extrude_cut_blind",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_009",
"source_name": "Mirror1",
"atomic_id": "pattern_mirror",
"execution_status": "deferred",
"unresolved": [
"mirror pattern source features or plane were not captured"
],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_010",
"source_name": "Mirror2",
"atomic_id": "pattern_mirror",
"execution_status": "deferred",
"unresolved": [
"mirror pattern source features or plane were not captured"
],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_011",
"source_name": "Plane2",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_012",
"source_name": "Cut-Extrude3",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_013",
"source_name": "Plane3",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_014",
"source_name": "Cut-Extrude4",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_015",
"source_name": "Boss-Extrude3",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_016",
"source_name": "Boss-Extrude4",
"atomic_id": "extrude_add_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_017",
"source_name": "Cut-Extrude5",
"atomic_id": "extrude_cut_blind",
"execution_status": "supported",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_018",
"source_name": "Plane4",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_019",
"source_name": "Mirror3",
"atomic_id": "pattern_mirror",
"execution_status": "deferred",
"unresolved": [
"mirror pattern source features or plane were not captured"
],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_020",
"source_name": "Chamfer1",
"atomic_id": "chamfer",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
}
],
"semantic_validation": {
"schema_version": "1.1.0",
"feature_count": 20,
"sketch_count": 10,
"deferred_feature_ids": [
"f_001",
"f_002",
"f_003",
"f_005",
"f_018",
"f_007",
"f_008",
"f_011",
"f_013",
"f_019",
"f_009",
"f_010",
"f_020"
],
"unresolved": [
{
"feature_id": "f_019",
"reasons": [
"mirror pattern source features or plane were not captured"
]
},
{
"feature_id": "f_009",
"reasons": [
"mirror pattern source features or plane were not captured"
]
},
{
"feature_id": "f_010",
"reasons": [
"mirror pattern source features or plane were not captured"
]
}
],
"future_rebuild_ready": false
}
}
+6 -6
View File
@@ -958,9 +958,9 @@
],
"meta": {
"unit": "mm",
"source": "009934.solidworks_evidence_v2.json",
"source": "01_紧固件/0103_螺母/010399_通用螺母/009934.solidworks_evidence_v2.json",
"source_schema": "solidworks.cad_evidence.v2",
"source_status": "complete_with_blockers",
"source_status": "hybrid_semantic_surfaceir_pending",
"document_truth": {
"mass_properties": {
"available": true,
@@ -970,12 +970,12 @@
"center_of_mass": [
0.003473722016553019,
8.200887174042614e-10,
-1.0741909887941224e-18
-9.207351332521049e-19
],
"principal_moments": [
2.3744247976046566e-07,
2.3744390350013543e-07,
4.543654594650758e-07
2.374424797376348e-07,
2.3744390352296683e-07,
4.5436545946507525e-07
],
"principal_axes": null,
"moment_of_inertia": null
+9 -15
View File
@@ -1,13 +1,14 @@
{
"source": "009934.solidworks_evidence_v2.json",
"source": "01_紧固件/0103_螺母/010399_通用螺母/009934.solidworks_evidence_v2.json",
"part_id": "009934",
"source_status": "complete_with_blockers",
"source_status": "hybrid_semantic_surfaceir_pending",
"source_self_validation": {
"schema": "solidworks.cad_evidence.self_validation.v1",
"passed": false,
"passed": true,
"meaning": "complete reusable extraction; not a claim of backend feature support or 1:1 rebuild",
"counts": {
"features": 33,
"opaque_exact_geometry_features": 0,
"typed_body_features": 3,
"direct_semantic_features": 1,
"typed_features_requiring_history": 2,
@@ -28,18 +29,14 @@
{
"feature": "旋转1",
"family": "revolve",
"passed": false,
"blockers": [
"revolve has no captured semantic selections"
]
"passed": true,
"blockers": []
},
{
"feature": "切除-旋转1",
"family": "revolve",
"passed": false,
"blockers": [
"revolve has no captured semantic selections"
]
"passed": true,
"blockers": []
},
{
"feature": "切除-拉伸1",
@@ -48,10 +45,7 @@
"blockers": []
}
],
"blockers": [
"旋转1: revolve has no captured semantic selections",
"切除-旋转1: revolve has no captured semantic selections"
]
"blockers": []
},
"step_inference": {
"available": true,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,203 @@
{
"source": "01_紧固件/0105_挡圈卡环/010502_GBT894轴用弹性挡圈/01 - Anel Externo.solidworks_evidence_v2.json",
"part_id": "01---Anel-Externo",
"source_status": "hybrid_semantic_surfaceir_pending",
"source_self_validation": {
"schema": "solidworks.cad_evidence.self_validation.v1",
"passed": true,
"meaning": "complete reusable extraction; not a claim of backend feature support or 1:1 rebuild",
"counts": {
"features": 19,
"opaque_exact_geometry_features": 0,
"typed_body_features": 1,
"direct_semantic_features": 0,
"typed_features_requiring_history": 1,
"complete_history_features": 1,
"spline_segments": 0,
"incomplete_splines": 0,
"exact_bcurves": 0,
"incomplete_bcurves": 0,
"exact_bsurfaces": 0,
"incomplete_bsurfaces": 0,
"exact_trimmed_faces": 0,
"incomplete_trimmed_faces": 0,
"missing_exact_geometry_catalog_references": 0,
"incomplete_topology_records": 0,
"skipped_getter_signatures": 0
},
"feature_contracts": [
{
"feature": "Revolução1",
"family": "revolve",
"passed": true,
"blockers": []
}
],
"blockers": []
},
"step_inference": {
"available": true,
"face_count": 28,
"surface_counts": {
"cone": 18,
"torus": 10
},
"axis_count": 18,
"repeat_spacings_mm": [
{
"spacing_mm": 0.8764,
"pair_count": 2
},
{
"spacing_mm": 4.1236,
"pair_count": 2
},
{
"spacing_mm": 0.0002,
"pair_count": 2
},
{
"spacing_mm": 3.2471,
"pair_count": 2
}
],
"metrics": {
"bounding_box_mm": [
-2.500005667,
-8.000986062,
-8.001675342,
2.506015762,
8.002363218,
8.001675342
],
"volume_mm3": 335.524697132,
"surface_area_mm2": 638.305086929,
"solid_count": 1,
"face_count": 28,
"edge_count": 56,
"vertex_count": 28
},
"error": null,
"truth_comparison": {
"expected_from_evidence": {
"bounding_box_mm": [
-2.5,
-8.000986062,
-8.001675242,
2.50600928,
8.002363218,
8.001675242
],
"volume_mm3": 335.524697132,
"surface_area_mm2": 638.305086929,
"solid_count": 1,
"face_count": 14,
"edge_count": 14,
"vertex_count": 0
},
"comparisons": {
"volume_mm3": {
"expected": 335.524697132,
"actual": 335.524697132,
"absolute_delta": 0.0,
"within_tolerance": true
},
"surface_area_mm2": {
"expected": 638.305086929,
"actual": 638.305086929,
"absolute_delta": 0.0,
"within_tolerance": true
},
"bounding_box_mm": {
"expected": [
-2.5,
-8.000986062,
-8.001675242,
2.50600928,
8.002363218,
8.001675242
],
"actual": [
-2.500005667,
-8.000986062,
-8.001675342,
2.506015762,
8.002363218,
8.001675342
],
"max_absolute_delta": 6.4820000003074085e-06,
"within_tolerance": true
},
"solid_count": {
"expected": 1,
"actual": 1,
"matches": true
},
"face_count": {
"expected": 14,
"actual": 28,
"matches": false
},
"edge_count": {
"expected": 14,
"actual": 56,
"matches": false
},
"vertex_count": {
"expected": 0,
"actual": 28,
"matches": false
}
},
"numeric_geometry_match": true,
"topology_counts_match": false
}
},
"features": [
{
"feature_id": "f_001",
"source_name": "Plano frontal",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_002",
"source_name": "Plano superior",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_003",
"source_name": "Plano direito",
"atomic_id": "reference_plane",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
},
{
"feature_id": "f_004",
"source_name": "Revolução1",
"atomic_id": "revolve_add",
"execution_status": "deferred",
"unresolved": [],
"step_inferred_selector_count": 0
}
],
"semantic_validation": {
"schema_version": "1.1.0",
"feature_count": 4,
"sketch_count": 2,
"deferred_feature_ids": [
"f_001",
"f_002",
"f_004",
"f_003"
],
"unresolved": [],
"future_rebuild_ready": true
}
}
+7 -13
View File
@@ -1162,10 +1162,7 @@
}
]
},
"execution_status": "deferred",
"unresolved": [
"hole has no captured semantic selections"
]
"execution_status": "deferred"
},
{
"id": "f_012",
@@ -1192,10 +1189,7 @@
}
]
},
"execution_status": "deferred",
"unresolved": [
"hole has no captured semantic selections"
]
"execution_status": "deferred"
},
{
"id": "f_015",
@@ -2056,9 +2050,9 @@
],
"meta": {
"unit": "mm",
"source": "011524.solidworks_evidence_v2.json",
"source": "01_紧固件/0106_销/010699_通用销/011524.solidworks_evidence_v2.json",
"source_schema": "solidworks.cad_evidence.v2",
"source_status": "complete_with_blockers",
"source_status": "hybrid_semantic_surfaceir_pending",
"document_truth": {
"mass_properties": {
"available": true,
@@ -2066,14 +2060,14 @@
"volume": 1.959194331462506e-06,
"surface_area": 0.0022937314106504267,
"center_of_mass": [
0.005536254769071257,
0.005536254769071256,
0.002772850696917079,
-0.0018885511388564435
],
"principal_moments": [
8.820786764021557e-08,
8.820786764021561e-08,
2.6274905890636556e-07,
3.3427347813947356e-07
3.342734781394733e-07
],
"principal_axes": null,
"moment_of_inertia": null

Some files were not shown because too many files have changed in this diff Show More