Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- mesh_service/feature_level_augmented.csv +0 -0
- mesh_service/part_level.csv +301 -0
- mesh_service/run_interface.py +1150 -0
- mesh_service/step_feature_detection.py +1075 -0
mesh_service/feature_level_augmented.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
mesh_service/part_level.csv
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
part_id,num_fillet,num_hole,num_plane,smallest_fillet_r,smallest_hole_r,load_face,load_direction,load_scale,global_size_initial,global_size_final,converged,top2_features,local_refine_needed,local_size,,thickness,maxsize,load_id
|
| 2 |
+
1,4,4,6,3.0226,2.4,bend_bottom,-1,low,3.775,2.4160000000000004,0,"FILLET,FACE",1,0.7644375000000001,,3.02,25.399999618530273,1
|
| 3 |
+
1,4,4,6,3.0226,2.4,bend_bottom,-1,medium,3.775,2.4160000000000004,1,"FILLET,FACE",1,1.0192500000000002,,3.02,25.399999618530273,2
|
| 4 |
+
1,4,4,6,3.0226,2.4,bend_bottom,-1,high,3.775,2.4160000000000004,1,"FILLET,FACE",1,1.0192500000000002,,3.02,25.399999618530273,3
|
| 5 |
+
1,4,4,6,3.0226,2.4,tension,-1,low,3.775,2.4160000000000004,1,"CIRC_HOLE,FACE",1,0.7644375000000001,,3.02,25.399999618530273,4
|
| 6 |
+
1,4,4,6,3.0226,2.4,tension,-1,medium,3.775,2.4160000000000004,1,"CIRC_HOLE,FACE",1,0.7644375000000001,,3.02,25.399999618530273,5
|
| 7 |
+
1,4,4,6,3.0226,2.4,tension,-1,high,3.775,2.4160000000000004,1,"CIRC_HOLE,FACE",1,0.7644375000000001,,3.02,25.399999618530273,6
|
| 8 |
+
1,4,4,6,3.0226,2.4,bend_bottom,1,low,3.775,2.4160000000000004,1,"FILLET,FACE",1,1.0192500000000002,,3.02,25.399999618530273,7
|
| 9 |
+
1,4,4,6,3.0226,2.4,bend_bottom,1,medium,3.775,2.4160000000000004,1,"FILLET,FACE",1,1.0192500000000002,,3.02,25.399999618530273,8
|
| 10 |
+
1,4,4,6,3.0226,2.4,bend_bottom,1,high,3.775,2.4160000000000004,1,"FILLET,FACE",1,1.0192500000000002,,3.02,25.399999618530273,9
|
| 11 |
+
1,4,4,6,3.0226,2.4,tension,1,low,3.775,2.4160000000000004,1,"CIRC_HOLE,FACE",1,1.0192500000000002,,3.02,25.399999618530273,10
|
| 12 |
+
1,4,4,6,3.0226,2.4,tension,1,medium,3.775,2.4160000000000004,1,"CIRC_HOLE,CIRC_HOLE",1,0.7644375000000001,,3.02,25.399999618530273,11
|
| 13 |
+
1,4,4,6,3.0226,2.4,tension,1,high,3.775,2.4160000000000004,1,"CIRC_HOLE,CIRC_HOLE",1,1.0192500000000002,,3.02,25.399999618530273,12
|
| 14 |
+
2,2,4,9,0.80264,2.4,bend_bottom,-1,low,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,13
|
| 15 |
+
2,2,4,9,0.80264,2.4,bend_bottom,-1,medium,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,14
|
| 16 |
+
2,2,4,9,0.80264,2.4,bend_bottom,-1,high,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,15
|
| 17 |
+
2,2,4,9,0.80264,2.4,tension,-1,low,2.5,1.6,1,"CIRC_HOLE,CIRC_HOLE",1,0.5062500000000001,,2,23.012399673461914,16
|
| 18 |
+
2,2,4,9,0.80264,2.4,tension,-1,medium,2.5,1.6,1,"CIRC_HOLE,CIRC_HOLE",1,0.5062500000000001,,2,23.012399673461914,17
|
| 19 |
+
2,2,4,9,0.80264,2.4,tension,-1,high,2.5,1.6,1,"CIRC_HOLE,CIRC_HOLE",1,0.5062500000000001,,2,23.012399673461914,18
|
| 20 |
+
2,2,4,9,0.80264,2.4,bend_bottom,1,low,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,19
|
| 21 |
+
2,2,4,9,0.80264,2.4,bend_bottom,1,medium,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,20
|
| 22 |
+
2,2,4,9,0.80264,2.4,bend_bottom,1,high,2.5,1.6,1,"FILLET,FACE",1,0.675,,2,23.012399673461914,21
|
| 23 |
+
2,2,4,9,0.80264,2.4,tension,1,low,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,23.012399673461914,22
|
| 24 |
+
2,2,4,9,0.80264,2.4,tension,1,medium,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.37968750000000007,,2,23.012399673461914,23
|
| 25 |
+
2,2,4,9,0.80264,2.4,tension,1,high,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,23.012399673461914,24
|
| 26 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,-1,low,3.3375,1.8773437499999999,1,"FACE,FACE",1,1.8773437499999999,,2.67,63.5,25
|
| 27 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,-1,medium,3.3375,1.8773437499999999,1,"FACE,FACE",1,1.8773437499999999,,2.67,63.5,26
|
| 28 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,-1,high,3.3375,1.8773437499999999,1,"FACE,FACE",1,1.8773437499999999,,2.67,63.5,27
|
| 29 |
+
8,3,8,17,0.66675,3.5433,tension,-1,low,3.3375,2.136,1,"CIRC_HOLE,FACE",1,0.67584375,,2.67,63.5,28
|
| 30 |
+
8,3,8,17,0.66675,3.5433,tension,-1,medium,3.3375,2.136,0,"CIRC_HOLE,FACE",1,0.67584375,,2.67,63.5,29
|
| 31 |
+
8,3,8,17,0.66675,3.5433,tension,-1,high,3.3375,2.136,0,"CIRC_HOLE,FACE",1,0.67584375,,2.67,63.5,30
|
| 32 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,1,low,3.3375,2.136,1,"FACE,CIRC_HOLE",1,0.901125,,2.67,63.5,31
|
| 33 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,1,medium,3.3375,2.136,1,"FACE,CIRC_HOLE",1,0.901125,,2.67,63.5,32
|
| 34 |
+
8,3,8,17,0.66675,3.5433,bend_bottom,1,high,3.3375,2.136,1,"FACE,CIRC_HOLE",1,0.901125,,2.67,63.5,33
|
| 35 |
+
8,3,8,17,0.66675,3.5433,tension,1,low,3.3375,2.136,1,"CIRC_HOLE,FACE",1,0.901125,,2.67,63.5,34
|
| 36 |
+
8,3,8,17,0.66675,3.5433,tension,1,medium,3.3375,2.136,1,"CIRC_HOLE,FACE",1,0.901125,,2.67,63.5,35
|
| 37 |
+
8,3,8,17,0.66675,3.5433,tension,1,high,3.3375,2.136,1,"CIRC_HOLE,FACE",1,0.901125,,2.67,63.5,36
|
| 38 |
+
12,1,12,21,2.032,2.7432,bend_bottom,-1,low,6.35,2.143125,0,"FACE,FACE",1,1.60734375,,4,127,37
|
| 39 |
+
12,1,12,21,2.032,2.7432,bend_bottom,-1,medium,6.35,2.143125,0,"FACE,FACE",1,1.60734375,,4,127,38
|
| 40 |
+
12,1,12,21,2.032,2.7432,bend_bottom,-1,high,6.35,2.143125,0,"FACE,FACE",1,1.60734375,,4,127,39
|
| 41 |
+
12,1,12,21,2.032,2.7432,tension,-1,low,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,40
|
| 42 |
+
12,1,12,21,2.032,2.7432,tension,-1,medium,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,41
|
| 43 |
+
12,1,12,21,2.032,2.7432,tension,-1,high,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,42
|
| 44 |
+
12,1,12,21,2.032,2.7432,bend_bottom,1,low,6.35,3.2512000000000003,1,"CIRC_HOLE,FACE",1,0.7715250000000001,,4,127,43
|
| 45 |
+
12,1,12,21,2.032,2.7432,bend_bottom,1,medium,6.35,3.2512000000000003,1,"CIRC_HOLE,FACE",1,0.7715250000000001,,4,127,44
|
| 46 |
+
12,1,12,21,2.032,2.7432,bend_bottom,1,high,6.35,3.2512000000000003,0,"FACE,CIRC_HOLE",1,1.0287000000000002,,4,127,45
|
| 47 |
+
12,1,12,21,2.032,2.7432,tension,1,low,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,46
|
| 48 |
+
12,1,12,21,2.032,2.7432,tension,1,medium,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,47
|
| 49 |
+
12,1,12,21,2.032,2.7432,tension,1,high,6.35,4.064,1,"CIRC_HOLE,FACE",1,2.286,,4,127,48
|
| 50 |
+
13,1,12,21,2.413,2.8956,bend_bottom,-1,low,7.619999694824244,4.286249828338637,1,"FILLET,FACE",1,2.4110155284404837,,4.83,152.3999938964849,49
|
| 51 |
+
13,1,12,21,2.413,2.8956,bend_bottom,-1,medium,7.619999694824244,4.286249828338637,1,"FILLET,FACE",1,2.4110155284404837,,4.83,152.3999938964849,50
|
| 52 |
+
13,1,12,21,2.413,2.8956,bend_bottom,-1,high,7.619999694824244,4.286249828338637,1,"FILLET,FACE",1,2.4110155284404837,,4.83,152.3999938964849,51
|
| 53 |
+
13,1,12,21,2.413,2.8956,tension,-1,low,7.619999694824244,3.9014398437500137,1,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,52
|
| 54 |
+
13,1,12,21,2.413,2.8956,tension,-1,medium,7.619999694824244,3.9014398437500137,1,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,53
|
| 55 |
+
13,1,12,21,2.413,2.8956,tension,-1,high,7.619999694824244,3.9014398437500137,1,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,54
|
| 56 |
+
13,1,12,21,2.413,2.8956,bend_bottom,1,low,7.619999694824244,3.9014398437500137,0,"CIRC_HOLE,FACE",1,1.2344399505615278,,4.83,152.3999938964849,55
|
| 57 |
+
13,1,12,21,2.413,2.8956,bend_bottom,1,medium,7.619999694824244,3.9014398437500137,0,"CIRC_HOLE,FACE",1,1.2344399505615278,,4.83,152.3999938964849,56
|
| 58 |
+
13,1,12,21,2.413,2.8956,bend_bottom,1,high,7.619999694824244,4.286249828338637,1,"FACE,CIRC_HOLE",1,3.2146873712539783,,4.83,152.3999938964849,57
|
| 59 |
+
13,1,12,21,2.413,2.8956,tension,1,low,7.619999694824244,3.9014398437500137,0,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,58
|
| 60 |
+
13,1,12,21,2.413,2.8956,tension,1,medium,7.619999694824244,3.9014398437500137,0,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,59
|
| 61 |
+
13,1,12,21,2.413,2.8956,tension,1,high,7.619999694824244,3.9014398437500137,0,"FACE,CIRC_HOLE",1,1.2344399505615278,,4.83,152.3999938964849,60
|
| 62 |
+
14,0,12,22,4.191,2.921,bend_bottom,-1,low,10.159999525547027,4.286249799840152,1,"FACE,FACE",1,4.286249799840152,,5.6,203.19999051094055,61
|
| 63 |
+
14,0,12,22,4.191,2.921,bend_bottom,-1,medium,10.159999525547027,4.286249799840152,1,"FACE,FACE",1,4.286249799840152,,5.6,203.19999051094055,62
|
| 64 |
+
14,0,12,22,4.191,2.921,bend_bottom,-1,high,10.159999525547027,6.095999715328216,1,"FACE,FACE",1,4.571999786496162,,5.6,203.19999051094055,63
|
| 65 |
+
14,0,12,22,4.191,2.921,tension,-1,low,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,64
|
| 66 |
+
14,0,12,22,4.191,2.921,tension,-1,medium,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,65
|
| 67 |
+
14,0,12,22,4.191,2.921,tension,-1,high,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,66
|
| 68 |
+
14,0,12,22,4.191,2.921,bend_bottom,1,low,10.159999525547027,5.201919757080079,1,"CIRC_HOLE,FACE",1,1.6459199231386188,,5.6,203.19999051094055,67
|
| 69 |
+
14,0,12,22,4.191,2.921,bend_bottom,1,medium,10.159999525547027,5.201919757080079,1,"CIRC_HOLE,FACE",1,1.6459199231386188,,5.6,203.19999051094055,68
|
| 70 |
+
14,0,12,22,4.191,2.921,bend_bottom,1,high,10.159999525547027,5.201919757080079,1,"CIRC_HOLE,FACE",1,1.6459199231386188,,5.6,203.19999051094055,69
|
| 71 |
+
14,0,12,22,4.191,2.921,tension,1,low,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,70
|
| 72 |
+
14,0,12,22,4.191,2.921,tension,1,medium,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,71
|
| 73 |
+
14,0,12,22,4.191,2.921,tension,1,high,10.159999525547027,6.502399696350098,1,"CIRC_HOLE,FACE",1,3.6575998291969305,,5.6,203.19999051094055,72
|
| 74 |
+
15,1,16,25,3.175,2.7432,bend_bottom,-1,low,12.7,6.096,0,"FILLET,FACE",1,2.57175,,6.35,254,73
|
| 75 |
+
15,1,16,25,3.175,2.7432,bend_bottom,-1,medium,12.7,6.096,0,"FILLET,FACE",1,2.57175,,6.35,254,74
|
| 76 |
+
15,1,16,25,3.175,2.7432,bend_bottom,-1,high,12.7,6.096,0,"FILLET,CIRC_HOLE",1,1.9288125000000003,,6.35,254,75
|
| 77 |
+
15,1,16,25,3.175,2.7432,tension,-1,low,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,76
|
| 78 |
+
15,1,16,25,3.175,2.7432,tension,-1,medium,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,77
|
| 79 |
+
15,1,16,25,3.175,2.7432,tension,-1,high,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,78
|
| 80 |
+
15,1,16,25,3.175,2.7432,bend_bottom,1,low,12.7,5.201920000000001,1,"CIRC_HOLE,FACE",1,1.6459200000000003,,6.35,254,79
|
| 81 |
+
15,1,16,25,3.175,2.7432,bend_bottom,1,medium,12.7,5.201920000000001,1,"CIRC_HOLE,FACE",1,1.6459200000000003,,6.35,254,80
|
| 82 |
+
15,1,16,25,3.175,2.7432,bend_bottom,1,high,12.7,5.201920000000001,1,"CIRC_HOLE,FACE",1,1.6459200000000003,,6.35,254,81
|
| 83 |
+
15,1,16,25,3.175,2.7432,tension,1,low,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,82
|
| 84 |
+
15,1,16,25,3.175,2.7432,tension,1,medium,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,83
|
| 85 |
+
15,1,16,25,3.175,2.7432,tension,1,high,12.7,6.502400000000001,1,"CIRC_HOLE,FACE",1,2.7432000000000003,,6.35,254,84
|
| 86 |
+
16,2,4,40,1.524,2.3495,bend_bottom,-1,low,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,85
|
| 87 |
+
16,2,4,40,1.524,2.3495,bend_bottom,-1,medium,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,86
|
| 88 |
+
16,2,4,40,1.524,2.3495,bend_bottom,-1,high,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,87
|
| 89 |
+
16,2,4,40,1.524,2.3495,tension,-1,low,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.5062500000000001,,2,25.399999499320984,88
|
| 90 |
+
16,2,4,40,1.524,2.3495,tension,-1,medium,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,25.399999499320984,89
|
| 91 |
+
16,2,4,40,1.524,2.3495,tension,-1,high,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.5062500000000001,,2,25.399999499320984,90
|
| 92 |
+
16,2,4,40,1.524,2.3495,bend_bottom,1,low,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,91
|
| 93 |
+
16,2,4,40,1.524,2.3495,bend_bottom,1,medium,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,92
|
| 94 |
+
16,2,4,40,1.524,2.3495,bend_bottom,1,high,2.5,1.6,0,"FACE,FILLET",1,0.5062500000000001,,2,25.399999499320984,93
|
| 95 |
+
16,2,4,40,1.524,2.3495,tension,1,low,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,25.399999499320984,94
|
| 96 |
+
16,2,4,40,1.524,2.3495,tension,1,medium,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,25.399999499320984,95
|
| 97 |
+
16,2,4,40,1.524,2.3495,tension,1,high,2.5,1.6,1,"CIRC_HOLE,FACE",1,0.675,,2,25.399999499320984,96
|
| 98 |
+
17,2,8,16,0.889,2.3495,bend_bottom,-1,low,2.225,1.4240000000000004,0,"FILLET,FACE",1,0.4505625000000001,,1.78,38.099998474121094,97
|
| 99 |
+
17,2,8,16,0.889,2.3495,bend_bottom,-1,medium,2.225,1.4240000000000004,0,"FILLET,FACE",1,0.4505625000000001,,1.78,38.099998474121094,98
|
| 100 |
+
17,2,8,16,0.889,2.3495,bend_bottom,-1,high,2.225,1.4240000000000004,0,"FILLET,FACE",1,0.4505625000000001,,1.78,38.099998474121094,99
|
| 101 |
+
17,2,8,16,0.889,2.3495,tension,-1,low,2.225,1.4240000000000004,1,"FACE,CIRC_HOLE",1,0.6007500000000001,,1.78,38.099998474121094,100
|
| 102 |
+
17,2,8,16,0.889,2.3495,tension,-1,medium,2.225,1.4240000000000004,1,"CIRC_HOLE,FACE",1,0.6007500000000001,,1.78,38.099998474121094,101
|
| 103 |
+
17,2,8,16,0.889,2.3495,tension,-1,high,2.225,1.4240000000000004,1,"CIRC_HOLE,FACE",1,0.6007500000000001,,1.78,38.099998474121094,102
|
| 104 |
+
17,2,8,16,0.889,2.3495,bend_bottom,1,low,2.225,1.4240000000000004,1,"FILLET,CIRC_HOLE",1,0.1900810546875,,1.78,38.099998474121094,103
|
| 105 |
+
17,2,8,16,0.889,2.3495,bend_bottom,1,medium,2.225,1.4240000000000004,1,"FILLET,CIRC_HOLE",1,0.1900810546875,,1.78,38.099998474121094,104
|
| 106 |
+
17,2,8,16,0.889,2.3495,bend_bottom,1,high,2.225,1.4240000000000004,1,"FILLET,FACE",1,0.4505625000000001,,1.78,38.099998474121094,105
|
| 107 |
+
17,2,8,16,0.889,2.3495,tension,1,low,2.225,1.4240000000000004,1,"FACE,CIRC_HOLE",1,0.6007500000000001,,1.78,38.099998474121094,106
|
| 108 |
+
17,2,8,16,0.889,2.3495,tension,1,medium,2.225,1.4240000000000004,1,"FACE,CIRC_HOLE",1,0.6007500000000001,,1.78,38.099998474121094,107
|
| 109 |
+
17,2,8,16,0.889,2.3495,tension,1,high,2.225,1.4240000000000004,1,"CIRC_HOLE,FACE",1,0.6007500000000001,,1.78,38.099998474121094,108
|
| 110 |
+
18,2,8,16,1.3335,2.159,bend_bottom,-1,low,2.5399999648332594,1.4287499802187085,1,"FILLET,FACE",1,0.8036718638730235,,1.78,50.79999929666519,109
|
| 111 |
+
18,2,8,16,1.3335,2.159,bend_bottom,-1,medium,2.5399999648332594,1.4287499802187085,1,"FILLET,FACE",1,0.8036718638730235,,1.78,50.79999929666519,110
|
| 112 |
+
18,2,8,16,1.3335,2.159,bend_bottom,-1,high,2.5399999648332594,1.4287499802187085,1,"FILLET,FACE",1,0.8036718638730235,,1.78,50.79999929666519,111
|
| 113 |
+
18,2,8,16,1.3335,2.159,tension,-1,low,2.5399999648332594,1.625599977493286,1,"FACE,CIRC_HOLE",1,0.6857999905049801,,1.78,50.79999929666519,112
|
| 114 |
+
18,2,8,16,1.3335,2.159,tension,-1,medium,2.5399999648332594,1.625599977493286,1,"FACE,CIRC_HOLE",1,0.6857999905049801,,1.78,50.79999929666519,113
|
| 115 |
+
18,2,8,16,1.3335,2.159,tension,-1,high,2.5399999648332594,1.625599977493286,1,"CIRC_HOLE,FACE",1,0.6857999905049801,,1.78,50.79999929666519,114
|
| 116 |
+
18,2,8,16,1.3335,2.159,bend_bottom,1,low,2.5399999648332594,1.5239999788999556,0,"FACE,CIRC_HOLE",1,0.6429374910984187,,1.78,50.79999929666519,115
|
| 117 |
+
18,2,8,16,1.3335,2.159,bend_bottom,1,medium,2.5399999648332594,1.5239999788999556,0,"FACE,CIRC_HOLE",1,0.6429374910984187,,1.78,50.79999929666519,116
|
| 118 |
+
18,2,8,16,1.3335,2.159,bend_bottom,1,high,2.5399999648332594,1.5239999788999556,0,"FACE,CIRC_HOLE",1,0.6429374910984187,,1.78,50.79999929666519,117
|
| 119 |
+
18,2,8,16,1.3335,2.159,tension,1,low,2.5399999648332594,1.625599977493286,1,"FACE,CIRC_HOLE",1,0.6857999905049801,,1.78,50.79999929666519,118
|
| 120 |
+
18,2,8,16,1.3335,2.159,tension,1,medium,2.5399999648332594,1.625599977493286,1,"FACE,CIRC_HOLE",1,0.6857999905049801,,1.78,50.79999929666519,119
|
| 121 |
+
18,2,8,16,1.3335,2.159,tension,1,high,2.5399999648332594,1.625599977493286,1,"FACE,CIRC_HOLE",1,0.6857999905049801,,1.78,50.79999929666519,120
|
| 122 |
+
19,2,8,16,1.3335,2.159,bend_bottom,-1,low,3.174999812245369,1.1429999324083329,0,"FACE,FILLET",1,0.6429374619796873,,1.78,63.49999624490738,121
|
| 123 |
+
19,2,8,16,1.3335,2.159,bend_bottom,-1,medium,3.174999812245369,1.1429999324083329,0,"FACE,FILLET",1,0.6429374619796873,,1.78,63.49999624490738,122
|
| 124 |
+
19,2,8,16,1.3335,2.159,bend_bottom,-1,high,3.174999812245369,1.1429999324083329,0,"FACE,FILLET",1,0.6429374619796873,,1.78,63.49999624490738,123
|
| 125 |
+
19,2,8,16,1.3335,2.159,tension,-1,low,3.174999812245369,1.6255999038696292,1,"CIRC_HOLE,FACE",1,0.6857999594449999,,1.78,63.49999624490738,124
|
| 126 |
+
19,2,8,16,1.3335,2.159,tension,-1,medium,3.174999812245369,1.6255999038696292,1,"CIRC_HOLE,FACE",1,0.6857999594449999,,1.78,63.49999624490738,125
|
| 127 |
+
19,2,8,16,1.3335,2.159,tension,-1,high,3.174999812245369,1.6255999038696292,1,"CIRC_HOLE,FACE",1,0.6857999594449999,,1.78,63.49999624490738,126
|
| 128 |
+
19,2,8,16,1.3335,2.159,bend_bottom,1,low,3.174999812245369,1.5239999098777774,0,"FACE,CIRC_HOLE",1,0.6429374619796873,,1.78,63.49999624490738,127
|
| 129 |
+
19,2,8,16,1.3335,2.159,bend_bottom,1,medium,3.174999812245369,1.1429999324083329,0,"FACE,FILLET",1,0.6429374619796873,,1.78,63.49999624490738,128
|
| 130 |
+
19,2,8,16,1.3335,2.159,bend_bottom,1,high,3.174999812245369,1.1429999324083329,0,"FACE,FILLET",1,0.6429374619796873,,1.78,63.49999624490738,129
|
| 131 |
+
19,2,8,16,1.3335,2.159,tension,1,low,3.174999812245369,1.6255999038696292,1,"CIRC_HOLE,FACE",1,0.6857999594449999,,1.78,63.49999624490738,130
|
| 132 |
+
19,2,8,16,1.3335,2.159,tension,1,medium,3.174999812245369,1.6255999038696292,1,"CIRC_HOLE,FACE",1,0.6857999594449999,,1.78,63.49999624490738,131
|
| 133 |
+
19,2,8,16,1.3335,2.159,tension,1,high,3.174999812245369,1.6255999038696292,1,"FACE,CIRC_HOLE",1,0.5143499695837499,,1.78,63.49999624490738,132
|
| 134 |
+
20,1,8,17,1.905,2.2479,bend_bottom,-1,low,3.810000014305115,2.4384000091552736,1,"FILLET,FACE",1,0.7715250028967857,,2.54,76.2000002861023,133
|
| 135 |
+
20,1,8,17,1.905,2.2479,bend_bottom,-1,medium,3.810000014305115,2.4384000091552736,1,"FILLET,FACE",1,0.7715250028967857,,2.54,76.2000002861023,134
|
| 136 |
+
20,1,8,17,1.905,2.2479,bend_bottom,-1,high,3.810000014305115,2.4384000091552736,1,"FILLET,FACE",1,0.7715250028967857,,2.54,76.2000002861023,135
|
| 137 |
+
20,1,8,17,1.905,2.2479,tension,-1,low,3.810000014305115,2.4384000091552736,1,"CIRC_HOLE,FACE",1,0.7715250028967857,,2.54,76.2000002861023,136
|
| 138 |
+
20,1,8,17,1.905,2.2479,tension,-1,medium,3.810000014305115,2.4384000091552736,1,"CIRC_HOLE,FACE",1,0.7715250028967857,,2.54,76.2000002861023,137
|
| 139 |
+
20,1,8,17,1.905,2.2479,tension,-1,high,3.810000014305115,2.4384000091552736,1,"CIRC_HOLE,FACE",1,0.7715250028967857,,2.54,76.2000002861023,138
|
| 140 |
+
20,1,8,17,1.905,2.2479,bend_bottom,1,low,3.810000014305115,2.4384000091552736,1,"FACE,CIRC_HOLE",1,1.028700003862381,,2.54,76.2000002861023,139
|
| 141 |
+
20,1,8,17,1.905,2.2479,bend_bottom,1,medium,3.810000014305115,2.4384000091552736,1,"FACE,CIRC_HOLE",1,1.3716000051498414,,2.54,76.2000002861023,140
|
| 142 |
+
20,1,8,17,1.905,2.2479,bend_bottom,1,high,3.810000014305115,2.4384000091552736,1,"FILLET,FACE",1,0.7715250028967857,,2.54,76.2000002861023,141
|
| 143 |
+
20,1,8,17,1.905,2.2479,tension,1,low,3.810000014305115,2.4384000091552736,1,"CIRC_HOLE,FACE",1,0.7715250028967857,,2.54,76.2000002861023,142
|
| 144 |
+
20,1,8,17,1.905,2.2479,tension,1,medium,3.810000014305115,2.4384000091552736,1,"CIRC_HOLE,FACE",1,0.7715250028967857,,2.54,76.2000002861023,143
|
| 145 |
+
20,1,8,17,1.905,2.2479,tension,1,high,3.810000014305115,2.4384000091552736,1,"FACE,CIRC_HOLE",1,0.7715250028967857,,2.54,76.2000002861023,144
|
| 146 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,-1,low,5.079999870061874,1.6073437088867648,1,"FACE,FACE",1,1.6073437088867648,,3.3,101.59999740123749,145
|
| 147 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,-1,medium,5.079999870061874,1.6073437088867648,0,"FACE,FACE",1,1.6073437088867648,,3.3,101.59999740123749,146
|
| 148 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,-1,high,5.079999870061874,2.8574999269098043,1,"FACE,CIRC_HOLE",1,2.8574999269098043,,3.3,101.59999740123749,147
|
| 149 |
+
21,1,8,17,2.4765,2.8067,tension,-1,low,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,148
|
| 150 |
+
21,1,8,17,2.4765,2.8067,tension,-1,medium,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,149
|
| 151 |
+
21,1,8,17,2.4765,2.8067,tension,-1,high,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,150
|
| 152 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,1,low,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,151
|
| 153 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,1,medium,5.079999870061874,3.0479999220371248,1,"CIRC_HOLE,FACE",1,1.7144999561458827,,3.3,101.59999740123749,152
|
| 154 |
+
21,1,8,17,2.4765,2.8067,bend_bottom,1,high,5.079999870061874,3.0479999220371248,1,"CIRC_HOLE,FACE",1,1.7144999561458827,,3.3,101.59999740123749,153
|
| 155 |
+
21,1,8,17,2.4765,2.8067,tension,1,low,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,154
|
| 156 |
+
21,1,8,17,2.4765,2.8067,tension,1,medium,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,155
|
| 157 |
+
21,1,8,17,2.4765,2.8067,tension,1,high,5.079999870061874,3.2511999168395995,0,"CIRC_HOLE,FACE",1,1.0286999736875297,,3.3,101.59999740123749,156
|
| 158 |
+
22,1,12,21,2.286,3.1242,bend_bottom,-1,low,7.619999694824219,3.2146873712539668,1,"FACE,FACE",1,3.2146873712539668,,4.57,152.39999389648438,157
|
| 159 |
+
22,1,12,21,2.286,3.1242,bend_bottom,-1,medium,7.619999694824219,3.2146873712539668,1,"FACE,FACE",1,3.2146873712539668,,4.57,152.39999389648438,158
|
| 160 |
+
22,1,12,21,2.286,3.1242,bend_bottom,-1,high,7.619999694824219,3.2146873712539668,1,"FACE,CIRC_HOLE",1,3.2146873712539668,,4.57,152.39999389648438,159
|
| 161 |
+
22,1,12,21,2.286,3.1242,tension,-1,low,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,160
|
| 162 |
+
22,1,12,21,2.286,3.1242,tension,-1,medium,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,161
|
| 163 |
+
22,1,12,21,2.286,3.1242,tension,-1,high,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,162
|
| 164 |
+
22,1,12,21,2.286,3.1242,bend_bottom,1,low,7.619999694824219,3.901439843750001,0,"FACE,CIRC_HOLE",1,1.2344399505615238,,4.57,152.39999389648438,163
|
| 165 |
+
22,1,12,21,2.286,3.1242,bend_bottom,1,medium,7.619999694824219,3.901439843750001,0,"FACE,CIRC_HOLE",1,1.2344399505615238,,4.57,152.39999389648438,164
|
| 166 |
+
22,1,12,21,2.286,3.1242,bend_bottom,1,high,7.619999694824219,3.901439843750001,0,"FACE,CIRC_HOLE",1,1.2344399505615238,,4.57,152.39999389648438,165
|
| 167 |
+
22,1,12,21,2.286,3.1242,tension,1,low,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,166
|
| 168 |
+
22,1,12,21,2.286,3.1242,tension,1,medium,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,167
|
| 169 |
+
22,1,12,21,2.286,3.1242,tension,1,high,7.619999694824219,3.901439843750001,1,"CIRC_HOLE,FACE",1,1.6459199340820316,,4.57,152.39999389648438,168
|
| 170 |
+
25,5,8,17,1.27,1.55,bend_bottom,-1,low,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,169
|
| 171 |
+
25,5,8,17,1.27,1.55,bend_bottom,-1,medium,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,170
|
| 172 |
+
25,5,8,17,1.27,1.55,bend_bottom,-1,high,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,171
|
| 173 |
+
25,5,8,17,1.27,1.55,tension,-1,low,3.125,2,0,"CIRC_HOLE,FACE",1,0.6328125,,2.5,30.162500381469727,172
|
| 174 |
+
25,5,8,17,1.27,1.55,tension,-1,medium,3.125,2,0,"CIRC_HOLE,FACE",1,0.6328125,,2.5,30.162500381469727,173
|
| 175 |
+
25,5,8,17,1.27,1.55,tension,-1,high,3.125,2,1,"CIRC_HOLE,FACE",1,0.6328125,,2.5,30.162500381469727,174
|
| 176 |
+
25,5,8,17,1.27,1.55,bend_bottom,1,low,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,175
|
| 177 |
+
25,5,8,17,1.27,1.55,bend_bottom,1,medium,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,176
|
| 178 |
+
25,5,8,17,1.27,1.55,bend_bottom,1,high,3.125,2.34375,0,"FILLET,FACE",1,0.988769531,,2.5,30.162500381469727,177
|
| 179 |
+
25,5,8,17,1.27,1.55,tension,1,low,3.125,2,0,"CIRC_HOLE,FACE",1,0.6328125,,2.5,30.162500381469727,178
|
| 180 |
+
25,5,8,17,1.27,1.55,tension,1,medium,3.125,2,0,"CIRC_HOLE,FACE",1,0.6328125,,2.5,30.162500381469727,179
|
| 181 |
+
25,5,8,17,1.27,1.55,tension,1,high,3.125,2,0,"FACE,CIRC_HOLE",1,0.6328125,,2.5,30.162500381469727,180
|
| 182 |
+
26,5,8,17,0.81,1.55,bend_bottom,-1,low,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,181
|
| 183 |
+
26,5,8,17,0.81,1.55,bend_bottom,-1,medium,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,182
|
| 184 |
+
26,5,8,17,0.81,1.55,bend_bottom,-1,high,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,183
|
| 185 |
+
26,5,8,17,0.81,1.55,tension,-1,low,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.0125000000000002,,3,44.84687423706055,184
|
| 186 |
+
26,5,8,17,0.81,1.55,tension,-1,medium,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.0125000000000002,,3,44.84687423706055,185
|
| 187 |
+
26,5,8,17,0.81,1.55,tension,-1,high,3.75,2.4000000000000004,0,"CIRC_HOLE,FACE",1,0.7593750000000001,,3,44.84687423706055,186
|
| 188 |
+
26,5,8,17,0.81,1.55,bend_bottom,1,low,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,187
|
| 189 |
+
26,5,8,17,0.81,1.55,bend_bottom,1,medium,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,188
|
| 190 |
+
26,5,8,17,0.81,1.55,bend_bottom,1,high,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,44.84687423706055,189
|
| 191 |
+
26,5,8,17,0.81,1.55,tension,1,low,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.35,,3,44.84687423706055,190
|
| 192 |
+
26,5,8,17,0.81,1.55,tension,1,medium,3.75,2.4000000000000004,1,"FACE,CIRC_HOLE",1,1.35,,3,44.84687423706055,191
|
| 193 |
+
26,5,8,17,0.81,1.55,tension,1,high,3.75,2.4000000000000004,1,"FACE,CIRC_HOLE",1,1.35,,3,44.84687423706055,192
|
| 194 |
+
27,5,8,17,0.81,1.75,bend_bottom,-1,low,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,193
|
| 195 |
+
27,5,8,17,0.81,1.75,bend_bottom,-1,medium,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,194
|
| 196 |
+
27,5,8,17,0.81,1.75,bend_bottom,-1,high,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,195
|
| 197 |
+
27,5,8,17,0.81,1.75,tension,-1,low,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.35,,3,59.928123474121094,196
|
| 198 |
+
27,5,8,17,0.81,1.75,tension,-1,medium,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.35,,3,59.928123474121094,197
|
| 199 |
+
27,5,8,17,0.81,1.75,tension,-1,high,3.75,2.4000000000000004,1,"CIRC_HOLE,FACE",1,1.35,,3,59.928123474121094,198
|
| 200 |
+
27,5,8,17,0.81,1.75,bend_bottom,1,low,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,199
|
| 201 |
+
27,5,8,17,0.81,1.75,bend_bottom,1,medium,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,200
|
| 202 |
+
27,5,8,17,0.81,1.75,bend_bottom,1,high,3.75,2.4000000000000004,0,"FILLET,FACE",1,0.7593750000000001,,3,59.928123474121094,201
|
| 203 |
+
27,5,8,17,0.81,1.75,tension,1,low,3.75,2.4000000000000004,0,"CIRC_HOLE,FACE",1,0.7593750000000001,,3,59.928123474121094,202
|
| 204 |
+
27,5,8,17,0.81,1.75,tension,1,medium,3.75,2.4000000000000004,0,"CIRC_HOLE,FACE",1,0.7593750000000001,,3,59.928123474121094,203
|
| 205 |
+
27,5,8,17,0.81,1.75,tension,1,high,3.75,2.4000000000000004,0,"CIRC_HOLE,FACE",1,0.7593750000000001,,3,59.928123474121094,204
|
| 206 |
+
29,0,4,17,16,2.1,bend_bottom,-1,low,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,205
|
| 207 |
+
29,0,4,17,16,2.1,bend_bottom,-1,medium,4.375,2.8000000000000003,0,"CIRC_HOLE,FACE",1,0.6644531250000001,,3.5,23,206
|
| 208 |
+
29,0,4,17,16,2.1,bend_bottom,-1,high,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,0.49833984375000007,,3.5,23,207
|
| 209 |
+
29,0,4,17,16,2.1,tension,-1,low,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,208
|
| 210 |
+
29,0,4,17,16,2.1,tension,-1,medium,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,209
|
| 211 |
+
29,0,4,17,16,2.1,tension,-1,high,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,210
|
| 212 |
+
29,0,4,17,16,2.1,bend_bottom,1,low,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,211
|
| 213 |
+
29,0,4,17,16,2.1,bend_bottom,1,medium,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,212
|
| 214 |
+
29,0,4,17,16,2.1,bend_bottom,1,high,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,213
|
| 215 |
+
29,0,4,17,16,2.1,tension,1,low,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,214
|
| 216 |
+
29,0,4,17,16,2.1,tension,1,medium,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,215
|
| 217 |
+
29,0,4,17,16,2.1,tension,1,high,4.375,2.8000000000000003,1,"CIRC_HOLE,FACE",1,1.1812500000000001,,3.5,23,216
|
| 218 |
+
36,2,16,12,0.762,1.7526,bend_bottom,-1,low,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.3847500000000001,,1.52,34.92500197887421,217
|
| 219 |
+
36,2,16,12,0.762,1.7526,bend_bottom,-1,medium,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.3847500000000001,,1.52,34.92500197887421,218
|
| 220 |
+
36,2,16,12,0.762,1.7526,bend_bottom,-1,high,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.3847500000000001,,1.52,34.92500197887421,219
|
| 221 |
+
36,2,16,12,0.762,1.7526,tension,-1,low,1.9,1.2160000000000002,1,"CIRC_HOLE,CIRC_HOLE",1,0.28856250000000006,,1.52,34.92500197887421,220
|
| 222 |
+
36,2,16,12,0.762,1.7526,tension,-1,medium,1.9,1.2160000000000002,1,"CIRC_HOLE,CIRC_HOLE",1,0.28856250000000006,,1.52,34.92500197887421,221
|
| 223 |
+
36,2,16,12,0.762,1.7526,tension,-1,high,1.9,1.2160000000000002,1,"CIRC_HOLE,CIRC_HOLE",1,0.28856250000000006,,1.52,34.92500197887421,222
|
| 224 |
+
36,2,16,12,0.762,1.7526,bend_bottom,1,low,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.3847500000000001,,1.52,34.92500197887421,223
|
| 225 |
+
36,2,16,12,0.762,1.7526,bend_bottom,1,medium,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.3847500000000001,,1.52,34.92500197887421,224
|
| 226 |
+
36,2,16,12,0.762,1.7526,bend_bottom,1,high,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.5130000000000001,,1.52,34.92500197887421,225
|
| 227 |
+
36,2,16,12,0.762,1.7526,tension,1,low,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.6840000000000002,,1.52,34.92500197887421,226
|
| 228 |
+
36,2,16,12,0.762,1.7526,tension,1,medium,1.9,1.2160000000000002,1,"CIRC_HOLE,FACE",1,0.6840000000000002,,1.52,34.92500197887421,227
|
| 229 |
+
36,2,16,12,0.762,1.7526,tension,1,high,1.9,1.2160000000000002,1,"CIRC_HOLE,CIRC_HOLE",1,0.6840000000000002,,1.52,34.92500197887421,228
|
| 230 |
+
37,2,8,12,0.762,1.7526,bend_bottom,-1,low,1.9050000607967377,1.2192000389099122,0,"FACE,CIRC_HOLE",1,0.3857625123113394,,1.52,38.10000121593475,229
|
| 231 |
+
37,2,8,12,0.762,1.7526,bend_bottom,-1,medium,1.9050000607967377,1.2192000389099122,0,"FACE,CIRC_HOLE",1,0.3857625123113394,,1.52,38.10000121593475,230
|
| 232 |
+
37,2,8,12,0.762,1.7526,bend_bottom,-1,high,1.9050000607967377,1.2192000389099122,0,"FACE,CIRC_HOLE",1,0.3857625123113394,,1.52,38.10000121593475,231
|
| 233 |
+
37,2,8,12,0.762,1.7526,tension,-1,low,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,CIRC_HOLE",1,0.2893218842335046,,1.52,38.10000121593475,232
|
| 234 |
+
37,2,8,12,0.762,1.7526,tension,-1,medium,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,CIRC_HOLE",1,0.2893218842335046,,1.52,38.10000121593475,233
|
| 235 |
+
37,2,8,12,0.762,1.7526,tension,-1,high,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,CIRC_HOLE",1,0.21699141317512843,,1.52,38.10000121593475,234
|
| 236 |
+
37,2,8,12,0.762,1.7526,bend_bottom,1,low,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,FACE",1,0.3857625123113394,,1.52,38.10000121593475,235
|
| 237 |
+
37,2,8,12,0.762,1.7526,bend_bottom,1,medium,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,FACE",1,0.3857625123113394,,1.52,38.10000121593475,236
|
| 238 |
+
37,2,8,12,0.762,1.7526,bend_bottom,1,high,1.9050000607967377,1.2192000389099122,1,"FACE,FILLET",1,0.3857625123113394,,1.52,38.10000121593475,237
|
| 239 |
+
37,2,8,12,0.762,1.7526,tension,1,low,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,FACE",1,0.5143500164151192,,1.52,38.10000121593475,238
|
| 240 |
+
37,2,8,12,0.762,1.7526,tension,1,medium,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,CIRC_HOLE",1,0.5143500164151192,,1.52,38.10000121593475,239
|
| 241 |
+
37,2,8,12,0.762,1.7526,tension,1,high,1.9050000607967377,1.2192000389099122,1,"CIRC_HOLE,FACE",1,0.5143500164151192,,1.52,38.10000121593475,240
|
| 242 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,-1,low,3.5,2.24,0,"FACE,CIRC_HOLE",1,0.7087500000000001,,2.8,66.674999833107,241
|
| 243 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,-1,medium,3.5,2.24,0,"FACE,CIRC_HOLE",1,0.7087500000000001,,2.8,66.674999833107,242
|
| 244 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,-1,high,3.5,2.24,1,"FILLET,FACE",1,0.7087500000000001,,2.8,66.674999833107,243
|
| 245 |
+
43,6,16,8,0.6985,2.0828,tension,-1,low,3.5,2.24,1,"CIRC_HOLE,CIRC_HOLE",1,0.7087500000000001,,2.8,66.674999833107,244
|
| 246 |
+
43,6,16,8,0.6985,2.0828,tension,-1,medium,3.5,2.24,1,"CIRC_HOLE,CIRC_HOLE",1,0.7087500000000001,,2.8,66.674999833107,245
|
| 247 |
+
43,6,16,8,0.6985,2.0828,tension,-1,high,3.5,2.24,1,"CIRC_HOLE,CIRC_HOLE",1,0.7087500000000001,,2.8,66.674999833107,246
|
| 248 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,1,low,3.5,2.24,0,"CIRC_HOLE,FACE",1,0.7087500000000001,,2.8,66.674999833107,247
|
| 249 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,1,medium,3.5,2.24,0,"FILLET,FACE",1,0.7087500000000001,,2.8,66.674999833107,248
|
| 250 |
+
43,6,16,8,0.6985,2.0828,bend_bottom,1,high,3.5,2.24,1,"FILLET,FACE",1,0.7087500000000001,,2.8,66.674999833107,249
|
| 251 |
+
43,6,16,8,0.6985,2.0828,tension,1,low,3.5,2.24,1,"CIRC_HOLE,CIRC_HOLE",1,0.398671875,,2.8,66.674999833107,250
|
| 252 |
+
43,6,16,8,0.6985,2.0828,tension,1,medium,3.5,2.24,1,"CIRC_HOLE,CIRC_HOLE",1,0.398671875,,2.8,66.674999833107,251
|
| 253 |
+
43,6,16,8,0.6985,2.0828,tension,1,high,3.5,2.24,1,"CIRC_HOLE,FACE",1,0.7087500000000001,,2.8,66.674999833107,252
|
| 254 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,-1,low,4.603749686479569,2.7622498118877417,1,"CIRC_HOLE,FACE",1,1.5537655191868547,,2.8,92.07499372959137,253
|
| 255 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,-1,medium,4.603749686479569,2.5896091986447574,0,"FILLET,FACE",1,1.456655174237676,,2.8,92.07499372959137,254
|
| 256 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,-1,high,4.603749686479569,2.5896091986447574,0,"FILLET,FACE",1,1.456655174237676,,2.8,92.07499372959137,255
|
| 257 |
+
44,6,16,8,0.6985,2.0828,tension,-1,low,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,256
|
| 258 |
+
44,6,16,8,0.6985,2.0828,tension,-1,medium,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,257
|
| 259 |
+
44,6,16,8,0.6985,2.0828,tension,-1,high,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,258
|
| 260 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,1,low,4.603749686479569,2.7622498118877417,0,"CIRC_HOLE,FACE",1,1.165324139390141,,2.8,92.07499372959137,259
|
| 261 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,1,medium,4.603749686479569,2.5896091986447574,0,"FILLET,FACE",1,1.456655174237676,,2.8,92.07499372959137,260
|
| 262 |
+
44,6,16,8,0.6985,2.0828,bend_bottom,1,high,4.603749686479569,2.5896091986447574,0,"FILLET,FACE",1,1.456655174237676,,2.8,92.07499372959137,261
|
| 263 |
+
44,6,16,8,0.6985,2.0828,tension,1,low,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,262
|
| 264 |
+
44,6,16,8,0.6985,2.0828,tension,1,medium,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,263
|
| 265 |
+
44,6,16,8,0.6985,2.0828,tension,1,high,4.603749686479569,2.946399799346924,1,"FACE,CIRC_HOLE",1,1.657349887132645,,2.8,92.07499372959137,264
|
| 266 |
+
50,1,0,25,3.0988,0,bend_bottom,-1,low,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,265
|
| 267 |
+
50,1,0,25,3.0988,0,bend_bottom,-1,medium,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,266
|
| 268 |
+
50,1,0,25,3.0988,0,bend_bottom,-1,high,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,267
|
| 269 |
+
50,1,0,25,3.0988,0,tension,-1,low,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,268
|
| 270 |
+
50,1,0,25,3.0988,0,tension,-1,medium,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,269
|
| 271 |
+
50,1,0,25,3.0988,0,tension,-1,high,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,270
|
| 272 |
+
50,1,0,25,3.0988,0,bend_bottom,1,low,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,271
|
| 273 |
+
50,1,0,25,3.0988,0,bend_bottom,1,medium,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,272
|
| 274 |
+
50,1,0,25,3.0988,0,bend_bottom,1,high,7.75,4.960000000000001,1,"FILLET,FACE",1,1.5693750000000002,,6.2,65.02399444580078,273
|
| 275 |
+
50,1,0,25,3.0988,0,tension,1,low,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,274
|
| 276 |
+
50,1,0,25,3.0988,0,tension,1,medium,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,275
|
| 277 |
+
50,1,0,25,3.0988,0,tension,1,high,7.75,3.26953125,1,"FACE,FACE",1,3.26953125,,6.2,65.02399444580078,276
|
| 278 |
+
51,2,16,12,1.397,2.0828,bend_bottom,-1,low,3.968749839067459,2.539999897003174,1,"FACE,CIRC_HOLE",1,1.071562456548214,,2.8,79.37499678134918,277
|
| 279 |
+
51,2,16,12,1.397,2.0828,bend_bottom,-1,medium,3.968749839067459,2.2324217844754455,1,"FACE,FILLET",1,1.255737253767438,,2.8,79.37499678134918,278
|
| 280 |
+
51,2,16,12,1.397,2.0828,bend_bottom,-1,high,3.968749839067459,2.2324217844754455,1,"FACE,FILLET",1,1.255737253767438,,2.8,79.37499678134918,279
|
| 281 |
+
51,2,16,12,1.397,2.0828,tension,-1,low,3.968749839067459,2.539999897003174,1,"CIRC_HOLE,CIRC_HOLE",1,0.8036718424111605,,2.8,79.37499678134918,280
|
| 282 |
+
51,2,16,12,1.397,2.0828,tension,-1,medium,3.968749839067459,2.539999897003174,0,"CIRC_HOLE,CIRC_HOLE",1,0.4520654113562778,,2.8,79.37499678134918,281
|
| 283 |
+
51,2,16,12,1.397,2.0828,tension,-1,high,3.968749839067459,2.539999897003174,0,"CIRC_HOLE,CIRC_HOLE",1,0.4520654113562778,,2.8,79.37499678134918,282
|
| 284 |
+
51,2,16,12,1.397,2.0828,bend_bottom,1,low,3.968749839067459,2.3812499034404757,0,"FILLET,FACE",1,1.0045898030139506,,2.8,79.37499678134918,283
|
| 285 |
+
51,2,16,12,1.397,2.0828,bend_bottom,1,medium,3.968749839067459,2.2324217844754455,1,"FACE,FILLET",1,1.255737253767438,,2.8,79.37499678134918,284
|
| 286 |
+
51,2,16,12,1.397,2.0828,bend_bottom,1,high,3.968749839067459,2.2324217844754455,1,"FACE,FILLET",1,1.255737253767438,,2.8,79.37499678134918,285
|
| 287 |
+
51,2,16,12,1.397,2.0828,tension,1,low,3.968749839067459,2.539999897003174,1,"CIRC_HOLE,CIRC_HOLE",1,0.8036718424111605,,2.8,79.37499678134918,286
|
| 288 |
+
51,2,16,12,1.397,2.0828,tension,1,medium,3.968749839067459,2.539999897003174,1,"CIRC_HOLE,CIRC_HOLE",1,0.8036718424111605,,2.8,79.37499678134918,287
|
| 289 |
+
51,2,16,12,1.397,2.0828,tension,1,high,3.968749839067459,2.539999897003174,1,"CIRC_HOLE,CIRC_HOLE",1,1.4287499420642855,,2.8,79.37499678134918,288
|
| 290 |
+
52,2,20,12,1.397,2.0828,bend_bottom,-1,low,6.032500296831131,3.0886401519775397,1,"FACE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,289
|
| 291 |
+
52,2,20,12,1.397,2.0828,bend_bottom,-1,medium,6.032500296831131,3.0886401519775397,1,"FACE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,290
|
| 292 |
+
52,2,20,12,1.397,2.0828,bend_bottom,-1,high,6.032500296831131,3.393281416967511,1,"FACE,FACE",1,3.393281416967511,,2.8,120.65000593662262,291
|
| 293 |
+
52,2,20,12,1.397,2.0828,tension,-1,low,6.032500296831131,3.0886401519775397,1,"CIRC_HOLE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,292
|
| 294 |
+
52,2,20,12,1.397,2.0828,tension,-1,medium,6.032500296831131,3.0886401519775397,1,"CIRC_HOLE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,293
|
| 295 |
+
52,2,20,12,1.397,2.0828,tension,-1,high,6.032500296831131,3.0886401519775397,1,"CIRC_HOLE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,294
|
| 296 |
+
52,2,20,12,1.397,2.0828,bend_bottom,1,low,6.032500296831131,3.0886401519775397,1,"FILLET,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,295
|
| 297 |
+
52,2,20,12,1.397,2.0828,bend_bottom,1,medium,6.032500296831131,2.316480113983155,0,"FACE,CIRC_HOLE",1,0.9772650480866434,,2.8,120.65000593662262,296
|
| 298 |
+
52,2,20,12,1.397,2.0828,bend_bottom,1,high,6.032500296831131,3.393281416967511,1,"FACE,FACE",1,3.393281416967511,,2.8,120.65000593662262,297
|
| 299 |
+
52,2,20,12,1.397,2.0828,tension,1,low,6.032500296831131,3.0886401519775397,1,"CIRC_HOLE,CIRC_HOLE",1,1.3030200641155245,,2.8,120.65000593662262,298
|
| 300 |
+
52,2,20,12,1.397,2.0828,tension,1,medium,6.032500296831131,3.0886401519775397,1,"CIRC_HOLE,FACE",1,0.9772650480866434,,2.8,120.65000593662262,299
|
| 301 |
+
52,2,20,12,1.397,2.0828,tension,1,high,6.032500296831131,2.4709121215820318,0,"CIRC_HOLE,CIRC_HOLE",1,0.5863590288519861,,2.8,120.65000593662262,300
|
mesh_service/run_interface.py
ADDED
|
@@ -0,0 +1,1150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## MVP 1 FILE!
|
| 2 |
+
from .step_feature_detection import (
|
| 3 |
+
load_step_model,
|
| 4 |
+
load_step_unit,
|
| 5 |
+
detect_faces,
|
| 6 |
+
detect_circular_holes,
|
| 7 |
+
detect_non_circular_holes,
|
| 8 |
+
detect_fillets,
|
| 9 |
+
mode_thickness,
|
| 10 |
+
detect_all_edges,
|
| 11 |
+
detect_all_faces,
|
| 12 |
+
)
|
| 13 |
+
import os, json
|
| 14 |
+
import pandas as pd
|
| 15 |
+
import gmsh
|
| 16 |
+
from OCP.Bnd import Bnd_Box
|
| 17 |
+
from OCP.BRepBndLib import BRepBndLib
|
| 18 |
+
import joblib
|
| 19 |
+
from OCP.TopExp import TopExp_Explorer
|
| 20 |
+
from OCP.TopAbs import TopAbs_FACE
|
| 21 |
+
from OCP.TopoDS import TopoDS
|
| 22 |
+
import torch
|
| 23 |
+
from torch import nn
|
| 24 |
+
import numpy as np
|
| 25 |
+
import matplotlib.pyplot as plt
|
| 26 |
+
from mpl_toolkits.mplot3d import Axes3D # noqa: F401
|
| 27 |
+
import plotly.graph_objects as go
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# ---------------------------------------------------------------------
|
| 31 |
+
# Matplotlib static PNG plotter (unchanged)
|
| 32 |
+
# ---------------------------------------------------------------------
|
| 33 |
+
def plot_mesh(
|
| 34 |
+
nodes,
|
| 35 |
+
elements,
|
| 36 |
+
title,
|
| 37 |
+
highlight_faces=None,
|
| 38 |
+
face_to_nodes=None,
|
| 39 |
+
save_path="mesh.png",
|
| 40 |
+
):
|
| 41 |
+
"""
|
| 42 |
+
Static 3D line-plot of the surface mesh for PNG export.
|
| 43 |
+
nodes: (N,3)
|
| 44 |
+
elements: list[(etype, conn)], conn is array of 0-based node indices
|
| 45 |
+
"""
|
| 46 |
+
fig = plt.figure(figsize=(7, 7))
|
| 47 |
+
ax = fig.add_subplot(111, projection="3d")
|
| 48 |
+
ax.view_init(elev=20, azim=45)
|
| 49 |
+
|
| 50 |
+
# normal mesh (black)
|
| 51 |
+
for (etype, conn) in elements:
|
| 52 |
+
for e in conn:
|
| 53 |
+
pts = nodes[e][:, :3]
|
| 54 |
+
pts = np.vstack([pts, pts[0]])
|
| 55 |
+
ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], "k-", linewidth=0.4)
|
| 56 |
+
|
| 57 |
+
# optional red overlay for selected faces
|
| 58 |
+
if highlight_faces and face_to_nodes:
|
| 59 |
+
for sf in highlight_faces:
|
| 60 |
+
if sf not in face_to_nodes:
|
| 61 |
+
continue
|
| 62 |
+
face_nodes = face_to_nodes[sf]
|
| 63 |
+
for fn in face_nodes:
|
| 64 |
+
pts = nodes[fn][:, :3]
|
| 65 |
+
pts = np.vstack([pts, pts[0]])
|
| 66 |
+
ax.plot(pts[:, 0], pts[:, 1], pts[:, 2], "r-", linewidth=1.2)
|
| 67 |
+
|
| 68 |
+
ax.set_title(title)
|
| 69 |
+
ax.set_axis_off()
|
| 70 |
+
plt.tight_layout()
|
| 71 |
+
plt.savefig(save_path, dpi=300)
|
| 72 |
+
plt.close()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ---------------------------------------------------------------------
|
| 76 |
+
# Face list helper for GUI (unchanged)
|
| 77 |
+
# ---------------------------------------------------------------------
|
| 78 |
+
def extract_faces_for_gui(step_path):
|
| 79 |
+
"""
|
| 80 |
+
Load the STEP file, detect faces, and return a list of
|
| 81 |
+
label/metadata dicts that the GUI can use.
|
| 82 |
+
"""
|
| 83 |
+
shape = load_step_model(step_path)
|
| 84 |
+
faces = detect_all_faces(shape)
|
| 85 |
+
|
| 86 |
+
face_list = []
|
| 87 |
+
for f in faces:
|
| 88 |
+
center = f["center"]
|
| 89 |
+
normal = f["normal"]
|
| 90 |
+
area = f.get("area", 0.0)
|
| 91 |
+
label = f"Face {f['face_id']} | normal={np.round(normal,3)} | area={area:.2f}"
|
| 92 |
+
|
| 93 |
+
face_list.append(
|
| 94 |
+
{
|
| 95 |
+
"label": label,
|
| 96 |
+
"face_id": f["face_id"],
|
| 97 |
+
"center": list(center),
|
| 98 |
+
"normal": list(normal),
|
| 99 |
+
"area": float(area),
|
| 100 |
+
}
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
return face_list
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# ---------------------------------------------------------------------
|
| 107 |
+
# Helpers for Plotly 3D mesh
|
| 108 |
+
# ---------------------------------------------------------------------
|
| 109 |
+
def mesh_edges_from_triangles(triangles):
|
| 110 |
+
"""
|
| 111 |
+
Given (M,3) triangle connectivity, return unique edges as (E,2).
|
| 112 |
+
"""
|
| 113 |
+
edges = set()
|
| 114 |
+
for tri in triangles:
|
| 115 |
+
a, b, c = tri
|
| 116 |
+
edges.add(tuple(sorted((a, b))))
|
| 117 |
+
edges.add(tuple(sorted((b, c))))
|
| 118 |
+
edges.add(tuple(sorted((c, a))))
|
| 119 |
+
return np.array(list(edges), dtype=int)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def make_plotly_mesh(
|
| 123 |
+
nodes,
|
| 124 |
+
triangles,
|
| 125 |
+
title="Refined Mesh",
|
| 126 |
+
view_mode="wireframe",
|
| 127 |
+
refined_tri_mask=None,
|
| 128 |
+
vertex_refinement_values=None,
|
| 129 |
+
):
|
| 130 |
+
"""
|
| 131 |
+
Build an interactive Plotly figure.
|
| 132 |
+
|
| 133 |
+
Parameters
|
| 134 |
+
----------
|
| 135 |
+
nodes : (N,3) ndarray
|
| 136 |
+
triangles : (M,3) ndarray of 0-based node indices
|
| 137 |
+
view_mode : {"wireframe", "highlight", "heatmap"}
|
| 138 |
+
- "wireframe": light-blue surface + black edges
|
| 139 |
+
- "highlight": base surface + red overlay on refined triangles
|
| 140 |
+
- "heatmap": surface colored by vertex_refinement_values
|
| 141 |
+
refined_tri_mask : (M,) bool array
|
| 142 |
+
True for triangles in locally-refined region (used in "highlight").
|
| 143 |
+
vertex_refinement_values : (N,) array
|
| 144 |
+
Per-node refinement intensity used for "heatmap".
|
| 145 |
+
Higher values → more refined → hotter colors.
|
| 146 |
+
"""
|
| 147 |
+
nodes = np.asarray(nodes)
|
| 148 |
+
triangles = np.asarray(triangles, dtype=int)
|
| 149 |
+
edges = mesh_edges_from_triangles(triangles)
|
| 150 |
+
n_nodes = nodes.shape[0]
|
| 151 |
+
|
| 152 |
+
if refined_tri_mask is None:
|
| 153 |
+
refined_tri_mask = np.zeros(triangles.shape[0], dtype=bool)
|
| 154 |
+
|
| 155 |
+
if vertex_refinement_values is None:
|
| 156 |
+
vertex_refinement_values = np.zeros(n_nodes, dtype=float)
|
| 157 |
+
|
| 158 |
+
fig = go.Figure()
|
| 159 |
+
|
| 160 |
+
# ----------------------------------------
|
| 161 |
+
# Base surface for all modes
|
| 162 |
+
# ----------------------------------------
|
| 163 |
+
if view_mode == "heatmap":
|
| 164 |
+
# Heatmap using per-vertex refinement values
|
| 165 |
+
fig.add_trace(
|
| 166 |
+
go.Mesh3d(
|
| 167 |
+
x=nodes[:, 0],
|
| 168 |
+
y=nodes[:, 1],
|
| 169 |
+
z=nodes[:, 2],
|
| 170 |
+
i=triangles[:, 0],
|
| 171 |
+
j=triangles[:, 1],
|
| 172 |
+
k=triangles[:, 2],
|
| 173 |
+
intensity=vertex_refinement_values,
|
| 174 |
+
colorscale="Turbo",
|
| 175 |
+
showscale=True,
|
| 176 |
+
flatshading=True,
|
| 177 |
+
opacity=0.9,
|
| 178 |
+
showlegend=False,
|
| 179 |
+
name="Local mesh size heatmap",
|
| 180 |
+
)
|
| 181 |
+
)
|
| 182 |
+
else:
|
| 183 |
+
# Simple uniform surface color
|
| 184 |
+
fig.add_trace(
|
| 185 |
+
go.Mesh3d(
|
| 186 |
+
x=nodes[:, 0],
|
| 187 |
+
y=nodes[:, 1],
|
| 188 |
+
z=nodes[:, 2],
|
| 189 |
+
i=triangles[:, 0],
|
| 190 |
+
j=triangles[:, 1],
|
| 191 |
+
k=triangles[:, 2],
|
| 192 |
+
color="lightblue",
|
| 193 |
+
flatshading=True,
|
| 194 |
+
lighting=dict(
|
| 195 |
+
ambient=0.6, diffuse=0.8, specular=0.2, roughness=0.9
|
| 196 |
+
),
|
| 197 |
+
lightposition=dict(x=0, y=0, z=1),
|
| 198 |
+
opacity=0.45 if view_mode == "wireframe" else 0.55,
|
| 199 |
+
showlegend=False,
|
| 200 |
+
showscale=False,
|
| 201 |
+
name="Surface",
|
| 202 |
+
)
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
# ----------------------------------------
|
| 206 |
+
# Highlight overlay (local refinement in red)
|
| 207 |
+
# ----------------------------------------
|
| 208 |
+
if view_mode == "highlight" and refined_tri_mask.any():
|
| 209 |
+
ref_tris = triangles[refined_tri_mask]
|
| 210 |
+
fig.add_trace(
|
| 211 |
+
go.Mesh3d(
|
| 212 |
+
x=nodes[:, 0],
|
| 213 |
+
y=nodes[:, 1],
|
| 214 |
+
z=nodes[:, 2],
|
| 215 |
+
i=ref_tris[:, 0],
|
| 216 |
+
j=ref_tris[:, 1],
|
| 217 |
+
k=ref_tris[:, 2],
|
| 218 |
+
color="red",
|
| 219 |
+
opacity=0.9,
|
| 220 |
+
flatshading=True,
|
| 221 |
+
showscale=False,
|
| 222 |
+
name="Locally refined",
|
| 223 |
+
)
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
# ----------------------------------------
|
| 227 |
+
# Wireframe edges for all modes
|
| 228 |
+
# ----------------------------------------
|
| 229 |
+
edge_width = 2 if view_mode != "heatmap" else 1
|
| 230 |
+
edge_color = "black" if view_mode != "heatmap" else "rgba(0,0,0,0.4)"
|
| 231 |
+
|
| 232 |
+
for e0, e1 in edges:
|
| 233 |
+
fig.add_trace(
|
| 234 |
+
go.Scatter3d(
|
| 235 |
+
x=[nodes[e0, 0], nodes[e1, 0]],
|
| 236 |
+
y=[nodes[e0, 1], nodes[e1, 1]],
|
| 237 |
+
z=[nodes[e0, 2], nodes[e1, 2]],
|
| 238 |
+
mode="lines",
|
| 239 |
+
line=dict(color=edge_color, width=edge_width),
|
| 240 |
+
showlegend=False,
|
| 241 |
+
)
|
| 242 |
+
)
|
| 243 |
+
|
| 244 |
+
fig.update_layout(
|
| 245 |
+
title=title,
|
| 246 |
+
scene=dict(aspectmode="data"),
|
| 247 |
+
margin=dict(l=0, r=0, t=30, b=0),
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
return fig
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
# ---------------------------------------------------------------------
|
| 254 |
+
# Main pipeline
|
| 255 |
+
# ---------------------------------------------------------------------
|
| 256 |
+
def get_result(
|
| 257 |
+
step_path,
|
| 258 |
+
thickness,
|
| 259 |
+
load_face,
|
| 260 |
+
load_direction,
|
| 261 |
+
load_scale,
|
| 262 |
+
selected_face_label=None,
|
| 263 |
+
faces_json=None,
|
| 264 |
+
view_mode="wireframe", # NEW optional flag, default keeps current behavior
|
| 265 |
+
):
|
| 266 |
+
# -----------------------------------------------------------------
|
| 267 |
+
# STEP → JSON features
|
| 268 |
+
# -----------------------------------------------------------------
|
| 269 |
+
def step_to_json(step_path, thickness, part_id=None):
|
| 270 |
+
shape = load_step_model(step_path)
|
| 271 |
+
part_id = part_id or os.path.splitext(os.path.basename(step_path))[0]
|
| 272 |
+
circular_holes = detect_circular_holes(shape)
|
| 273 |
+
fillets = detect_fillets(shape)
|
| 274 |
+
faces = detect_all_faces(shape)
|
| 275 |
+
print(f"Detected {len(faces)} faces")
|
| 276 |
+
|
| 277 |
+
features = {
|
| 278 |
+
"part_id": part_id,
|
| 279 |
+
"thickness": thickness,
|
| 280 |
+
"circular_holes": circular_holes,
|
| 281 |
+
"fillets": fillets,
|
| 282 |
+
"faces": faces,
|
| 283 |
+
}
|
| 284 |
+
out_path = "output/example.json"
|
| 285 |
+
with open(out_path, "w") as f:
|
| 286 |
+
json.dump(features, f, indent=2)
|
| 287 |
+
|
| 288 |
+
step_to_json(step_path, thickness)
|
| 289 |
+
|
| 290 |
+
# -----------------------------------------------------------------
|
| 291 |
+
# Load geometry into gmsh
|
| 292 |
+
# -----------------------------------------------------------------
|
| 293 |
+
gmsh.open(step_path)
|
| 294 |
+
gmsh.model.occ.synchronize()
|
| 295 |
+
|
| 296 |
+
surfaces = gmsh.model.getEntities(dim=2)
|
| 297 |
+
surface_info = []
|
| 298 |
+
for dim, tag in surfaces:
|
| 299 |
+
mass_props = gmsh.model.occ.getMass(dim, tag)
|
| 300 |
+
com = gmsh.model.occ.getCenterOfMass(dim, tag)
|
| 301 |
+
surface_info.append({"tag": tag, "center": com, "area": mass_props})
|
| 302 |
+
|
| 303 |
+
with open("output/example.json") as f:
|
| 304 |
+
features = json.load(f)
|
| 305 |
+
faces = features["faces"]
|
| 306 |
+
|
| 307 |
+
num_fillet = len(features["fillets"])
|
| 308 |
+
num_hole = len(features["circular_holes"])
|
| 309 |
+
num_faces = len(features["faces"])
|
| 310 |
+
num_plane = num_faces - num_fillet - num_hole
|
| 311 |
+
|
| 312 |
+
smallest_fillet_r = min(
|
| 313 |
+
(f["radius"] for f in features["fillets"]), default=0.0
|
| 314 |
+
)
|
| 315 |
+
smallest_hole_r = min(
|
| 316 |
+
(h["radius"] for h in features["circular_holes"]), default=0.0
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
print(
|
| 320 |
+
f"num_fillet: {num_fillet}, num_hole: {num_hole}, num_plane: {num_plane}"
|
| 321 |
+
)
|
| 322 |
+
print(
|
| 323 |
+
f"smallest_fillet_r: {smallest_fillet_r}, smallest_hole_r: {smallest_hole_r}"
|
| 324 |
+
)
|
| 325 |
+
|
| 326 |
+
# -----------------------------------------------------------------
|
| 327 |
+
# Bounding box → max dimension
|
| 328 |
+
# -----------------------------------------------------------------
|
| 329 |
+
def detect_maxsize(shape):
|
| 330 |
+
bbox = Bnd_Box()
|
| 331 |
+
BRepBndLib.Add_s(shape, bbox)
|
| 332 |
+
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
| 333 |
+
size_x = xmax - xmin
|
| 334 |
+
size_y = ymax - ymin
|
| 335 |
+
size_z = zmax - zmin
|
| 336 |
+
maxsize = max(size_x, size_y, size_z)
|
| 337 |
+
return maxsize, (size_x, size_y, size_z)
|
| 338 |
+
|
| 339 |
+
shape = load_step_model(step_path)
|
| 340 |
+
maxsize, (sx, sy, sz) = detect_maxsize(shape)
|
| 341 |
+
print("maxsize:", maxsize)
|
| 342 |
+
|
| 343 |
+
# -----------------------------------------------------------------
|
| 344 |
+
# Heuristic face roles (bottom/left/front/back)
|
| 345 |
+
# -----------------------------------------------------------------
|
| 346 |
+
def find_the_faces(step_path):
|
| 347 |
+
shape = load_step_model(step_path)
|
| 348 |
+
faces_local = detect_all_faces(shape)
|
| 349 |
+
z_vals = [f["center"][2] for f in faces_local]
|
| 350 |
+
x_vals = [f["center"][0] for f in faces_local]
|
| 351 |
+
y_vals = [f["center"][1] for f in faces_local]
|
| 352 |
+
|
| 353 |
+
idx_bottom = z_vals.index(min(z_vals))
|
| 354 |
+
idx_left = x_vals.index(min(x_vals))
|
| 355 |
+
idx_front = y_vals.index(min(y_vals))
|
| 356 |
+
idx_back = y_vals.index(max(y_vals))
|
| 357 |
+
|
| 358 |
+
return {
|
| 359 |
+
"bottom": faces_local[idx_bottom],
|
| 360 |
+
"left": faces_local[idx_left],
|
| 361 |
+
"front": faces_local[idx_front],
|
| 362 |
+
"back": faces_local[idx_back],
|
| 363 |
+
"bottom_idx": idx_bottom,
|
| 364 |
+
"left_idx": idx_left,
|
| 365 |
+
"front_idx": idx_front,
|
| 366 |
+
"back_idx": idx_back,
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
face_dict = find_the_faces(step_path)
|
| 370 |
+
|
| 371 |
+
part_row = {
|
| 372 |
+
"num_fillet": num_fillet,
|
| 373 |
+
"num_hole": num_hole,
|
| 374 |
+
"num_plane": num_plane,
|
| 375 |
+
"smallest_fillet_r": smallest_fillet_r,
|
| 376 |
+
"smallest_hole_r": smallest_hole_r,
|
| 377 |
+
"maxsize": maxsize,
|
| 378 |
+
"thickness": thickness,
|
| 379 |
+
"load_face": load_face,
|
| 380 |
+
"load_direction": load_direction,
|
| 381 |
+
"load_scale": load_scale,
|
| 382 |
+
}
|
| 383 |
+
df_part = pd.DataFrame([part_row])
|
| 384 |
+
|
| 385 |
+
# classify face types
|
| 386 |
+
face_type_dict = {}
|
| 387 |
+
for hole in features["circular_holes"]:
|
| 388 |
+
for f in hole["faces"]:
|
| 389 |
+
face_type_dict[f["face_id"]] = "CIRC_HOLE"
|
| 390 |
+
for fillet in features["fillets"]:
|
| 391 |
+
for f in fillet["faces"]:
|
| 392 |
+
face_type_dict[f["face_id"]] = "FILLET"
|
| 393 |
+
for f in features["faces"]:
|
| 394 |
+
if f["face_id"] not in face_type_dict:
|
| 395 |
+
face_type_dict[f["face_id"]] = "FACE"
|
| 396 |
+
|
| 397 |
+
# -----------------------------------------------------------------
|
| 398 |
+
# Choose load/fixed faces (GUI-aware)
|
| 399 |
+
# -----------------------------------------------------------------
|
| 400 |
+
load_face_data = None
|
| 401 |
+
fix_face_data = None
|
| 402 |
+
|
| 403 |
+
if selected_face_label is not None and faces_json is not None and faces_json != "":
|
| 404 |
+
faces_from_gui = json.loads(faces_json)
|
| 405 |
+
label_to_face = {f["label"]: f for f in faces_from_gui}
|
| 406 |
+
|
| 407 |
+
if selected_face_label not in label_to_face:
|
| 408 |
+
raise ValueError(
|
| 409 |
+
f"Selected load face '{selected_face_label}' not found in faces_json."
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
load_face_data = label_to_face[selected_face_label]
|
| 413 |
+
|
| 414 |
+
load_center = np.array(load_face_data["center"], dtype=float)
|
| 415 |
+
all_faces_gui = list(label_to_face.values())
|
| 416 |
+
distances = [
|
| 417 |
+
np.linalg.norm(np.array(f["center"], dtype=float) - load_center)
|
| 418 |
+
for f in all_faces_gui
|
| 419 |
+
]
|
| 420 |
+
fix_face_data = all_faces_gui[int(np.argmax(distances))]
|
| 421 |
+
else:
|
| 422 |
+
if load_face == "bend_bottom":
|
| 423 |
+
load_face_data = face_dict["bottom"]
|
| 424 |
+
fix_face_data = face_dict["left"]
|
| 425 |
+
elif load_face == "tension":
|
| 426 |
+
load_face_data = face_dict["back"]
|
| 427 |
+
fix_face_data = face_dict["front"]
|
| 428 |
+
else:
|
| 429 |
+
raise ValueError(
|
| 430 |
+
f"No GUI selection and unsupported load_face value '{load_face}'."
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
load_face_center = np.array(load_face_data["center"])
|
| 434 |
+
load_face_normal = np.array(load_face_data["normal"])
|
| 435 |
+
fix_face_center = np.array(fix_face_data["center"])
|
| 436 |
+
fix_face_normal = np.array(fix_face_data["normal"])
|
| 437 |
+
|
| 438 |
+
# -----------------------------------------------------------------
|
| 439 |
+
# BBox per CAD face
|
| 440 |
+
# -----------------------------------------------------------------
|
| 441 |
+
def get_face_bbox_dict(shape):
|
| 442 |
+
bbox_dict = {}
|
| 443 |
+
exp = TopExp_Explorer(shape, TopAbs_FACE)
|
| 444 |
+
face_id = 0
|
| 445 |
+
while exp.More():
|
| 446 |
+
face_shape = exp.Current()
|
| 447 |
+
face = TopoDS.Face_s(face_shape)
|
| 448 |
+
bbox = Bnd_Box()
|
| 449 |
+
BRepBndLib.Add_s(face, bbox)
|
| 450 |
+
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
| 451 |
+
bbox_dx = xmax - xmin
|
| 452 |
+
bbox_dy = ymax - ymin
|
| 453 |
+
bbox_dz = zmax - zmin
|
| 454 |
+
bbox_dict[face_id] = (bbox_dx, bbox_dy, bbox_dz)
|
| 455 |
+
face_id += 1
|
| 456 |
+
exp.Next()
|
| 457 |
+
return bbox_dict
|
| 458 |
+
|
| 459 |
+
shape = load_step_model(step_path)
|
| 460 |
+
bbox_dict = get_face_bbox_dict(shape)
|
| 461 |
+
|
| 462 |
+
# -----------------------------------------------------------------
|
| 463 |
+
# Build per-face feature table
|
| 464 |
+
# -----------------------------------------------------------------
|
| 465 |
+
rows1 = []
|
| 466 |
+
for f in faces:
|
| 467 |
+
row = {}
|
| 468 |
+
row["face_id"] = f["face_id"]
|
| 469 |
+
row["ftype"] = face_type_dict[f["face_id"]]
|
| 470 |
+
row["cx"], row["cy"], row["cz"] = f["center"]
|
| 471 |
+
row["nx"], row["ny"], row["nz"] = f["normal"]
|
| 472 |
+
row["area"] = f.get("area")
|
| 473 |
+
if f["face_id"] == load_face_data["face_id"]:
|
| 474 |
+
row["role"] = "load"
|
| 475 |
+
elif f["face_id"] == fix_face_data["face_id"]:
|
| 476 |
+
row["role"] = "fixed"
|
| 477 |
+
else:
|
| 478 |
+
row["role"] = "free"
|
| 479 |
+
dx, dy, dz = bbox_dict[f["face_id"]]
|
| 480 |
+
row["bbox_dx"] = dx
|
| 481 |
+
row["bbox_dy"] = dy
|
| 482 |
+
row["bbox_dz"] = dz
|
| 483 |
+
|
| 484 |
+
fc = np.array(f["center"])
|
| 485 |
+
row["dist_load"] = np.linalg.norm(fc - np.array(load_face_center))
|
| 486 |
+
row["nlx"], row["nly"], row["nlz"] = load_face_normal
|
| 487 |
+
row["dist_fixed"] = np.linalg.norm(fc - np.array(fix_face_center))
|
| 488 |
+
row["nfx"], row["nfy"], row["nfz"] = fix_face_normal
|
| 489 |
+
|
| 490 |
+
row["load_face"] = load_face
|
| 491 |
+
row["load_direction"] = load_direction
|
| 492 |
+
row["load_scale"] = load_scale
|
| 493 |
+
row["maxsize"] = maxsize
|
| 494 |
+
row["thickness"] = thickness
|
| 495 |
+
rows1.append(row)
|
| 496 |
+
df_feature = pd.DataFrame(rows1)
|
| 497 |
+
|
| 498 |
+
df_feature = df_feature.fillna(0.0)
|
| 499 |
+
df_part = df_part.fillna(0.0)
|
| 500 |
+
|
| 501 |
+
# -----------------------------------------------------------------
|
| 502 |
+
# Model 1: refine_needed classifier
|
| 503 |
+
# -----------------------------------------------------------------
|
| 504 |
+
class RefineClassifier(nn.Module):
|
| 505 |
+
def __init__(self, in_dim):
|
| 506 |
+
super().__init__()
|
| 507 |
+
self.encoder = nn.Sequential(
|
| 508 |
+
nn.Linear(in_dim, 256),
|
| 509 |
+
nn.BatchNorm1d(256),
|
| 510 |
+
nn.ReLU(),
|
| 511 |
+
nn.Dropout(0.2),
|
| 512 |
+
nn.Linear(256, 128),
|
| 513 |
+
nn.BatchNorm1d(128),
|
| 514 |
+
nn.ReLU(),
|
| 515 |
+
nn.Dropout(0.1),
|
| 516 |
+
nn.Linear(128, 64),
|
| 517 |
+
nn.ReLU(),
|
| 518 |
+
)
|
| 519 |
+
self.head = nn.Linear(64, 1)
|
| 520 |
+
|
| 521 |
+
def forward(self, x):
|
| 522 |
+
h = self.encoder(x)
|
| 523 |
+
return self.head(h)
|
| 524 |
+
|
| 525 |
+
preprocessor1 = joblib.load("saved_model/preprocessor1.pkl")
|
| 526 |
+
feature_cols1 = [
|
| 527 |
+
c
|
| 528 |
+
for c in df_feature.columns
|
| 529 |
+
if c
|
| 530 |
+
not in [
|
| 531 |
+
"part_id",
|
| 532 |
+
"refine_needed",
|
| 533 |
+
"converged",
|
| 534 |
+
"local_size",
|
| 535 |
+
"global_size_initial",
|
| 536 |
+
"global_size_final",
|
| 537 |
+
"load_id",
|
| 538 |
+
]
|
| 539 |
+
]
|
| 540 |
+
X1 = preprocessor1.transform(df_feature[feature_cols1])
|
| 541 |
+
X1_t = torch.tensor(
|
| 542 |
+
X1.toarray() if hasattr(X1, "toarray") else X1, dtype=torch.float32
|
| 543 |
+
)
|
| 544 |
+
|
| 545 |
+
model1 = RefineClassifier(X1_t.shape[1])
|
| 546 |
+
state_dict = torch.load("saved_model/model1.pt", map_location="cpu")
|
| 547 |
+
model1.load_state_dict(state_dict)
|
| 548 |
+
model1.eval()
|
| 549 |
+
|
| 550 |
+
with torch.no_grad():
|
| 551 |
+
logits1 = model1(X1_t)
|
| 552 |
+
probs1 = torch.sigmoid(logits1).cpu().numpy().flatten()
|
| 553 |
+
df_feature["refine_prob"] = probs1
|
| 554 |
+
|
| 555 |
+
top2 = df_feature.sort_values("refine_prob", ascending=False).head(2)
|
| 556 |
+
df_feature.drop(columns=["refine_prob"], inplace=True)
|
| 557 |
+
|
| 558 |
+
# -----------------------------------------------------------------
|
| 559 |
+
# Model 2: part-level convergence
|
| 560 |
+
# -----------------------------------------------------------------
|
| 561 |
+
preprocessor2 = joblib.load("saved_model/preprocessor2.pkl")
|
| 562 |
+
|
| 563 |
+
feature_cols = [
|
| 564 |
+
col
|
| 565 |
+
for col in df_feature.columns
|
| 566 |
+
if col
|
| 567 |
+
not in [
|
| 568 |
+
"part_id",
|
| 569 |
+
"load_id",
|
| 570 |
+
"refine_needed",
|
| 571 |
+
"converged",
|
| 572 |
+
"local_size",
|
| 573 |
+
"global_size_initial",
|
| 574 |
+
"global_size_final",
|
| 575 |
+
]
|
| 576 |
+
]
|
| 577 |
+
part_level_cols = [
|
| 578 |
+
col
|
| 579 |
+
for col in df_part.columns
|
| 580 |
+
if col
|
| 581 |
+
not in [
|
| 582 |
+
"part_id",
|
| 583 |
+
"converged",
|
| 584 |
+
"load_id",
|
| 585 |
+
"top2_features",
|
| 586 |
+
"global_size_initial",
|
| 587 |
+
"global_size_final",
|
| 588 |
+
"local_refine_needed",
|
| 589 |
+
"local_size",
|
| 590 |
+
]
|
| 591 |
+
]
|
| 592 |
+
|
| 593 |
+
def extract_supervised_features_model2():
|
| 594 |
+
part_row_local = df_part.iloc[0]
|
| 595 |
+
feat_rows = df_feature
|
| 596 |
+
|
| 597 |
+
# fixed
|
| 598 |
+
fixed_row = feat_rows[feat_rows.role == "fixed"]
|
| 599 |
+
if len(fixed_row):
|
| 600 |
+
X_fixed = preprocessor1.transform(fixed_row[feature_cols])
|
| 601 |
+
with torch.no_grad():
|
| 602 |
+
fixed_prob = torch.sigmoid(
|
| 603 |
+
model1(torch.tensor(X_fixed, dtype=torch.float32))
|
| 604 |
+
).item()
|
| 605 |
+
fixed_feats = list(fixed_row.iloc[0][feature_cols]) + [fixed_prob]
|
| 606 |
+
else:
|
| 607 |
+
fixed_feats = [0] * (len(feature_cols) + 1)
|
| 608 |
+
|
| 609 |
+
# load
|
| 610 |
+
load_row = feat_rows[feat_rows.role == "load"]
|
| 611 |
+
if len(load_row):
|
| 612 |
+
X_load = preprocessor1.transform(load_row[feature_cols])
|
| 613 |
+
with torch.no_grad():
|
| 614 |
+
load_prob = torch.sigmoid(
|
| 615 |
+
model1(torch.tensor(X_load, dtype=torch.float32))
|
| 616 |
+
).item()
|
| 617 |
+
load_feats = list(load_row.iloc[0][feature_cols]) + [load_prob]
|
| 618 |
+
else:
|
| 619 |
+
load_feats = [0] * (len(feature_cols) + 1)
|
| 620 |
+
|
| 621 |
+
# free
|
| 622 |
+
free_rows = feat_rows[feat_rows.role == "free"]
|
| 623 |
+
if len(free_rows):
|
| 624 |
+
X_free = preprocessor1.transform(free_rows[feature_cols])
|
| 625 |
+
with torch.no_grad():
|
| 626 |
+
free_probs = torch.sigmoid(
|
| 627 |
+
model1(torch.tensor(X_free, dtype=torch.float32))
|
| 628 |
+
).cpu().numpy().flatten()
|
| 629 |
+
best_idx = np.argmax(free_probs)
|
| 630 |
+
free_feats = (
|
| 631 |
+
list(free_rows.iloc[best_idx][feature_cols])
|
| 632 |
+
+ [free_probs[best_idx]]
|
| 633 |
+
)
|
| 634 |
+
else:
|
| 635 |
+
free_feats = [0] * (len(feature_cols) + 1)
|
| 636 |
+
|
| 637 |
+
global_feats = list(part_row_local[part_level_cols])
|
| 638 |
+
return fixed_feats + load_feats + free_feats + global_feats
|
| 639 |
+
|
| 640 |
+
col_names = (
|
| 641 |
+
[f"fixed_{c}" for c in feature_cols]
|
| 642 |
+
+ ["fixed_refine_prob"]
|
| 643 |
+
+ [f"load_{c}" for c in feature_cols]
|
| 644 |
+
+ ["load_refine_prob"]
|
| 645 |
+
+ [f"free_{c}" for c in feature_cols]
|
| 646 |
+
+ ["free_refine_prob"]
|
| 647 |
+
+ part_level_cols
|
| 648 |
+
)
|
| 649 |
+
|
| 650 |
+
row = extract_supervised_features_model2()
|
| 651 |
+
df_sup = pd.DataFrame([row], columns=col_names)
|
| 652 |
+
|
| 653 |
+
missing_cols = set(preprocessor2.feature_names_in_) - set(df_sup.columns)
|
| 654 |
+
for c in missing_cols:
|
| 655 |
+
df_sup[c] = 0.0
|
| 656 |
+
df_sup = df_sup[preprocessor2.feature_names_in_]
|
| 657 |
+
|
| 658 |
+
X2 = preprocessor2.transform(df_sup)
|
| 659 |
+
print("X2 contains NaN?", np.isnan(X2).any())
|
| 660 |
+
X2_t = torch.tensor(
|
| 661 |
+
X2.toarray() if hasattr(X2, "toarray") else X2, dtype=torch.float32
|
| 662 |
+
)
|
| 663 |
+
|
| 664 |
+
class ConvergeClassifier(nn.Module):
|
| 665 |
+
def __init__(self, in_dim):
|
| 666 |
+
super().__init__()
|
| 667 |
+
self.encoder = nn.Sequential(
|
| 668 |
+
nn.Linear(in_dim, 64),
|
| 669 |
+
nn.BatchNorm1d(64),
|
| 670 |
+
nn.ReLU(),
|
| 671 |
+
nn.Dropout(0.2),
|
| 672 |
+
nn.Linear(64, 32),
|
| 673 |
+
nn.ReLU(),
|
| 674 |
+
)
|
| 675 |
+
self.head = nn.Linear(32, 2)
|
| 676 |
+
|
| 677 |
+
def forward(self, x):
|
| 678 |
+
h = self.encoder(x)
|
| 679 |
+
return self.head(h)
|
| 680 |
+
|
| 681 |
+
model2 = ConvergeClassifier(X2_t.shape[1])
|
| 682 |
+
state_dict = torch.load("saved_model/model2.pt", map_location="cpu")
|
| 683 |
+
model2.load_state_dict(state_dict)
|
| 684 |
+
model2.eval()
|
| 685 |
+
|
| 686 |
+
with torch.no_grad():
|
| 687 |
+
logits2 = model2(X2_t)
|
| 688 |
+
probs2 = torch.softmax(logits2, dim=1).cpu().numpy()
|
| 689 |
+
preds2 = probs2.argmax(1)
|
| 690 |
+
|
| 691 |
+
print("Model 2 (part-level converge) predictions:")
|
| 692 |
+
print("Probability not converged:", probs2[:, 0])
|
| 693 |
+
print("Probability converged:", probs2[:, 1])
|
| 694 |
+
print("Predicted class (0: unconverged, 1: converged):", preds2)
|
| 695 |
+
|
| 696 |
+
# -----------------------------------------------------------------
|
| 697 |
+
# Model 3: local size regressor
|
| 698 |
+
# -----------------------------------------------------------------
|
| 699 |
+
class LocalRefineRegressor(nn.Module):
|
| 700 |
+
def __init__(self, in_dim):
|
| 701 |
+
super().__init__()
|
| 702 |
+
self.encoder = nn.Sequential(
|
| 703 |
+
nn.Linear(in_dim, 256),
|
| 704 |
+
nn.BatchNorm1d(256),
|
| 705 |
+
nn.ReLU(),
|
| 706 |
+
nn.Dropout(0.2),
|
| 707 |
+
nn.Linear(256, 128),
|
| 708 |
+
nn.BatchNorm1d(128),
|
| 709 |
+
nn.ReLU(),
|
| 710 |
+
nn.Dropout(0.1),
|
| 711 |
+
nn.Linear(128, 64),
|
| 712 |
+
nn.ReLU(),
|
| 713 |
+
)
|
| 714 |
+
self.head = nn.Linear(64, 1)
|
| 715 |
+
|
| 716 |
+
def forward(self, x):
|
| 717 |
+
h = self.encoder(x)
|
| 718 |
+
return self.head(h)
|
| 719 |
+
|
| 720 |
+
preprocessor3 = joblib.load("saved_model/preprocessor3.pkl")
|
| 721 |
+
feature_cols3 = [
|
| 722 |
+
c
|
| 723 |
+
for c in df_feature.columns
|
| 724 |
+
if c
|
| 725 |
+
not in [
|
| 726 |
+
"part_id",
|
| 727 |
+
"refine_needed",
|
| 728 |
+
"converged",
|
| 729 |
+
"local_size",
|
| 730 |
+
"global_size_initial",
|
| 731 |
+
"global_size_final",
|
| 732 |
+
"load_id",
|
| 733 |
+
]
|
| 734 |
+
]
|
| 735 |
+
X3 = preprocessor3.transform(df_feature[feature_cols3])
|
| 736 |
+
X3_t = torch.tensor(
|
| 737 |
+
X3.toarray() if hasattr(X3, "toarray") else X3, dtype=torch.float32
|
| 738 |
+
)
|
| 739 |
+
|
| 740 |
+
model3 = LocalRefineRegressor(X3_t.shape[1])
|
| 741 |
+
state_dict = torch.load("saved_model/model3.pt", map_location="cpu")
|
| 742 |
+
model3.load_state_dict(state_dict)
|
| 743 |
+
model3.eval()
|
| 744 |
+
|
| 745 |
+
with torch.no_grad():
|
| 746 |
+
logits1 = model1(X1_t)
|
| 747 |
+
probs1 = torch.sigmoid(logits1).cpu().numpy().flatten()
|
| 748 |
+
df_feature["refine_prob"] = probs1
|
| 749 |
+
|
| 750 |
+
top2 = df_feature.sort_values("refine_prob", ascending=False).head(2)
|
| 751 |
+
|
| 752 |
+
with torch.no_grad():
|
| 753 |
+
local_size_pred = model3(X3_t).cpu().numpy().flatten()
|
| 754 |
+
df_feature["predicted_local_size"] = local_size_pred
|
| 755 |
+
|
| 756 |
+
top2_with_local_size = top2.copy()
|
| 757 |
+
top2_with_local_size["predicted_local_size"] = df_feature.loc[
|
| 758 |
+
top2.index, "predicted_local_size"
|
| 759 |
+
]
|
| 760 |
+
|
| 761 |
+
print("Top-2 faces needing refinement and their predicted local sizes:")
|
| 762 |
+
print(
|
| 763 |
+
top2_with_local_size[
|
| 764 |
+
["face_id", "ftype", "predicted_local_size"]
|
| 765 |
+
].to_string(index=False)
|
| 766 |
+
)
|
| 767 |
+
df_feature.drop(columns=["refine_prob", "predicted_local_size"], inplace=True)
|
| 768 |
+
|
| 769 |
+
# -----------------------------------------------------------------
|
| 770 |
+
# Model 4: global size regressor
|
| 771 |
+
# -----------------------------------------------------------------
|
| 772 |
+
preprocessor4 = joblib.load("saved_model/preprocessor4.pkl")
|
| 773 |
+
|
| 774 |
+
feature_cols = [
|
| 775 |
+
col
|
| 776 |
+
for col in df_feature.columns
|
| 777 |
+
if col
|
| 778 |
+
not in [
|
| 779 |
+
"part_id",
|
| 780 |
+
"load_id",
|
| 781 |
+
"refine_needed",
|
| 782 |
+
"converged",
|
| 783 |
+
"local_size",
|
| 784 |
+
"global_size_initial",
|
| 785 |
+
"global_size_final",
|
| 786 |
+
]
|
| 787 |
+
]
|
| 788 |
+
part_level_cols = [
|
| 789 |
+
col
|
| 790 |
+
for col in df_part.columns
|
| 791 |
+
if col
|
| 792 |
+
not in [
|
| 793 |
+
"part_id",
|
| 794 |
+
"converged",
|
| 795 |
+
"load_id",
|
| 796 |
+
"top2_features",
|
| 797 |
+
"global_size_initial",
|
| 798 |
+
"global_size_final",
|
| 799 |
+
"local_refine_needed",
|
| 800 |
+
"local_size",
|
| 801 |
+
]
|
| 802 |
+
]
|
| 803 |
+
|
| 804 |
+
def extract_supervised_features_model4():
|
| 805 |
+
part_row_local = df_part.iloc[0]
|
| 806 |
+
feat_rows = df_feature
|
| 807 |
+
|
| 808 |
+
fixed_row = feat_rows[feat_rows.role == "fixed"]
|
| 809 |
+
if len(fixed_row):
|
| 810 |
+
X_fixed = preprocessor1.transform(fixed_row[feature_cols])
|
| 811 |
+
with torch.no_grad():
|
| 812 |
+
fixed_prob = torch.sigmoid(
|
| 813 |
+
model1(torch.tensor(X_fixed, dtype=torch.float32))
|
| 814 |
+
).item()
|
| 815 |
+
fixed_feats = list(fixed_row.iloc[0][feature_cols]) + [fixed_prob]
|
| 816 |
+
else:
|
| 817 |
+
fixed_feats = [0] * (len(feature_cols) + 1)
|
| 818 |
+
|
| 819 |
+
load_row = feat_rows[feat_rows.role == "load"]
|
| 820 |
+
if len(load_row):
|
| 821 |
+
X_load = preprocessor1.transform(load_row[feature_cols])
|
| 822 |
+
with torch.no_grad():
|
| 823 |
+
load_prob = torch.sigmoid(
|
| 824 |
+
model1(torch.tensor(X_load, dtype=torch.float32))
|
| 825 |
+
).item()
|
| 826 |
+
load_feats = list(load_row.iloc[0][feature_cols]) + [load_prob]
|
| 827 |
+
else:
|
| 828 |
+
load_feats = [0] * (len(feature_cols) + 1)
|
| 829 |
+
|
| 830 |
+
free_rows = feat_rows[feat_rows.role == "free"]
|
| 831 |
+
if len(free_rows):
|
| 832 |
+
X_free = preprocessor1.transform(free_rows[feature_cols])
|
| 833 |
+
with torch.no_grad():
|
| 834 |
+
free_probs = torch.sigmoid(
|
| 835 |
+
model1(torch.tensor(X_free, dtype=torch.float32))
|
| 836 |
+
).cpu().numpy().flatten()
|
| 837 |
+
best_idx = np.argmax(free_probs)
|
| 838 |
+
free_feats = (
|
| 839 |
+
list(free_rows.iloc[best_idx][feature_cols])
|
| 840 |
+
+ [free_probs[best_idx]]
|
| 841 |
+
)
|
| 842 |
+
else:
|
| 843 |
+
free_feats = [0] * (len(feature_cols) + 1)
|
| 844 |
+
|
| 845 |
+
global_feats = list(part_row_local[part_level_cols])
|
| 846 |
+
return fixed_feats + load_feats + free_feats + global_feats
|
| 847 |
+
|
| 848 |
+
col_names = (
|
| 849 |
+
[f"fixed_{c}" for c in feature_cols]
|
| 850 |
+
+ ["fixed_refine_prob"]
|
| 851 |
+
+ [f"load_{c}" for c in feature_cols]
|
| 852 |
+
+ ["load_refine_prob"]
|
| 853 |
+
+ [f"free_{c}" for c in feature_cols]
|
| 854 |
+
+ ["free_refine_prob"]
|
| 855 |
+
+ part_level_cols
|
| 856 |
+
)
|
| 857 |
+
row = extract_supervised_features_model4()
|
| 858 |
+
df_sup = pd.DataFrame([row], columns=col_names)
|
| 859 |
+
|
| 860 |
+
missing_cols = set(preprocessor4.feature_names_in_) - set(df_sup.columns)
|
| 861 |
+
for c in missing_cols:
|
| 862 |
+
df_sup[c] = 0.0
|
| 863 |
+
df_sup = df_sup[preprocessor4.feature_names_in_]
|
| 864 |
+
|
| 865 |
+
X4 = preprocessor4.transform(df_sup)
|
| 866 |
+
X4_t = torch.tensor(
|
| 867 |
+
X4.toarray() if hasattr(X4, "toarray") else X4, dtype=torch.float32
|
| 868 |
+
)
|
| 869 |
+
|
| 870 |
+
class GlobalSizeRegressor(nn.Module):
|
| 871 |
+
def __init__(self, in_dim):
|
| 872 |
+
super().__init__()
|
| 873 |
+
self.encoder = nn.Sequential(
|
| 874 |
+
nn.Linear(in_dim, 64),
|
| 875 |
+
nn.BatchNorm1d(64),
|
| 876 |
+
nn.ReLU(),
|
| 877 |
+
nn.Dropout(0.2),
|
| 878 |
+
nn.Linear(64, 32),
|
| 879 |
+
nn.ReLU(),
|
| 880 |
+
)
|
| 881 |
+
self.head = nn.Linear(32, 1)
|
| 882 |
+
|
| 883 |
+
def forward(self, x):
|
| 884 |
+
h = self.encoder(x)
|
| 885 |
+
return self.head(h)
|
| 886 |
+
|
| 887 |
+
model4 = GlobalSizeRegressor(X4_t.shape[1])
|
| 888 |
+
state_dict = torch.load("saved_model/model4.pt", map_location="cpu")
|
| 889 |
+
model4.load_state_dict(state_dict)
|
| 890 |
+
model4.eval()
|
| 891 |
+
|
| 892 |
+
with torch.no_grad():
|
| 893 |
+
pred4 = model4(X4_t).item()
|
| 894 |
+
|
| 895 |
+
print("Model 4 (part-level global_size) prediction:")
|
| 896 |
+
print(f"Predicted global_size_final: {pred4:.4f}")
|
| 897 |
+
|
| 898 |
+
# -----------------------------------------------------------------
|
| 899 |
+
# Human-readable sentence
|
| 900 |
+
# -----------------------------------------------------------------
|
| 901 |
+
prob_converged = probs2[0, 1]
|
| 902 |
+
pred_class = preds2[0]
|
| 903 |
+
global_size = pred4
|
| 904 |
+
face1, face2 = top2_with_local_size.itertuples(index=False)
|
| 905 |
+
|
| 906 |
+
if prob_converged > 0.7:
|
| 907 |
+
converge_text = "very likely to converge"
|
| 908 |
+
elif prob_converged > 0.45:
|
| 909 |
+
converge_text = "likely to converge"
|
| 910 |
+
else:
|
| 911 |
+
converge_text = "unlikely to converge"
|
| 912 |
+
|
| 913 |
+
sentence = (
|
| 914 |
+
f"The model predicts that the simulation is {converge_text} "
|
| 915 |
+
f"(confidence: {prob_converged:.2%}). "
|
| 916 |
+
f"To improve accuracy, local refinement will be applied on a {face1.ftype.lower()} (ID {face1.face_id}) "
|
| 917 |
+
f"and a {face2.ftype.lower()} (ID {face2.face_id}), "
|
| 918 |
+
f"with predicted mesh sizes {face1.predicted_local_size:.3f} mm and {face2.predicted_local_size:.3f} mm respectively. "
|
| 919 |
+
f"The overall global mesh size is set to {global_size:.3f} mm."
|
| 920 |
+
)
|
| 921 |
+
print(sentence)
|
| 922 |
+
|
| 923 |
+
# -----------------------------------------------------------------
|
| 924 |
+
# Baseline global-only mesh (for PNG)
|
| 925 |
+
# -----------------------------------------------------------------
|
| 926 |
+
gmsh.model.mesh.clear()
|
| 927 |
+
gmsh.option.setNumber("Mesh.Optimize", 1)
|
| 928 |
+
gmsh.option.setNumber("Mesh.OptimizeNetgen", 1)
|
| 929 |
+
gmsh.option.setNumber("Mesh.Smoothing", 10)
|
| 930 |
+
gmsh.option.setNumber("Mesh.Algorithm", 5)
|
| 931 |
+
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
|
| 932 |
+
gmsh.option.setNumber("Mesh.MeshSizeFromPoints", 0)
|
| 933 |
+
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", 0)
|
| 934 |
+
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", float(global_size))
|
| 935 |
+
|
| 936 |
+
gmsh.model.mesh.generate(2)
|
| 937 |
+
node_tags_b, node_coords_b, _ = gmsh.model.mesh.getNodes()
|
| 938 |
+
nodes_b = node_coords_b.reshape(-1, 3)
|
| 939 |
+
|
| 940 |
+
types_b, elem_tags_list_b, node_tags_list_b = gmsh.model.mesh.getElements(
|
| 941 |
+
dim=2
|
| 942 |
+
)
|
| 943 |
+
|
| 944 |
+
elements_b = []
|
| 945 |
+
for etype, ntags in zip(types_b, node_tags_list_b):
|
| 946 |
+
name, dim, order, nnode, *_ = gmsh.model.mesh.getElementProperties(etype)
|
| 947 |
+
if nnode in (3, 4):
|
| 948 |
+
conn = ntags.reshape(-1, nnode).astype(int) - 1
|
| 949 |
+
elements_b.append((etype, conn))
|
| 950 |
+
|
| 951 |
+
plot_mesh(
|
| 952 |
+
nodes_b,
|
| 953 |
+
elements_b,
|
| 954 |
+
title="Baseline Mesh (Global Only)",
|
| 955 |
+
highlight_faces=None,
|
| 956 |
+
face_to_nodes=None,
|
| 957 |
+
save_path="output/mesh_global_only.png",
|
| 958 |
+
)
|
| 959 |
+
|
| 960 |
+
# -----------------------------------------------------------------
|
| 961 |
+
# Refined mesh with background field
|
| 962 |
+
# -----------------------------------------------------------------
|
| 963 |
+
gmsh.model.mesh.clear()
|
| 964 |
+
gmsh.option.setNumber("Mesh.Optimize", 1)
|
| 965 |
+
gmsh.option.setNumber("Mesh.OptimizeNetgen", 1)
|
| 966 |
+
gmsh.option.setNumber("Mesh.Smoothing", 10)
|
| 967 |
+
gmsh.option.setNumber("Mesh.Algorithm", 5)
|
| 968 |
+
gmsh.option.setNumber("Mesh.MeshSizeFromCurvature", 0)
|
| 969 |
+
|
| 970 |
+
gmsh.option.setNumber("Mesh.CharacteristicLengthMin", global_size / 4)
|
| 971 |
+
gmsh.option.setNumber("Mesh.CharacteristicLengthMax", float(global_size))
|
| 972 |
+
|
| 973 |
+
face_ids = top2_with_local_size["face_id"].tolist()
|
| 974 |
+
local_sizes = top2_with_local_size["predicted_local_size"].round(3).tolist()
|
| 975 |
+
|
| 976 |
+
# Collect curves for distance
|
| 977 |
+
curve_ids = []
|
| 978 |
+
for fid in face_ids:
|
| 979 |
+
surf = fid - 1
|
| 980 |
+
boundary = gmsh.model.getBoundary([(2, surf)], oriented=False)
|
| 981 |
+
for (dim, ctag) in boundary:
|
| 982 |
+
if dim == 1:
|
| 983 |
+
curve_ids.append(ctag)
|
| 984 |
+
|
| 985 |
+
# Distance field
|
| 986 |
+
f_dist = gmsh.model.mesh.field.add("Distance")
|
| 987 |
+
gmsh.model.mesh.field.setNumbers(f_dist, "CurvesList", curve_ids)
|
| 988 |
+
gmsh.model.mesh.field.setNumber(f_dist, "Sampling", 200)
|
| 989 |
+
|
| 990 |
+
# Threshold field
|
| 991 |
+
min_local = min(local_sizes) * 2
|
| 992 |
+
f_th = gmsh.model.mesh.field.add("Threshold")
|
| 993 |
+
gmsh.model.mesh.field.setNumber(f_th, "InField", f_dist)
|
| 994 |
+
gmsh.model.mesh.field.setNumber(f_th, "SizeMin", float(min_local))
|
| 995 |
+
gmsh.model.mesh.field.setNumber(f_th, "SizeMax", float(global_size))
|
| 996 |
+
gmsh.model.mesh.field.setNumber(f_th, "DistMin", 0.0)
|
| 997 |
+
gmsh.model.mesh.field.setNumber(f_th, "DistMax", global_size * 2.0)
|
| 998 |
+
|
| 999 |
+
# Min field (clamped)
|
| 1000 |
+
f_min = gmsh.model.mesh.field.add("Min")
|
| 1001 |
+
gmsh.model.mesh.field.setNumbers(f_min, "FieldsList", [f_th])
|
| 1002 |
+
|
| 1003 |
+
gmsh.model.mesh.field.setAsBackgroundMesh(f_min)
|
| 1004 |
+
|
| 1005 |
+
gmsh.model.occ.synchronize()
|
| 1006 |
+
gmsh.model.mesh.generate(2)
|
| 1007 |
+
gmsh.model.mesh.optimize("Netgen")
|
| 1008 |
+
gmsh.model.occ.synchronize()
|
| 1009 |
+
|
| 1010 |
+
# -----------------------------------------------------------------
|
| 1011 |
+
# Extract FE mesh nodes & triangles (compact indexing)
|
| 1012 |
+
# -----------------------------------------------------------------
|
| 1013 |
+
all_node_tags, all_node_coords, _ = gmsh.model.mesh.getNodes()
|
| 1014 |
+
all_nodes = all_node_coords.reshape(-1, 3)
|
| 1015 |
+
|
| 1016 |
+
types_all, elem_tags_all, elem_nodes_all = gmsh.model.mesh.getElements()
|
| 1017 |
+
|
| 1018 |
+
used_node_tags = np.unique(
|
| 1019 |
+
np.concatenate([arr for arr in elem_nodes_all])
|
| 1020 |
+
)
|
| 1021 |
+
used_idx = used_node_tags - 1
|
| 1022 |
+
nodes_r = all_nodes[used_idx]
|
| 1023 |
+
|
| 1024 |
+
tag_to_new_index = {tag: i for i, tag in enumerate(used_node_tags)}
|
| 1025 |
+
|
| 1026 |
+
triangle_list = []
|
| 1027 |
+
for etype, conn in zip(types_all, elem_nodes_all):
|
| 1028 |
+
name, dim, order, nnode, *_ = gmsh.model.mesh.getElementProperties(etype)
|
| 1029 |
+
if dim != 2:
|
| 1030 |
+
continue
|
| 1031 |
+
|
| 1032 |
+
if nnode == 3:
|
| 1033 |
+
raw = conn.reshape(-1, 3)
|
| 1034 |
+
remapped = np.vectorize(tag_to_new_index.get)(raw)
|
| 1035 |
+
triangle_list.extend(remapped)
|
| 1036 |
+
elif nnode == 4:
|
| 1037 |
+
raw = conn.reshape(-1, 4)
|
| 1038 |
+
remapped = np.vectorize(tag_to_new_index.get)(raw)
|
| 1039 |
+
for quad in remapped:
|
| 1040 |
+
triangle_list.append([quad[0], quad[1], quad[2]])
|
| 1041 |
+
triangle_list.append([quad[0], quad[2], quad[3]])
|
| 1042 |
+
|
| 1043 |
+
triangle_array = np.array(triangle_list, dtype=int)
|
| 1044 |
+
|
| 1045 |
+
print("Triangles extracted:", triangle_array.shape)
|
| 1046 |
+
print("FE MESH NODES:", len(nodes_r))
|
| 1047 |
+
print("FE MESH TRIANGLES:", len(triangle_array))
|
| 1048 |
+
|
| 1049 |
+
# For PNG plotter
|
| 1050 |
+
elements_r = [(0, triangle_array)]
|
| 1051 |
+
|
| 1052 |
+
# -----------------------------------------------------------------
|
| 1053 |
+
# Mapping gmsh surfaces → local node indices (for PNG highlight)
|
| 1054 |
+
# -----------------------------------------------------------------
|
| 1055 |
+
face_to_nodes = {}
|
| 1056 |
+
for (dim, sid) in gmsh.model.getEntities(dim=2):
|
| 1057 |
+
etypes_s, elem_tags_s, ntags_s = gmsh.model.mesh.getElements(
|
| 1058 |
+
dim=2, tag=sid
|
| 1059 |
+
)
|
| 1060 |
+
surface_conn_local = []
|
| 1061 |
+
for etype, ntags in zip(etypes_s, ntags_s):
|
| 1062 |
+
_, _, _, nnode, *_ = gmsh.model.mesh.getElementProperties(etype)
|
| 1063 |
+
if nnode in (3, 4):
|
| 1064 |
+
raw = ntags.reshape(-1, nnode)
|
| 1065 |
+
# map gmsh node tags → compact indices
|
| 1066 |
+
mapped = np.vectorize(tag_to_new_index.get)(raw)
|
| 1067 |
+
surface_conn_local.extend(mapped)
|
| 1068 |
+
face_to_nodes[sid] = surface_conn_local
|
| 1069 |
+
|
| 1070 |
+
highlight_gmsh_ids = [fid - 1 for fid in face_ids]
|
| 1071 |
+
|
| 1072 |
+
# -----------------------------------------------------------------
|
| 1073 |
+
# Local refinement mask (which triangles are "near" refined faces)
|
| 1074 |
+
# -----------------------------------------------------------------
|
| 1075 |
+
refined_nodes_set = set()
|
| 1076 |
+
for sid in highlight_gmsh_ids:
|
| 1077 |
+
if sid in face_to_nodes:
|
| 1078 |
+
for conn in face_to_nodes[sid]:
|
| 1079 |
+
for n in conn:
|
| 1080 |
+
refined_nodes_set.add(int(n))
|
| 1081 |
+
|
| 1082 |
+
refined_tri_mask = np.zeros(triangle_array.shape[0], dtype=bool)
|
| 1083 |
+
for t_idx, tri in enumerate(triangle_array):
|
| 1084 |
+
if any(int(v) in refined_nodes_set for v in tri):
|
| 1085 |
+
refined_tri_mask[t_idx] = True
|
| 1086 |
+
|
| 1087 |
+
# -----------------------------------------------------------------
|
| 1088 |
+
# Heatmap: per-vertex "local mesh size" (smaller → more refined)
|
| 1089 |
+
# -----------------------------------------------------------------
|
| 1090 |
+
n_nodes = nodes_r.shape[0]
|
| 1091 |
+
n_tris = triangle_array.shape[0]
|
| 1092 |
+
|
| 1093 |
+
tri_sizes = np.zeros(n_tris, dtype=float)
|
| 1094 |
+
for t_idx, (a, b, c) in enumerate(triangle_array):
|
| 1095 |
+
pa, pb, pc = nodes_r[[a, b, c]]
|
| 1096 |
+
area = 0.5 * np.linalg.norm(np.cross(pb - pa, pc - pa))
|
| 1097 |
+
# characteristic length ~ sqrt(area)
|
| 1098 |
+
tri_sizes[t_idx] = np.sqrt(area + 1e-16)
|
| 1099 |
+
|
| 1100 |
+
vertex_sum = np.zeros(n_nodes, dtype=float)
|
| 1101 |
+
vertex_count = np.zeros(n_nodes, dtype=float)
|
| 1102 |
+
for t_idx, tri in enumerate(triangle_array):
|
| 1103 |
+
s = tri_sizes[t_idx]
|
| 1104 |
+
for v in tri:
|
| 1105 |
+
vertex_sum[v] += s
|
| 1106 |
+
vertex_count[v] += 1.0
|
| 1107 |
+
|
| 1108 |
+
vertex_size = vertex_sum / np.maximum(vertex_count, 1.0)
|
| 1109 |
+
|
| 1110 |
+
smin, smax = float(vertex_size.min()), float(vertex_size.max())
|
| 1111 |
+
if smax > smin:
|
| 1112 |
+
# invert so small elements → high intensity
|
| 1113 |
+
vertex_refinement_values = 1.0 - (vertex_size - smin) / (smax - smin)
|
| 1114 |
+
else:
|
| 1115 |
+
vertex_refinement_values = np.zeros_like(vertex_size)
|
| 1116 |
+
|
| 1117 |
+
# -----------------------------------------------------------------
|
| 1118 |
+
# Save refined mesh PNG with red outlines on refined faces
|
| 1119 |
+
# -----------------------------------------------------------------
|
| 1120 |
+
plot_mesh(
|
| 1121 |
+
nodes_r,
|
| 1122 |
+
elements_r,
|
| 1123 |
+
title="Refined Mesh (Local + Global)",
|
| 1124 |
+
highlight_faces=highlight_gmsh_ids,
|
| 1125 |
+
face_to_nodes=face_to_nodes,
|
| 1126 |
+
save_path="output/mesh_refined.png",
|
| 1127 |
+
)
|
| 1128 |
+
|
| 1129 |
+
gmsh.write("output/mesh_refined.msh")
|
| 1130 |
+
|
| 1131 |
+
# -----------------------------------------------------------------
|
| 1132 |
+
# Interactive Plotly figure (wireframe / highlight / heatmap)
|
| 1133 |
+
# -----------------------------------------------------------------
|
| 1134 |
+
interactive_fig = make_plotly_mesh(
|
| 1135 |
+
nodes_r,
|
| 1136 |
+
triangle_array,
|
| 1137 |
+
title="Refined Mesh (Local + Global)",
|
| 1138 |
+
view_mode=view_mode,
|
| 1139 |
+
refined_tri_mask=refined_tri_mask,
|
| 1140 |
+
vertex_refinement_values=vertex_refinement_values,
|
| 1141 |
+
)
|
| 1142 |
+
|
| 1143 |
+
msh_path = "output/mesh_refined.msh"
|
| 1144 |
+
return (
|
| 1145 |
+
sentence,
|
| 1146 |
+
"output/mesh_global_only.png",
|
| 1147 |
+
"output/mesh_refined.png",
|
| 1148 |
+
interactive_fig,
|
| 1149 |
+
msh_path,
|
| 1150 |
+
)
|
mesh_service/step_feature_detection.py
ADDED
|
@@ -0,0 +1,1075 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import json
|
| 3 |
+
from OCP.STEPControl import STEPControl_Reader
|
| 4 |
+
from OCP.TopAbs import TopAbs_FACE, TopAbs_EDGE, TopAbs_IN, TopAbs_REVERSED, TopAbs_VERTEX
|
| 5 |
+
from OCP.TopExp import TopExp_Explorer, TopExp
|
| 6 |
+
from OCP.TopoDS import TopoDS, TopoDS_Face, TopoDS_Edge
|
| 7 |
+
from OCP.BRepAdaptor import BRepAdaptor_Surface, BRepAdaptor_Curve
|
| 8 |
+
from OCP.BRep import BRep_Tool
|
| 9 |
+
from OCP.GeomAbs import GeomAbs_Cylinder, GeomAbs_Plane, GeomAbs_Circle, GeomAbs_Line
|
| 10 |
+
from OCP.BRepGProp import BRepGProp
|
| 11 |
+
from OCP.GProp import GProp_GProps
|
| 12 |
+
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
| 13 |
+
from OCP.gp import gp_Pnt, gp_Vec
|
| 14 |
+
import numpy as np
|
| 15 |
+
from OCP.Bnd import Bnd_Box
|
| 16 |
+
from OCP.BRepBndLib import BRepBndLib
|
| 17 |
+
from collections import Counter
|
| 18 |
+
|
| 19 |
+
def face_bbox_center(face):
|
| 20 |
+
box = Bnd_Box()
|
| 21 |
+
BRepBndLib.Add_s(face, box)
|
| 22 |
+
xmin, ymin, zmin, xmax, ymax, zmax = box.Get()
|
| 23 |
+
return [
|
| 24 |
+
(xmin + xmax) / 2,
|
| 25 |
+
(ymin + ymax) / 2,
|
| 26 |
+
(zmin + zmax) / 2
|
| 27 |
+
]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def load_step_model(step_path):
|
| 31 |
+
"""Load a STEP file and return the shape object."""
|
| 32 |
+
reader = STEPControl_Reader()
|
| 33 |
+
status = reader.ReadFile(step_path)
|
| 34 |
+
if status != 1:
|
| 35 |
+
raise RuntimeError(f"Failed to load STEP file: {step_path}")
|
| 36 |
+
reader.TransferRoot()
|
| 37 |
+
shape = reader.Shape()
|
| 38 |
+
return shape
|
| 39 |
+
|
| 40 |
+
def load_step_unit(step_path):
|
| 41 |
+
with open(step_path, "r") as f:
|
| 42 |
+
for line in f:
|
| 43 |
+
if "SI_UNIT" in line:
|
| 44 |
+
print("STEP file unit info:", line.strip())
|
| 45 |
+
return line.strip()
|
| 46 |
+
|
| 47 |
+
def detect_faces(shape):
|
| 48 |
+
faces = []
|
| 49 |
+
exp = TopExp_Explorer(shape, TopAbs_FACE)
|
| 50 |
+
idx = 0
|
| 51 |
+
while exp.More():
|
| 52 |
+
face_shape = exp.Current()
|
| 53 |
+
face = TopoDS.Face_s(face_shape)
|
| 54 |
+
surf = BRep_Tool.Surface_s(face)
|
| 55 |
+
faces.append({"face": face, "surface": surf})
|
| 56 |
+
idx = idx+1
|
| 57 |
+
exp.Next()
|
| 58 |
+
return faces
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def is_hole(face, shape):
|
| 62 |
+
"""
|
| 63 |
+
Determine if a cylindrical face is a hole (concave) rather than a boss (convex).
|
| 64 |
+
Basic approach: check if the face normal points toward the solid interior.
|
| 65 |
+
"""
|
| 66 |
+
# Get a point and normal on the cylindrical surface
|
| 67 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 68 |
+
u_min, u_max = adaptor.FirstUParameter(), adaptor.LastUParameter()
|
| 69 |
+
v_min, v_max = adaptor.FirstVParameter(), adaptor.LastVParameter()
|
| 70 |
+
u_mid = (u_min + u_max) / 2
|
| 71 |
+
v_mid = (v_min + v_max) / 2
|
| 72 |
+
|
| 73 |
+
# Get point and normal vector at mid-parameter
|
| 74 |
+
point = adaptor.Value(u_mid, v_mid)
|
| 75 |
+
d1u = gp_Vec()
|
| 76 |
+
d1v = gp_Vec()
|
| 77 |
+
adaptor.D1(u_mid, v_mid, point, d1u, d1v)
|
| 78 |
+
|
| 79 |
+
# Calculate normal vector (cross product of partial derivatives)
|
| 80 |
+
normal = d1u.Crossed(d1v)
|
| 81 |
+
normal.Normalize()
|
| 82 |
+
|
| 83 |
+
# Offset point along normal direction
|
| 84 |
+
offset = 0.01 # Small offset distance
|
| 85 |
+
test_point = gp_Pnt(
|
| 86 |
+
point.X() + normal.X() * offset,
|
| 87 |
+
point.Y() + normal.Y() * offset,
|
| 88 |
+
point.Z() + normal.Z() * offset
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
# Check if the offset point is inside or outside the solid
|
| 92 |
+
classifier = BRepClass3d_SolidClassifier(shape)
|
| 93 |
+
classifier.Perform(test_point, 1e-7)
|
| 94 |
+
|
| 95 |
+
# If normal points outward, offset point should be outside -> this is a hole
|
| 96 |
+
# If normal points outward, offset point is still inside -> this is a boss
|
| 97 |
+
return classifier.State() == TopAbs_IN
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def compute_face_angle_span(face, axis_location, axis_direction):
|
| 101 |
+
axis_loc = np.array(axis_location, dtype=float)
|
| 102 |
+
axis_dir = np.array(axis_direction, dtype=float)
|
| 103 |
+
axis_dir = axis_dir / np.linalg.norm(axis_dir)
|
| 104 |
+
ref = np.array([1.0, 0.0, 0.0], dtype=float)
|
| 105 |
+
if abs(np.dot(ref, axis_dir)) > 0.9:
|
| 106 |
+
ref = np.array([0.0, 1.0, 0.0], dtype=float)
|
| 107 |
+
|
| 108 |
+
e1 = np.cross(axis_dir, ref)
|
| 109 |
+
e1 = e1 / np.linalg.norm(e1)
|
| 110 |
+
e2 = np.cross(axis_dir, e1)
|
| 111 |
+
e2 = e2 / np.linalg.norm(e2)
|
| 112 |
+
|
| 113 |
+
angles = []
|
| 114 |
+
|
| 115 |
+
edge_exp = TopExp_Explorer(face, TopAbs_EDGE)
|
| 116 |
+
while edge_exp.More():
|
| 117 |
+
edge_shape = edge_exp.Current()
|
| 118 |
+
vert_exp = TopExp_Explorer(edge_shape, TopAbs_VERTEX)
|
| 119 |
+
while vert_exp.More():
|
| 120 |
+
vert_shape = vert_exp.Current()
|
| 121 |
+
vertex = TopoDS.Vertex_s(vert_shape)
|
| 122 |
+
p = BRep_Tool.Pnt_s(vertex)
|
| 123 |
+
v = np.array([p.X(), p.Y(), p.Z()], dtype=float) - axis_loc
|
| 124 |
+
|
| 125 |
+
v_proj = v - np.dot(v, axis_dir) * axis_dir
|
| 126 |
+
norm_v = np.linalg.norm(v_proj)
|
| 127 |
+
if norm_v < 1e-6:
|
| 128 |
+
vert_exp.Next()
|
| 129 |
+
continue
|
| 130 |
+
|
| 131 |
+
x = np.dot(v_proj, e1)
|
| 132 |
+
y = np.dot(v_proj, e2)
|
| 133 |
+
theta = math.atan2(y, x)
|
| 134 |
+
angles.append(theta)
|
| 135 |
+
|
| 136 |
+
vert_exp.Next()
|
| 137 |
+
edge_exp.Next()
|
| 138 |
+
|
| 139 |
+
if not angles:
|
| 140 |
+
return 0.0
|
| 141 |
+
|
| 142 |
+
angles = np.array(angles)
|
| 143 |
+
angles = np.mod(angles, 2.0 * math.pi)
|
| 144 |
+
angles.sort()
|
| 145 |
+
|
| 146 |
+
diffs = np.diff(angles)
|
| 147 |
+
wrap_gap = (angles[0] + 2.0 * math.pi) - angles[-1]
|
| 148 |
+
max_gap = max(diffs.max(initial=0.0), wrap_gap)
|
| 149 |
+
|
| 150 |
+
coverage_rad = 2.0 * math.pi - max_gap
|
| 151 |
+
coverage_deg = math.degrees(coverage_rad)
|
| 152 |
+
return float(coverage_deg)
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def detect_circular_holes(shape, min_radius=0.5, max_radius=50.0, merge_tolerance=1e-3):
|
| 156 |
+
"""
|
| 157 |
+
Detect all cylindrical (round) holes, including through-holes and blind holes.
|
| 158 |
+
Merges faces belonging to the same hole.
|
| 159 |
+
Returns: List of dicts with center, radius, normal, area, etc.
|
| 160 |
+
"""
|
| 161 |
+
candidate_faces = []
|
| 162 |
+
idx = 0
|
| 163 |
+
|
| 164 |
+
for fdict in detect_faces(shape):
|
| 165 |
+
face = fdict["face"]
|
| 166 |
+
surf = fdict["surface"]
|
| 167 |
+
|
| 168 |
+
# Check if surface type is cylindrical
|
| 169 |
+
surface_type = surf.DynamicType().Name()
|
| 170 |
+
if "Cylindrical" not in surface_type:
|
| 171 |
+
continue
|
| 172 |
+
|
| 173 |
+
# Use BRepAdaptor_Surface to get cylinder parameters
|
| 174 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 175 |
+
|
| 176 |
+
# Confirm it's a cylinder type
|
| 177 |
+
if adaptor.GetType() != GeomAbs_Cylinder:
|
| 178 |
+
continue
|
| 179 |
+
|
| 180 |
+
cylinder = adaptor.Cylinder()
|
| 181 |
+
radius = cylinder.Radius()
|
| 182 |
+
|
| 183 |
+
# Only consider reasonable hole sizes
|
| 184 |
+
if not (min_radius <= radius <= max_radius):
|
| 185 |
+
continue
|
| 186 |
+
|
| 187 |
+
# **Key judgment: check if it's a hole (not a boss)**
|
| 188 |
+
if not is_hole(face, shape):
|
| 189 |
+
continue # Skip bosses/shafts
|
| 190 |
+
|
| 191 |
+
# Get axis direction and location
|
| 192 |
+
axis_direction = cylinder.Axis().Direction()
|
| 193 |
+
axis_location = cylinder.Axis().Location()
|
| 194 |
+
axis_dir_vec = [axis_direction.X(), axis_direction.Y(), axis_direction.Z()]
|
| 195 |
+
axis_loc_vec = [axis_location.X(), axis_location.Y(), axis_location.Z()]
|
| 196 |
+
|
| 197 |
+
# Calculate surface area and center
|
| 198 |
+
gprops = GProp_GProps()
|
| 199 |
+
BRepGProp.SurfaceProperties_s(face, gprops)
|
| 200 |
+
area = gprops.Mass()
|
| 201 |
+
center = gprops.CentreOfMass()
|
| 202 |
+
|
| 203 |
+
angle_span = compute_face_angle_span(face, axis_loc_vec, axis_dir_vec)
|
| 204 |
+
|
| 205 |
+
candidate_faces.append({
|
| 206 |
+
"face_id": idx,
|
| 207 |
+
"face": face,
|
| 208 |
+
"radius": float(radius),
|
| 209 |
+
"center": [center.X(), center.Y(), center.Z()],
|
| 210 |
+
"axis_direction": [axis_direction.X(), axis_direction.Y(), axis_direction.Z()],
|
| 211 |
+
"axis_location": [axis_location.X(), axis_location.Y(), axis_location.Z()],
|
| 212 |
+
"area": float(area),
|
| 213 |
+
"angle_span": float(angle_span)
|
| 214 |
+
})
|
| 215 |
+
|
| 216 |
+
idx = idx+1
|
| 217 |
+
|
| 218 |
+
# Group faces that belong to the same hole
|
| 219 |
+
holes = merge_coaxial_cylinders(candidate_faces, merge_tolerance)
|
| 220 |
+
|
| 221 |
+
print(f"Detected {len(holes)} circular holes (from {len(candidate_faces)} cylindrical faces)")
|
| 222 |
+
return holes
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def merge_coaxial_cylinders(faces, tolerance=1e-3, angle_threshold_deg=350.0):
|
| 226 |
+
"""
|
| 227 |
+
Merge cylindrical faces that share the same axis (belong to same hole).
|
| 228 |
+
Two cylinders belong to the same hole if:
|
| 229 |
+
1. They have the same radius (within tolerance)
|
| 230 |
+
2. They are coaxial (share the same axis)
|
| 231 |
+
"""
|
| 232 |
+
if not faces:
|
| 233 |
+
return []
|
| 234 |
+
|
| 235 |
+
merged_holes = []
|
| 236 |
+
used = [False] * len(faces)
|
| 237 |
+
|
| 238 |
+
for i, face1 in enumerate(faces):
|
| 239 |
+
if used[i]:
|
| 240 |
+
continue
|
| 241 |
+
|
| 242 |
+
# Start a new hole group
|
| 243 |
+
hole_group = [face1]
|
| 244 |
+
used[i] = True
|
| 245 |
+
|
| 246 |
+
# Find all faces belonging to the same hole
|
| 247 |
+
for j, face2 in enumerate(faces):
|
| 248 |
+
if used[j] or i == j:
|
| 249 |
+
continue
|
| 250 |
+
|
| 251 |
+
# Check if same radius
|
| 252 |
+
if abs(face1["radius"] - face2["radius"]) > tolerance:
|
| 253 |
+
continue
|
| 254 |
+
|
| 255 |
+
# Check if coaxial
|
| 256 |
+
if are_coaxial(face1, face2, tolerance):
|
| 257 |
+
hole_group.append(face2)
|
| 258 |
+
used[j] = True
|
| 259 |
+
|
| 260 |
+
total_angle = sum(f.get("angle_span", 0.0) for f in hole_group)
|
| 261 |
+
total_angle = min(total_angle, 360)
|
| 262 |
+
if total_angle < angle_threshold_deg:
|
| 263 |
+
continue
|
| 264 |
+
|
| 265 |
+
# Compute merged hole properties
|
| 266 |
+
merged_hole = merge_hole_group(hole_group, i)
|
| 267 |
+
merged_holes.append(merged_hole)
|
| 268 |
+
|
| 269 |
+
return merged_holes
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def are_coaxial(face1, face2, tolerance=1e-3):
|
| 273 |
+
"""
|
| 274 |
+
Check if two cylindrical faces are coaxial (share the same axis).
|
| 275 |
+
"""
|
| 276 |
+
import numpy as np
|
| 277 |
+
|
| 278 |
+
# Get axis directions
|
| 279 |
+
dir1 = np.array(face1["axis_direction"])
|
| 280 |
+
dir2 = np.array(face2["axis_direction"])
|
| 281 |
+
|
| 282 |
+
# Normalize
|
| 283 |
+
dir1 = dir1 / np.linalg.norm(dir1)
|
| 284 |
+
dir2 = dir2 / np.linalg.norm(dir2)
|
| 285 |
+
|
| 286 |
+
# Check if parallel (dot product close to ±1)
|
| 287 |
+
dot_product = abs(np.dot(dir1, dir2))
|
| 288 |
+
if abs(dot_product - 1.0) > tolerance:
|
| 289 |
+
return False
|
| 290 |
+
|
| 291 |
+
# Check if axes are coincident
|
| 292 |
+
# Distance from point on axis1 to axis2
|
| 293 |
+
loc1 = np.array(face1["axis_location"])
|
| 294 |
+
loc2 = np.array(face2["axis_location"])
|
| 295 |
+
|
| 296 |
+
# Vector from loc1 to loc2
|
| 297 |
+
vec = loc2 - loc1
|
| 298 |
+
|
| 299 |
+
# Distance from loc2 to axis1 (perpendicular distance)
|
| 300 |
+
distance = np.linalg.norm(vec - np.dot(vec, dir1) * dir1)
|
| 301 |
+
|
| 302 |
+
return distance < tolerance
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def merge_hole_group(hole_group, id):
|
| 306 |
+
"""
|
| 307 |
+
Merge multiple faces belonging to the same hole into one hole feature,
|
| 308 |
+
and record all involved faces for downstream mapping.
|
| 309 |
+
"""
|
| 310 |
+
import numpy as np
|
| 311 |
+
|
| 312 |
+
representative = hole_group[0]
|
| 313 |
+
total_area = sum(face["area"] for face in hole_group)
|
| 314 |
+
|
| 315 |
+
centers = np.array([face["center"] for face in hole_group])
|
| 316 |
+
areas = np.array([face["area"] for face in hole_group])
|
| 317 |
+
avg_center = np.average(centers, axis=0, weights=areas)
|
| 318 |
+
|
| 319 |
+
# Clean up floating point errors
|
| 320 |
+
avg_center = np.round(avg_center, decimals=6)
|
| 321 |
+
faces_info = [
|
| 322 |
+
{
|
| 323 |
+
"face_id": face.get("face_id"),
|
| 324 |
+
"center": [round(x, 6) for x in face["center"]],
|
| 325 |
+
"area": round(face["area"], 6),
|
| 326 |
+
"angle_span": round(face.get("angle_span", 0.0), 4),
|
| 327 |
+
"normal": [round(x, 6) for x in face.get("normal", [0,0,0])]
|
| 328 |
+
}
|
| 329 |
+
for face in hole_group
|
| 330 |
+
]
|
| 331 |
+
|
| 332 |
+
return {
|
| 333 |
+
"name": f"CIRC_HOLE_{id}",
|
| 334 |
+
"type": "circular_hole",
|
| 335 |
+
"radius": round(representative["radius"], 6),
|
| 336 |
+
"center": avg_center.tolist(),
|
| 337 |
+
"normal": representative["axis_direction"],
|
| 338 |
+
"area": round(total_area, 6),
|
| 339 |
+
"num_faces": len(hole_group),
|
| 340 |
+
"faces": faces_info
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def detect_non_circular_holes(shape, min_area=1.0, max_area=1000.0, distance_threshold=10.0):
|
| 346 |
+
"""
|
| 347 |
+
Detect non-circular holes (rectangular holes, slots, elongated holes).
|
| 348 |
+
Strategy: Find groups of planar faces that form a closed pocket.
|
| 349 |
+
"""
|
| 350 |
+
result = []
|
| 351 |
+
planar_faces = []
|
| 352 |
+
|
| 353 |
+
# 1. Collect all planar faces that might be part of a hole
|
| 354 |
+
for fdict in detect_faces(shape):
|
| 355 |
+
face = fdict["face"]
|
| 356 |
+
surf = fdict["surface"]
|
| 357 |
+
|
| 358 |
+
surface_type = surf.DynamicType().Name()
|
| 359 |
+
if "Plane" not in surface_type:
|
| 360 |
+
continue
|
| 361 |
+
|
| 362 |
+
# Check if this plane is a hole (concave)
|
| 363 |
+
if not is_hole(face, shape):
|
| 364 |
+
continue
|
| 365 |
+
|
| 366 |
+
# Get face properties
|
| 367 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 368 |
+
|
| 369 |
+
# Confirm it's a plane type
|
| 370 |
+
if adaptor.GetType() != GeomAbs_Plane:
|
| 371 |
+
continue
|
| 372 |
+
|
| 373 |
+
plane = adaptor.Plane()
|
| 374 |
+
normal = plane.Axis().Direction()
|
| 375 |
+
|
| 376 |
+
gprops = GProp_GProps()
|
| 377 |
+
BRepGProp.SurfaceProperties_s(face, gprops)
|
| 378 |
+
area = gprops.Mass()
|
| 379 |
+
center = gprops.CentreOfMass()
|
| 380 |
+
|
| 381 |
+
# Filter by area
|
| 382 |
+
if not (min_area <= area <= max_area):
|
| 383 |
+
continue
|
| 384 |
+
|
| 385 |
+
planar_faces.append({
|
| 386 |
+
"face": face,
|
| 387 |
+
"center": [center.X(), center.Y(), center.Z()],
|
| 388 |
+
"normal": [normal.X(), normal.Y(), normal.Z()],
|
| 389 |
+
"area": float(area)
|
| 390 |
+
})
|
| 391 |
+
|
| 392 |
+
# 2. Group adjacent planar faces that might form a hole
|
| 393 |
+
holes = group_planar_holes(planar_faces, distance_threshold)
|
| 394 |
+
|
| 395 |
+
# 3. Classify hole type (rectangular, slot, etc.)
|
| 396 |
+
for hole_group in holes:
|
| 397 |
+
hole_type = classify_planar_hole(hole_group)
|
| 398 |
+
result.append(hole_type)
|
| 399 |
+
|
| 400 |
+
print(f"Detected {len(result)} non-circular holes (from {len(planar_faces)} planar faces)")
|
| 401 |
+
return result
|
| 402 |
+
|
| 403 |
+
|
| 404 |
+
def group_planar_holes(faces, distance_threshold=10.0):
|
| 405 |
+
"""
|
| 406 |
+
Group planar faces that are close to each other and might form a hole.
|
| 407 |
+
"""
|
| 408 |
+
if not faces:
|
| 409 |
+
return []
|
| 410 |
+
|
| 411 |
+
groups = []
|
| 412 |
+
used = [False] * len(faces)
|
| 413 |
+
|
| 414 |
+
for i, face1 in enumerate(faces):
|
| 415 |
+
if used[i]:
|
| 416 |
+
continue
|
| 417 |
+
|
| 418 |
+
# Start a new group
|
| 419 |
+
group = [face1]
|
| 420 |
+
used[i] = True
|
| 421 |
+
|
| 422 |
+
# Find nearby faces with similar normal direction
|
| 423 |
+
for j, face2 in enumerate(faces):
|
| 424 |
+
if used[j] or i == j:
|
| 425 |
+
continue
|
| 426 |
+
|
| 427 |
+
# Check if normals are similar (parallel faces)
|
| 428 |
+
normal1 = np.array(face1["normal"])
|
| 429 |
+
normal2 = np.array(face2["normal"])
|
| 430 |
+
|
| 431 |
+
# Normalize
|
| 432 |
+
normal1 = normal1 / np.linalg.norm(normal1)
|
| 433 |
+
normal2 = normal2 / np.linalg.norm(normal2)
|
| 434 |
+
|
| 435 |
+
dot = abs(np.dot(normal1, normal2))
|
| 436 |
+
|
| 437 |
+
if dot > 0.9: # Nearly parallel
|
| 438 |
+
# Check distance between centers
|
| 439 |
+
center1 = np.array(face1["center"])
|
| 440 |
+
center2 = np.array(face2["center"])
|
| 441 |
+
distance = np.linalg.norm(center2 - center1)
|
| 442 |
+
|
| 443 |
+
if distance < distance_threshold:
|
| 444 |
+
group.append(face2)
|
| 445 |
+
used[j] = True
|
| 446 |
+
|
| 447 |
+
groups.append(group)
|
| 448 |
+
|
| 449 |
+
return groups
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
def classify_planar_hole(face_group):
|
| 453 |
+
"""
|
| 454 |
+
Classify a group of planar faces into hole types.
|
| 455 |
+
Simple classification based on number of faces and geometry.
|
| 456 |
+
"""
|
| 457 |
+
num_faces = len(face_group)
|
| 458 |
+
|
| 459 |
+
# Calculate total area and average center
|
| 460 |
+
total_area = sum(f["area"] for f in face_group)
|
| 461 |
+
centers = np.array([f["center"] for f in face_group])
|
| 462 |
+
avg_center = np.mean(centers, axis=0)
|
| 463 |
+
|
| 464 |
+
# Use the first face's normal as representative
|
| 465 |
+
representative_normal = face_group[0]["normal"]
|
| 466 |
+
|
| 467 |
+
# Simple classification based on number of faces
|
| 468 |
+
if num_faces == 1:
|
| 469 |
+
hole_type = "simple_pocket"
|
| 470 |
+
elif num_faces == 4:
|
| 471 |
+
hole_type = "rectangular_hole"
|
| 472 |
+
elif num_faces == 2:
|
| 473 |
+
hole_type = "slot"
|
| 474 |
+
elif num_faces >= 5:
|
| 475 |
+
hole_type = "complex_pocket"
|
| 476 |
+
else:
|
| 477 |
+
hole_type = "irregular_hole"
|
| 478 |
+
|
| 479 |
+
return {
|
| 480 |
+
"type": hole_type,
|
| 481 |
+
"center": np.round(avg_center, 6).tolist(),
|
| 482 |
+
"normal": representative_normal,
|
| 483 |
+
"area": round(total_area, 6),
|
| 484 |
+
"num_faces": num_faces
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
def detect_fillets(shape, min_radius=0.1, max_radius=50.0,
|
| 488 |
+
classify_type=True, hole_merge_tolerance=1e-3,
|
| 489 |
+
fillet_merge_tolerance=1e-3,
|
| 490 |
+
min_total_angle_deg=3.0,
|
| 491 |
+
max_total_angle_deg=330.0):
|
| 492 |
+
hole_features = detect_circular_holes(
|
| 493 |
+
shape,
|
| 494 |
+
min_radius=min_radius,
|
| 495 |
+
max_radius=max_radius,
|
| 496 |
+
merge_tolerance=hole_merge_tolerance
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
hole_cylinders = []
|
| 500 |
+
for h in hole_features:
|
| 501 |
+
hole_cylinders.append({
|
| 502 |
+
"radius": h["radius"],
|
| 503 |
+
"axis_direction": h["normal"],
|
| 504 |
+
"axis_location": h["center"],
|
| 505 |
+
})
|
| 506 |
+
|
| 507 |
+
fillet_candidates = []
|
| 508 |
+
face_idx = 0
|
| 509 |
+
|
| 510 |
+
for fdict in detect_faces(shape):
|
| 511 |
+
face = fdict["face"]
|
| 512 |
+
surf = fdict["surface"]
|
| 513 |
+
|
| 514 |
+
surface_type = surf.DynamicType().Name()
|
| 515 |
+
if "Cylindrical" not in surface_type:
|
| 516 |
+
continue
|
| 517 |
+
|
| 518 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 519 |
+
if adaptor.GetType() != GeomAbs_Cylinder:
|
| 520 |
+
continue
|
| 521 |
+
|
| 522 |
+
cylinder = adaptor.Cylinder()
|
| 523 |
+
radius = cylinder.Radius()
|
| 524 |
+
|
| 525 |
+
if not (min_radius <= radius <= max_radius):
|
| 526 |
+
continue
|
| 527 |
+
|
| 528 |
+
axis_direction = cylinder.Axis().Direction()
|
| 529 |
+
axis_location = cylinder.Axis().Location()
|
| 530 |
+
axis_dir_vec = [axis_direction.X(), axis_direction.Y(), axis_direction.Z()]
|
| 531 |
+
axis_loc_vec = [axis_location.X(), axis_location.Y(), axis_location.Z()]
|
| 532 |
+
|
| 533 |
+
is_on_hole_cylinder = False
|
| 534 |
+
for hc in hole_cylinders:
|
| 535 |
+
if abs(hc["radius"] - radius) > hole_merge_tolerance:
|
| 536 |
+
continue
|
| 537 |
+
|
| 538 |
+
if are_coaxial(
|
| 539 |
+
hc,
|
| 540 |
+
{"axis_direction": axis_dir_vec, "axis_location": axis_loc_vec},
|
| 541 |
+
tolerance=hole_merge_tolerance,
|
| 542 |
+
):
|
| 543 |
+
is_on_hole_cylinder = True
|
| 544 |
+
break
|
| 545 |
+
|
| 546 |
+
if is_on_hole_cylinder:
|
| 547 |
+
continue
|
| 548 |
+
|
| 549 |
+
gprops = GProp_GProps()
|
| 550 |
+
BRepGProp.SurfaceProperties_s(face, gprops)
|
| 551 |
+
area = gprops.Mass()
|
| 552 |
+
center = gprops.CentreOfMass()
|
| 553 |
+
|
| 554 |
+
if not is_fillet_surface(face, shape, radius, area,
|
| 555 |
+
full_angle_threshold_deg=330.0,
|
| 556 |
+
min_angle_threshold_deg=3.0):
|
| 557 |
+
continue
|
| 558 |
+
|
| 559 |
+
angle_span = compute_face_angle_span(face, axis_loc_vec, axis_dir_vec)
|
| 560 |
+
|
| 561 |
+
if classify_type:
|
| 562 |
+
if is_internal_fillet(face, shape):
|
| 563 |
+
fillet_type = "internal_fillet"
|
| 564 |
+
else:
|
| 565 |
+
fillet_type = "external_fillet"
|
| 566 |
+
else:
|
| 567 |
+
fillet_type = "fillet"
|
| 568 |
+
|
| 569 |
+
fillet_candidates.append({
|
| 570 |
+
"face_id": face_idx,
|
| 571 |
+
"face": face,
|
| 572 |
+
"name": f"FILLET_FACE_{face_idx}",
|
| 573 |
+
"type": fillet_type,
|
| 574 |
+
"radius": float(radius),
|
| 575 |
+
"center": [center.X(), center.Y(), center.Z()],
|
| 576 |
+
"axis_direction": axis_dir_vec,
|
| 577 |
+
"axis_location": axis_loc_vec,
|
| 578 |
+
"area": float(area),
|
| 579 |
+
"angle_span": float(angle_span),
|
| 580 |
+
})
|
| 581 |
+
|
| 582 |
+
face_idx += 1
|
| 583 |
+
|
| 584 |
+
merged_fillets = merge_coaxial_fillets(
|
| 585 |
+
fillet_candidates,
|
| 586 |
+
tolerance=fillet_merge_tolerance,
|
| 587 |
+
min_total_angle_deg=min_total_angle_deg,
|
| 588 |
+
max_total_angle_deg=max_total_angle_deg,
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
print(f"Detected {len(merged_fillets)} fillet features (from {len(fillet_candidates)} cylindrical faces)")
|
| 592 |
+
|
| 593 |
+
return merged_fillets
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
def is_fillet_surface(face, shape, radius, area,
|
| 597 |
+
full_angle_threshold_deg=330.0,
|
| 598 |
+
min_angle_threshold_deg=3.0):
|
| 599 |
+
|
| 600 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 601 |
+
if adaptor.GetType() != GeomAbs_Cylinder:
|
| 602 |
+
return False
|
| 603 |
+
|
| 604 |
+
cylinder = adaptor.Cylinder()
|
| 605 |
+
axis_direction = cylinder.Axis().Direction()
|
| 606 |
+
axis_location = cylinder.Axis().Location()
|
| 607 |
+
|
| 608 |
+
axis_dir_vec = [axis_direction.X(), axis_direction.Y(), axis_direction.Z()]
|
| 609 |
+
axis_loc_vec = [axis_location.X(), axis_location.Y(), axis_location.Z()]
|
| 610 |
+
|
| 611 |
+
angle_span = compute_face_angle_span(face, axis_loc_vec, axis_dir_vec)
|
| 612 |
+
|
| 613 |
+
if angle_span >= full_angle_threshold_deg:
|
| 614 |
+
return False
|
| 615 |
+
|
| 616 |
+
if angle_span <= min_angle_threshold_deg:
|
| 617 |
+
return False
|
| 618 |
+
|
| 619 |
+
return True
|
| 620 |
+
|
| 621 |
+
|
| 622 |
+
def is_internal_fillet(face, shape):
|
| 623 |
+
"""
|
| 624 |
+
Determine if a cylindrical fillet is internal (concave) or external (convex)
|
| 625 |
+
WITHOUT using adaptor.Normal (which does not exist).
|
| 626 |
+
|
| 627 |
+
Method:
|
| 628 |
+
- Get point P(u,v) on surface
|
| 629 |
+
- Compute radial direction = (P - axis_location) - projection onto axis_dir
|
| 630 |
+
- Normal = normalized radial direction
|
| 631 |
+
- Offset P slightly along normal, test if inside solid
|
| 632 |
+
"""
|
| 633 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 634 |
+
|
| 635 |
+
# Must be cylinder
|
| 636 |
+
if adaptor.GetType() != GeomAbs_Cylinder:
|
| 637 |
+
return False
|
| 638 |
+
|
| 639 |
+
cylinder = adaptor.Cylinder()
|
| 640 |
+
|
| 641 |
+
# Cylinder axis
|
| 642 |
+
axis = cylinder.Axis()
|
| 643 |
+
axis_loc = axis.Location()
|
| 644 |
+
axis_dir = axis.Direction()
|
| 645 |
+
axis_dir_vec = gp_Vec(axis_dir.X(), axis_dir.Y(), axis_dir.Z())
|
| 646 |
+
axis_dir_vec.Normalize()
|
| 647 |
+
|
| 648 |
+
# Pick midpoint parameter
|
| 649 |
+
u_min, u_max = adaptor.FirstUParameter(), adaptor.LastUParameter()
|
| 650 |
+
v_min, v_max = adaptor.FirstVParameter(), adaptor.LastVParameter()
|
| 651 |
+
u_mid = 0.5 * (u_min + u_max)
|
| 652 |
+
v_mid = 0.5 * (v_min + v_max)
|
| 653 |
+
|
| 654 |
+
# Point on surface
|
| 655 |
+
p = adaptor.Value(u_mid, v_mid)
|
| 656 |
+
|
| 657 |
+
# Compute radial vector: r = (p - axis_loc) - projection onto axis_dir
|
| 658 |
+
vec_p_axis = gp_Vec(p.X() - axis_loc.X(),
|
| 659 |
+
p.Y() - axis_loc.Y(),
|
| 660 |
+
p.Z() - axis_loc.Z())
|
| 661 |
+
# Projection length
|
| 662 |
+
proj_len = vec_p_axis.Dot(axis_dir_vec)
|
| 663 |
+
# Projection vector
|
| 664 |
+
proj_vec = gp_Vec(axis_dir_vec.X() * proj_len,
|
| 665 |
+
axis_dir_vec.Y() * proj_len,
|
| 666 |
+
axis_dir_vec.Z() * proj_len)
|
| 667 |
+
# Radial direction (perpendicular to axis)
|
| 668 |
+
radial_vec = gp_Vec(vec_p_axis.X() - proj_vec.X(),
|
| 669 |
+
vec_p_axis.Y() - proj_vec.Y(),
|
| 670 |
+
vec_p_axis.Z() - proj_vec.Z())
|
| 671 |
+
radial_vec.Normalize()
|
| 672 |
+
|
| 673 |
+
# Normal = radial direction
|
| 674 |
+
normal = radial_vec
|
| 675 |
+
|
| 676 |
+
# Offset point slightly along normal
|
| 677 |
+
offset = 1e-4
|
| 678 |
+
offset_p = gp_Pnt(p.X() + normal.X() * offset,
|
| 679 |
+
p.Y() + normal.Y() * offset,
|
| 680 |
+
p.Z() + normal.Z() * offset)
|
| 681 |
+
|
| 682 |
+
# Test whether it's inside
|
| 683 |
+
classifier = BRepClass3d_SolidClassifier(shape)
|
| 684 |
+
classifier.Perform(offset_p, 1e-7)
|
| 685 |
+
|
| 686 |
+
# If offset point is inside → normal pointing inward → concave fillet
|
| 687 |
+
return classifier.State() == TopAbs_IN
|
| 688 |
+
|
| 689 |
+
|
| 690 |
+
def merge_coaxial_fillets(faces, tolerance=1e-3,
|
| 691 |
+
min_total_angle_deg=3.0,
|
| 692 |
+
max_total_angle_deg=330.0):
|
| 693 |
+
if not faces:
|
| 694 |
+
return []
|
| 695 |
+
|
| 696 |
+
import numpy as np
|
| 697 |
+
|
| 698 |
+
merged_fillets = []
|
| 699 |
+
used = [False] * len(faces)
|
| 700 |
+
|
| 701 |
+
for i, f1 in enumerate(faces):
|
| 702 |
+
if used[i]:
|
| 703 |
+
continue
|
| 704 |
+
|
| 705 |
+
group = [f1]
|
| 706 |
+
used[i] = True
|
| 707 |
+
|
| 708 |
+
for j, f2 in enumerate(faces):
|
| 709 |
+
if used[j] or i == j:
|
| 710 |
+
continue
|
| 711 |
+
if f1.get("type") != f2.get("type"):
|
| 712 |
+
continue
|
| 713 |
+
|
| 714 |
+
if abs(f1["radius"] - f2["radius"]) > tolerance:
|
| 715 |
+
continue
|
| 716 |
+
|
| 717 |
+
if not are_coaxial(f1, f2, tolerance=tolerance):
|
| 718 |
+
continue
|
| 719 |
+
|
| 720 |
+
group.append(f2)
|
| 721 |
+
used[j] = True
|
| 722 |
+
|
| 723 |
+
total_angle = sum(g.get("angle_span", 0.0) for g in group)
|
| 724 |
+
total_angle = min(total_angle, 360.0)
|
| 725 |
+
|
| 726 |
+
if total_angle < min_total_angle_deg:
|
| 727 |
+
continue
|
| 728 |
+
|
| 729 |
+
if total_angle > max_total_angle_deg:
|
| 730 |
+
continue
|
| 731 |
+
|
| 732 |
+
merged = merge_fillet_coaxial_group(group, len(merged_fillets), total_angle)
|
| 733 |
+
merged_fillets.append(merged)
|
| 734 |
+
|
| 735 |
+
return merged_fillets
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
def merge_fillet_coaxial_group(fillet_group, group_index, total_angle):
|
| 739 |
+
if not fillet_group:
|
| 740 |
+
return {}
|
| 741 |
+
|
| 742 |
+
areas = np.array([f["area"] for f in fillet_group], dtype=float)
|
| 743 |
+
radii = np.array([f["radius"] for f in fillet_group], dtype=float)
|
| 744 |
+
centers = np.array([f["center"] for f in fillet_group], dtype=float)
|
| 745 |
+
axis_dirs = np.array([f["axis_direction"] for f in fillet_group], dtype=float)
|
| 746 |
+
axis_locs = np.array([f["axis_location"] for f in fillet_group], dtype=float)
|
| 747 |
+
|
| 748 |
+
total_area = float(areas.sum()) if areas.size > 0 else 0.0
|
| 749 |
+
|
| 750 |
+
if total_area > 0:
|
| 751 |
+
avg_center = (centers * areas[:, None]).sum(axis=0) / total_area
|
| 752 |
+
avg_radius = float((radii * areas).sum() / total_area)
|
| 753 |
+
avg_axis_dir = (axis_dirs * areas[:, None]).sum(axis=0) / total_area
|
| 754 |
+
avg_axis_loc = (axis_locs * areas[:, None]).sum(axis=0) / total_area
|
| 755 |
+
else:
|
| 756 |
+
avg_center = centers.mean(axis=0)
|
| 757 |
+
avg_radius = float(radii.mean())
|
| 758 |
+
avg_axis_dir = axis_dirs.mean(axis=0)
|
| 759 |
+
avg_axis_loc = axis_locs.mean(axis=0)
|
| 760 |
+
|
| 761 |
+
norm = np.linalg.norm(avg_axis_dir)
|
| 762 |
+
if norm > 0:
|
| 763 |
+
avg_axis_dir = avg_axis_dir / norm
|
| 764 |
+
|
| 765 |
+
avg_center = np.round(avg_center, 6)
|
| 766 |
+
avg_axis_dir = np.round(avg_axis_dir, 6)
|
| 767 |
+
avg_axis_loc = np.round(avg_axis_loc, 6)
|
| 768 |
+
|
| 769 |
+
faces_info = [
|
| 770 |
+
{
|
| 771 |
+
"face_id": f.get("face_id"),
|
| 772 |
+
"center": [round(x, 6) for x in f["center"]],
|
| 773 |
+
"area": round(f["area"], 6),
|
| 774 |
+
"angle_span": round(f.get("angle_span", 0.0), 4),
|
| 775 |
+
"normal": [round(x, 6) for x in f.get("axis_direction", avg_axis_dir.tolist())],
|
| 776 |
+
}
|
| 777 |
+
for f in fillet_group
|
| 778 |
+
]
|
| 779 |
+
|
| 780 |
+
fillet_type = fillet_group[0].get("type", "fillet")
|
| 781 |
+
|
| 782 |
+
return {
|
| 783 |
+
"name": f"FILLET_{group_index}",
|
| 784 |
+
"type": fillet_type,
|
| 785 |
+
"radius": round(avg_radius, 6),
|
| 786 |
+
"center": [float(x) for x in avg_center],
|
| 787 |
+
"normal": [float(x) for x in avg_axis_dir],
|
| 788 |
+
"axis_direction": [float(x) for x in avg_axis_dir],
|
| 789 |
+
"axis_location": [float(x) for x in avg_axis_loc],
|
| 790 |
+
"area": round(total_area, 6),
|
| 791 |
+
"num_faces": len(fillet_group),
|
| 792 |
+
"total_angle": round(float(total_angle), 4),
|
| 793 |
+
"faces": faces_info,
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
|
| 797 |
+
|
| 798 |
+
# def classify_fillets_by_size(fillets):
|
| 799 |
+
# """
|
| 800 |
+
# Classify fillets into categories based on radius for FEA mesh refinement.
|
| 801 |
+
# """
|
| 802 |
+
# classification = {
|
| 803 |
+
# "small": [], # R < 2mm - need very fine mesh
|
| 804 |
+
# "medium": [], # 2mm <= R < 5mm - need fine mesh
|
| 805 |
+
# "large": [] # R >= 5mm - standard mesh
|
| 806 |
+
# }
|
| 807 |
+
|
| 808 |
+
# for fillet in fillets:
|
| 809 |
+
# radius = fillet["radius"]
|
| 810 |
+
# if radius < 2.0:
|
| 811 |
+
# classification["small"].append(fillet)
|
| 812 |
+
# elif radius < 5.0:
|
| 813 |
+
# classification["medium"].append(fillet)
|
| 814 |
+
# else:
|
| 815 |
+
# classification["large"].append(fillet)
|
| 816 |
+
|
| 817 |
+
# print(f"\nFillet Classification:")
|
| 818 |
+
# print(f" Small fillets (R<2mm): {len(classification['small'])}")
|
| 819 |
+
# print(f" Medium fillets (2-5mm): {len(classification['medium'])}")
|
| 820 |
+
# print(f" Large fillets (R>5mm): {len(classification['large'])}")
|
| 821 |
+
|
| 822 |
+
# return classification
|
| 823 |
+
|
| 824 |
+
def detect_thickness(shape, sample_density=3, min_thickness=0.01, max_thickness=100.0):
|
| 825 |
+
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
| 826 |
+
from OCP.gp import gp_Pnt
|
| 827 |
+
thickness_results = []
|
| 828 |
+
exp = TopExp_Explorer(shape, TopAbs_FACE)
|
| 829 |
+
classifier = BRepClass3d_SolidClassifier(shape)
|
| 830 |
+
while exp.More():
|
| 831 |
+
face_shape = exp.Current()
|
| 832 |
+
face = TopoDS.Face_s(face_shape)
|
| 833 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 834 |
+
umin, umax = adaptor.FirstUParameter(), adaptor.LastUParameter()
|
| 835 |
+
vmin, vmax = adaptor.FirstVParameter(), adaptor.LastVParameter()
|
| 836 |
+
for i in range(sample_density):
|
| 837 |
+
for j in range(sample_density):
|
| 838 |
+
u = umin + (umax - umin) * i / (sample_density - 1)
|
| 839 |
+
v = vmin + (vmax - vmin) * j / (sample_density - 1)
|
| 840 |
+
pnt = adaptor.Value(u, v)
|
| 841 |
+
|
| 842 |
+
p = gp_Pnt()
|
| 843 |
+
du = gp_Vec()
|
| 844 |
+
dv = gp_Vec()
|
| 845 |
+
adaptor.D1(u, v, p, du, dv)
|
| 846 |
+
|
| 847 |
+
normal_vec = du.Crossed(dv)
|
| 848 |
+
normal_vec.Normalize()
|
| 849 |
+
if normal_vec.Magnitude() < 1e-8:
|
| 850 |
+
continue
|
| 851 |
+
normal_vec.Normalize()
|
| 852 |
+
thickness = shoot_ray_find_thickness(
|
| 853 |
+
shape, pnt, normal_vec, min_thickness, max_thickness
|
| 854 |
+
)
|
| 855 |
+
if thickness:
|
| 856 |
+
thickness_results.append({
|
| 857 |
+
"point": [pnt.X(), pnt.Y(), pnt.Z()],
|
| 858 |
+
"normal": [normal_vec.X(), normal_vec.Y(), normal_vec.Z()],
|
| 859 |
+
"thickness": thickness
|
| 860 |
+
})
|
| 861 |
+
exp.Next()
|
| 862 |
+
return thickness_results
|
| 863 |
+
|
| 864 |
+
def shoot_ray_find_thickness(shape, point, normal, min_thickness, max_thickness):
|
| 865 |
+
from OCP.gp import gp_Pnt, gp_Vec
|
| 866 |
+
from OCP.BRepClass3d import BRepClass3d_SolidClassifier
|
| 867 |
+
|
| 868 |
+
step = min_thickness / 2
|
| 869 |
+
max_dist = max_thickness
|
| 870 |
+
for dist in np.arange(step, max_dist, step):
|
| 871 |
+
test_point = gp_Pnt(
|
| 872 |
+
point.X() + normal.X() * dist,
|
| 873 |
+
point.Y() + normal.Y() * dist,
|
| 874 |
+
point.Z() + normal.Z() * dist,
|
| 875 |
+
)
|
| 876 |
+
classifier = BRepClass3d_SolidClassifier(shape)
|
| 877 |
+
classifier.Perform(test_point, 1e-7)
|
| 878 |
+
if classifier.State() != TopAbs_IN:
|
| 879 |
+
pos_thick = dist
|
| 880 |
+
break
|
| 881 |
+
else:
|
| 882 |
+
pos_thick = max_dist
|
| 883 |
+
for dist in np.arange(step, max_dist, step):
|
| 884 |
+
test_point = gp_Pnt(
|
| 885 |
+
point.X() - normal.X() * dist,
|
| 886 |
+
point.Y() - normal.Y() * dist,
|
| 887 |
+
point.Z() - normal.Z() * dist,
|
| 888 |
+
)
|
| 889 |
+
classifier = BRepClass3d_SolidClassifier(shape)
|
| 890 |
+
classifier.Perform(test_point, 1e-7)
|
| 891 |
+
if classifier.State() != TopAbs_IN:
|
| 892 |
+
neg_thick = dist
|
| 893 |
+
break
|
| 894 |
+
else:
|
| 895 |
+
neg_thick = max_dist
|
| 896 |
+
return pos_thick + neg_thick
|
| 897 |
+
|
| 898 |
+
def mode_thickness(shape, decimal=2, threshold=0.1):
|
| 899 |
+
thickness_results = detect_thickness(shape, sample_density=3, min_thickness=0.01, max_thickness=100.0)
|
| 900 |
+
vals = [round(float(r['thickness']), decimal) for r in thickness_results if float(r['thickness']) >= threshold]
|
| 901 |
+
counter = Counter(vals)
|
| 902 |
+
mode_val, count = counter.most_common(1)[0]
|
| 903 |
+
return mode_val
|
| 904 |
+
|
| 905 |
+
def detect_all_edges(shape):
|
| 906 |
+
"""
|
| 907 |
+
Detect all UNIQUE edges shared by two faces.
|
| 908 |
+
Stores: name, start point, end point, angle, normals, type.
|
| 909 |
+
"""
|
| 910 |
+
|
| 911 |
+
# 1. Collect all faces
|
| 912 |
+
faces = []
|
| 913 |
+
face_exp = TopExp_Explorer(shape, TopAbs_FACE)
|
| 914 |
+
while face_exp.More():
|
| 915 |
+
faces.append(TopoDS.Face_s(face_exp.Current()))
|
| 916 |
+
face_exp.Next()
|
| 917 |
+
|
| 918 |
+
results = []
|
| 919 |
+
seen = set() # edge deduplication via TShape()
|
| 920 |
+
idx = 0 # MUST initialize once
|
| 921 |
+
|
| 922 |
+
# 2. Iterate edges
|
| 923 |
+
edge_exp = TopExp_Explorer(shape, TopAbs_EDGE)
|
| 924 |
+
while edge_exp.More():
|
| 925 |
+
edge = TopoDS.Edge_s(edge_exp.Current())
|
| 926 |
+
|
| 927 |
+
# --- dedup by underlying TShape ---
|
| 928 |
+
tid = hash(edge.TShape())
|
| 929 |
+
if tid in seen:
|
| 930 |
+
edge_exp.Next()
|
| 931 |
+
continue
|
| 932 |
+
seen.add(tid)
|
| 933 |
+
|
| 934 |
+
# --- find adjacent faces ---
|
| 935 |
+
adjacent_faces = []
|
| 936 |
+
for face in faces:
|
| 937 |
+
ex2 = TopExp_Explorer(face, TopAbs_EDGE)
|
| 938 |
+
while ex2.More():
|
| 939 |
+
if ex2.Current().IsSame(edge):
|
| 940 |
+
adjacent_faces.append(face)
|
| 941 |
+
break
|
| 942 |
+
ex2.Next()
|
| 943 |
+
|
| 944 |
+
if len(adjacent_faces) != 2:
|
| 945 |
+
edge_exp.Next()
|
| 946 |
+
continue
|
| 947 |
+
|
| 948 |
+
# --- compute normals & dihedral angle ---
|
| 949 |
+
normals = []
|
| 950 |
+
for face in adjacent_faces:
|
| 951 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 952 |
+
u = 0.5 * (adaptor.FirstUParameter() + adaptor.LastUParameter())
|
| 953 |
+
v = 0.5 * (adaptor.FirstVParameter() + adaptor.LastVParameter())
|
| 954 |
+
du = gp_Vec()
|
| 955 |
+
dv = gp_Vec()
|
| 956 |
+
temp_p = gp_Pnt()
|
| 957 |
+
adaptor.D1(u, v, temp_p, du, dv)
|
| 958 |
+
n = du.Crossed(dv)
|
| 959 |
+
if n.Magnitude() > 1e-6:
|
| 960 |
+
n.Normalize()
|
| 961 |
+
normals.append(n)
|
| 962 |
+
else:
|
| 963 |
+
normals.append(None)
|
| 964 |
+
|
| 965 |
+
if None in normals:
|
| 966 |
+
edge_exp.Next()
|
| 967 |
+
continue
|
| 968 |
+
|
| 969 |
+
# angle
|
| 970 |
+
import numpy as np
|
| 971 |
+
dot = np.clip(normals[0].Dot(normals[1]), -1.0, 1.0)
|
| 972 |
+
angle_deg = float(np.degrees(np.arccos(dot)))
|
| 973 |
+
|
| 974 |
+
# skip perfectly smooth edges
|
| 975 |
+
if abs(angle_deg - 180.0) < 1e-3:
|
| 976 |
+
edge_exp.Next()
|
| 977 |
+
continue
|
| 978 |
+
|
| 979 |
+
# --- get start / end points ---
|
| 980 |
+
verts = []
|
| 981 |
+
vexp = TopExp_Explorer(edge, TopAbs_VERTEX)
|
| 982 |
+
while vexp.More():
|
| 983 |
+
v = TopoDS.Vertex_s(vexp.Current())
|
| 984 |
+
p = BRep_Tool.Pnt_s(v)
|
| 985 |
+
verts.append([p.X(), p.Y(), p.Z()])
|
| 986 |
+
vexp.Next()
|
| 987 |
+
|
| 988 |
+
if len(verts) < 2:
|
| 989 |
+
edge_exp.Next()
|
| 990 |
+
continue
|
| 991 |
+
|
| 992 |
+
start = verts[0]
|
| 993 |
+
end = verts[-1]
|
| 994 |
+
|
| 995 |
+
# --- classify edge type ---
|
| 996 |
+
curve_adaptor = BRepAdaptor_Curve(edge)
|
| 997 |
+
from OCP.GeomAbs import GeomAbs_Line, GeomAbs_Circle
|
| 998 |
+
t = curve_adaptor.GetType()
|
| 999 |
+
|
| 1000 |
+
if t == GeomAbs_Line:
|
| 1001 |
+
e_type = "line"
|
| 1002 |
+
elif t == GeomAbs_Circle:
|
| 1003 |
+
e_type = "circle"
|
| 1004 |
+
else:
|
| 1005 |
+
e_type = "other"
|
| 1006 |
+
|
| 1007 |
+
# --- store result ---
|
| 1008 |
+
results.append({
|
| 1009 |
+
"name": f"EDGE_{idx}",
|
| 1010 |
+
"start": start,
|
| 1011 |
+
"end": end,
|
| 1012 |
+
"angle": angle_deg,
|
| 1013 |
+
"normals": [
|
| 1014 |
+
[normals[0].X(), normals[0].Y(), normals[0].Z()],
|
| 1015 |
+
[normals[1].X(), normals[1].Y(), normals[1].Z()]
|
| 1016 |
+
],
|
| 1017 |
+
"edge_type": e_type
|
| 1018 |
+
})
|
| 1019 |
+
|
| 1020 |
+
idx += 1 # safe!
|
| 1021 |
+
|
| 1022 |
+
edge_exp.Next()
|
| 1023 |
+
|
| 1024 |
+
return results
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
|
| 1028 |
+
|
| 1029 |
+
def detect_all_faces(shape):
|
| 1030 |
+
faces = []
|
| 1031 |
+
face_exp = TopExp_Explorer(shape, TopAbs_FACE)
|
| 1032 |
+
face_id = 0
|
| 1033 |
+
|
| 1034 |
+
while face_exp.More():
|
| 1035 |
+
face_shape = face_exp.Current()
|
| 1036 |
+
face = TopoDS.Face_s(face_shape)
|
| 1037 |
+
|
| 1038 |
+
adaptor = BRepAdaptor_Surface(face, True)
|
| 1039 |
+
|
| 1040 |
+
# sample param
|
| 1041 |
+
umin, umax = adaptor.FirstUParameter(), adaptor.LastUParameter()
|
| 1042 |
+
vmin, vmax = adaptor.FirstVParameter(), adaptor.LastVParameter()
|
| 1043 |
+
u = (umin + umax) * 0.5
|
| 1044 |
+
v = (vmin + vmax) * 0.5
|
| 1045 |
+
|
| 1046 |
+
# center
|
| 1047 |
+
|
| 1048 |
+
|
| 1049 |
+
# normal
|
| 1050 |
+
du = gp_Vec()
|
| 1051 |
+
dv = gp_Vec()
|
| 1052 |
+
temp_p = gp_Pnt()
|
| 1053 |
+
adaptor.D1(u, v, temp_p, du, dv)
|
| 1054 |
+
normal = du.Crossed(dv)
|
| 1055 |
+
if normal.Magnitude() > 1e-8:
|
| 1056 |
+
normal.Normalize()
|
| 1057 |
+
|
| 1058 |
+
# --- NEW: compute area ---
|
| 1059 |
+
gprops = GProp_GProps()
|
| 1060 |
+
BRepGProp.SurfaceProperties_s(face, gprops)
|
| 1061 |
+
area = gprops.Mass()
|
| 1062 |
+
center = face_bbox_center(face)
|
| 1063 |
+
|
| 1064 |
+
faces.append({
|
| 1065 |
+
"name": f"FACE_{face_id}",
|
| 1066 |
+
"face_id": face_id,
|
| 1067 |
+
"center": center,
|
| 1068 |
+
"normal": [normal.X(), normal.Y(), normal.Z()],
|
| 1069 |
+
"area": float(area) # ← added
|
| 1070 |
+
})
|
| 1071 |
+
|
| 1072 |
+
face_id += 1
|
| 1073 |
+
face_exp.Next()
|
| 1074 |
+
|
| 1075 |
+
return faces
|