├── .gitignore ├── .gitmodules ├── Bethany ├── README.md ├── create_datasets.sh ├── examples │ ├── 00000069.json │ ├── 00001005.json │ ├── 00001010.json │ ├── 00001011.json │ ├── 00001616.json │ ├── 00003763.json │ ├── 00003877.json │ ├── 00003999.json │ ├── 00004691.json │ ├── 00005081.json │ ├── 00005082.json │ ├── 00015361.json │ ├── 00280150.json │ ├── 00529152.json │ ├── 00633023.json │ └── 00809394.json ├── json2dataset.py ├── json2py.py ├── json2step.py ├── lib │ ├── DataExchange.py │ ├── __init__.py │ ├── cad2code.py │ ├── curves.py │ ├── extrude.py │ ├── file_utils.py │ ├── macro.py │ ├── math_utils.py │ ├── sketch.py │ ├── timeout.py │ └── visualize.py ├── png2jpg.py ├── py2step.py ├── requirements.txt └── step2img.py ├── LICENSE ├── Media ├── hand-drawn.png ├── logo.drawio └── logo.png ├── OpenECAD_TinyLLaVA.patch ├── README.md ├── aioscript.sh ├── run_model.ipynb └── run_model.py /.gitignore: -------------------------------------------------------------------------------- 1 | data/ 2 | __pycache__/ 3 | *.tar* 4 | *_json/ 5 | *_step/ 6 | *_image/ 7 | *.7z 8 | data.json 9 | output.* 10 | _*/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "TinyLLaVA_Factory"] 2 | path = TinyLLaVA_Factory 3 | url = https://github.com/TinyLLaVA/TinyLLaVA_Factory.git 4 | [submodule "OpenECADv2toSTEP"] 5 | path = OpenECADv2toSTEP 6 | url = https://github.com/Yuki-Kokomi/OpenECADv2toSTEP.git 7 | -------------------------------------------------------------------------------- /Bethany/README.md: -------------------------------------------------------------------------------- 1 | # Bethany 2 | 3 | ## Prerequisites 4 | 5 | - Linux 6 | - Python 3.10 7 | 8 | ## Dependencies 9 | 10 | Install python package dependencies through pip: 11 | 12 | ```bash 13 | $ pip install -r requirements.txt 14 | ``` 15 | 16 | Install [pythonocc](https://github.com/tpaviot/pythonocc-core) (OpenCASCADE) by conda: 17 | 18 | ```bash 19 | $ conda install -c conda-forge pythonocc-core=7.5.1 20 | ``` -------------------------------------------------------------------------------- /Bethany/create_datasets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ENV_NAME=Bethany 4 | 5 | CONDA_INIT_SCRIPT="$(conda info --base)/etc/profile.d/conda.sh" 6 | 7 | if [ -f "$CONDA_INIT_SCRIPT" ]; then 8 | # 初始化 conda 9 | source "$CONDA_INIT_SCRIPT" 10 | else 11 | echo "Error: Cannot find conda initialization script." 12 | exit 1 13 | fi 14 | 15 | conda activate "$ENV_NAME" 16 | 17 | 18 | conda activate Bethany 19 | 20 | JSON_SOURCE=./examples/ 21 | 22 | OUT_DIR=./examples/_out 23 | 24 | # python json2step.py --src $JSON_SOURCE -o $OUT_DIR/steps_segment/ --mode segment 25 | # python step2img.py --src $OUT_DIR/steps_segment/ -o $OUT_DIR/images_segment/ --mode segment 26 | # python img2seg.py --src $OUT_DIR/images_segment/ -o $OUT_DIR/images_mask --srcc $OUT_DIR/images_transparent/ 27 | 28 | python json2step.py --src $JSON_SOURCE -o $OUT_DIR/steps_default/ --mode default 29 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_default/ --mode default 30 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_transparent/ --mode transparent 31 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_orthographic/ --mode orthographic 32 | 33 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_default_directout_3k.json --ignore True --mode default --token 3072 34 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_transparent_directout_3k.json --ignore True --mode transparent --token 3072 35 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_orthographic_directout_3k.json --ignore True --mode orthographic --token 3072 36 | python json2py.py --src $JSON_SOURCE -o $OUT_DIR/codes_python/ 37 | python py2step.py --src $OUT_DIR/codes_python/ -o $OUT_DIR/steps_from_py/ 38 | python step2img.py --src $OUT_DIR/steps_from_py/ -o $OUT_DIR/images_from_py/ 39 | 40 | conda deactivate 41 | -------------------------------------------------------------------------------- /Bethany/examples/00003763.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "F94fSs5Rrru6Csr_1": { 4 | "name": "Extrude 4", 5 | "type": "ExtrudeFeature", 6 | "profiles": [ 7 | { 8 | "profile": "JRG", 9 | "sketch": "FgQr27bSD2lOoPV_1" 10 | } 11 | ], 12 | "extent_two": { 13 | "distance": { 14 | "type": "ModelParameter", 15 | "role": "AgainstDistance", 16 | "name": "none", 17 | "value": 0.0 18 | }, 19 | "type": "DistanceExtentDefinition", 20 | "taper_angle": { 21 | "type": "ModelParameter", 22 | "role": "Side2TaperAngle", 23 | "name": "none", 24 | "value": 0.0 25 | } 26 | }, 27 | "extent_one": { 28 | "distance": { 29 | "type": "ModelParameter", 30 | "role": "AlongDistance", 31 | "name": "none", 32 | "value": -0.030457750000000002 33 | }, 34 | "type": "DistanceExtentDefinition", 35 | "taper_angle": { 36 | "type": "ModelParameter", 37 | "role": "TaperAngle", 38 | "name": "none", 39 | "value": 0.0 40 | } 41 | }, 42 | "operation": "NewBodyFeatureOperation", 43 | "start_extent": { 44 | "type": "ProfilePlaneStartDefinition" 45 | }, 46 | "extent_type": "OneSideFeatureExtentType" 47 | }, 48 | "FJvbya9Ckc0JTKP_0": { 49 | "name": "Extrude 1", 50 | "type": "ExtrudeFeature", 51 | "profiles": [ 52 | { 53 | "profile": "JGC", 54 | "sketch": "F1cZkOtDYlVqWtk_0" 55 | } 56 | ], 57 | "extent_two": { 58 | "distance": { 59 | "type": "ModelParameter", 60 | "role": "AgainstDistance", 61 | "name": "none", 62 | "value": 0.0 63 | }, 64 | "type": "DistanceExtentDefinition", 65 | "taper_angle": { 66 | "type": "ModelParameter", 67 | "role": "Side2TaperAngle", 68 | "name": "none", 69 | "value": 0.0 70 | } 71 | }, 72 | "extent_one": { 73 | "distance": { 74 | "type": "ModelParameter", 75 | "role": "AlongDistance", 76 | "name": "none", 77 | "value": 0.10793309 78 | }, 79 | "type": "DistanceExtentDefinition", 80 | "taper_angle": { 81 | "type": "ModelParameter", 82 | "role": "TaperAngle", 83 | "name": "none", 84 | "value": 0.0 85 | } 86 | }, 87 | "operation": "NewBodyFeatureOperation", 88 | "start_extent": { 89 | "type": "ProfilePlaneStartDefinition" 90 | }, 91 | "extent_type": "OneSideFeatureExtentType" 92 | }, 93 | "F34Rmfkp42JY8ON_1": { 94 | "transform": { 95 | "origin": { 96 | "y": -0.23403896, 97 | "x": 0.0, 98 | "z": 0.0 99 | }, 100 | "y_axis": { 101 | "y": 0.0, 102 | "x": -0.0, 103 | "z": 1.0 104 | }, 105 | "x_axis": { 106 | "y": 0.0, 107 | "x": 1.0, 108 | "z": 0.0 109 | }, 110 | "z_axis": { 111 | "y": -1.0, 112 | "x": 0.0, 113 | "z": 0.0 114 | } 115 | }, 116 | "type": "Sketch", 117 | "name": "Sketch 3", 118 | "profiles": { 119 | "JNC": { 120 | "loops": [ 121 | { 122 | "is_outer": true, 123 | "profile_curves": [ 124 | { 125 | "center_point": { 126 | "y": 0.0, 127 | "x": 0.0, 128 | "z": 0.0 129 | }, 130 | "type": "Circle3D", 131 | "radius": 0.02144662, 132 | "curve": "JNB", 133 | "normal": { 134 | "y": -1.0, 135 | "x": 0.0, 136 | "z": 0.0 137 | } 138 | } 139 | ] 140 | } 141 | ], 142 | "properties": {} 143 | } 144 | }, 145 | "reference_plane": {} 146 | }, 147 | "FZopFG87VXSkIrt_1": { 148 | "name": "Extrude 2", 149 | "type": "ExtrudeFeature", 150 | "profiles": [ 151 | { 152 | "profile": "JJC", 153 | "sketch": "FKI3A6vhIzpZNfN_1" 154 | } 155 | ], 156 | "extent_two": { 157 | "distance": { 158 | "type": "ModelParameter", 159 | "role": "AgainstDistance", 160 | "name": "none", 161 | "value": 0.0 162 | }, 163 | "type": "DistanceExtentDefinition", 164 | "taper_angle": { 165 | "type": "ModelParameter", 166 | "role": "Side2TaperAngle", 167 | "name": "none", 168 | "value": 0.0 169 | } 170 | }, 171 | "extent_one": { 172 | "distance": { 173 | "type": "ModelParameter", 174 | "role": "AlongDistance", 175 | "name": "none", 176 | "value": 0.12610587 177 | }, 178 | "type": "DistanceExtentDefinition", 179 | "taper_angle": { 180 | "type": "ModelParameter", 181 | "role": "TaperAngle", 182 | "name": "none", 183 | "value": 0.0 184 | } 185 | }, 186 | "operation": "JoinFeatureOperation", 187 | "start_extent": { 188 | "type": "ProfilePlaneStartDefinition" 189 | }, 190 | "extent_type": "OneSideFeatureExtentType" 191 | }, 192 | "F1cZkOtDYlVqWtk_0": { 193 | "transform": { 194 | "origin": { 195 | "y": 0.0, 196 | "x": 0.0, 197 | "z": 0.0 198 | }, 199 | "y_axis": { 200 | "y": 0.0, 201 | "x": -0.0, 202 | "z": 1.0 203 | }, 204 | "x_axis": { 205 | "y": 0.0, 206 | "x": 1.0, 207 | "z": 0.0 208 | }, 209 | "z_axis": { 210 | "y": -1.0, 211 | "x": 0.0, 212 | "z": 0.0 213 | } 214 | }, 215 | "type": "Sketch", 216 | "name": "Sketch 1", 217 | "profiles": { 218 | "JGC": { 219 | "loops": [ 220 | { 221 | "is_outer": true, 222 | "profile_curves": [ 223 | { 224 | "center_point": { 225 | "y": 0.0, 226 | "x": 0.0, 227 | "z": 0.0 228 | }, 229 | "type": "Circle3D", 230 | "radius": 0.04175514, 231 | "curve": "JGB", 232 | "normal": { 233 | "y": -1.0, 234 | "x": 0.0, 235 | "z": 0.0 236 | } 237 | } 238 | ] 239 | } 240 | ], 241 | "properties": {} 242 | } 243 | }, 244 | "reference_plane": {} 245 | }, 246 | "FgQr27bSD2lOoPV_1": { 247 | "transform": { 248 | "origin": { 249 | "y": -0.23403896, 250 | "x": 0.0, 251 | "z": 0.0 252 | }, 253 | "y_axis": { 254 | "y": 0.0, 255 | "x": -0.0, 256 | "z": 1.0 257 | }, 258 | "x_axis": { 259 | "y": 0.0, 260 | "x": 1.0, 261 | "z": 0.0 262 | }, 263 | "z_axis": { 264 | "y": -1.0, 265 | "x": 0.0, 266 | "z": 0.0 267 | } 268 | }, 269 | "type": "Sketch", 270 | "name": "Sketch 4", 271 | "profiles": { 272 | "JRG": { 273 | "loops": [ 274 | { 275 | "is_outer": true, 276 | "profile_curves": [ 277 | { 278 | "center_point": { 279 | "y": 0.0, 280 | "x": 0.0, 281 | "z": 0.0 282 | }, 283 | "type": "Circle3D", 284 | "radius": 0.05449525, 285 | "curve": "JRF", 286 | "normal": { 287 | "y": -1.0, 288 | "x": 0.0, 289 | "z": 0.0 290 | } 291 | } 292 | ] 293 | }, 294 | { 295 | "is_outer": true, 296 | "profile_curves": [ 297 | { 298 | "center_point": { 299 | "y": 0.0, 300 | "x": 0.0, 301 | "z": 0.0 302 | }, 303 | "type": "Circle3D", 304 | "radius": 0.02891399, 305 | "curve": "JRJ", 306 | "normal": { 307 | "y": -1.0, 308 | "x": 0.0, 309 | "z": 0.0 310 | } 311 | } 312 | ] 313 | } 314 | ], 315 | "properties": {} 316 | }, 317 | "JRC": { 318 | "loops": [ 319 | { 320 | "is_outer": true, 321 | "profile_curves": [ 322 | { 323 | "center_point": { 324 | "y": 0.0, 325 | "x": 0.0, 326 | "z": 0.0 327 | }, 328 | "type": "Circle3D", 329 | "radius": 0.02144662, 330 | "curve": "JRB", 331 | "normal": { 332 | "y": -1.0, 333 | "x": 0.0, 334 | "z": 0.0 335 | } 336 | } 337 | ] 338 | } 339 | ], 340 | "properties": {} 341 | }, 342 | "JRK": { 343 | "loops": [ 344 | { 345 | "is_outer": true, 346 | "profile_curves": [ 347 | { 348 | "center_point": { 349 | "y": 0.0, 350 | "x": 0.0, 351 | "z": 0.0 352 | }, 353 | "type": "Circle3D", 354 | "radius": 0.02144662, 355 | "curve": "JRB", 356 | "normal": { 357 | "y": -1.0, 358 | "x": 0.0, 359 | "z": 0.0 360 | } 361 | } 362 | ] 363 | }, 364 | { 365 | "is_outer": true, 366 | "profile_curves": [ 367 | { 368 | "center_point": { 369 | "y": 0.0, 370 | "x": 0.0, 371 | "z": 0.0 372 | }, 373 | "type": "Circle3D", 374 | "radius": 0.02891399, 375 | "curve": "JRJ", 376 | "normal": { 377 | "y": -1.0, 378 | "x": 0.0, 379 | "z": 0.0 380 | } 381 | } 382 | ] 383 | } 384 | ], 385 | "properties": {} 386 | } 387 | }, 388 | "reference_plane": {} 389 | }, 390 | "FKI3A6vhIzpZNfN_1": { 391 | "transform": { 392 | "origin": { 393 | "y": -0.10793309, 394 | "x": 0.0, 395 | "z": 0.0 396 | }, 397 | "y_axis": { 398 | "y": 0.0, 399 | "x": -0.0, 400 | "z": 1.0 401 | }, 402 | "x_axis": { 403 | "y": 0.0, 404 | "x": 1.0, 405 | "z": 0.0 406 | }, 407 | "z_axis": { 408 | "y": -1.0, 409 | "x": 0.0, 410 | "z": 0.0 411 | } 412 | }, 413 | "type": "Sketch", 414 | "name": "Sketch 2", 415 | "profiles": { 416 | "JJC": { 417 | "loops": [ 418 | { 419 | "is_outer": true, 420 | "profile_curves": [ 421 | { 422 | "center_point": { 423 | "y": 0.0, 424 | "x": 0.0, 425 | "z": 0.0 426 | }, 427 | "type": "Circle3D", 428 | "radius": 0.02891399, 429 | "curve": "JJB", 430 | "normal": { 431 | "y": -1.0, 432 | "x": 0.0, 433 | "z": 0.0 434 | } 435 | } 436 | ] 437 | } 438 | ], 439 | "properties": {} 440 | } 441 | }, 442 | "reference_plane": {} 443 | }, 444 | "F4IsHPXzRLoUquG_1": { 445 | "name": "Extrude 3", 446 | "type": "ExtrudeFeature", 447 | "profiles": [ 448 | { 449 | "profile": "JNC", 450 | "sketch": "F34Rmfkp42JY8ON_1" 451 | } 452 | ], 453 | "extent_two": { 454 | "distance": { 455 | "type": "ModelParameter", 456 | "role": "AgainstDistance", 457 | "name": "none", 458 | "value": 0.0 459 | }, 460 | "type": "DistanceExtentDefinition", 461 | "taper_angle": { 462 | "type": "ModelParameter", 463 | "role": "Side2TaperAngle", 464 | "name": "none", 465 | "value": 0.0 466 | } 467 | }, 468 | "extent_one": { 469 | "distance": { 470 | "type": "ModelParameter", 471 | "role": "AlongDistance", 472 | "name": "none", 473 | "value": -0.26565213 474 | }, 475 | "type": "DistanceExtentDefinition", 476 | "taper_angle": { 477 | "type": "ModelParameter", 478 | "role": "TaperAngle", 479 | "name": "none", 480 | "value": 0.0 481 | } 482 | }, 483 | "operation": "CutFeatureOperation", 484 | "start_extent": { 485 | "type": "ProfilePlaneStartDefinition" 486 | }, 487 | "extent_type": "OneSideFeatureExtentType" 488 | } 489 | }, 490 | "properties": { 491 | "bounding_box": { 492 | "max_point": { 493 | "y": 0.025400000000000002, 494 | "x": 0.054495252668857574, 495 | "z": 0.054495252668857574 496 | }, 497 | "type": "BoundingBox3D", 498 | "min_point": { 499 | "y": -0.32511954, 500 | "x": -0.054495252668857574, 501 | "z": -0.054495252668857574 502 | } 503 | } 504 | }, 505 | "sequence": [ 506 | { 507 | "index": 0, 508 | "type": "Sketch", 509 | "entity": "F1cZkOtDYlVqWtk_0" 510 | }, 511 | { 512 | "index": 1, 513 | "type": "ExtrudeFeature", 514 | "entity": "FJvbya9Ckc0JTKP_0" 515 | }, 516 | { 517 | "index": 2, 518 | "type": "Sketch", 519 | "entity": "FKI3A6vhIzpZNfN_1" 520 | }, 521 | { 522 | "index": 3, 523 | "type": "ExtrudeFeature", 524 | "entity": "FZopFG87VXSkIrt_1" 525 | }, 526 | { 527 | "index": 4, 528 | "type": "Sketch", 529 | "entity": "F34Rmfkp42JY8ON_1" 530 | }, 531 | { 532 | "index": 5, 533 | "type": "ExtrudeFeature", 534 | "entity": "F4IsHPXzRLoUquG_1" 535 | }, 536 | { 537 | "index": 6, 538 | "type": "Sketch", 539 | "entity": "FgQr27bSD2lOoPV_1" 540 | }, 541 | { 542 | "index": 7, 543 | "type": "ExtrudeFeature", 544 | "entity": "F94fSs5Rrru6Csr_1" 545 | } 546 | ] 547 | } -------------------------------------------------------------------------------- /Bethany/examples/00003877.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "Fz5ZkhSFew6soRB_0": { 4 | "name": "Extrude 1", 5 | "type": "ExtrudeFeature", 6 | "profiles": [ 7 | { 8 | "profile": "JGC", 9 | "sketch": "FvqT0Llm0TkI5qe_0" 10 | } 11 | ], 12 | "extent_two": { 13 | "distance": { 14 | "type": "ModelParameter", 15 | "role": "AgainstDistance", 16 | "name": "none", 17 | "value": 0.0 18 | }, 19 | "type": "DistanceExtentDefinition", 20 | "taper_angle": { 21 | "type": "ModelParameter", 22 | "role": "Side2TaperAngle", 23 | "name": "none", 24 | "value": 0.0 25 | } 26 | }, 27 | "extent_one": { 28 | "distance": { 29 | "type": "ModelParameter", 30 | "role": "AlongDistance", 31 | "name": "none", 32 | "value": 0.02 33 | }, 34 | "type": "DistanceExtentDefinition", 35 | "taper_angle": { 36 | "type": "ModelParameter", 37 | "role": "TaperAngle", 38 | "name": "none", 39 | "value": 0.0 40 | } 41 | }, 42 | "operation": "NewBodyFeatureOperation", 43 | "start_extent": { 44 | "type": "ProfilePlaneStartDefinition" 45 | }, 46 | "extent_type": "OneSideFeatureExtentType" 47 | }, 48 | "FR6JugW7scIQZrM_1": { 49 | "transform": { 50 | "origin": { 51 | "y": 0.0, 52 | "x": 0.0, 53 | "z": 0.0 54 | }, 55 | "y_axis": { 56 | "y": 0.0, 57 | "x": -0.0, 58 | "z": 1.0 59 | }, 60 | "x_axis": { 61 | "y": 0.0, 62 | "x": 1.0, 63 | "z": 0.0 64 | }, 65 | "z_axis": { 66 | "y": -1.0, 67 | "x": 0.0, 68 | "z": 0.0 69 | } 70 | }, 71 | "type": "Sketch", 72 | "name": "Sketch 2", 73 | "profiles": { 74 | "JJC": { 75 | "loops": [ 76 | { 77 | "is_outer": true, 78 | "profile_curves": [ 79 | { 80 | "center_point": { 81 | "y": 0.01, 82 | "x": 0.0065, 83 | "z": 0.0 84 | }, 85 | "type": "Circle3D", 86 | "radius": 0.0017526, 87 | "curve": "JJB", 88 | "normal": { 89 | "y": -1.0, 90 | "x": 0.0, 91 | "z": 0.0 92 | } 93 | } 94 | ] 95 | } 96 | ], 97 | "properties": {} 98 | } 99 | }, 100 | "reference_plane": {} 101 | }, 102 | "F8dMsQ0VNGp1h7Y_1": { 103 | "name": "Extrude 2", 104 | "type": "ExtrudeFeature", 105 | "profiles": [ 106 | { 107 | "profile": "JJC", 108 | "sketch": "FR6JugW7scIQZrM_1" 109 | } 110 | ], 111 | "extent_two": { 112 | "distance": { 113 | "type": "ModelParameter", 114 | "role": "AgainstDistance", 115 | "name": "none", 116 | "value": 0.0 117 | }, 118 | "type": "DistanceExtentDefinition", 119 | "taper_angle": { 120 | "type": "ModelParameter", 121 | "role": "Side2TaperAngle", 122 | "name": "none", 123 | "value": 0.0 124 | } 125 | }, 126 | "extent_one": { 127 | "distance": { 128 | "type": "ModelParameter", 129 | "role": "AlongDistance", 130 | "name": "none", 131 | "value": -0.025 132 | }, 133 | "type": "DistanceExtentDefinition", 134 | "taper_angle": { 135 | "type": "ModelParameter", 136 | "role": "TaperAngle", 137 | "name": "none", 138 | "value": 0.0 139 | } 140 | }, 141 | "operation": "CutFeatureOperation", 142 | "start_extent": { 143 | "type": "ProfilePlaneStartDefinition" 144 | }, 145 | "extent_type": "OneSideFeatureExtentType" 146 | }, 147 | "FvqT0Llm0TkI5qe_0": { 148 | "transform": { 149 | "origin": { 150 | "y": 0.0, 151 | "x": 0.0, 152 | "z": 0.0 153 | }, 154 | "y_axis": { 155 | "y": 1.0, 156 | "x": 0.0, 157 | "z": 0.0 158 | }, 159 | "x_axis": { 160 | "y": 0.0, 161 | "x": 1.0, 162 | "z": 0.0 163 | }, 164 | "z_axis": { 165 | "y": 0.0, 166 | "x": 0.0, 167 | "z": 1.0 168 | } 169 | }, 170 | "type": "Sketch", 171 | "name": "Sketch 1", 172 | "profiles": { 173 | "JGC": { 174 | "loops": [ 175 | { 176 | "is_outer": true, 177 | "profile_curves": [ 178 | { 179 | "type": "Line3D", 180 | "start_point": { 181 | "y": 0.01, 182 | "x": 0.0, 183 | "z": 0.0 184 | }, 185 | "curve": "JGB", 186 | "end_point": { 187 | "y": 0.0, 188 | "x": 0.0, 189 | "z": 0.0 190 | } 191 | }, 192 | { 193 | "type": "Line3D", 194 | "start_point": { 195 | "y": 0.0, 196 | "x": 0.0, 197 | "z": 0.0 198 | }, 199 | "curve": "JGF", 200 | "end_point": { 201 | "y": 0.0, 202 | "x": 0.01, 203 | "z": 0.0 204 | } 205 | }, 206 | { 207 | "type": "Line3D", 208 | "start_point": { 209 | "y": 0.0, 210 | "x": 0.01, 211 | "z": 0.0 212 | }, 213 | "curve": "JGJ", 214 | "end_point": { 215 | "y": 0.003, 216 | "x": 0.01, 217 | "z": 0.0 218 | } 219 | }, 220 | { 221 | "type": "Line3D", 222 | "start_point": { 223 | "y": 0.003, 224 | "x": 0.01, 225 | "z": 0.0 226 | }, 227 | "curve": "JGN", 228 | "end_point": { 229 | "y": 0.003, 230 | "x": 0.003, 231 | "z": 0.0 232 | } 233 | }, 234 | { 235 | "type": "Line3D", 236 | "start_point": { 237 | "y": 0.003, 238 | "x": 0.003, 239 | "z": 0.0 240 | }, 241 | "curve": "JGR", 242 | "end_point": { 243 | "y": 0.01, 244 | "x": 0.003, 245 | "z": 0.0 246 | } 247 | }, 248 | { 249 | "type": "Line3D", 250 | "start_point": { 251 | "y": 0.01, 252 | "x": 0.0, 253 | "z": 0.0 254 | }, 255 | "curve": "JGV", 256 | "end_point": { 257 | "y": 0.01, 258 | "x": 0.003, 259 | "z": 0.0 260 | } 261 | } 262 | ] 263 | } 264 | ], 265 | "properties": {} 266 | } 267 | }, 268 | "reference_plane": {} 269 | } 270 | }, 271 | "properties": { 272 | "bounding_box": { 273 | "max_point": { 274 | "y": 0.01, 275 | "x": 0.01, 276 | "z": 0.02 277 | }, 278 | "type": "BoundingBox3D", 279 | "min_point": { 280 | "y": 0.0, 281 | "x": 0.0, 282 | "z": 0.0 283 | } 284 | } 285 | }, 286 | "sequence": [ 287 | { 288 | "index": 0, 289 | "type": "Sketch", 290 | "entity": "FvqT0Llm0TkI5qe_0" 291 | }, 292 | { 293 | "index": 1, 294 | "type": "ExtrudeFeature", 295 | "entity": "Fz5ZkhSFew6soRB_0" 296 | }, 297 | { 298 | "index": 2, 299 | "type": "Sketch", 300 | "entity": "FR6JugW7scIQZrM_1" 301 | }, 302 | { 303 | "index": 3, 304 | "type": "ExtrudeFeature", 305 | "entity": "F8dMsQ0VNGp1h7Y_1" 306 | } 307 | ] 308 | } -------------------------------------------------------------------------------- /Bethany/examples/00005081.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "F5c4pqDbip87cOv_1": { 4 | "name": "Extrude 2", 5 | "type": "ExtrudeFeature", 6 | "profiles": [ 7 | { 8 | "profile": "JJK", 9 | "sketch": "FZ8bL1M2q7EXt0u_1" 10 | } 11 | ], 12 | "extent_two": { 13 | "distance": { 14 | "type": "ModelParameter", 15 | "role": "AgainstDistance", 16 | "name": "none", 17 | "value": 0.0 18 | }, 19 | "type": "DistanceExtentDefinition", 20 | "taper_angle": { 21 | "type": "ModelParameter", 22 | "role": "Side2TaperAngle", 23 | "name": "none", 24 | "value": 0.0 25 | } 26 | }, 27 | "extent_one": { 28 | "distance": { 29 | "type": "ModelParameter", 30 | "role": "AlongDistance", 31 | "name": "none", 32 | "value": 0.0014 33 | }, 34 | "type": "DistanceExtentDefinition", 35 | "taper_angle": { 36 | "type": "ModelParameter", 37 | "role": "TaperAngle", 38 | "name": "none", 39 | "value": 0.0 40 | } 41 | }, 42 | "operation": "JoinFeatureOperation", 43 | "start_extent": { 44 | "type": "ProfilePlaneStartDefinition" 45 | }, 46 | "extent_type": "OneSideFeatureExtentType" 47 | }, 48 | "FZ8bL1M2q7EXt0u_1": { 49 | "transform": { 50 | "origin": { 51 | "y": -0.0, 52 | "x": 0.0, 53 | "z": 0.0016 54 | }, 55 | "y_axis": { 56 | "y": 1.0, 57 | "x": 0.0, 58 | "z": 0.0 59 | }, 60 | "x_axis": { 61 | "y": 0.0, 62 | "x": 1.0, 63 | "z": 0.0 64 | }, 65 | "z_axis": { 66 | "y": 0.0, 67 | "x": 0.0, 68 | "z": 1.0 69 | } 70 | }, 71 | "type": "Sketch", 72 | "name": "Sketch 2", 73 | "profiles": { 74 | "JJC": { 75 | "loops": [ 76 | { 77 | "is_outer": true, 78 | "profile_curves": [ 79 | { 80 | "center_point": { 81 | "y": 0.0, 82 | "x": 0.0, 83 | "z": 0.0 84 | }, 85 | "type": "Circle3D", 86 | "radius": 0.0025, 87 | "curve": "JJB", 88 | "normal": { 89 | "y": 0.0, 90 | "x": 0.0, 91 | "z": 1.0 92 | } 93 | } 94 | ] 95 | } 96 | ], 97 | "properties": {} 98 | }, 99 | "JJK": { 100 | "loops": [ 101 | { 102 | "is_outer": true, 103 | "profile_curves": [ 104 | { 105 | "center_point": { 106 | "y": 0.0, 107 | "x": 0.0, 108 | "z": 0.0 109 | }, 110 | "type": "Circle3D", 111 | "radius": 0.0025, 112 | "curve": "JJB", 113 | "normal": { 114 | "y": 0.0, 115 | "x": 0.0, 116 | "z": 1.0 117 | } 118 | } 119 | ] 120 | }, 121 | { 122 | "is_outer": true, 123 | "profile_curves": [ 124 | { 125 | "center_point": { 126 | "y": 0.0, 127 | "x": 0.0, 128 | "z": 0.0 129 | }, 130 | "type": "Circle3D", 131 | "radius": 0.00345, 132 | "curve": "JJF", 133 | "normal": { 134 | "y": 0.0, 135 | "x": 0.0, 136 | "z": 1.0 137 | } 138 | } 139 | ] 140 | } 141 | ], 142 | "properties": {} 143 | } 144 | }, 145 | "reference_plane": {} 146 | }, 147 | "F5BhbWqXcUw0QKR_0": { 148 | "transform": { 149 | "origin": { 150 | "y": 0.0, 151 | "x": 0.0, 152 | "z": 0.0 153 | }, 154 | "y_axis": { 155 | "y": 1.0, 156 | "x": 0.0, 157 | "z": 0.0 158 | }, 159 | "x_axis": { 160 | "y": 0.0, 161 | "x": 1.0, 162 | "z": 0.0 163 | }, 164 | "z_axis": { 165 | "y": 0.0, 166 | "x": 0.0, 167 | "z": 1.0 168 | } 169 | }, 170 | "type": "Sketch", 171 | "name": "Sketch 1", 172 | "profiles": { 173 | "JGC": { 174 | "loops": [ 175 | { 176 | "is_outer": true, 177 | "profile_curves": [ 178 | { 179 | "type": "Line3D", 180 | "start_point": { 181 | "y": 0.0075, 182 | "x": 0.0035, 183 | "z": 0.0 184 | }, 185 | "curve": "JGB", 186 | "end_point": { 187 | "y": 0.0075, 188 | "x": -0.0035, 189 | "z": 0.0 190 | } 191 | }, 192 | { 193 | "center_point": { 194 | "y": 0.006, 195 | "x": -0.0035, 196 | "z": 0.0 197 | }, 198 | "normal": { 199 | "y": 0.0, 200 | "x": 0.0, 201 | "z": 1.0 202 | }, 203 | "end_point": { 204 | "y": 0.006, 205 | "x": -0.005, 206 | "z": 0.0 207 | }, 208 | "start_angle": 0.0, 209 | "curve": "JGR", 210 | "end_angle": 1.5707963267948966, 211 | "radius": 0.0015, 212 | "type": "Arc3D", 213 | "start_point": { 214 | "y": 0.0075, 215 | "x": -0.0035, 216 | "z": 0.0 217 | }, 218 | "reference_vector": { 219 | "y": 1.0, 220 | "x": 0.0, 221 | "z": 0.0 222 | } 223 | }, 224 | { 225 | "type": "Line3D", 226 | "start_point": { 227 | "y": 0.006, 228 | "x": -0.005, 229 | "z": 0.0 230 | }, 231 | "curve": "JGN", 232 | "end_point": { 233 | "y": -0.006, 234 | "x": -0.005, 235 | "z": 0.0 236 | } 237 | }, 238 | { 239 | "center_point": { 240 | "y": -0.006, 241 | "x": -0.0035, 242 | "z": 0.0 243 | }, 244 | "normal": { 245 | "y": 0.0, 246 | "x": 0.0, 247 | "z": 1.0 248 | }, 249 | "end_point": { 250 | "y": -0.006, 251 | "x": -0.005, 252 | "z": 0.0 253 | }, 254 | "start_angle": 0.0, 255 | "curve": "JGV", 256 | "end_angle": 1.5707963267948966, 257 | "radius": 0.0015, 258 | "type": "Arc3D", 259 | "start_point": { 260 | "y": -0.0075, 261 | "x": -0.0035, 262 | "z": 0.0 263 | }, 264 | "reference_vector": { 265 | "y": 0.0, 266 | "x": -1.0, 267 | "z": 0.0 268 | } 269 | }, 270 | { 271 | "type": "Line3D", 272 | "start_point": { 273 | "y": -0.0075, 274 | "x": 0.0035, 275 | "z": 0.0 276 | }, 277 | "curve": "JGF", 278 | "end_point": { 279 | "y": -0.0075, 280 | "x": -0.0035, 281 | "z": 0.0 282 | } 283 | }, 284 | { 285 | "center_point": { 286 | "y": -0.006, 287 | "x": 0.0035, 288 | "z": 0.0 289 | }, 290 | "normal": { 291 | "y": 0.0, 292 | "x": 0.0, 293 | "z": 1.0 294 | }, 295 | "end_point": { 296 | "y": -0.006, 297 | "x": 0.005, 298 | "z": 0.0 299 | }, 300 | "start_angle": 0.0, 301 | "curve": "JGZ", 302 | "end_angle": 1.5707963267948966, 303 | "radius": 0.0015, 304 | "type": "Arc3D", 305 | "start_point": { 306 | "y": -0.0075, 307 | "x": 0.0035, 308 | "z": 0.0 309 | }, 310 | "reference_vector": { 311 | "y": -1.0, 312 | "x": 0.0, 313 | "z": 0.0 314 | } 315 | }, 316 | { 317 | "type": "Line3D", 318 | "start_point": { 319 | "y": 0.006, 320 | "x": 0.005, 321 | "z": 0.0 322 | }, 323 | "curve": "JGJ", 324 | "end_point": { 325 | "y": -0.006, 326 | "x": 0.005, 327 | "z": 0.0 328 | } 329 | }, 330 | { 331 | "center_point": { 332 | "y": 0.006, 333 | "x": 0.0035, 334 | "z": 0.0 335 | }, 336 | "normal": { 337 | "y": 0.0, 338 | "x": 0.0, 339 | "z": 1.0 340 | }, 341 | "end_point": { 342 | "y": 0.006, 343 | "x": 0.005, 344 | "z": 0.0 345 | }, 346 | "start_angle": 0.0, 347 | "curve": "JGd", 348 | "end_angle": 1.5707963267948966, 349 | "radius": 0.0015, 350 | "type": "Arc3D", 351 | "start_point": { 352 | "y": 0.0075, 353 | "x": 0.0035, 354 | "z": 0.0 355 | }, 356 | "reference_vector": { 357 | "y": 0.0, 358 | "x": 1.0, 359 | "z": 0.0 360 | } 361 | } 362 | ] 363 | }, 364 | { 365 | "is_outer": true, 366 | "profile_curves": [ 367 | { 368 | "center_point": { 369 | "y": 0.0, 370 | "x": 0.0, 371 | "z": 0.0 372 | }, 373 | "type": "Circle3D", 374 | "radius": 0.0025, 375 | "curve": "JGh", 376 | "normal": { 377 | "y": 0.0, 378 | "x": 0.0, 379 | "z": 1.0 380 | } 381 | } 382 | ] 383 | } 384 | ], 385 | "properties": {} 386 | }, 387 | "JGG": { 388 | "loops": [ 389 | { 390 | "is_outer": true, 391 | "profile_curves": [ 392 | { 393 | "center_point": { 394 | "y": 0.0, 395 | "x": 0.0, 396 | "z": 0.0 397 | }, 398 | "type": "Circle3D", 399 | "radius": 0.0025, 400 | "curve": "JGh", 401 | "normal": { 402 | "y": 0.0, 403 | "x": 0.0, 404 | "z": 1.0 405 | } 406 | } 407 | ] 408 | } 409 | ], 410 | "properties": {} 411 | } 412 | }, 413 | "reference_plane": {} 414 | }, 415 | "FwAoVHn0IYyGbWo_0": { 416 | "name": "Extrude 1", 417 | "type": "ExtrudeFeature", 418 | "profiles": [ 419 | { 420 | "profile": "JGC", 421 | "sketch": "F5BhbWqXcUw0QKR_0" 422 | } 423 | ], 424 | "extent_two": { 425 | "distance": { 426 | "type": "ModelParameter", 427 | "role": "AgainstDistance", 428 | "name": "none", 429 | "value": 0.0 430 | }, 431 | "type": "DistanceExtentDefinition", 432 | "taper_angle": { 433 | "type": "ModelParameter", 434 | "role": "Side2TaperAngle", 435 | "name": "none", 436 | "value": 0.0 437 | } 438 | }, 439 | "extent_one": { 440 | "distance": { 441 | "type": "ModelParameter", 442 | "role": "AlongDistance", 443 | "name": "none", 444 | "value": 0.0016 445 | }, 446 | "type": "DistanceExtentDefinition", 447 | "taper_angle": { 448 | "type": "ModelParameter", 449 | "role": "TaperAngle", 450 | "name": "none", 451 | "value": 0.0 452 | } 453 | }, 454 | "operation": "NewBodyFeatureOperation", 455 | "start_extent": { 456 | "type": "ProfilePlaneStartDefinition" 457 | }, 458 | "extent_type": "OneSideFeatureExtentType" 459 | } 460 | }, 461 | "properties": { 462 | "bounding_box": { 463 | "max_point": { 464 | "y": 0.007499999999999999, 465 | "x": 0.005000000000000001, 466 | "z": 0.003 467 | }, 468 | "type": "BoundingBox3D", 469 | "min_point": { 470 | "y": -0.0075, 471 | "x": -0.005000000000000001, 472 | "z": 0.0 473 | } 474 | } 475 | }, 476 | "sequence": [ 477 | { 478 | "index": 0, 479 | "type": "Sketch", 480 | "entity": "F5BhbWqXcUw0QKR_0" 481 | }, 482 | { 483 | "index": 1, 484 | "type": "ExtrudeFeature", 485 | "entity": "FwAoVHn0IYyGbWo_0" 486 | }, 487 | { 488 | "index": 2, 489 | "type": "Sketch", 490 | "entity": "FZ8bL1M2q7EXt0u_1" 491 | }, 492 | { 493 | "index": 3, 494 | "type": "ExtrudeFeature", 495 | "entity": "F5c4pqDbip87cOv_1" 496 | } 497 | ] 498 | } -------------------------------------------------------------------------------- /Bethany/examples/00005082.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "F99k1D8wnwW2DMl_0": { 4 | "transform": { 5 | "origin": { 6 | "y": 0.0, 7 | "x": 0.0, 8 | "z": 0.0 9 | }, 10 | "y_axis": { 11 | "y": 1.0, 12 | "x": 0.0, 13 | "z": 0.0 14 | }, 15 | "x_axis": { 16 | "y": 0.0, 17 | "x": 1.0, 18 | "z": 0.0 19 | }, 20 | "z_axis": { 21 | "y": 0.0, 22 | "x": 0.0, 23 | "z": 1.0 24 | } 25 | }, 26 | "type": "Sketch", 27 | "name": "Sketch 1", 28 | "profiles": { 29 | "JGC": { 30 | "loops": [ 31 | { 32 | "is_outer": true, 33 | "profile_curves": [ 34 | { 35 | "type": "Line3D", 36 | "start_point": { 37 | "y": 0.01, 38 | "x": 0.0085, 39 | "z": 0.0 40 | }, 41 | "curve": "JGB", 42 | "end_point": { 43 | "y": 0.01, 44 | "x": -0.0085, 45 | "z": 0.0 46 | } 47 | }, 48 | { 49 | "center_point": { 50 | "y": 0.0085, 51 | "x": -0.0085, 52 | "z": 0.0 53 | }, 54 | "normal": { 55 | "y": 0.0, 56 | "x": 0.0, 57 | "z": 1.0 58 | }, 59 | "end_point": { 60 | "y": 0.0085, 61 | "x": -0.01, 62 | "z": 0.0 63 | }, 64 | "start_angle": 0.0, 65 | "curve": "JGV", 66 | "end_angle": 1.5707963267948966, 67 | "radius": 0.0015, 68 | "type": "Arc3D", 69 | "start_point": { 70 | "y": 0.01, 71 | "x": -0.0085, 72 | "z": 0.0 73 | }, 74 | "reference_vector": { 75 | "y": 1.0, 76 | "x": 0.0, 77 | "z": 0.0 78 | } 79 | }, 80 | { 81 | "type": "Line3D", 82 | "start_point": { 83 | "y": 0.0085, 84 | "x": -0.01, 85 | "z": 0.0 86 | }, 87 | "curve": "JGN", 88 | "end_point": { 89 | "y": -0.0085, 90 | "x": -0.01, 91 | "z": 0.0 92 | } 93 | }, 94 | { 95 | "center_point": { 96 | "y": -0.0085, 97 | "x": -0.0085, 98 | "z": 0.0 99 | }, 100 | "normal": { 101 | "y": 0.0, 102 | "x": 0.0, 103 | "z": 1.0 104 | }, 105 | "end_point": { 106 | "y": -0.0085, 107 | "x": -0.01, 108 | "z": 0.0 109 | }, 110 | "start_angle": 0.0, 111 | "curve": "JGZ", 112 | "end_angle": 1.5707963267948966, 113 | "radius": 0.0015, 114 | "type": "Arc3D", 115 | "start_point": { 116 | "y": -0.01, 117 | "x": -0.0085, 118 | "z": 0.0 119 | }, 120 | "reference_vector": { 121 | "y": 0.0, 122 | "x": -1.0, 123 | "z": 0.0 124 | } 125 | }, 126 | { 127 | "type": "Line3D", 128 | "start_point": { 129 | "y": -0.01, 130 | "x": 0.0085, 131 | "z": 0.0 132 | }, 133 | "curve": "JGF", 134 | "end_point": { 135 | "y": -0.01, 136 | "x": -0.0085, 137 | "z": 0.0 138 | } 139 | }, 140 | { 141 | "center_point": { 142 | "y": -0.0085, 143 | "x": 0.0085, 144 | "z": 0.0 145 | }, 146 | "normal": { 147 | "y": 0.0, 148 | "x": 0.0, 149 | "z": 1.0 150 | }, 151 | "end_point": { 152 | "y": -0.0085, 153 | "x": 0.01, 154 | "z": 0.0 155 | }, 156 | "start_angle": 0.0, 157 | "curve": "JGd", 158 | "end_angle": 1.5707963267948966, 159 | "radius": 0.0015, 160 | "type": "Arc3D", 161 | "start_point": { 162 | "y": -0.01, 163 | "x": 0.0085, 164 | "z": 0.0 165 | }, 166 | "reference_vector": { 167 | "y": -1.0, 168 | "x": 0.0, 169 | "z": 0.0 170 | } 171 | }, 172 | { 173 | "type": "Line3D", 174 | "start_point": { 175 | "y": 0.0085, 176 | "x": 0.01, 177 | "z": 0.0 178 | }, 179 | "curve": "JGJ", 180 | "end_point": { 181 | "y": -0.0085, 182 | "x": 0.01, 183 | "z": 0.0 184 | } 185 | }, 186 | { 187 | "center_point": { 188 | "y": 0.0085, 189 | "x": 0.0085, 190 | "z": 0.0 191 | }, 192 | "normal": { 193 | "y": 0.0, 194 | "x": 0.0, 195 | "z": 1.0 196 | }, 197 | "end_point": { 198 | "y": 0.0085, 199 | "x": 0.01, 200 | "z": 0.0 201 | }, 202 | "start_angle": 0.0, 203 | "curve": "JGh", 204 | "end_angle": 1.5707963267948966, 205 | "radius": 0.0015, 206 | "type": "Arc3D", 207 | "start_point": { 208 | "y": 0.01, 209 | "x": 0.0085, 210 | "z": 0.0 211 | }, 212 | "reference_vector": { 213 | "y": 0.0, 214 | "x": 1.0, 215 | "z": 0.0 216 | } 217 | } 218 | ] 219 | }, 220 | { 221 | "is_outer": true, 222 | "profile_curves": [ 223 | { 224 | "center_point": { 225 | "y": 0.0, 226 | "x": 0.0, 227 | "z": 0.0 228 | }, 229 | "type": "Circle3D", 230 | "radius": 0.0025, 231 | "curve": "JGR", 232 | "normal": { 233 | "y": 0.0, 234 | "x": 0.0, 235 | "z": 1.0 236 | } 237 | } 238 | ] 239 | } 240 | ], 241 | "properties": {} 242 | }, 243 | "JGG": { 244 | "loops": [ 245 | { 246 | "is_outer": true, 247 | "profile_curves": [ 248 | { 249 | "center_point": { 250 | "y": 0.0, 251 | "x": 0.0, 252 | "z": 0.0 253 | }, 254 | "type": "Circle3D", 255 | "radius": 0.0025, 256 | "curve": "JGR", 257 | "normal": { 258 | "y": 0.0, 259 | "x": 0.0, 260 | "z": 1.0 261 | } 262 | } 263 | ] 264 | } 265 | ], 266 | "properties": {} 267 | } 268 | }, 269 | "reference_plane": {} 270 | }, 271 | "FVF0CQZ4DLkH0Xw_1": { 272 | "name": "Extrude 2", 273 | "type": "ExtrudeFeature", 274 | "profiles": [ 275 | { 276 | "profile": "JJK", 277 | "sketch": "FeTIkbBD8FHuMLr_1" 278 | } 279 | ], 280 | "extent_two": { 281 | "distance": { 282 | "type": "ModelParameter", 283 | "role": "AgainstDistance", 284 | "name": "none", 285 | "value": 0.0 286 | }, 287 | "type": "DistanceExtentDefinition", 288 | "taper_angle": { 289 | "type": "ModelParameter", 290 | "role": "Side2TaperAngle", 291 | "name": "none", 292 | "value": 0.0 293 | } 294 | }, 295 | "extent_one": { 296 | "distance": { 297 | "type": "ModelParameter", 298 | "role": "AlongDistance", 299 | "name": "none", 300 | "value": -0.0015 301 | }, 302 | "type": "DistanceExtentDefinition", 303 | "taper_angle": { 304 | "type": "ModelParameter", 305 | "role": "TaperAngle", 306 | "name": "none", 307 | "value": 0.0 308 | } 309 | }, 310 | "operation": "CutFeatureOperation", 311 | "start_extent": { 312 | "type": "ProfilePlaneStartDefinition" 313 | }, 314 | "extent_type": "OneSideFeatureExtentType" 315 | }, 316 | "FuPAaUoL6yD51aa_0": { 317 | "name": "Extrude 1", 318 | "type": "ExtrudeFeature", 319 | "profiles": [ 320 | { 321 | "profile": "JGC", 322 | "sketch": "F99k1D8wnwW2DMl_0" 323 | } 324 | ], 325 | "extent_two": { 326 | "distance": { 327 | "type": "ModelParameter", 328 | "role": "AgainstDistance", 329 | "name": "none", 330 | "value": 0.0 331 | }, 332 | "type": "DistanceExtentDefinition", 333 | "taper_angle": { 334 | "type": "ModelParameter", 335 | "role": "Side2TaperAngle", 336 | "name": "none", 337 | "value": 0.0 338 | } 339 | }, 340 | "extent_one": { 341 | "distance": { 342 | "type": "ModelParameter", 343 | "role": "AlongDistance", 344 | "name": "none", 345 | "value": 0.003 346 | }, 347 | "type": "DistanceExtentDefinition", 348 | "taper_angle": { 349 | "type": "ModelParameter", 350 | "role": "TaperAngle", 351 | "name": "none", 352 | "value": 0.0 353 | } 354 | }, 355 | "operation": "NewBodyFeatureOperation", 356 | "start_extent": { 357 | "type": "ProfilePlaneStartDefinition" 358 | }, 359 | "extent_type": "OneSideFeatureExtentType" 360 | }, 361 | "FeTIkbBD8FHuMLr_1": { 362 | "transform": { 363 | "origin": { 364 | "y": 0.0, 365 | "x": 0.0, 366 | "z": 0.003 367 | }, 368 | "y_axis": { 369 | "y": 1.0, 370 | "x": 0.0, 371 | "z": 0.0 372 | }, 373 | "x_axis": { 374 | "y": 0.0, 375 | "x": 1.0, 376 | "z": 0.0 377 | }, 378 | "z_axis": { 379 | "y": 0.0, 380 | "x": 0.0, 381 | "z": 1.0 382 | } 383 | }, 384 | "type": "Sketch", 385 | "name": "Sketch 2", 386 | "profiles": { 387 | "JJC": { 388 | "loops": [ 389 | { 390 | "is_outer": true, 391 | "profile_curves": [ 392 | { 393 | "center_point": { 394 | "y": 0.0, 395 | "x": 0.0, 396 | "z": 0.0 397 | }, 398 | "type": "Circle3D", 399 | "radius": 0.0025, 400 | "curve": "JJB", 401 | "normal": { 402 | "y": 0.0, 403 | "x": 0.0, 404 | "z": 1.0 405 | } 406 | } 407 | ] 408 | } 409 | ], 410 | "properties": {} 411 | }, 412 | "JJK": { 413 | "loops": [ 414 | { 415 | "is_outer": true, 416 | "profile_curves": [ 417 | { 418 | "center_point": { 419 | "y": 0.0, 420 | "x": 0.0, 421 | "z": 0.0 422 | }, 423 | "type": "Circle3D", 424 | "radius": 0.0025, 425 | "curve": "JJB", 426 | "normal": { 427 | "y": 0.0, 428 | "x": 0.0, 429 | "z": 1.0 430 | } 431 | } 432 | ] 433 | }, 434 | { 435 | "is_outer": true, 436 | "profile_curves": [ 437 | { 438 | "center_point": { 439 | "y": 0.0, 440 | "x": 0.0, 441 | "z": 0.0 442 | }, 443 | "type": "Circle3D", 444 | "radius": 0.005, 445 | "curve": "JJF", 446 | "normal": { 447 | "y": 0.0, 448 | "x": 0.0, 449 | "z": 1.0 450 | } 451 | } 452 | ] 453 | } 454 | ], 455 | "properties": {} 456 | } 457 | }, 458 | "reference_plane": {} 459 | } 460 | }, 461 | "properties": { 462 | "bounding_box": { 463 | "max_point": { 464 | "y": 0.01, 465 | "x": 0.01, 466 | "z": 0.003 467 | }, 468 | "type": "BoundingBox3D", 469 | "min_point": { 470 | "y": -0.01, 471 | "x": -0.01, 472 | "z": 0.0 473 | } 474 | } 475 | }, 476 | "sequence": [ 477 | { 478 | "index": 0, 479 | "type": "Sketch", 480 | "entity": "F99k1D8wnwW2DMl_0" 481 | }, 482 | { 483 | "index": 1, 484 | "type": "ExtrudeFeature", 485 | "entity": "FuPAaUoL6yD51aa_0" 486 | }, 487 | { 488 | "index": 2, 489 | "type": "Sketch", 490 | "entity": "FeTIkbBD8FHuMLr_1" 491 | }, 492 | { 493 | "index": 3, 494 | "type": "ExtrudeFeature", 495 | "entity": "FVF0CQZ4DLkH0Xw_1" 496 | } 497 | ] 498 | } -------------------------------------------------------------------------------- /Bethany/examples/00280150.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "FmrV6HbxzaryA5E_1": { 4 | "name": "Extrude 4", 5 | "type": "ExtrudeFeature", 6 | "profiles": [ 7 | { 8 | "profile": "JVC", 9 | "sketch": "FJmIje6KzlYzJQu_1" 10 | } 11 | ], 12 | "extent_two": { 13 | "distance": { 14 | "type": "ModelParameter", 15 | "role": "AgainstDistance", 16 | "name": "none", 17 | "value": 0.0 18 | }, 19 | "type": "DistanceExtentDefinition", 20 | "taper_angle": { 21 | "type": "ModelParameter", 22 | "role": "Side2TaperAngle", 23 | "name": "none", 24 | "value": 0.0 25 | } 26 | }, 27 | "extent_one": { 28 | "distance": { 29 | "type": "ModelParameter", 30 | "role": "AlongDistance", 31 | "name": "none", 32 | "value": 0.012700000000000001 33 | }, 34 | "type": "DistanceExtentDefinition", 35 | "taper_angle": { 36 | "type": "ModelParameter", 37 | "role": "TaperAngle", 38 | "name": "none", 39 | "value": 0.0 40 | } 41 | }, 42 | "operation": "JoinFeatureOperation", 43 | "start_extent": { 44 | "type": "ProfilePlaneStartDefinition" 45 | }, 46 | "extent_type": "OneSideFeatureExtentType" 47 | }, 48 | "FsNWntU6DsmMLjO_1": { 49 | "transform": { 50 | "origin": { 51 | "y": 0.0, 52 | "x": -0.0254, 53 | "z": 0.0254 54 | }, 55 | "y_axis": { 56 | "y": -0.0, 57 | "x": 0.0, 58 | "z": 1.0 59 | }, 60 | "x_axis": { 61 | "y": 0.0, 62 | "x": -1.0, 63 | "z": 0.0 64 | }, 65 | "z_axis": { 66 | "y": 1.0, 67 | "x": 0.0, 68 | "z": 0.0 69 | } 70 | }, 71 | "type": "Sketch", 72 | "name": "Sketch 3", 73 | "profiles": { 74 | "JNC": { 75 | "loops": [ 76 | { 77 | "is_outer": true, 78 | "profile_curves": [ 79 | { 80 | "center_point": { 81 | "y": 0.0, 82 | "x": 0.0, 83 | "z": 0.0 84 | }, 85 | "type": "Circle3D", 86 | "radius": 0.0127, 87 | "curve": "JNB", 88 | "normal": { 89 | "y": 1.0, 90 | "x": 0.0, 91 | "z": 0.0 92 | } 93 | } 94 | ] 95 | } 96 | ], 97 | "properties": {} 98 | } 99 | }, 100 | "reference_plane": {} 101 | }, 102 | "FsVXe897f08Tjls_1": { 103 | "transform": { 104 | "origin": { 105 | "y": -0.0508, 106 | "x": -0.0254, 107 | "z": 0.0254 108 | }, 109 | "y_axis": { 110 | "y": 0.0, 111 | "x": -0.0, 112 | "z": 1.0 113 | }, 114 | "x_axis": { 115 | "y": 0.0, 116 | "x": 1.0, 117 | "z": 0.0 118 | }, 119 | "z_axis": { 120 | "y": -1.0, 121 | "x": 0.0, 122 | "z": 0.0 123 | } 124 | }, 125 | "type": "Sketch", 126 | "name": "Sketch 4", 127 | "profiles": {}, 128 | "reference_plane": {} 129 | }, 130 | "FbeI0JR1aX5BPSI_0": { 131 | "name": "Extrude 1", 132 | "type": "ExtrudeFeature", 133 | "profiles": [ 134 | { 135 | "profile": "JGC", 136 | "sketch": "FaMBajUC8yyUjTc_0" 137 | } 138 | ], 139 | "extent_two": { 140 | "distance": { 141 | "type": "ModelParameter", 142 | "role": "AgainstDistance", 143 | "name": "none", 144 | "value": 0.0 145 | }, 146 | "type": "DistanceExtentDefinition", 147 | "taper_angle": { 148 | "type": "ModelParameter", 149 | "role": "Side2TaperAngle", 150 | "name": "none", 151 | "value": 0.0 152 | } 153 | }, 154 | "extent_one": { 155 | "distance": { 156 | "type": "ModelParameter", 157 | "role": "AlongDistance", 158 | "name": "none", 159 | "value": 0.050800000000000005 160 | }, 161 | "type": "DistanceExtentDefinition", 162 | "taper_angle": { 163 | "type": "ModelParameter", 164 | "role": "TaperAngle", 165 | "name": "none", 166 | "value": 0.0 167 | } 168 | }, 169 | "operation": "NewBodyFeatureOperation", 170 | "start_extent": { 171 | "type": "ProfilePlaneStartDefinition" 172 | }, 173 | "extent_type": "OneSideFeatureExtentType" 174 | }, 175 | "FJmIje6KzlYzJQu_1": { 176 | "transform": { 177 | "origin": { 178 | "y": -0.0254, 179 | "x": 0.0, 180 | "z": 0.0254 181 | }, 182 | "y_axis": { 183 | "y": 0.0, 184 | "x": 0.0, 185 | "z": 1.0 186 | }, 187 | "x_axis": { 188 | "y": 1.0, 189 | "x": 0.0, 190 | "z": 0.0 191 | }, 192 | "z_axis": { 193 | "y": 0.0, 194 | "x": 1.0, 195 | "z": 0.0 196 | } 197 | }, 198 | "type": "Sketch", 199 | "name": "Sketch 5", 200 | "profiles": { 201 | "JVC": { 202 | "loops": [ 203 | { 204 | "is_outer": true, 205 | "profile_curves": [ 206 | { 207 | "type": "Line3D", 208 | "start_point": { 209 | "y": 0.012700000000000003, 210 | "x": -0.012700000000000003, 211 | "z": 0.0 212 | }, 213 | "curve": "JVB", 214 | "end_point": { 215 | "y": 0.012700000000000003, 216 | "x": 0.0127, 217 | "z": 0.0 218 | } 219 | }, 220 | { 221 | "type": "Line3D", 222 | "start_point": { 223 | "y": 0.012700000000000003, 224 | "x": 0.0127, 225 | "z": 0.0 226 | }, 227 | "curve": "JVN", 228 | "end_point": { 229 | "y": -0.0127, 230 | "x": 0.0127, 231 | "z": 0.0 232 | } 233 | }, 234 | { 235 | "type": "Line3D", 236 | "start_point": { 237 | "y": -0.0127, 238 | "x": -0.012700000000000003, 239 | "z": 0.0 240 | }, 241 | "curve": "JVF", 242 | "end_point": { 243 | "y": -0.0127, 244 | "x": 0.0127, 245 | "z": 0.0 246 | } 247 | }, 248 | { 249 | "type": "Line3D", 250 | "start_point": { 251 | "y": 0.012700000000000003, 252 | "x": -0.012700000000000003, 253 | "z": 0.0 254 | }, 255 | "curve": "JVJ", 256 | "end_point": { 257 | "y": -0.0127, 258 | "x": -0.012700000000000003, 259 | "z": 0.0 260 | } 261 | } 262 | ] 263 | } 264 | ], 265 | "properties": {} 266 | } 267 | }, 268 | "reference_plane": {} 269 | }, 270 | "FfmWQ7YnYtyaUaY_1": { 271 | "name": "Extrude 2", 272 | "type": "ExtrudeFeature", 273 | "profiles": [ 274 | { 275 | "profile": "JJC", 276 | "sketch": "F9BMMoQbB6HQfC2_1" 277 | } 278 | ], 279 | "extent_two": { 280 | "distance": { 281 | "type": "ModelParameter", 282 | "role": "AgainstDistance", 283 | "name": "none", 284 | "value": 0.0 285 | }, 286 | "type": "DistanceExtentDefinition", 287 | "taper_angle": { 288 | "type": "ModelParameter", 289 | "role": "Side2TaperAngle", 290 | "name": "none", 291 | "value": 0.0 292 | } 293 | }, 294 | "extent_one": { 295 | "distance": { 296 | "type": "ModelParameter", 297 | "role": "AlongDistance", 298 | "name": "none", 299 | "value": -0.050800000000000005 300 | }, 301 | "type": "DistanceExtentDefinition", 302 | "taper_angle": { 303 | "type": "ModelParameter", 304 | "role": "TaperAngle", 305 | "name": "none", 306 | "value": 0.0 307 | } 308 | }, 309 | "operation": "CutFeatureOperation", 310 | "start_extent": { 311 | "type": "ProfilePlaneStartDefinition" 312 | }, 313 | "extent_type": "OneSideFeatureExtentType" 314 | }, 315 | "FaMBajUC8yyUjTc_0": { 316 | "transform": { 317 | "origin": { 318 | "y": 0.0, 319 | "x": 0.0, 320 | "z": 0.0 321 | }, 322 | "y_axis": { 323 | "y": 0.0, 324 | "x": -0.0, 325 | "z": 1.0 326 | }, 327 | "x_axis": { 328 | "y": 0.0, 329 | "x": 1.0, 330 | "z": 0.0 331 | }, 332 | "z_axis": { 333 | "y": -1.0, 334 | "x": 0.0, 335 | "z": 0.0 336 | } 337 | }, 338 | "type": "Sketch", 339 | "name": "Sketch 1", 340 | "profiles": { 341 | "JGC": { 342 | "loops": [ 343 | { 344 | "is_outer": true, 345 | "profile_curves": [ 346 | { 347 | "type": "Line3D", 348 | "start_point": { 349 | "y": 0.0, 350 | "x": 0.0, 351 | "z": 0.0 352 | }, 353 | "curve": "JGB", 354 | "end_point": { 355 | "y": 0.0, 356 | "x": -0.0508, 357 | "z": 0.0 358 | } 359 | }, 360 | { 361 | "type": "Line3D", 362 | "start_point": { 363 | "y": 0.0, 364 | "x": -0.0508, 365 | "z": 0.0 366 | }, 367 | "curve": "JGN", 368 | "end_point": { 369 | "y": 0.0508, 370 | "x": -0.0508, 371 | "z": 0.0 372 | } 373 | }, 374 | { 375 | "type": "Line3D", 376 | "start_point": { 377 | "y": 0.0508, 378 | "x": 0.0, 379 | "z": 0.0 380 | }, 381 | "curve": "JGF", 382 | "end_point": { 383 | "y": 0.0508, 384 | "x": -0.0508, 385 | "z": 0.0 386 | } 387 | }, 388 | { 389 | "type": "Line3D", 390 | "start_point": { 391 | "y": 0.0, 392 | "x": 0.0, 393 | "z": 0.0 394 | }, 395 | "curve": "JGJ", 396 | "end_point": { 397 | "y": 0.0508, 398 | "x": 0.0, 399 | "z": 0.0 400 | } 401 | } 402 | ] 403 | } 404 | ], 405 | "properties": {} 406 | } 407 | }, 408 | "reference_plane": {} 409 | }, 410 | "F9BMMoQbB6HQfC2_1": { 411 | "transform": { 412 | "origin": { 413 | "y": -0.0254, 414 | "x": -0.0254, 415 | "z": 0.0508 416 | }, 417 | "y_axis": { 418 | "y": 1.0, 419 | "x": 0.0, 420 | "z": -0.0 421 | }, 422 | "x_axis": { 423 | "y": 0.0, 424 | "x": 1.0, 425 | "z": 0.0 426 | }, 427 | "z_axis": { 428 | "y": 0.0, 429 | "x": -0.0, 430 | "z": 1.0 431 | } 432 | }, 433 | "type": "Sketch", 434 | "name": "Sketch 2", 435 | "profiles": { 436 | "JJC": { 437 | "loops": [ 438 | { 439 | "is_outer": true, 440 | "profile_curves": [ 441 | { 442 | "center_point": { 443 | "y": 0.0, 444 | "x": 0.0, 445 | "z": 0.0 446 | }, 447 | "type": "Circle3D", 448 | "radius": 0.0127, 449 | "curve": "JJB", 450 | "normal": { 451 | "y": 0.0, 452 | "x": -0.0, 453 | "z": 1.0 454 | } 455 | } 456 | ] 457 | } 458 | ], 459 | "properties": {} 460 | } 461 | }, 462 | "reference_plane": {} 463 | }, 464 | "FDEzbkABrHGG40C_1": { 465 | "name": "Extrude 3", 466 | "type": "ExtrudeFeature", 467 | "profiles": [ 468 | { 469 | "profile": "JNC", 470 | "sketch": "FsNWntU6DsmMLjO_1" 471 | } 472 | ], 473 | "extent_two": { 474 | "distance": { 475 | "type": "ModelParameter", 476 | "role": "AgainstDistance", 477 | "name": "none", 478 | "value": 0.0 479 | }, 480 | "type": "DistanceExtentDefinition", 481 | "taper_angle": { 482 | "type": "ModelParameter", 483 | "role": "Side2TaperAngle", 484 | "name": "none", 485 | "value": 0.0 486 | } 487 | }, 488 | "extent_one": { 489 | "distance": { 490 | "type": "ModelParameter", 491 | "role": "AlongDistance", 492 | "name": "none", 493 | "value": 0.012700000000000001 494 | }, 495 | "type": "DistanceExtentDefinition", 496 | "taper_angle": { 497 | "type": "ModelParameter", 498 | "role": "TaperAngle", 499 | "name": "none", 500 | "value": 0.0 501 | } 502 | }, 503 | "operation": "JoinFeatureOperation", 504 | "start_extent": { 505 | "type": "ProfilePlaneStartDefinition" 506 | }, 507 | "extent_type": "OneSideFeatureExtentType" 508 | } 509 | }, 510 | "properties": { 511 | "bounding_box": { 512 | "max_point": { 513 | "y": 0.012700000000000001, 514 | "x": 0.012700000000000001, 515 | "z": 0.05080000000000001 516 | }, 517 | "type": "BoundingBox3D", 518 | "min_point": { 519 | "y": -0.050800000000000005, 520 | "x": -0.05080000000000002, 521 | "z": 0.0 522 | } 523 | } 524 | }, 525 | "sequence": [ 526 | { 527 | "index": 0, 528 | "type": "Sketch", 529 | "entity": "FaMBajUC8yyUjTc_0" 530 | }, 531 | { 532 | "index": 1, 533 | "type": "ExtrudeFeature", 534 | "entity": "FbeI0JR1aX5BPSI_0" 535 | }, 536 | { 537 | "index": 2, 538 | "type": "Sketch", 539 | "entity": "F9BMMoQbB6HQfC2_1" 540 | }, 541 | { 542 | "index": 3, 543 | "type": "ExtrudeFeature", 544 | "entity": "FfmWQ7YnYtyaUaY_1" 545 | }, 546 | { 547 | "index": 4, 548 | "type": "Sketch", 549 | "entity": "FsNWntU6DsmMLjO_1" 550 | }, 551 | { 552 | "index": 5, 553 | "type": "ExtrudeFeature", 554 | "entity": "FDEzbkABrHGG40C_1" 555 | }, 556 | { 557 | "index": 6, 558 | "type": "Sketch", 559 | "entity": "FsVXe897f08Tjls_1" 560 | }, 561 | { 562 | "index": 7, 563 | "type": "Sketch", 564 | "entity": "FJmIje6KzlYzJQu_1" 565 | }, 566 | { 567 | "index": 8, 568 | "type": "ExtrudeFeature", 569 | "entity": "FmrV6HbxzaryA5E_1" 570 | } 571 | ] 572 | } -------------------------------------------------------------------------------- /Bethany/examples/00529152.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "FJuq88FpdEHHyl9_0": { 4 | "reference_plane": {}, 5 | "type": "Sketch", 6 | "name": "Sketch 1", 7 | "profiles": { 8 | "JGC": { 9 | "loops": [ 10 | { 11 | "profile_curves": [ 12 | { 13 | "start_point": { 14 | "y": 0.0, 15 | "x": 0.01, 16 | "z": 0.0 17 | }, 18 | "type": "Line3D", 19 | "curve": "JGB", 20 | "end_point": { 21 | "y": 0.0, 22 | "x": 0.0, 23 | "z": 0.0 24 | } 25 | }, 26 | { 27 | "start_point": { 28 | "y": 0.0, 29 | "x": 0.0, 30 | "z": 0.0 31 | }, 32 | "type": "Line3D", 33 | "curve": "JGN", 34 | "end_point": { 35 | "y": 0.01, 36 | "x": 0.0, 37 | "z": 0.0 38 | } 39 | }, 40 | { 41 | "start_point": { 42 | "y": 0.01, 43 | "x": 0.01, 44 | "z": 0.0 45 | }, 46 | "type": "Line3D", 47 | "curve": "JGF", 48 | "end_point": { 49 | "y": 0.01, 50 | "x": 0.0, 51 | "z": 0.0 52 | } 53 | }, 54 | { 55 | "start_point": { 56 | "y": 0.0, 57 | "x": 0.01, 58 | "z": 0.0 59 | }, 60 | "type": "Line3D", 61 | "curve": "JGJ", 62 | "end_point": { 63 | "y": 0.01, 64 | "x": 0.01, 65 | "z": 0.0 66 | } 67 | } 68 | ], 69 | "is_outer": true 70 | }, 71 | { 72 | "profile_curves": [ 73 | { 74 | "center_point": { 75 | "y": 0.005, 76 | "x": 0.005, 77 | "z": 0.0 78 | }, 79 | "radius": 0.00150015, 80 | "type": "Circle3D", 81 | "curve": "JGR", 82 | "normal": { 83 | "y": -1.0, 84 | "x": 0.0, 85 | "z": 0.0 86 | } 87 | } 88 | ], 89 | "is_outer": true 90 | } 91 | ], 92 | "properties": {} 93 | }, 94 | "JGG": { 95 | "loops": [ 96 | { 97 | "profile_curves": [ 98 | { 99 | "center_point": { 100 | "y": 0.005, 101 | "x": 0.005, 102 | "z": 0.0 103 | }, 104 | "radius": 0.00150015, 105 | "type": "Circle3D", 106 | "curve": "JGR", 107 | "normal": { 108 | "y": -1.0, 109 | "x": 0.0, 110 | "z": 0.0 111 | } 112 | } 113 | ], 114 | "is_outer": true 115 | } 116 | ], 117 | "properties": {} 118 | } 119 | }, 120 | "transform": { 121 | "origin": { 122 | "y": 0.0, 123 | "x": 0.0, 124 | "z": 0.0 125 | }, 126 | "y_axis": { 127 | "y": 0.0, 128 | "x": -0.0, 129 | "z": 1.0 130 | }, 131 | "x_axis": { 132 | "y": 0.0, 133 | "x": 1.0, 134 | "z": 0.0 135 | }, 136 | "z_axis": { 137 | "y": -1.0, 138 | "x": 0.0, 139 | "z": 0.0 140 | } 141 | } 142 | }, 143 | "FRCqhvaTLC45hNi_0": { 144 | "reference_plane": {}, 145 | "type": "Sketch", 146 | "name": "Sketch 2", 147 | "profiles": { 148 | "JIG": { 149 | "loops": [ 150 | { 151 | "profile_curves": [ 152 | { 153 | "center_point": { 154 | "y": 0.005, 155 | "x": 0.005, 156 | "z": 0.0 157 | }, 158 | "radius": 0.00150015, 159 | "type": "Circle3D", 160 | "curve": "JIR", 161 | "normal": { 162 | "y": 0.0, 163 | "x": 1.0, 164 | "z": 0.0 165 | } 166 | } 167 | ], 168 | "is_outer": true 169 | } 170 | ], 171 | "properties": {} 172 | }, 173 | "JIC": { 174 | "loops": [ 175 | { 176 | "profile_curves": [ 177 | { 178 | "start_point": { 179 | "y": 0.0, 180 | "x": 0.01, 181 | "z": 0.0 182 | }, 183 | "type": "Line3D", 184 | "curve": "JIB", 185 | "end_point": { 186 | "y": 0.0, 187 | "x": 0.0, 188 | "z": 0.0 189 | } 190 | }, 191 | { 192 | "start_point": { 193 | "y": 0.0, 194 | "x": 0.0, 195 | "z": 0.0 196 | }, 197 | "type": "Line3D", 198 | "curve": "JIN", 199 | "end_point": { 200 | "y": 0.01, 201 | "x": 0.0, 202 | "z": 0.0 203 | } 204 | }, 205 | { 206 | "start_point": { 207 | "y": 0.01, 208 | "x": 0.01, 209 | "z": 0.0 210 | }, 211 | "type": "Line3D", 212 | "curve": "JIF", 213 | "end_point": { 214 | "y": 0.01, 215 | "x": 0.0, 216 | "z": 0.0 217 | } 218 | }, 219 | { 220 | "start_point": { 221 | "y": 0.0, 222 | "x": 0.01, 223 | "z": 0.0 224 | }, 225 | "type": "Line3D", 226 | "curve": "JIJ", 227 | "end_point": { 228 | "y": 0.01, 229 | "x": 0.01, 230 | "z": 0.0 231 | } 232 | } 233 | ], 234 | "is_outer": true 235 | }, 236 | { 237 | "profile_curves": [ 238 | { 239 | "center_point": { 240 | "y": 0.005, 241 | "x": 0.005, 242 | "z": 0.0 243 | }, 244 | "radius": 0.00150015, 245 | "type": "Circle3D", 246 | "curve": "JIR", 247 | "normal": { 248 | "y": 0.0, 249 | "x": 1.0, 250 | "z": 0.0 251 | } 252 | } 253 | ], 254 | "is_outer": true 255 | } 256 | ], 257 | "properties": {} 258 | } 259 | }, 260 | "transform": { 261 | "origin": { 262 | "y": 0.0, 263 | "x": 0.0, 264 | "z": 0.0 265 | }, 266 | "y_axis": { 267 | "y": 0.0, 268 | "x": 0.0, 269 | "z": 1.0 270 | }, 271 | "x_axis": { 272 | "y": 1.0, 273 | "x": 0.0, 274 | "z": 0.0 275 | }, 276 | "z_axis": { 277 | "y": 0.0, 278 | "x": 1.0, 279 | "z": 0.0 280 | } 281 | } 282 | }, 283 | "FzDqYq1JMieZAmV_0": { 284 | "reference_plane": {}, 285 | "type": "Sketch", 286 | "name": "Sketch 3", 287 | "profiles": { 288 | "JKG": { 289 | "loops": [ 290 | { 291 | "profile_curves": [ 292 | { 293 | "center_point": { 294 | "y": 0.005, 295 | "x": 0.005, 296 | "z": 0.0 297 | }, 298 | "radius": 0.00150015, 299 | "type": "Circle3D", 300 | "curve": "JKR", 301 | "normal": { 302 | "y": 0.0, 303 | "x": 0.0, 304 | "z": 1.0 305 | } 306 | } 307 | ], 308 | "is_outer": true 309 | } 310 | ], 311 | "properties": {} 312 | }, 313 | "JKC": { 314 | "loops": [ 315 | { 316 | "profile_curves": [ 317 | { 318 | "start_point": { 319 | "y": 0.0, 320 | "x": 0.01, 321 | "z": 0.0 322 | }, 323 | "type": "Line3D", 324 | "curve": "JKB", 325 | "end_point": { 326 | "y": 0.0, 327 | "x": 0.0, 328 | "z": 0.0 329 | } 330 | }, 331 | { 332 | "start_point": { 333 | "y": 0.0, 334 | "x": 0.0, 335 | "z": 0.0 336 | }, 337 | "type": "Line3D", 338 | "curve": "JKN", 339 | "end_point": { 340 | "y": 0.01, 341 | "x": 0.0, 342 | "z": 0.0 343 | } 344 | }, 345 | { 346 | "start_point": { 347 | "y": 0.01, 348 | "x": 0.01, 349 | "z": 0.0 350 | }, 351 | "type": "Line3D", 352 | "curve": "JKF", 353 | "end_point": { 354 | "y": 0.01, 355 | "x": 0.0, 356 | "z": 0.0 357 | } 358 | }, 359 | { 360 | "start_point": { 361 | "y": 0.0, 362 | "x": 0.01, 363 | "z": 0.0 364 | }, 365 | "type": "Line3D", 366 | "curve": "JKJ", 367 | "end_point": { 368 | "y": 0.01, 369 | "x": 0.01, 370 | "z": 0.0 371 | } 372 | } 373 | ], 374 | "is_outer": true 375 | }, 376 | { 377 | "profile_curves": [ 378 | { 379 | "center_point": { 380 | "y": 0.005, 381 | "x": 0.005, 382 | "z": 0.0 383 | }, 384 | "radius": 0.00150015, 385 | "type": "Circle3D", 386 | "curve": "JKR", 387 | "normal": { 388 | "y": 0.0, 389 | "x": 0.0, 390 | "z": 1.0 391 | } 392 | } 393 | ], 394 | "is_outer": true 395 | } 396 | ], 397 | "properties": {} 398 | } 399 | }, 400 | "transform": { 401 | "origin": { 402 | "y": 0.0, 403 | "x": 0.0, 404 | "z": 0.0 405 | }, 406 | "y_axis": { 407 | "y": 1.0, 408 | "x": 0.0, 409 | "z": 0.0 410 | }, 411 | "x_axis": { 412 | "y": 0.0, 413 | "x": 1.0, 414 | "z": 0.0 415 | }, 416 | "z_axis": { 417 | "y": 0.0, 418 | "x": 0.0, 419 | "z": 1.0 420 | } 421 | } 422 | }, 423 | "FEguRxck489nlUH_0": { 424 | "extent_two": { 425 | "distance": { 426 | "role": "AgainstDistance", 427 | "type": "ModelParameter", 428 | "name": "none", 429 | "value": 0.0 430 | }, 431 | "type": "DistanceExtentDefinition", 432 | "taper_angle": { 433 | "role": "Side2TaperAngle", 434 | "type": "ModelParameter", 435 | "name": "none", 436 | "value": 0.0 437 | } 438 | }, 439 | "name": "Extrude 1", 440 | "extent_one": { 441 | "distance": { 442 | "role": "AlongDistance", 443 | "type": "ModelParameter", 444 | "name": "none", 445 | "value": -0.0015 446 | }, 447 | "type": "DistanceExtentDefinition", 448 | "taper_angle": { 449 | "role": "TaperAngle", 450 | "type": "ModelParameter", 451 | "name": "none", 452 | "value": 0.0 453 | } 454 | }, 455 | "extent_type": "OneSideFeatureExtentType", 456 | "operation": "NewBodyFeatureOperation", 457 | "start_extent": { 458 | "type": "ProfilePlaneStartDefinition" 459 | }, 460 | "type": "ExtrudeFeature", 461 | "profiles": [ 462 | { 463 | "profile": "JGC", 464 | "sketch": "FJuq88FpdEHHyl9_0" 465 | } 466 | ] 467 | }, 468 | "FfFCWbj9yhmlzJq_1": { 469 | "extent_two": { 470 | "distance": { 471 | "role": "AgainstDistance", 472 | "type": "ModelParameter", 473 | "name": "none", 474 | "value": 0.0 475 | }, 476 | "type": "DistanceExtentDefinition", 477 | "taper_angle": { 478 | "role": "Side2TaperAngle", 479 | "type": "ModelParameter", 480 | "name": "none", 481 | "value": 0.0 482 | } 483 | }, 484 | "name": "Extrude 2", 485 | "extent_one": { 486 | "distance": { 487 | "role": "AlongDistance", 488 | "type": "ModelParameter", 489 | "name": "none", 490 | "value": 0.0015 491 | }, 492 | "type": "DistanceExtentDefinition", 493 | "taper_angle": { 494 | "role": "TaperAngle", 495 | "type": "ModelParameter", 496 | "name": "none", 497 | "value": 0.0 498 | } 499 | }, 500 | "extent_type": "OneSideFeatureExtentType", 501 | "operation": "JoinFeatureOperation", 502 | "start_extent": { 503 | "type": "ProfilePlaneStartDefinition" 504 | }, 505 | "type": "ExtrudeFeature", 506 | "profiles": [ 507 | { 508 | "profile": "JKC", 509 | "sketch": "FzDqYq1JMieZAmV_0" 510 | } 511 | ] 512 | }, 513 | "FZQ8SCqstPv0Z03_1": { 514 | "extent_two": { 515 | "distance": { 516 | "role": "AgainstDistance", 517 | "type": "ModelParameter", 518 | "name": "none", 519 | "value": 0.0 520 | }, 521 | "type": "DistanceExtentDefinition", 522 | "taper_angle": { 523 | "role": "Side2TaperAngle", 524 | "type": "ModelParameter", 525 | "name": "none", 526 | "value": 0.0 527 | } 528 | }, 529 | "name": "Extrude 3", 530 | "extent_one": { 531 | "distance": { 532 | "role": "AlongDistance", 533 | "type": "ModelParameter", 534 | "name": "none", 535 | "value": 0.0015 536 | }, 537 | "type": "DistanceExtentDefinition", 538 | "taper_angle": { 539 | "role": "TaperAngle", 540 | "type": "ModelParameter", 541 | "name": "none", 542 | "value": 0.0 543 | } 544 | }, 545 | "extent_type": "OneSideFeatureExtentType", 546 | "operation": "JoinFeatureOperation", 547 | "start_extent": { 548 | "type": "ProfilePlaneStartDefinition" 549 | }, 550 | "type": "ExtrudeFeature", 551 | "profiles": [ 552 | { 553 | "profile": "JIC", 554 | "sketch": "FRCqhvaTLC45hNi_0" 555 | } 556 | ] 557 | } 558 | }, 559 | "properties": { 560 | "bounding_box": { 561 | "max_point": { 562 | "y": 0.010000000000000002, 563 | "x": 0.010000000000000002, 564 | "z": 0.010000000000000002 565 | }, 566 | "type": "BoundingBox3D", 567 | "min_point": { 568 | "y": 0.0, 569 | "x": 0.0, 570 | "z": 0.0 571 | } 572 | } 573 | }, 574 | "sequence": [ 575 | { 576 | "index": 0, 577 | "type": "Sketch", 578 | "entity": "FJuq88FpdEHHyl9_0" 579 | }, 580 | { 581 | "index": 1, 582 | "type": "Sketch", 583 | "entity": "FRCqhvaTLC45hNi_0" 584 | }, 585 | { 586 | "index": 2, 587 | "type": "Sketch", 588 | "entity": "FzDqYq1JMieZAmV_0" 589 | }, 590 | { 591 | "index": 3, 592 | "type": "ExtrudeFeature", 593 | "entity": "FEguRxck489nlUH_0" 594 | }, 595 | { 596 | "index": 4, 597 | "type": "ExtrudeFeature", 598 | "entity": "FfFCWbj9yhmlzJq_1" 599 | }, 600 | { 601 | "index": 5, 602 | "type": "ExtrudeFeature", 603 | "entity": "FZQ8SCqstPv0Z03_1" 604 | } 605 | ] 606 | } -------------------------------------------------------------------------------- /Bethany/examples/00633023.json: -------------------------------------------------------------------------------- 1 | { 2 | "entities": { 3 | "FpO04MEuGua24Pb_0": { 4 | "reference_plane": {}, 5 | "type": "Sketch", 6 | "name": "Sketch 1", 7 | "profiles": { 8 | "JGC": { 9 | "loops": [ 10 | { 11 | "profile_curves": [ 12 | { 13 | "center_point": { 14 | "y": 0.0, 15 | "x": 0.0, 16 | "z": 0.0 17 | }, 18 | "radius": 0.009, 19 | "type": "Circle3D", 20 | "curve": "JGB", 21 | "normal": { 22 | "y": 0.0, 23 | "x": 0.0, 24 | "z": 1.0 25 | } 26 | } 27 | ], 28 | "is_outer": true 29 | } 30 | ], 31 | "properties": {} 32 | } 33 | }, 34 | "transform": { 35 | "origin": { 36 | "y": 0.0, 37 | "x": 0.0, 38 | "z": 0.0 39 | }, 40 | "y_axis": { 41 | "y": 1.0, 42 | "x": 0.0, 43 | "z": 0.0 44 | }, 45 | "x_axis": { 46 | "y": 0.0, 47 | "x": 1.0, 48 | "z": 0.0 49 | }, 50 | "z_axis": { 51 | "y": 0.0, 52 | "x": 0.0, 53 | "z": 1.0 54 | } 55 | } 56 | }, 57 | "FzNOyOEp7IOU6cG_0": { 58 | "extent_two": { 59 | "distance": { 60 | "role": "AgainstDistance", 61 | "type": "ModelParameter", 62 | "name": "none", 63 | "value": 0.0 64 | }, 65 | "type": "DistanceExtentDefinition", 66 | "taper_angle": { 67 | "role": "Side2TaperAngle", 68 | "type": "ModelParameter", 69 | "name": "none", 70 | "value": 0.0 71 | } 72 | }, 73 | "name": "Extrude 1", 74 | "extent_one": { 75 | "distance": { 76 | "role": "AlongDistance", 77 | "type": "ModelParameter", 78 | "name": "none", 79 | "value": 0.005 80 | }, 81 | "type": "DistanceExtentDefinition", 82 | "taper_angle": { 83 | "role": "TaperAngle", 84 | "type": "ModelParameter", 85 | "name": "none", 86 | "value": 0.0 87 | } 88 | }, 89 | "extent_type": "OneSideFeatureExtentType", 90 | "operation": "NewBodyFeatureOperation", 91 | "start_extent": { 92 | "type": "ProfilePlaneStartDefinition" 93 | }, 94 | "type": "ExtrudeFeature", 95 | "profiles": [ 96 | { 97 | "profile": "JGC", 98 | "sketch": "FpO04MEuGua24Pb_0" 99 | } 100 | ] 101 | }, 102 | "FmWYDK3pbyUD5IL_1": { 103 | "reference_plane": {}, 104 | "type": "Sketch", 105 | "name": "Sketch 2", 106 | "profiles": { 107 | "JJC": { 108 | "loops": [ 109 | { 110 | "profile_curves": [ 111 | { 112 | "center_point": { 113 | "y": 0.0, 114 | "x": 0.0, 115 | "z": 0.0 116 | }, 117 | "radius": 0.0062, 118 | "type": "Circle3D", 119 | "curve": "JJB", 120 | "normal": { 121 | "y": 0.0, 122 | "x": 0.0, 123 | "z": 1.0 124 | } 125 | } 126 | ], 127 | "is_outer": true 128 | } 129 | ], 130 | "properties": {} 131 | } 132 | }, 133 | "transform": { 134 | "origin": { 135 | "y": 0.0, 136 | "x": 0.0, 137 | "z": 0.005 138 | }, 139 | "y_axis": { 140 | "y": 1.0, 141 | "x": 0.0, 142 | "z": 0.0 143 | }, 144 | "x_axis": { 145 | "y": 0.0, 146 | "x": 1.0, 147 | "z": 0.0 148 | }, 149 | "z_axis": { 150 | "y": 0.0, 151 | "x": 0.0, 152 | "z": 1.0 153 | } 154 | } 155 | }, 156 | "FgMDBvjh4kgvPkY_1": { 157 | "extent_two": { 158 | "distance": { 159 | "role": "AgainstDistance", 160 | "type": "ModelParameter", 161 | "name": "none", 162 | "value": 0.0 163 | }, 164 | "type": "DistanceExtentDefinition", 165 | "taper_angle": { 166 | "role": "Side2TaperAngle", 167 | "type": "ModelParameter", 168 | "name": "none", 169 | "value": 0.0 170 | } 171 | }, 172 | "name": "Extrude 2", 173 | "extent_one": { 174 | "distance": { 175 | "role": "AlongDistance", 176 | "type": "ModelParameter", 177 | "name": "none", 178 | "value": 0.007 179 | }, 180 | "type": "DistanceExtentDefinition", 181 | "taper_angle": { 182 | "role": "TaperAngle", 183 | "type": "ModelParameter", 184 | "name": "none", 185 | "value": 0.0 186 | } 187 | }, 188 | "extent_type": "OneSideFeatureExtentType", 189 | "operation": "JoinFeatureOperation", 190 | "start_extent": { 191 | "type": "ProfilePlaneStartDefinition" 192 | }, 193 | "type": "ExtrudeFeature", 194 | "profiles": [ 195 | { 196 | "profile": "JJC", 197 | "sketch": "FmWYDK3pbyUD5IL_1" 198 | } 199 | ] 200 | } 201 | }, 202 | "properties": { 203 | "bounding_box": { 204 | "max_point": { 205 | "y": 0.009000000000000003, 206 | "x": 0.009000000000000003, 207 | "z": 0.014 208 | }, 209 | "type": "BoundingBox3D", 210 | "min_point": { 211 | "y": -0.009000000000000003, 212 | "x": -0.009000000000000003, 213 | "z": 0.0 214 | } 215 | } 216 | }, 217 | "sequence": [ 218 | { 219 | "index": 0, 220 | "type": "Sketch", 221 | "entity": "FpO04MEuGua24Pb_0" 222 | }, 223 | { 224 | "index": 1, 225 | "type": "ExtrudeFeature", 226 | "entity": "FzNOyOEp7IOU6cG_0" 227 | }, 228 | { 229 | "index": 2, 230 | "type": "Sketch", 231 | "entity": "FmWYDK3pbyUD5IL_1" 232 | }, 233 | { 234 | "index": 3, 235 | "type": "ExtrudeFeature", 236 | "entity": "FgMDBvjh4kgvPkY_1" 237 | } 238 | ] 239 | } -------------------------------------------------------------------------------- /Bethany/json2dataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import json 4 | import argparse 5 | import sys 6 | sys.path.append(".") 7 | from lib.extrude import CADSequence 8 | 9 | from lib.cad2code import get_cad_code 10 | from count_tokens.count import count_tokens_in_string 11 | 12 | parser = argparse.ArgumentParser() 13 | parser.add_argument('--src', type=str, required=True, help="source folder") 14 | parser.add_argument('--idx', type=int, default=0, help="export n files starting from idx.") 15 | parser.add_argument('--num', type=int, default=-1, help="number of shapes to export. -1 exports all shapes.") 16 | parser.add_argument('--filter', type=str, default=None, help="filter folder") 17 | parser.add_argument('--ignore', type=bool, default=False, help="ignore too long code") 18 | parser.add_argument('--mode', type=str, default="default", help="mode of generation") 19 | parser.add_argument('--token', type=int, default=2048, help="limit of tokens count") 20 | parser.add_argument('-o', '--outputs', type=str, default=None, help="save filename") 21 | args = parser.parse_args() 22 | 23 | src_dir = args.src 24 | print(src_dir) 25 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("json")))) 26 | if args.num != -1: 27 | out_paths = out_paths[args.idx:args.idx+args.num] 28 | 29 | 30 | from multiprocessing import Process, cpu_count, Manager 31 | 32 | #tmp_folder="./_tmp/" 33 | #ensure_dir(tmp_folder) 34 | 35 | num_processes = cpu_count() 36 | 37 | def main_process(process_id, result_list): 38 | json_data = [] 39 | for index in range(process_id, len(out_paths), num_processes): 40 | print(f"{index + 1}/{len(out_paths)}",end='\r') 41 | path = out_paths[index] 42 | name = path.split("/")[-1].split(".")[0] 43 | 44 | data = {} 45 | data["id"] = f"{name}" 46 | data["image"] = f"{name}.jpg" 47 | data["conversations"] = [] 48 | 49 | if args.filter is not None: 50 | filter_dir = args.filter 51 | filter_path = os.path.join(filter_dir, name + ".jpg") 52 | if not os.path.isfile(filter_path): 53 | continue 54 | 55 | try: 56 | with open(path, 'r') as fp: 57 | src_data = json.load(fp) 58 | cad_seq = CADSequence.from_dict(src_data) 59 | cad_code = get_cad_code(cad_seq) 60 | 61 | conversation_human = {"from": "human"} 62 | if args.mode == "transparent": 63 | conversation_human["value"] = f"\nThis image is a transparent view of a 3D model from a certain angle. Please try to use OpenECAD-style API to render this model." 64 | elif args.mode == "orthographic": 65 | conversation_human["value"] = f"\nThis image contains 4 views of a 3D model from a certain angle and three orthographic views. Please try to use OpenECAD-style API to render this model." 66 | else: 67 | conversation_human["value"] = f"\nThis image is a view of a 3D model from a certain angle. Please try to use OpenECAD-style API to render this model." 68 | data["conversations"].append(conversation_human) 69 | conversation_gpt = {"from": "gpt"} 70 | conversation_gpt["value"] = f"Of course, here are the codes:\n```python\n{cad_code}```" 71 | num_tokens = count_tokens_in_string(cad_code) 72 | if num_tokens > args.token: 73 | continue 74 | data["conversations"].append(conversation_gpt) 75 | json_data.append(data) 76 | except Exception as e: 77 | print(f"load and create failed. Error: {e}") 78 | continue 79 | 80 | result_list.append(json_data) 81 | #json_str = json.dumps(json_data, indent=4) # `indent=4` 用于美化输出, 使其更易读 82 | #with open(os.path.join(tmp_folder ,f"data{process_id}.json"), "w") as json_file: 83 | # json_file.write(json_str) 84 | 85 | ## python export2step.py --src ./ --filter ./ 86 | 87 | if __name__ == "__main__": 88 | with Manager() as manager: 89 | # 创建一个共享的列表 90 | result_list = manager.list() 91 | 92 | processes = [] 93 | for i in range(num_processes): 94 | process = Process(target=main_process, args=(i, result_list,)) 95 | processes.append(process) 96 | process.start() 97 | 98 | # 等待所有进程完成 99 | for process in processes: 100 | process.join() 101 | 102 | result_list = list(result_list) 103 | json_res = [] 104 | for result in result_list: 105 | json_res += result 106 | 107 | print() 108 | print(len(json_res)) 109 | 110 | with open(f'{args.outputs}', 'w') as f: 111 | json.dump(json_res, f, indent=4) 112 | 113 | print('任务完成') -------------------------------------------------------------------------------- /Bethany/json2py.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import json 4 | import argparse 5 | import sys 6 | sys.path.append(".") 7 | from lib.extrude import CADSequence 8 | from lib.file_utils import ensure_dir 9 | 10 | from lib.cad2code import get_cad_code 11 | 12 | import traceback 13 | 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument('--src', type=str, required=True, help="source folder") 16 | parser.add_argument('--idx', type=int, default=0, help="export n files starting from idx.") 17 | parser.add_argument('--num', type=int, default=-1, help="number of shapes to export. -1 exports all shapes.") 18 | parser.add_argument('--filter', type=str, default=None, help="filter folder") 19 | parser.add_argument('-o', '--outputs', type=str, default=None, help="save folder") 20 | args = parser.parse_args() 21 | 22 | src_dir = args.src 23 | print(src_dir) 24 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("json")))) 25 | if args.num != -1: 26 | out_paths = out_paths[args.idx:args.idx+args.num] 27 | save_dir = args.src + "_py" if args.outputs is None else args.outputs 28 | ensure_dir(save_dir) 29 | 30 | from multiprocessing import Process, cpu_count, Manager 31 | 32 | num_processes = cpu_count() 33 | 34 | def main_process(process_id): 35 | for index in range(process_id, len(out_paths), num_processes): 36 | print(f"{index + 1}/{len(out_paths)}",end='\r') 37 | path = out_paths[index] 38 | name = path.split("/")[-1].split(".")[0] 39 | 40 | if args.filter is not None: 41 | filter_dir = args.filter 42 | filter_path = os.path.join(filter_dir, name + ".jpg") 43 | if not os.path.isfile(filter_path): 44 | continue 45 | 46 | try: 47 | with open(path, 'r') as fp: 48 | src_data = json.load(fp) 49 | cad_seq = CADSequence.from_dict(src_data) 50 | cad_code = get_cad_code(cad_seq) 51 | #print(cad_code) 52 | except Exception as e: 53 | print(f"load and create failed. Error: {e}") 54 | traceback.print_exc() 55 | continue 56 | 57 | save_path = os.path.join(save_dir, name + ".py") 58 | with open(save_path, 'w', encoding='utf-8') as file: 59 | file.write(cad_code) 60 | ## python export2step.py --src ./ --filter ./ 61 | 62 | if __name__ == "__main__": 63 | with Manager() as manager: 64 | # 创建一个共享的列表 65 | 66 | processes = [] 67 | for i in range(num_processes): 68 | process = Process(target=main_process, args=(i, )) 69 | processes.append(process) 70 | process.start() 71 | 72 | # 等待所有进程完成 73 | for process in processes: 74 | process.join() 75 | 76 | print('任务完成') 77 | -------------------------------------------------------------------------------- /Bethany/json2step.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import json 4 | import numpy as np 5 | from OCC.Core.BRepCheck import BRepCheck_Analyzer 6 | from lib.DataExchange import write_step_file 7 | import argparse 8 | import sys 9 | sys.path.append(".") 10 | from lib.extrude import CADSequence 11 | from lib.visualize import vec2CADsolid, create_CAD, create_CAD_index 12 | from lib.file_utils import ensure_dir 13 | 14 | 15 | import traceback 16 | 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('--src', type=str, required=True, help="source folder") 19 | parser.add_argument('--idx', type=int, default=0, help="export n files starting from idx.") 20 | parser.add_argument('--num', type=int, default=-1, help="number of shapes to export. -1 exports all shapes.") 21 | parser.add_argument('--mode', type=str, default="default", help="mode of generation") 22 | parser.add_argument('--filter', action="store_true", help="use opencascade analyzer to filter invalid model") 23 | parser.add_argument('-o', '--outputs', type=str, default=None, help="save folder") 24 | args = parser.parse_args() 25 | 26 | src_dir = args.src 27 | print(src_dir) 28 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("json")))) 29 | if args.num != -1: 30 | out_paths = out_paths[args.idx:args.idx+args.num] 31 | save_dir = args.src + "_step" if args.outputs is None else args.outputs 32 | ensure_dir(save_dir) 33 | 34 | 35 | from multiprocessing import Process, cpu_count 36 | 37 | num_processes = cpu_count() 38 | 39 | from OCC.Core.TDocStd import TDocStd_Document 40 | from OCC.Core.TCollection import TCollection_ExtendedString 41 | 42 | from lib.timeout import timeout_decorator 43 | def main_process(process_id): 44 | if args.mode == "segment": 45 | for index in range(process_id, len(out_paths), num_processes): 46 | path = out_paths[index] 47 | @timeout_decorator 48 | def main__(): 49 | with open(path, 'r') as fp: 50 | data = json.load(fp) 51 | cad_seq = CADSequence.from_dict(data) 52 | 53 | for i in range(len(cad_seq.seq)): 54 | doc_name = TCollection_ExtendedString("pythonocc-doc") 55 | doc = TDocStd_Document(doc_name) 56 | out_shape = create_CAD_index(doc, cad_seq, index=i) 57 | 58 | name = path.split("/")[-1].split(".")[0] 59 | sub_folder = os.path.join(save_dir, name) 60 | ensure_dir(sub_folder) 61 | save_path = os.path.join(sub_folder, name + f".{i:02d}.step") 62 | write_step_file(doc, save_path, application_protocol="AP242DIS") 63 | 64 | doc_name = TCollection_ExtendedString("pythonocc-doc") 65 | doc = TDocStd_Document(doc_name) 66 | out_shape = create_CAD(doc, cad_seq) 67 | 68 | if args.filter: 69 | analyzer = BRepCheck_Analyzer(out_shape) 70 | if not analyzer.IsValid(): 71 | print("detect invalid.") 72 | return 73 | name = path.split("/")[-1].split(".")[0] 74 | save_path = os.path.join(save_dir, name + f".step") 75 | write_step_file(doc, save_path, application_protocol="AP242DIS") 76 | try: 77 | main__() 78 | except Exception as e: 79 | print(f"load and create failed. Error: {e}") 80 | traceback.print_exc() 81 | continue 82 | elif args.mode == "default": 83 | for index in range(process_id, len(out_paths), num_processes): 84 | path = out_paths[index] 85 | @timeout_decorator 86 | def main__(): 87 | name = path.split("/")[-1].split(".")[0] 88 | save_path = os.path.join(save_dir, name + f".step") 89 | if os.path.isfile(save_path): return 90 | with open(path, 'r') as fp: 91 | data = json.load(fp) 92 | cad_seq = CADSequence.from_dict(data) 93 | doc_name = TCollection_ExtendedString("pythonocc-doc") 94 | doc = TDocStd_Document(doc_name) 95 | cad = CADSequence(cad_seq) 96 | out_shape = create_CAD(doc, cad_seq) 97 | if args.filter: 98 | analyzer = BRepCheck_Analyzer(out_shape) 99 | if not analyzer.IsValid(): 100 | print("detect invalid.") 101 | return 102 | 103 | write_step_file(doc, save_path, application_protocol="AP242DIS") 104 | try: 105 | main__() 106 | except Exception as e: 107 | print(f"load and create failed. Error: {e}") 108 | traceback.print_exc() 109 | continue 110 | 111 | 112 | ## python export2step.py --src ./ --outputs ./ 113 | 114 | if __name__ == "__main__": 115 | processes = [] 116 | for i in range(num_processes): 117 | process = Process(target=main_process, args=(i,)) 118 | processes.append(process) 119 | process.start() 120 | 121 | # 等待所有进程完成 122 | for process in processes: 123 | process.join() 124 | 125 | print('任务完成') -------------------------------------------------------------------------------- /Bethany/lib/DataExchange.py: -------------------------------------------------------------------------------- 1 | import os 2 | from OCC.Core.STEPControl import STEPControl_Reader, STEPControl_Writer, STEPControl_AsIs 3 | from OCC.Core.STEPCAFControl import STEPCAFControl_Writer 4 | 5 | from OCC.Core.Interface import Interface_Static_SetCVal 6 | from OCC.Core.IFSelect import IFSelect_RetDone, IFSelect_ItemsByEntity 7 | from OCC.Core.TDocStd import TDocStd_Document 8 | from OCC.Core.TCollection import TCollection_ExtendedString 9 | 10 | try: 11 | import svgwrite 12 | HAVE_SVGWRITE = True 13 | except ImportError: 14 | HAVE_SVGWRITE = False 15 | 16 | def write_step_file(a_shape, filename, application_protocol="AP242DIS"): 17 | """ exports a shape to a STEP file 18 | a_shape: the topods_shape to export (a compound, a solid etc.) 19 | filename: the filename 20 | application protocol: "AP203" or "AP214IS" or "AP242DIS" 21 | """ 22 | # a few checks 23 | #if a_shape.IsNull(): 24 | # raise AssertionError("Shape %s is null." % a_shape) 25 | if application_protocol not in ["AP203", "AP214IS", "AP242DIS"]: 26 | raise AssertionError("application_protocol must be either AP203 or AP214IS. You passed %s." % application_protocol) 27 | if os.path.isfile(filename): 28 | print("Warning: %s file already exists and will be replaced" % filename) 29 | # creates and initialise the step exporter 30 | step_writer = STEPCAFControl_Writer() 31 | Interface_Static_SetCVal("write.step.schema", application_protocol) 32 | 33 | # transfer shapes and write file 34 | step_writer.Transfer(a_shape, STEPControl_AsIs) 35 | status = step_writer.Write(filename) 36 | 37 | if not status == IFSelect_RetDone: 38 | raise IOError("Error while writing shape to STEP file.") 39 | if not os.path.isfile(filename): 40 | raise IOError("File %s was not saved to filesystem." % filename) 41 | -------------------------------------------------------------------------------- /Bethany/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuki-Kokomi/OpenECAD_Project/a65d6611282dc16001799f966f9c327c3c015479/Bethany/lib/__init__.py -------------------------------------------------------------------------------- /Bethany/lib/cad2code.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from lib.extrude import CADSequence 3 | import lib.curves 4 | from lib.math_utils import * 5 | 6 | tol = 1e-10 7 | def get_sketchplane(cad_seq, index): 8 | ext = cad_seq.seq[index] 9 | res = f"SketchPlane{index} = add_sketchplane(\n" 10 | res += "\torigin= {}, normal= {}, x_axis= {})\n".format( 11 | np.array2string(ext.sketch_plane.origin.round(4), separator=', '), np.array2string(ext.sketch_plane.normal.round(4), separator=', '), 12 | np.array2string(ext.sketch_plane.x_axis.round(4), separator=', ')) 13 | return res 14 | 15 | def get_sketchplane_ref(cad_seq, index): 16 | if index == 0: return get_sketchplane(cad_seq, index) # 第一个没有参考 17 | target_ext = cad_seq.seq[index] 18 | target_plane = target_ext.sketch_plane 19 | target_x_axis = target_plane.x_axis 20 | target_n_axis = target_plane.normal 21 | target_origin = target_plane.origin 22 | for i in range(0, index): 23 | ref_ext = cad_seq.seq[i] 24 | ref_plane = ref_ext.sketch_plane 25 | ref_x_axis = ref_plane.x_axis 26 | ref_y_axis = ref_plane.y_axis 27 | ref_n_axis = ref_plane.normal 28 | ref_origin = ref_plane.origin 29 | if are_parallel(target_n_axis, ref_n_axis): 30 | # sameplane or extent_one/two 31 | reverse = False if np.dot(target_n_axis, ref_n_axis) > 0 else True 32 | vec_origin = target_origin - ref_origin 33 | #if (np.linalg.norm(vec_origin) < tol) or (np.dot(vec_origin, ref_n_axis) < tol and np.abs(np.dot(vec_origin, ref_x_axis)) > tol): 34 | if is_point_on_plane(ref_origin, ref_x_axis, ref_y_axis, target_origin): 35 | # sameplane if OO' is same point or OO' and normal_axis are vertical. 36 | typeop = "sameplane" 37 | origin = map_3d_to_2d(ref_origin, ref_x_axis, ref_y_axis, target_origin) 38 | angle = calculate_rotation_angle(ref_x_axis, target_x_axis, ref_n_axis) 39 | res = f"SketchPlane{index} = add_sketchplane_ref(\n" 40 | res += f"\tExtrude{i}, origin = {np.array2string(origin.round(4), separator=', ')}, type = \"{typeop}\"" 41 | if np.abs(angle) > tol: 42 | res += f", angle = {number_to_pi_string(angle)}" 43 | if reverse: 44 | res += ", reverse = True" 45 | res += ")\n" 46 | return res 47 | #elif (are_parallel(vec_origin, ref_n_axis) and np.dot(vec_origin, ref_n_axis) > 0) or (np.dot(vec_origin - ref_ext.extent_one * ref_n_axis, ref_n_axis) < tol): 48 | elif (are_parallel(vec_origin, ref_n_axis) and np.dot(vec_origin, ref_n_axis) > 0) and np.abs(distance_of_point_and_plane(ref_origin, ref_x_axis, ref_y_axis, target_origin) - np.abs(ref_ext.extent_one)) < tol: 49 | typeop = "extent_one" 50 | ref_origin_ = ref_origin + ref_n_axis * ref_ext.extent_one 51 | origin = map_3d_to_2d(ref_origin_, ref_x_axis, ref_y_axis, target_origin) 52 | angle = calculate_rotation_angle(ref_x_axis, target_x_axis, ref_n_axis) 53 | res = f"SketchPlane{index} = add_sketchplane_ref(\n" 54 | res += f"\tExtrude{i}, origin = {np.array2string(origin.round(4), separator=', ')}, type = \"{typeop}\"" 55 | if np.abs(angle) > tol: 56 | res += f", angle = {number_to_pi_string(angle)}" 57 | if reverse: 58 | res += ", reverse = True" 59 | res += ")\n" 60 | return res 61 | #elif (are_parallel(vec_origin, ref_n_axis) and np.dot(vec_origin, ref_n_axis) < 0) or (np.dot(vec_origin + ref_ext.extent_two * ref_n_axis, ref_n_axis) < tol): 62 | elif (are_parallel(vec_origin, ref_n_axis) and np.dot(vec_origin, ref_n_axis) < 0) and np.abs(distance_of_point_and_plane(ref_origin, ref_x_axis, ref_y_axis, target_origin) - np.abs(ref_ext.extent_two)) < tol: 63 | typeop = "extent_two" 64 | ref_origin_ = ref_origin - ref_n_axis * ref_ext.extent_two 65 | origin = map_3d_to_2d(ref_origin_, ref_x_axis, ref_y_axis, target_origin) 66 | angle = calculate_rotation_angle(ref_x_axis, target_x_axis, ref_n_axis) 67 | res = f"SketchPlane{index} = add_sketchplane_ref(\n" 68 | res += f"\tExtrude{i}, origin = {np.array2string(origin.round(4), separator=', ')}, type = \"{typeop}\"" 69 | if np.abs(angle) > tol: 70 | res += f", angle = {number_to_pi_string(angle)}" 71 | if reverse: 72 | res += ", reverse = True" 73 | res += ")\n" 74 | return res 75 | else: 76 | pass # next situation 77 | elif np.dot(target_n_axis, ref_n_axis) < tol: 78 | typeop = "line" 79 | # line 80 | for j, loop in enumerate(ref_ext.profile.children): 81 | for k, curve in enumerate(loop.children): 82 | if type(curve) == lib.curves.Line: 83 | start_point = map_2d_to_3d(ref_origin, ref_x_axis, ref_y_axis, curve.start_point) 84 | end_point = map_2d_to_3d(ref_origin, ref_x_axis, ref_y_axis, curve.end_point) 85 | ref_x_axis_ = unit_vector(end_point - start_point) 86 | ref_y_axis_ = ref_n_axis 87 | ref_n_axis_ = find_n_from_x_and_y(ref_x_axis_, ref_y_axis_) 88 | ref_origin_ = start_point 89 | reverse = False if np.dot(target_n_axis, ref_n_axis_) > 0 else True 90 | vec_origin = target_origin - ref_origin_ 91 | if are_parallel(target_n_axis, ref_n_axis_): 92 | #if (np.linalg.norm(vec_origin) < tol) or (np.dot(vec_origin, ref_n_axis_) < tol and np.abs(np.dot(vec_origin, ref_x_axis_)) > tol): 93 | 94 | if is_point_on_plane(ref_origin_, ref_x_axis_, ref_y_axis_, target_origin): 95 | # if SO' is same point or SO' and normal_axis are vertical. 96 | origin = map_3d_to_2d(ref_origin_, ref_x_axis_, ref_y_axis_, target_origin) 97 | angle = calculate_rotation_angle(ref_x_axis_, target_x_axis, ref_n_axis_) 98 | res = f"SketchPlane{index} = add_sketchplane_ref(\n" 99 | res += f"\tExtrude{i}, origin= {np.array2string(origin.round(4), separator=', ')}, type= \"{typeop}\", line= Line{i}_{j}_{k}" 100 | if np.abs(angle) > tol: 101 | res += f", angle= {number_to_pi_string(angle)}" 102 | if reverse: 103 | res += ", reverse= True" 104 | res += ")\n" 105 | return res 106 | 107 | return get_sketchplane(cad_seq, index) # 查找失败,使用绝对坐标定义 108 | 109 | 110 | 111 | 112 | 113 | 114 | def get_cad_code(cad_seq): 115 | cad_code = "" 116 | for i, ext in enumerate(cad_seq.seq): 117 | # SketchPlane 118 | cad_code += get_sketchplane_ref(cad_seq, i) 119 | # Loops 120 | cad_code += f"Loops{i} = []\n" 121 | for j, loop in enumerate(ext.profile.children): 122 | # Curves 123 | cad_code += f"Curves{i}_{j} = []\n" 124 | for k, curve in enumerate(loop.children): 125 | if type(curve) == lib.curves.Line: 126 | cad_code += f"Line{i}_{j}_{k} = add_line(start= {np.array2string(curve.start_point.round(4), separator=', ')}, end= {np.array2string(curve.end_point.round(4), separator=', ')})\n" 127 | #cad_code += f"Curves{i}_{j}.append(Line{i}_{j}_{k})\n" 128 | elif type(curve) == lib.curves.Arc: 129 | cad_code += f"Arc{i}_{j}_{k} = add_arc(start= {np.array2string(curve.start_point.round(4), separator=', ')}, " 130 | cad_code += f"end= {np.array2string(curve.end_point.round(4), separator=', ')}, mid= {np.array2string(curve.mid_point.round(4), separator=', ')})\n" 131 | #cad_code += f"Curves{i}_{j}.append(Arc{i}_{j}_{k})\n" 132 | elif type(curve) == lib.curves.Circle: 133 | cad_code += f"Circle{i}_{j}_{k} = add_circle(center= {np.array2string(curve.center.round(4), separator=', ')}, radius= {np.array2string(np.float64(curve.radius).round(4), separator=', ')})\n" 134 | #cad_code += f"Curves{i}_{j}.append(Circle{i}_{j}_{k})\n" 135 | cad_code += f"Loop{i}_{j} = add_loop(Curves{i}_{j})\n" 136 | #cad_code += f"Loops{i}.append(Loop{i}_{j})\n" 137 | # Profile 138 | cad_code += f"Profile{i} = add_profile(Loops{i})\n" 139 | # Sketch 140 | cad_code += f"Sketch{i} = add_sketch(sketch_plane= SketchPlane{i}, profile= Profile{i})\n" 141 | #cad_code += "\tsketch_position= {}, sketch_size= {})\n".format( 142 | # np.array2string(ext.sketch_pos.round(4), separator=', '), np.array2string(ext.sketch_size.round(4), separator=', ')) 143 | # Finally: Extrude 144 | cad_code += f"Extrude{i} = add_extrude(sketch= Sketch{i},\n" 145 | cad_code += "\toperation= {}, type= {}, extent_one= {}, extent_two= {})\n".format( 146 | ext.operation, ext.extent_type, np.array2string(np.float64(ext.extent_one).round(4), separator=', '), np.array2string(np.float64(ext.extent_two).round(4), separator=', ')) 147 | return cad_code -------------------------------------------------------------------------------- /Bethany/lib/extrude.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import random 3 | from .sketch import Profile 4 | from .macro import * 5 | from .math_utils import cartesian2polar, polar2cartesian, polar_parameterization, polar_parameterization_inverse 6 | 7 | 8 | class CoordSystem(object): 9 | """Local coordinate system for sketch plane.""" 10 | def __init__(self, origin, theta, phi, gamma, y_axis=None, is_numerical=False): 11 | self.origin = origin 12 | self._theta = theta # 0~pi 13 | self._phi = phi # -pi~pi 14 | self._gamma = gamma # -pi~pi 15 | self._y_axis = y_axis # (theta, phi) 16 | self.is_numerical = is_numerical 17 | 18 | @property 19 | def normal(self): 20 | return polar2cartesian([self._theta, self._phi]) 21 | 22 | @property 23 | def x_axis(self): 24 | normal_3d, x_axis_3d = polar_parameterization_inverse(self._theta, self._phi, self._gamma) 25 | return x_axis_3d 26 | 27 | @property 28 | def y_axis(self): 29 | if self._y_axis is None: 30 | return np.cross(self.normal, self.x_axis) 31 | return polar2cartesian(self._y_axis) 32 | 33 | @staticmethod 34 | def from_dict(stat): 35 | origin = np.array([stat["origin"]["x"] * 1000, stat["origin"]["y"] * 1000, stat["origin"]["z"] * 1000]) 36 | normal_3d = np.array([stat["z_axis"]["x"], stat["z_axis"]["y"], stat["z_axis"]["z"]]) 37 | x_axis_3d = np.array([stat["x_axis"]["x"], stat["x_axis"]["y"], stat["x_axis"]["z"]]) 38 | y_axis_3d = np.array([stat["y_axis"]["x"], stat["y_axis"]["y"], stat["y_axis"]["z"]]) 39 | theta, phi, gamma = polar_parameterization(normal_3d, x_axis_3d) 40 | return CoordSystem(origin, theta, phi, gamma, y_axis=cartesian2polar(y_axis_3d)) 41 | 42 | @staticmethod 43 | def from_vector(vec, is_numerical=False, n=256): 44 | origin = vec[:3] 45 | theta, phi, gamma = vec[3:] 46 | system = CoordSystem(origin, theta, phi, gamma) 47 | if is_numerical: 48 | system.denumericalize(n) 49 | return system 50 | 51 | def __str__(self): 52 | return "origin: {}, normal: {}, x_axis: {}, y_axis: {}".format( 53 | self.origin.round(4), self.normal.round(4), self.x_axis.round(4), self.y_axis.round(4)) 54 | 55 | def transform(self, translation, scale): 56 | self.origin = (self.origin + translation) * scale 57 | 58 | def numericalize(self, n=256): 59 | """NOTE: shall only be called after normalization""" 60 | # assert np.max(self.origin) <= 1.0 and np.min(self.origin) >= -1.0 # TODO: origin can be out-of-bound! 61 | self.origin = ((self.origin + 1.0) / 2 * n).round().clip(min=0, max=n-1).astype(np.int) 62 | tmp = np.array([self._theta, self._phi, self._gamma]) 63 | self._theta, self._phi, self._gamma = ((tmp / np.pi + 1.0) / 2 * n).round().clip( 64 | min=0, max=n-1).astype(np.int) 65 | self.is_numerical = True 66 | 67 | def denumericalize(self, n=256): 68 | self.origin = self.origin / n * 2 - 1.0 69 | tmp = np.array([self._theta, self._phi, self._gamma]) 70 | self._theta, self._phi, self._gamma = (tmp / n * 2 - 1.0) * np.pi 71 | self.is_numerical = False 72 | 73 | def to_vector(self): 74 | return np.array([*self.origin, self._theta, self._phi, self._gamma]) 75 | 76 | 77 | class Extrude(object): 78 | """Single extrude operation with corresponding a sketch profile. 79 | NOTE: only support single sketch profile. Extrusion with multiple profiles is decomposed.""" 80 | def __init__(self, profile: Profile, sketch_plane: CoordSystem, 81 | operation, extent_type, extent_one, extent_two, sketch_pos, sketch_size): 82 | """ 83 | Args: 84 | profile (Profile): normalized sketch profile 85 | sketch_plane (CoordSystem): coordinate system for sketch plane 86 | operation (int): index of EXTRUDE_OPERATIONS, see macro.py 87 | extent_type (int): index of EXTENT_TYPE, see macro.py 88 | extent_one (float): extrude distance in normal direction (NOTE: it's negative in some data) 89 | extent_two (float): extrude distance in opposite direction 90 | sketch_pos (np.array): the global 3D position of sketch starting point 91 | sketch_size (float): size of the sketch 92 | """ 93 | self.profile = profile # normalized sketch 94 | self.sketch_plane = sketch_plane 95 | self.operation = operation 96 | self.extent_type = extent_type 97 | self.extent_one = extent_one 98 | self.extent_two = extent_two 99 | 100 | self.sketch_pos = sketch_pos 101 | self.sketch_size = sketch_size 102 | 103 | @staticmethod 104 | def from_dict(all_stat, extrude_id, sketch_dim=256): 105 | """construct Extrude from json data 106 | 107 | Args: 108 | all_stat (dict): all json data 109 | extrude_id (str): entity ID for this extrude 110 | sketch_dim (int, optional): sketch normalization size. Defaults to 256. 111 | 112 | Returns: 113 | list: one or more Extrude instances 114 | """ 115 | extrude_entity = all_stat["entities"][extrude_id] 116 | assert extrude_entity["start_extent"]["type"] == "ProfilePlaneStartDefinition" 117 | 118 | all_skets = [] 119 | n = len(extrude_entity["profiles"]) 120 | for i in range(len(extrude_entity["profiles"])): 121 | sket_id, profile_id = extrude_entity["profiles"][i]["sketch"], extrude_entity["profiles"][i]["profile"] 122 | sket_entity = all_stat["entities"][sket_id] 123 | sket_profile = Profile.from_dict(sket_entity["profiles"][profile_id]) 124 | sket_plane = CoordSystem.from_dict(sket_entity["transform"]) 125 | # normalize profile 126 | #point = sket_profile.start_point 127 | point = np.array([0.0,0.0,0.0]) 128 | sket_pos = point[0] * sket_plane.x_axis + point[1] * sket_plane.y_axis + sket_plane.origin 129 | sket_size = sket_profile.bbox_size 130 | sket_profile.normalize(sketch_dim) 131 | all_skets.append((sket_profile, sket_plane, sket_pos, sket_size)) 132 | 133 | operation = EXTRUDE_OPERATIONS.index(extrude_entity["operation"]) 134 | extent_type = EXTENT_TYPE.index(extrude_entity["extent_type"]) 135 | extent_one = extrude_entity["extent_one"]["distance"]["value"] * 1000 136 | extent_two = 0.0 137 | if extrude_entity["extent_type"] == "TwoSidesFeatureExtentType": 138 | extent_two = extrude_entity["extent_two"]["distance"]["value"] * 1000 139 | 140 | if operation == EXTRUDE_OPERATIONS.index("NewBodyFeatureOperation"): 141 | all_operations = [operation] + [EXTRUDE_OPERATIONS.index("JoinFeatureOperation")] * (n - 1) 142 | else: 143 | all_operations = [operation] * n 144 | 145 | return [Extrude(all_skets[i][0], all_skets[i][1], all_operations[i], extent_type, extent_one, extent_two, 146 | all_skets[i][2], all_skets[i][3]) for i in range(n)] 147 | 148 | @staticmethod 149 | def from_vector(vec, is_numerical=False, n=256): 150 | """vector representation: commands [SOL, ..., SOL, ..., EXT]""" 151 | assert vec[-1][0] == EXT_IDX and vec[0][0] == SOL_IDX 152 | profile_vec = np.concatenate([vec[:-1], EOS_VEC[np.newaxis]]) 153 | profile = Profile.from_vector(profile_vec, is_numerical=is_numerical) 154 | ext_vec = vec[-1][-N_ARGS_EXT:] 155 | 156 | sket_pos = ext_vec[N_ARGS_PLANE:N_ARGS_PLANE + 3] 157 | sket_size = ext_vec[N_ARGS_PLANE + N_ARGS_TRANS - 1] 158 | sket_plane = CoordSystem.from_vector(np.concatenate([sket_pos, ext_vec[:N_ARGS_PLANE]])) 159 | ext_param = ext_vec[-N_ARGS_EXT_PARAM:] 160 | 161 | res = Extrude(profile, sket_plane, int(ext_param[2]), int(ext_param[3]), ext_param[0], ext_param[1], 162 | sket_pos, sket_size) 163 | if is_numerical: 164 | res.denumericalize(n) 165 | return res 166 | 167 | def __str__(self): 168 | s = "Sketch-Extrude pair:" 169 | s += "\n -" + str(self.sketch_plane) 170 | s += "\n -sketch position: {}, sketch size: {}".format(self.sketch_pos.round(4), self.sketch_size.round(4)) 171 | s += "\n -operation:{}, type:{}, extent_one:{}, extent_two:{}".format( 172 | self.operation, self.extent_type, self.extent_one.round(4), self.extent_two.round(4)) 173 | s += "\n -" + str(self.profile) 174 | return s 175 | 176 | def transform(self, translation, scale): 177 | """linear transformation""" 178 | # self.profile.transform(np.array([0, 0]), scale) 179 | self.sketch_plane.transform(translation, scale) 180 | self.extent_one *= scale 181 | self.extent_two *= scale 182 | self.sketch_pos = (self.sketch_pos + translation) * scale 183 | self.sketch_size *= scale 184 | 185 | def numericalize(self, n=256): 186 | """quantize the representation. 187 | NOTE: shall only be called after CADSequence.normalize (the shape lies in unit cube, -1~1)""" 188 | assert -2.0 <= self.extent_one <= 2.0 and -2.0 <= self.extent_two <= 2.0 189 | self.profile.numericalize(n) 190 | self.sketch_plane.numericalize(n) 191 | self.extent_one = ((self.extent_one + 1.0) / 2 * n).round().clip(min=0, max=n-1).astype(np.int) 192 | self.extent_two = ((self.extent_two + 1.0) / 2 * n).round().clip(min=0, max=n-1).astype(np.int) 193 | self.operation = int(self.operation) 194 | self.extent_type = int(self.extent_type) 195 | 196 | self.sketch_pos = ((self.sketch_pos + 1.0) / 2 * n).round().clip(min=0, max=n-1).astype(np.int) 197 | self.sketch_size = (self.sketch_size / 2 * n).round().clip(min=0, max=n-1).astype(np.int) 198 | 199 | def denumericalize(self, n=256): 200 | """de-quantize the representation.""" 201 | self.extent_one = self.extent_one / n * 2 - 1.0 202 | self.extent_two = self.extent_two / n * 2 - 1.0 203 | self.sketch_plane.denumericalize(n) 204 | self.sketch_pos = self.sketch_pos / n * 2 - 1.0 205 | self.sketch_size = self.sketch_size / n * 2 206 | 207 | self.operation = self.operation 208 | self.extent_type = self.extent_type 209 | 210 | def flip_sketch(self, axis): 211 | self.profile.flip(axis) 212 | self.profile.normalize() 213 | 214 | def to_vector(self, max_n_loops=6, max_len_loop=15, pad=True): 215 | """vector representation: commands [SOL, ..., SOL, ..., EXT]""" 216 | profile_vec = self.profile.to_vector(max_n_loops, max_len_loop, pad=False) 217 | if profile_vec is None: 218 | return None 219 | sket_plane_orientation = self.sketch_plane.to_vector()[3:] 220 | ext_param = list(sket_plane_orientation) + list(self.sketch_pos) + [self.sketch_size] + \ 221 | [self.extent_one, self.extent_two, self.operation, self.extent_type] 222 | ext_vec = np.array([EXT_IDX, *[PAD_VAL] * N_ARGS_SKETCH, *ext_param]) 223 | vec = np.concatenate([profile_vec[:-1], ext_vec[np.newaxis], profile_vec[-1:]], axis=0) # NOTE: last one is EOS 224 | if pad: 225 | pad_len = max_n_loops * max_len_loop - vec.shape[0] 226 | vec = np.concatenate([vec, EOS_VEC[np.newaxis].repeat(pad_len, axis=0)], axis=0) 227 | return vec 228 | 229 | 230 | class CADSequence(object): 231 | """A CAD modeling sequence, a series of extrude operations.""" 232 | def __init__(self, extrude_seq, bbox=None): 233 | self.seq = extrude_seq 234 | self.bbox = bbox 235 | 236 | @staticmethod 237 | def from_dict(all_stat): 238 | """construct CADSequence from json data""" 239 | seq = [] 240 | for item in all_stat["sequence"]: 241 | if item["type"] == "ExtrudeFeature": 242 | extrude_ops = Extrude.from_dict(all_stat, item["entity"]) 243 | seq.extend(extrude_ops) 244 | bbox_info = all_stat["properties"]["bounding_box"] 245 | max_point = np.array([bbox_info["max_point"]["x"] * 1000, bbox_info["max_point"]["y"] * 1000, bbox_info["max_point"]["z"] * 1000]) 246 | min_point = np.array([bbox_info["min_point"]["x"] * 1000, bbox_info["min_point"]["y"] * 1000, bbox_info["min_point"]["z"] * 1000]) 247 | bbox = np.stack([max_point, min_point], axis=0) 248 | return CADSequence(seq, bbox) 249 | 250 | @staticmethod 251 | def from_vector(vec, is_numerical=False, n=256): 252 | commands = vec[:, 0] 253 | ext_indices = [-1] + np.where(commands == EXT_IDX)[0].tolist() 254 | ext_seq = [] 255 | for i in range(len(ext_indices) - 1): 256 | start, end = ext_indices[i], ext_indices[i + 1] 257 | ext_seq.append(Extrude.from_vector(vec[start+1:end+1], is_numerical, n)) 258 | cad_seq = CADSequence(ext_seq) 259 | return cad_seq 260 | 261 | def __str__(self): 262 | return "" + "\n".join(["({})".format(i) + str(ext) for i, ext in enumerate(self.seq)]) 263 | 264 | def to_vector(self, max_n_ext=10, max_n_loops=6, max_len_loop=15, max_total_len=60, pad=False): 265 | if len(self.seq) > max_n_ext: 266 | return None 267 | vec_seq = [] 268 | for item in self.seq: 269 | vec = item.to_vector(max_n_loops, max_len_loop, pad=False) 270 | if vec is None: 271 | return None 272 | vec = vec[:-1] # last one is EOS, removed 273 | vec_seq.append(vec) 274 | 275 | vec_seq = np.concatenate(vec_seq, axis=0) 276 | vec_seq = np.concatenate([vec_seq, EOS_VEC[np.newaxis]], axis=0) 277 | 278 | # add EOS padding 279 | if pad and vec_seq.shape[0] < max_total_len: 280 | pad_len = max_total_len - vec_seq.shape[0] 281 | vec_seq = np.concatenate([vec_seq, EOS_VEC[np.newaxis].repeat(pad_len, axis=0)], axis=0) 282 | 283 | return vec_seq 284 | 285 | def transform(self, translation, scale): 286 | """linear transformation""" 287 | for item in self.seq: 288 | item.transform(translation, scale) 289 | 290 | def normalize(self, size=1.0): 291 | """(1)normalize the shape into unit cube (-1~1). """ 292 | scale = size * NORM_FACTOR / np.max(np.abs(self.bbox)) 293 | self.transform(0.0, scale) 294 | 295 | def numericalize(self, n=256): 296 | for item in self.seq: 297 | item.numericalize(n) 298 | 299 | def flip_sketch(self, axis): 300 | for item in self.seq: 301 | item.flip_sketch(axis) 302 | 303 | def random_transform(self): 304 | for item in self.seq: 305 | # random transform sketch 306 | scale = random.uniform(0.8, 1.2) 307 | item.profile.transform(-np.array([128, 128]), scale) 308 | translate = np.array([random.randint(-5, 5), random.randint(-5, 5)], dtype=np.int) + 128 309 | item.profile.transform(translate, 1) 310 | 311 | # random transform and scale extrusion 312 | t = 0.05 313 | translate = np.array([random.uniform(-t, t), random.uniform(-t, t), random.uniform(-t, t)]) 314 | scale = random.uniform(0.8, 1.2) 315 | # item.sketch_plane.transform(translate, scale) 316 | item.sketch_pos = (item.sketch_pos + translate) * scale 317 | item.extent_one *= random.uniform(0.8, 1.2) 318 | item.extent_two *= random.uniform(0.8, 1.2) 319 | 320 | def random_flip_sketch(self): 321 | for item in self.seq: 322 | flip_idx = random.randint(0, 3) 323 | if flip_idx > 0: 324 | item.flip_sketch(['x', 'y', 'xy'][flip_idx - 1]) 325 | -------------------------------------------------------------------------------- /Bethany/lib/file_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import shutil 5 | import csv 6 | 7 | 8 | def save_args(args, save_dir): 9 | param_path = os.path.join(save_dir, 'params.json') 10 | 11 | with open(param_path, 'w') as fp: 12 | json.dump(args.__dict__, fp, indent=4, sort_keys=True) 13 | 14 | 15 | def ensure_dir(path): 16 | """ 17 | create path by first checking its existence, 18 | :param paths: path 19 | :return: 20 | """ 21 | if not os.path.exists(path): 22 | os.makedirs(path) 23 | 24 | 25 | def ensure_dirs(paths): 26 | """ 27 | create paths by first checking their existence 28 | :param paths: list of path 29 | :return: 30 | """ 31 | if isinstance(paths, list) and not isinstance(paths, str): 32 | for path in paths: 33 | ensure_dir(path) 34 | else: 35 | ensure_dir(paths) 36 | 37 | 38 | def remkdir(path): 39 | """ 40 | if dir exists, remove it and create a new one 41 | :param path: 42 | :return: 43 | """ 44 | if os.path.exists(path): 45 | shutil.rmtree(path) 46 | os.makedirs(path) 47 | 48 | 49 | def cycle(iterable): 50 | while True: 51 | for x in iterable: 52 | yield x 53 | -------------------------------------------------------------------------------- /Bethany/lib/macro.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | ALL_COMMANDS = ['Line', 'Arc', 'Circle', 'EOS', 'SOL', 'Ext'] 4 | LINE_IDX = ALL_COMMANDS.index('Line') 5 | ARC_IDX = ALL_COMMANDS.index('Arc') 6 | CIRCLE_IDX = ALL_COMMANDS.index('Circle') 7 | EOS_IDX = ALL_COMMANDS.index('EOS') 8 | SOL_IDX = ALL_COMMANDS.index('SOL') 9 | EXT_IDX = ALL_COMMANDS.index('Ext') 10 | 11 | EXTRUDE_OPERATIONS = ["NewBodyFeatureOperation", "JoinFeatureOperation", 12 | "CutFeatureOperation", "IntersectFeatureOperation"] 13 | EXTENT_TYPE = ["OneSideFeatureExtentType", "SymmetricFeatureExtentType", 14 | "TwoSidesFeatureExtentType"] 15 | 16 | PAD_VAL = -1 17 | N_ARGS_SKETCH = 5 # sketch parameters: x, y, alpha, f, r 18 | N_ARGS_PLANE = 3 # sketch plane orientation: theta, phi, gamma 19 | N_ARGS_TRANS = 4 # sketch plane origin + sketch bbox size: p_x, p_y, p_z, s 20 | N_ARGS_EXT_PARAM = 4 # extrusion parameters: e1, e2, b, u 21 | N_ARGS_EXT = N_ARGS_PLANE + N_ARGS_TRANS + N_ARGS_EXT_PARAM 22 | N_ARGS = N_ARGS_SKETCH + N_ARGS_EXT 23 | 24 | SOL_VEC = np.array([SOL_IDX, *([PAD_VAL] * N_ARGS)]) 25 | EOS_VEC = np.array([EOS_IDX, *([PAD_VAL] * N_ARGS)]) 26 | 27 | CMD_ARGS_MASK = np.array([[1, 1, 0, 0, 0, *[0]*N_ARGS_EXT], # line 28 | [1, 1, 1, 1, 0, *[0]*N_ARGS_EXT], # arc 29 | [1, 1, 0, 0, 1, *[0]*N_ARGS_EXT], # circle 30 | [0, 0, 0, 0, 0, *[0]*N_ARGS_EXT], # EOS 31 | [0, 0, 0, 0, 0, *[0]*N_ARGS_EXT], # SOL 32 | [*[0]*N_ARGS_SKETCH, *[1]*N_ARGS_EXT]]) # Extrude 33 | 34 | NORM_FACTOR = 0.75 # scale factor for normalization to prevent overflow during augmentation 35 | 36 | MAX_N_EXT = 10 # maximum number of extrusion 37 | MAX_N_LOOPS = 6 # maximum number of loops per sketch 38 | MAX_N_CURVES = 15 # maximum number of curves per loop 39 | MAX_TOTAL_LEN = 60 # maximum cad sequence length 40 | ARGS_DIM = 256 41 | -------------------------------------------------------------------------------- /Bethany/lib/math_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import numpy as np 3 | 4 | 5 | def rads_to_degs(rads): 6 | """Convert an angle from radians to degrees""" 7 | return 180 * rads / math.pi 8 | 9 | def distance_of_point_and_plane(origin, x_axis, y_axis, point): 10 | # 确保输入是 numpy 数组 11 | origin = np.array(origin) 12 | x_axis = np.array(x_axis) 13 | y_axis = np.array(y_axis) 14 | point = np.array(point) 15 | normal_vector = np.cross(x_axis, y_axis) 16 | distance = np.dot(point - origin, normal_vector) 17 | return np.abs(distance) 18 | 19 | def is_point_on_plane(origin, x_axis, y_axis, point, tolerance=1e-10): 20 | """ 21 | 判断一个3D点是否在给定的平面上 22 | 23 | 参数: 24 | origin - 平面的原点坐标,形状为 (3,) 25 | x_axis - 平面X轴的方向向量,形状为 (3,) 26 | y_axis - 平面Y轴的方向向量,形状为 (3,) 27 | point - 要检查的3D点,形状为 (3,) 28 | tolerance - 容忍度,默认值为 1e-10 29 | 30 | 返回: 31 | 如果点在平面上返回 True,否则返回 False 32 | """ 33 | # 确保输入是 numpy 数组 34 | origin = np.array(origin) 35 | x_axis = np.array(x_axis) 36 | y_axis = np.array(y_axis) 37 | point = np.array(point) 38 | 39 | # 计算法向量 40 | normal_vector = np.cross(x_axis, y_axis) 41 | 42 | # 计算点到平面的距离 43 | distance = np.dot(point - origin, normal_vector) 44 | 45 | # 判断距离是否在容忍度范围内 46 | return np.abs(distance) < tolerance 47 | 48 | 49 | def number_to_pi_string(number): 50 | """ 51 | 将数字转换为带有 np.pi 的字符串表示形式 52 | 53 | 参数: 54 | number - 输入数字,可以是 np.pi 的倍数 55 | 56 | 返回: 57 | 带有 np.pi 的字符串表示形式 58 | """ 59 | # 定义一个容忍度来比较浮点数 60 | tolerance = 1e-10 61 | 62 | # 预定义一些常见的 π 的倍数及其对应的字符串表示 63 | pi_factors = { 64 | np.pi: 'np.pi', 65 | np.pi / 2: 'np.pi/2', 66 | np.pi / 3: 'np.pi/3', 67 | np.pi / 4: 'np.pi/4', 68 | np.pi / 6: 'np.pi/6', 69 | 2 * np.pi: '2*np.pi', 70 | 3 * np.pi / 2: '3*np.pi/2', 71 | 3 * np.pi / 4: '3*np.pi/4', 72 | 5 * np.pi / 6: '5*np.pi/6', 73 | 5 * np.pi / 3: '5*np.pi/3', 74 | 7 * np.pi / 6: '7*np.pi/6', 75 | 4 * np.pi / 3: '4*np.pi/3', 76 | } 77 | 78 | # 检查输入数字是否接近这些常见的 π 倍数 79 | for key, value in pi_factors.items(): 80 | if np.abs(np.abs(number) - key) < tolerance: 81 | return value if number > 0 else '-' + value 82 | 83 | # 如果数字不在预定义的 π 倍数中,返回原数字 84 | return str(number.round(6)) 85 | 86 | def are_parallel(v1, v2, tol=1e-10): 87 | # 计算叉积 88 | cross_product = np.cross(v1, v2) 89 | # 判断叉积是否接近于零向量 90 | return np.all(np.abs(cross_product) < tol) 91 | 92 | def find_circle_center_and_radius(start_point, end_point, mid_point): 93 | # Calculate midpoints of the chords 94 | mid_point_start_end = (start_point + end_point) / 2 95 | mid_point_start_mid = (start_point + mid_point) / 2 96 | 97 | # Calculate direction vectors of the chords 98 | direction_start_end = end_point - start_point 99 | direction_start_mid = mid_point - start_point 100 | 101 | # Calculate perpendicular direction vectors 102 | perp_start_end = np.array([-direction_start_end[1], direction_start_end[0]]) 103 | perp_start_mid = np.array([-direction_start_mid[1], direction_start_mid[0]]) 104 | 105 | # Solve for the intersection of the perpendicular bisectors 106 | A = np.array([perp_start_end, -perp_start_mid]).T 107 | b = mid_point_start_mid - mid_point_start_end 108 | 109 | # Solve the linear system 110 | t, s = np.linalg.solve(A, b) 111 | 112 | # Calculate the center 113 | center = mid_point_start_end + t * perp_start_end 114 | 115 | # Calculate the radius 116 | radius = np.linalg.norm(center - start_point) 117 | 118 | return center, radius 119 | 120 | def rotate_vector(vector, axis, angle): 121 | """ 122 | 将一个3D向量绕指定轴逆时针旋转给定角度 123 | 124 | 参数: 125 | vector - 要旋转的3D向量,形状为 (3,) 126 | axis - 旋转轴,形状为 (3,) 127 | angle - 旋转角度(弧度) 128 | 129 | 返回: 130 | 旋转后的3D向量,形状为 (3,) 131 | """ 132 | # 确保输入是 numpy 数组 133 | vector = np.array(vector) 134 | axis = np.array(axis) 135 | 136 | # 计算单位轴向量 137 | axis = axis / np.linalg.norm(axis) 138 | 139 | # 计算旋转矩阵的各个分量 140 | cos_theta = np.cos(angle) 141 | sin_theta = np.sin(angle) 142 | cross_product = np.cross(axis, vector) 143 | dot_product = np.dot(axis, vector) 144 | 145 | # 计算旋转后的向量 146 | rotated_vector = (vector * cos_theta + 147 | cross_product * sin_theta + 148 | axis * dot_product * (1 - cos_theta)) 149 | 150 | return rotated_vector 151 | 152 | def calculate_rotation_angle(v1, v2, axis): 153 | """ 154 | 计算从向量 v1 到向量 v2 绕给定轴的逆时针旋转角度 155 | 156 | 参数: 157 | v1 - 初始向量,形状为 (3,) 158 | v2 - 旋转后的向量,形状为 (3,) 159 | axis - 旋转轴,形状为 (3,) 160 | 161 | 返回: 162 | 旋转角度(弧度),范围 (-pi, pi] 163 | """ 164 | # 确保输入是 numpy 数组 165 | v1 = np.array(v1) 166 | v2 = np.array(v2) 167 | axis = np.array(axis) 168 | 169 | # 计算单位轴向量 170 | axis = axis / np.linalg.norm(axis) 171 | 172 | # 计算点积 173 | dot_product = np.dot(v1, v2) 174 | 175 | # 计算叉积 176 | cross_product = np.cross(v1, v2) 177 | 178 | # 计算叉积在旋转轴上的投影长度 179 | projection_length = np.dot(cross_product, axis) 180 | 181 | # 计算向量的范数 182 | norm_v1 = np.linalg.norm(v1) 183 | norm_v2 = np.linalg.norm(v2) 184 | 185 | # 计算角度的余弦值和正弦值 186 | cos_theta = dot_product / (norm_v1 * norm_v2) 187 | sin_theta = projection_length / (norm_v1 * norm_v2) 188 | 189 | # 使用 arctan2 计算角度 190 | angle = np.arctan2(sin_theta, cos_theta) 191 | 192 | return angle 193 | 194 | def map_2d_to_3d(origin, x_axis, y_axis, point): 195 | u, v = point 196 | return origin + u * x_axis + v * y_axis 197 | 198 | def map_3d_to_2d(origin, x_axis, y_axis, point_3d): 199 | """ 200 | 将三维空间中的点转换为二维平面上的点 201 | 202 | 参数: 203 | origin - 原点坐标 (Ox, Oy, Oz),形状为 (3,) 204 | x_axis - X轴向量 (Xx, Xy, Xz),形状为 (3,) 205 | y_axis - Y轴向量 (Yx, Yy, Yz),形状为 (3,) 206 | point_3d - 三维空间中的点 (Px, Py, Pz),形状为 (3,) 207 | 208 | 返回: 209 | 二维平面上的点 (u, v),形状为 (2,) 210 | """ 211 | # 确保输入是 numpy 数组 212 | origin = np.array(origin) 213 | x_axis = np.array(x_axis) 214 | y_axis = np.array(y_axis) 215 | point_3d = np.array(point_3d) 216 | 217 | # 构建矩阵 A 和向量 b 218 | A = np.vstack([x_axis, y_axis]).T 219 | b = point_3d - origin 220 | 221 | # 求解线性方程组 Ax = b 222 | uv = np.linalg.lstsq(A, b, rcond=None)[0] 223 | 224 | return uv 225 | 226 | 227 | def unit_vector(vector): 228 | """ 229 | 计算给定向量的单位向量 230 | 231 | 参数: 232 | vector - 输入向量,形状为 (n,) 233 | 234 | 返回: 235 | 单位向量,形状为 (n,) 236 | """ 237 | # 计算向量的范数 238 | norm = np.linalg.norm(vector) 239 | if norm == 0: 240 | raise ValueError("零向量没有单位向量") 241 | # 计算单位向量 242 | unit_vector = vector / norm 243 | return unit_vector 244 | 245 | 246 | def find_n_from_x_and_y(x, y): 247 | """ 248 | Given vectors x and y, find a vector n such that y = n × x. 249 | Assumes that n is orthogonal to x. 250 | 251 | Parameters: 252 | x (numpy array): The vector x. 253 | y (numpy array): The vector y. 254 | 255 | Returns: 256 | numpy array: The vector n. 257 | """ 258 | # Step 1: Compute the cross product of x and y to get n' 259 | n_prime = np.cross(x, y) 260 | 261 | # Step 2: Normalize n' to get the unit vector 262 | n_prime_unit = n_prime / np.linalg.norm(n_prime) 263 | 264 | # Step 3: Determine the correct sign of n_prime_unit 265 | # To ensure y = n × x, we should check if the direction is correct 266 | if np.allclose(np.cross(n_prime_unit, x), y): 267 | n = n_prime_unit 268 | else: 269 | n = -n_prime_unit 270 | 271 | return n 272 | 273 | def angle_from_vector_to_x(vec): 274 | """computer the angle (0~2pi) between a unit vector and positive x axis""" 275 | angle = 0.0 276 | # 2 | 1 277 | # ------- 278 | # 3 | 4 279 | if vec[0] >= 0: 280 | if vec[1] >= 0: 281 | # Qadrant 1 282 | angle = math.asin(vec[1]) 283 | else: 284 | # Qadrant 4 285 | angle = 2.0 * math.pi - math.asin(-vec[1]) 286 | else: 287 | if vec[1] >= 0: 288 | # Qadrant 2 289 | angle = math.pi - math.asin(vec[1]) 290 | else: 291 | # Qadrant 3 292 | angle = math.pi + math.asin(-vec[1]) 293 | return angle 294 | 295 | 296 | def cartesian2polar(vec, with_radius=False): 297 | """convert a vector in cartesian coordinates to polar(spherical) coordinates""" 298 | vec = vec.round(6) 299 | norm = np.linalg.norm(vec) 300 | theta = np.arccos(vec[2] / norm) # (0, pi) 301 | phi = np.arctan(vec[1] / (vec[0] + 1e-15)) # (-pi, pi) # FIXME: -0.0 cannot be identified here 302 | if not with_radius: 303 | return np.array([theta, phi]) 304 | else: 305 | return np.array([theta, phi, norm]) 306 | 307 | 308 | def polar2cartesian(vec): 309 | """convert a vector in polar(spherical) coordinates to cartesian coordinates""" 310 | r = 1 if len(vec) == 2 else vec[2] 311 | theta, phi = vec[0], vec[1] 312 | x = r * np.sin(theta) * np.cos(phi) 313 | y = r * np.sin(theta) * np.sin(phi) 314 | z = r * np.cos(theta) 315 | return np.array([x, y, z]) 316 | 317 | 318 | def rotate_by_x(vec, theta): 319 | mat = np.array([[1, 0, 0], 320 | [0, np.cos(theta), -np.sin(theta)], 321 | [0, np.sin(theta), np.cos(theta)]]) 322 | return np.dot(mat, vec) 323 | 324 | 325 | def rotate_by_y(vec, theta): 326 | mat = np.array([[np.cos(theta), 0, np.sin(theta)], 327 | [0, 1, 0], 328 | [-np.sin(theta), 0, np.cos(theta)]]) 329 | return np.dot(mat, vec) 330 | 331 | 332 | def rotate_by_z(vec, phi): 333 | mat = np.array([[np.cos(phi), -np.sin(phi), 0], 334 | [np.sin(phi), np.cos(phi), 0], 335 | [0, 0, 1]]) 336 | return np.dot(mat, vec) 337 | 338 | 339 | def polar_parameterization(normal_3d, x_axis_3d): 340 | """represent a coordinate system by its rotation from the standard 3D coordinate system 341 | 342 | Args: 343 | normal_3d (np.array): unit vector for normal direction (z-axis) 344 | x_axis_3d (np.array): unit vector for x-axis 345 | 346 | Returns: 347 | theta, phi, gamma: axis-angle rotation 348 | """ 349 | normal_polar = cartesian2polar(normal_3d) 350 | theta = normal_polar[0] 351 | phi = normal_polar[1] 352 | 353 | ref_x = rotate_by_z(rotate_by_y(np.array([1, 0, 0]), theta), phi) 354 | 355 | gamma = np.arccos(np.dot(x_axis_3d, ref_x).round(6)) 356 | if np.dot(np.cross(ref_x, x_axis_3d), normal_3d) < 0: 357 | gamma = -gamma 358 | return theta, phi, gamma 359 | 360 | 361 | def polar_parameterization_inverse(theta, phi, gamma): 362 | """build a coordinate system by the given rotation from the standard 3D coordinate system""" 363 | normal_3d = polar2cartesian([theta, phi]) 364 | ref_x = rotate_by_z(rotate_by_y(np.array([1, 0, 0]), theta), phi) 365 | ref_y = np.cross(normal_3d, ref_x) 366 | x_axis_3d = ref_x * np.cos(gamma) + ref_y * np.sin(gamma) 367 | return normal_3d, x_axis_3d 368 | -------------------------------------------------------------------------------- /Bethany/lib/sketch.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib 3 | matplotlib.use('TkAgg') 4 | import matplotlib.pyplot as plt 5 | from .curves import * 6 | from .macro import * 7 | 8 | 9 | ########################## base ########################### 10 | class SketchBase(object): 11 | """Base class for sketch (a collection of curves). """ 12 | def __init__(self, children, reorder=True): 13 | self.children = children 14 | 15 | if reorder: 16 | self.reorder() 17 | 18 | @staticmethod 19 | def from_dict(stat): 20 | """construct sketch from json data 21 | 22 | Args: 23 | stat (dict): dict from json data 24 | """ 25 | raise NotImplementedError 26 | 27 | @staticmethod 28 | def from_vector(vec, start_point, is_numerical=True): 29 | """construct sketch from vector representation 30 | 31 | Args: 32 | vec (np.array): (seq_len, n_args) 33 | start_point (np.array): (2, ). If none, implicitly defined as the last end point. 34 | """ 35 | raise NotImplementedError 36 | 37 | def reorder(self): 38 | """rearrange the curves to follow counter-clockwise direction""" 39 | raise NotImplementedError 40 | 41 | @property 42 | def start_point(self): 43 | return self.children[0].start_point 44 | 45 | @property 46 | def end_point(self): 47 | return self.children[-1].end_point 48 | 49 | @property 50 | def bbox(self): 51 | """compute bounding box (min/max points) of the sketch""" 52 | all_points = np.concatenate([child.bbox for child in self.children], axis=0) 53 | return np.stack([np.min(all_points, axis=0), np.max(all_points, axis=0)], axis=0) 54 | 55 | @property 56 | def bbox_size(self): 57 | """compute bounding box size (max of height and width)""" 58 | bbox_min, bbox_max = self.bbox[0], self.bbox[1] 59 | bbox_size = np.max(np.abs(np.concatenate([bbox_max - self.start_point, bbox_min - self.start_point]))) 60 | return bbox_size 61 | 62 | @property 63 | def global_trans(self): 64 | """start point + sketch size (bbox_size)""" 65 | return np.concatenate([self.start_point, np.array([self.bbox_size])]) 66 | 67 | def transform(self, translate, scale): 68 | """linear transformation""" 69 | for child in self.children: 70 | child.transform(translate, scale) 71 | 72 | def flip(self, axis): 73 | for child in self.children: 74 | child.flip(axis) 75 | self.reorder() 76 | 77 | def numericalize(self, n=256): 78 | """quantize curve parameters into integers""" 79 | for child in self.children: 80 | child.numericalize(n) 81 | 82 | def normalize(self, size=256): 83 | """normalize within the given size, with start_point in the middle center""" 84 | cur_size = self.bbox_size 85 | scale = (size / 2 * NORM_FACTOR - 1) / cur_size # prevent potential overflow if data augmentation applied 86 | #self.transform(-self.start_point, scale) 87 | #self.transform(np.array((size / 2, size / 2)), 1) 88 | 89 | def denormalize(self, bbox_size, size=256): 90 | """inverse procedure of normalize method""" 91 | scale = bbox_size / (size / 2 * NORM_FACTOR - 1) 92 | #self.transform(-np.array((size / 2, size / 2)), scale) 93 | 94 | def to_vector(self): 95 | """convert to vector representation""" 96 | raise NotImplementedError 97 | 98 | def draw(self, ax): 99 | """draw sketch on matplotlib ax""" 100 | raise NotImplementedError 101 | 102 | def to_image(self): 103 | """convert to image""" 104 | fig, ax = plt.subplots() 105 | self.draw(ax) 106 | ax.axis('equal') 107 | fig.canvas.draw() 108 | X = np.array(fig.canvas.renderer.buffer_rgba())[:, :, :3] 109 | plt.close(fig) 110 | return X 111 | 112 | def sample_points(self, n=32): 113 | """uniformly sample points from the sketch""" 114 | raise NotImplementedError 115 | 116 | 117 | ####################### loop & profile ####################### 118 | class Loop(SketchBase): 119 | """Sketch loop, a sequence of connected curves.""" 120 | @staticmethod 121 | def from_dict(stat): 122 | all_curves = [construct_curve_from_dict(item) for item in stat['profile_curves']] 123 | this_loop = Loop(all_curves) 124 | this_loop.is_outer = stat['is_outer'] 125 | return this_loop 126 | 127 | def __str__(self): 128 | return "Loop:" + "\n -" + "\n -".join([str(curve) for curve in self.children]) 129 | 130 | @staticmethod 131 | def from_vector(vec, start_point=None, is_numerical=True): 132 | all_curves = [] 133 | if start_point is None: 134 | # FIXME: explicit for loop can be avoided here 135 | for i in range(vec.shape[0]): 136 | if vec[i][0] == EOS_IDX: 137 | start_point = vec[i - 1][1:3] 138 | break 139 | for i in range(vec.shape[0]): 140 | type = vec[i][0] 141 | if type == SOL_IDX: 142 | continue 143 | elif type == EOS_IDX: 144 | break 145 | else: 146 | curve = construct_curve_from_vector(vec[i], start_point, is_numerical=is_numerical) 147 | start_point = vec[i][1:3] # current curve's end_point serves as next curve's start_point 148 | all_curves.append(curve) 149 | return Loop(all_curves) 150 | 151 | def reorder(self): 152 | """reorder by starting left most and counter-clockwise""" 153 | if len(self.children) <= 1: 154 | return 155 | 156 | start_curve_idx = -1 157 | sx, sy = 10000, 10000 158 | 159 | # correct start-end point order 160 | if np.allclose(self.children[0].start_point, self.children[1].start_point) or \ 161 | np.allclose(self.children[0].start_point, self.children[1].end_point): 162 | self.children[0].reverse() 163 | 164 | # correct start-end point order and find left-most point 165 | for i, curve in enumerate(self.children): 166 | if i < len(self.children) - 1 and np.allclose(curve.end_point, self.children[i + 1].end_point): 167 | self.children[i + 1].reverse() 168 | if round(curve.start_point[0], 6) < round(sx, 6) or \ 169 | (round(curve.start_point[0], 6) == round(sx, 6) and round(curve.start_point[1], 6) < round(sy, 6)): 170 | start_curve_idx = i 171 | sx, sy = curve.start_point 172 | 173 | self.children = self.children[start_curve_idx:] + self.children[:start_curve_idx] 174 | 175 | # ensure mostly counter-clock wise 176 | if isinstance(self.children[0], Circle) or isinstance(self.children[-1], Circle): # FIXME: hard-coded 177 | return 178 | start_vec = self.children[0].direction() 179 | end_vec = self.children[-1].direction(from_start=False) 180 | if np.cross(end_vec, start_vec) <= 0: 181 | for curve in self.children: 182 | curve.reverse() 183 | self.children.reverse() 184 | 185 | def to_vector(self, max_len=None, add_sol=True, add_eos=True): 186 | loop_vec = np.stack([curve.to_vector() for curve in self.children], axis=0) 187 | if add_sol: 188 | loop_vec = np.concatenate([SOL_VEC[np.newaxis], loop_vec], axis=0) 189 | if add_eos: 190 | loop_vec = np.concatenate([loop_vec, EOS_VEC[np.newaxis]], axis=0) 191 | if max_len is None: 192 | return loop_vec 193 | 194 | if loop_vec.shape[0] > max_len: 195 | return None 196 | elif loop_vec.shape[0] < max_len: 197 | pad_vec = np.tile(EOS_VEC, max_len - loop_vec.shape[0]).reshape((-1, len(EOS_VEC))) 198 | loop_vec = np.concatenate([loop_vec, pad_vec], axis=0) # (max_len, 1 + N_ARGS) 199 | return loop_vec 200 | 201 | def draw(self, ax): 202 | colors = ['red', 'blue', 'green', 'brown', 'pink', 'yellow', 'purple', 'black'] * 10 203 | for i, curve in enumerate(self.children): 204 | curve.draw(ax, colors[i]) 205 | 206 | def sample_points(self, n=32): 207 | points = np.stack([curve.sample_points(n) for curve in self.children], axis=0) # (n_curves, n, 2) 208 | return points 209 | 210 | 211 | class Profile(SketchBase): 212 | """Sketch profile,a closed region formed by one or more loops. 213 | The outer-most loop is placed at first.""" 214 | @staticmethod 215 | def from_dict(stat): 216 | all_loops = [Loop.from_dict(item) for item in stat['loops']] 217 | return Profile(all_loops) 218 | 219 | def __str__(self): 220 | return "Profile:" + "\n -".join([str(loop) for loop in self.children]) 221 | 222 | @staticmethod 223 | def from_vector(vec, start_point=None, is_numerical=True): 224 | all_loops = [] 225 | command = vec[:, 0] 226 | end_idx = command.tolist().index(EOS_IDX) 227 | indices = np.where(command[:end_idx] == SOL_IDX)[0].tolist() + [end_idx] 228 | for i in range(len(indices) - 1): 229 | loop_vec = vec[indices[i]:indices[i + 1]] 230 | loop_vec = np.concatenate([loop_vec, EOS_VEC[np.newaxis]], axis=0) 231 | if loop_vec[0][0] == SOL_IDX and loop_vec[1][0] not in [SOL_IDX, EOS_IDX]: 232 | all_loops.append(Loop.from_vector(loop_vec, is_numerical=is_numerical)) 233 | return Profile(all_loops) 234 | 235 | def reorder(self): 236 | if len(self.children) <= 1: 237 | return 238 | all_loops_bbox_min = np.stack([loop.bbox[0] for loop in self.children], axis=0).round(6) 239 | ind = np.lexsort(all_loops_bbox_min.transpose()[[1, 0]]) 240 | self.children = [self.children[i] for i in ind] 241 | 242 | def draw(self, ax): 243 | for i, loop in enumerate(self.children): 244 | loop.draw(ax) 245 | ax.text(loop.start_point[0], loop.start_point[1], str(i)) 246 | 247 | def to_vector(self, max_n_loops=None, max_len_loop=None, pad=True): 248 | loop_vecs = [loop.to_vector(None, add_eos=False) for loop in self.children] 249 | if max_n_loops is not None and len(loop_vecs) > max_n_loops: 250 | return None 251 | for vec in loop_vecs: 252 | if max_len_loop is not None and vec.shape[0] > max_len_loop: 253 | return None 254 | profile_vec = np.concatenate(loop_vecs, axis=0) 255 | profile_vec = np.concatenate([profile_vec, EOS_VEC[np.newaxis]], axis=0) 256 | if pad: 257 | pad_len = max_n_loops * max_len_loop - profile_vec.shape[0] 258 | profile_vec = np.concatenate([profile_vec, EOS_VEC[np.newaxis].repeat(pad_len, axis=0)], axis=0) 259 | return profile_vec 260 | 261 | def sample_points(self, n=32): 262 | points = np.concatenate([loop.sample_points(n) for loop in self.children], axis=0) 263 | return points 264 | -------------------------------------------------------------------------------- /Bethany/lib/timeout.py: -------------------------------------------------------------------------------- 1 | 2 | import signal 3 | 4 | # 定义超时异常 5 | class TimeoutException(Exception): 6 | pass 7 | 8 | # 处理超时信号 9 | def handler(signum, frame): 10 | raise TimeoutException() 11 | 12 | # 设置超时时间(秒) 13 | timeout = 30 14 | 15 | # 使用装饰器设置超时 16 | def timeout_decorator(func): 17 | def wrapper(*args, **kwargs): 18 | # 设置信号处理器 19 | signal.signal(signal.SIGALRM, handler) 20 | # 启动闹钟 21 | signal.alarm(timeout) 22 | try: 23 | result = func(*args, **kwargs) 24 | except TimeoutException: 25 | print("Function timed out!") 26 | result = None 27 | finally: 28 | # 关闭闹钟 29 | signal.alarm(0) 30 | return result 31 | return wrapper -------------------------------------------------------------------------------- /Bethany/lib/visualize.py: -------------------------------------------------------------------------------- 1 | from OCC.Core.gp import gp_Pnt, gp_Dir, gp_Circ, gp_Pln, gp_Vec, gp_Ax3, gp_Ax2, gp_Lin 2 | from OCC.Core.BRepBuilderAPI import (BRepBuilderAPI_MakeEdge, BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakeWire) 3 | from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakePrism 4 | from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Fuse, BRepAlgoAPI_Common 5 | from OCC.Core.GC import GC_MakeArcOfCircle 6 | from OCC.Extend.DataExchange import write_stl_file 7 | from OCC.Core.Bnd import Bnd_Box 8 | from OCC.Core.BRepBndLib import brepbndlib_Add 9 | from copy import copy 10 | from .extrude import * 11 | from .sketch import Loop, Profile 12 | from .curves import * 13 | import os 14 | import trimesh 15 | from trimesh.sample import sample_surface 16 | import random 17 | 18 | from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB 19 | from OCC.Core.TDocStd import TDocStd_Document 20 | from OCC.Core.XCAFDoc import XCAFDoc_DocumentTool, XCAFDoc_ColorGen 21 | from OCC.Core.AIS import AIS_Shape 22 | 23 | # 创建不同颜色 24 | red = Quantity_Color(1.0, 0.1, 0.1, Quantity_TOC_RGB) 25 | green = Quantity_Color(0.1, 1.0, 0.1, Quantity_TOC_RGB) 26 | blue = Quantity_Color(0.1, 0.1, 1.0, Quantity_TOC_RGB) 27 | white = Quantity_Color(1.0, 1.0, 1.0, Quantity_TOC_RGB) 28 | gray = Quantity_Color(0.1, 0.1, 0.1, Quantity_TOC_RGB) 29 | 30 | import random 31 | def generate_random_color(): 32 | r = random.uniform(0, 1) 33 | g = random.uniform(0, 1) 34 | b = random.uniform(0, 1) 35 | return Quantity_Color(r, g, b, Quantity_TOC_RGB) 36 | import numpy as np 37 | def color_distance(color1, color2): 38 | rgb1 = np.array([color1.Red(), color1.Green(), color1.Blue()]) 39 | rgb2 = np.array([color2.Red(), color2.Green(), color2.Blue()]) 40 | return np.linalg.norm(rgb1 - rgb2) 41 | 42 | def vec2CADsolid(vec, is_numerical=True, n=256): 43 | cad = CADSequence.from_vector(vec, is_numerical=is_numerical, n=256) 44 | cad = create_CAD(cad) 45 | return cad 46 | 47 | 48 | from copy import deepcopy 49 | def create_CAD_index(doc: TDocStd_Document, cad_seq: CADSequence, index = 0, color = red): 50 | """create a 3D CAD model from CADSequence. Only support extrude with boolean operation.""" 51 | if len(cad_seq.seq) != 1: 52 | _ = create_CAD(doc, cad_seq) 53 | extrude_op = cad_seq.seq[index] 54 | profile = copy(extrude_op.profile) # use copy to prevent changing extrude_op internally 55 | profile.denormalize(extrude_op.sketch_size) 56 | 57 | sketch_plane = copy(extrude_op.sketch_plane) 58 | #sketch_plane.origin = extrude_op.sketch_pos 59 | 60 | face = create_profile_face(profile, sketch_plane) 61 | normal = gp_Dir(*extrude_op.sketch_plane.normal) 62 | ext_vec = gp_Vec(normal).Multiplied(extrude_op.extent_one) 63 | body = BRepPrimAPI_MakePrism(face, ext_vec).Shape() 64 | 65 | shape_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main()) 66 | color_tool = XCAFDoc_DocumentTool.ColorTool(doc.Main()) 67 | label = shape_tool.AddShape(body) 68 | color_tool.SetColor(label, red, XCAFDoc_ColorGen) 69 | 70 | if extrude_op.extent_type == EXTENT_TYPE.index("SymmetricFeatureExtentType"): 71 | body_sym = BRepPrimAPI_MakePrism(face, ext_vec.Reversed()).Shape() 72 | body = BRepAlgoAPI_Fuse(body, body_sym).Shape() 73 | label_sym = shape_tool.AddShape(body_sym) 74 | color_tool.SetColor(label_sym, red, XCAFDoc_ColorGen) 75 | if extrude_op.extent_type == EXTENT_TYPE.index("TwoSidesFeatureExtentType"): 76 | ext_vec = gp_Vec(normal.Reversed()).Multiplied(extrude_op.extent_two) 77 | body_two = BRepPrimAPI_MakePrism(face, ext_vec).Shape() 78 | body = BRepAlgoAPI_Fuse(body, body_two).Shape() 79 | label_two = shape_tool.AddShape(body_two) 80 | color_tool.SetColor(label_two, red, XCAFDoc_ColorGen) 81 | return body 82 | 83 | def create_CAD(doc: TDocStd_Document, cad_seq: CADSequence): 84 | """create a 3D CAD model from CADSequence. Only support extrude with boolean operation.""" 85 | body = create_by_extrude(doc, cad_seq.seq[0]) 86 | 87 | for extrude_op in cad_seq.seq[1:]: 88 | new_body = create_by_extrude(doc, extrude_op) 89 | 90 | if extrude_op.operation == EXTRUDE_OPERATIONS.index("NewBodyFeatureOperation") or \ 91 | extrude_op.operation == EXTRUDE_OPERATIONS.index("JoinFeatureOperation"): 92 | body = BRepAlgoAPI_Fuse(body, new_body).Shape() 93 | elif extrude_op.operation == EXTRUDE_OPERATIONS.index("CutFeatureOperation"): 94 | body = BRepAlgoAPI_Cut(body, new_body).Shape() 95 | elif extrude_op.operation == EXTRUDE_OPERATIONS.index("IntersectFeatureOperation"): 96 | body = BRepAlgoAPI_Common(body, new_body).Shape() 97 | 98 | shape_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main()) 99 | _ = shape_tool.AddShape(body) 100 | return body 101 | 102 | 103 | def create_by_extrude(doc: TDocStd_Document, extrude_op: Extrude): 104 | """create a solid body from Extrude instance.""" 105 | profile = copy(extrude_op.profile) # use copy to prevent changing extrude_op internally 106 | profile.denormalize(extrude_op.sketch_size) 107 | 108 | sketch_plane = copy(extrude_op.sketch_plane) 109 | #sketch_plane.origin = extrude_op.sketch_pos 110 | 111 | face = create_profile_face(profile, sketch_plane) 112 | normal = gp_Dir(*extrude_op.sketch_plane.normal) 113 | ext_vec = gp_Vec(normal).Multiplied(extrude_op.extent_one) 114 | body = BRepPrimAPI_MakePrism(face, ext_vec).Shape() 115 | if extrude_op.extent_type == EXTENT_TYPE.index("SymmetricFeatureExtentType"): 116 | body_sym = BRepPrimAPI_MakePrism(face, ext_vec.Reversed()).Shape() 117 | body = BRepAlgoAPI_Fuse(body, body_sym).Shape() 118 | if extrude_op.extent_type == EXTENT_TYPE.index("TwoSidesFeatureExtentType"): 119 | ext_vec = gp_Vec(normal.Reversed()).Multiplied(extrude_op.extent_two) 120 | body_two = BRepPrimAPI_MakePrism(face, ext_vec).Shape() 121 | body = BRepAlgoAPI_Fuse(body, body_two).Shape() 122 | 123 | return body 124 | 125 | 126 | def create_profile_face(profile: Profile, sketch_plane: CoordSystem): 127 | """create a face from a sketch profile and the sketch plane""" 128 | origin = gp_Pnt(*sketch_plane.origin) 129 | normal = gp_Dir(*sketch_plane.normal) 130 | x_axis = gp_Dir(*sketch_plane.x_axis) 131 | gp_face = gp_Pln(gp_Ax3(origin, normal, x_axis)) 132 | 133 | all_loops = [create_loop_3d(loop, sketch_plane) for loop in profile.children] 134 | topo_face = BRepBuilderAPI_MakeFace(gp_face, all_loops[0]) 135 | for loop in all_loops[1:]: 136 | topo_face.Add(loop.Reversed()) 137 | return topo_face.Face() 138 | 139 | 140 | def create_loop_3d(loop: Loop, sketch_plane: CoordSystem): 141 | """create a 3D sketch loop""" 142 | topo_wire = BRepBuilderAPI_MakeWire() 143 | for curve in loop.children: 144 | topo_edge = create_edge_3d(curve, sketch_plane) 145 | if topo_edge == -1: # omitted 146 | continue 147 | topo_wire.Add(topo_edge) 148 | return topo_wire.Wire() 149 | 150 | 151 | def create_edge_3d(curve: CurveBase, sketch_plane: CoordSystem): 152 | """create a 3D edge""" 153 | if isinstance(curve, Line): 154 | if np.allclose(curve.start_point, curve.end_point): 155 | return -1 156 | start_point = point_local2global(curve.start_point, sketch_plane) 157 | end_point = point_local2global(curve.end_point, sketch_plane) 158 | topo_edge = BRepBuilderAPI_MakeEdge(start_point, end_point) 159 | elif isinstance(curve, Circle): 160 | center = point_local2global(curve.center, sketch_plane) 161 | axis = gp_Dir(*sketch_plane.normal) 162 | gp_circle = gp_Circ(gp_Ax2(center, axis), abs(float(curve.radius))) 163 | topo_edge = BRepBuilderAPI_MakeEdge(gp_circle) 164 | elif isinstance(curve, Arc): 165 | # print(curve.start_point, curve.mid_point, curve.end_point) 166 | start_point = point_local2global(curve.start_point, sketch_plane) 167 | mid_point = point_local2global(curve.mid_point, sketch_plane) 168 | end_point = point_local2global(curve.end_point, sketch_plane) 169 | arc = GC_MakeArcOfCircle(start_point, mid_point, end_point).Value() 170 | topo_edge = BRepBuilderAPI_MakeEdge(arc) 171 | else: 172 | raise NotImplementedError(type(curve)) 173 | return topo_edge.Edge() 174 | 175 | 176 | def point_local2global(point, sketch_plane: CoordSystem, to_gp_Pnt=True): 177 | """convert point in sketch plane local coordinates to global coordinates""" 178 | g_point = point[0] * sketch_plane.x_axis + point[1] * sketch_plane.y_axis + sketch_plane.origin 179 | if to_gp_Pnt: 180 | return gp_Pnt(*g_point) 181 | return g_point 182 | 183 | 184 | def CADsolid2pc(shape, n_points, name=None): 185 | """convert opencascade solid to point clouds""" 186 | bbox = Bnd_Box() 187 | brepbndlib_Add(shape, bbox) 188 | if bbox.IsVoid(): 189 | raise ValueError("box check failed") 190 | 191 | if name is None: 192 | name = random.randint(100000, 999999) 193 | write_stl_file(shape, "tmp_out_{}.stl".format(name)) 194 | out_mesh = trimesh.load("tmp_out_{}.stl".format(name)) 195 | os.system("rm tmp_out_{}.stl".format(name)) 196 | out_pc, _ = sample_surface(out_mesh, n_points) 197 | return out_pc 198 | -------------------------------------------------------------------------------- /Bethany/png2jpg.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import os 4 | import glob 5 | sys.path.append(".") 6 | from PIL import Image 7 | 8 | parser = argparse.ArgumentParser() 9 | parser.add_argument('--src', type=str, required=True, help="source folder") 10 | 11 | from PIL import Image 12 | import os 13 | 14 | 15 | parser = argparse.ArgumentParser() 16 | parser.add_argument('--src', type=str, required=True, help="source folder") 17 | args = parser.parse_args() 18 | 19 | src_dir = args.src 20 | print(src_dir) 21 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("png")))) 22 | 23 | def convert_png_to_jpg_and_delete(png_path, jpg_path): 24 | # 打开PNG文件 25 | png_image = Image.open(png_path) 26 | 27 | # 转换为RGB模式 28 | rgb_image = png_image.convert('RGB') 29 | 30 | # 保存为JPG文件 31 | rgb_image.save(jpg_path, "JPEG") 32 | 33 | # 删除原始的PNG文件 34 | os.remove(png_path) 35 | print(f"{png_path} has been deleted.") 36 | 37 | 38 | from multiprocessing import Process, cpu_count 39 | 40 | num_processes = cpu_count() 41 | 42 | def main_process(process_id): 43 | 44 | for index in range(process_id, len(out_paths), num_processes): 45 | print(f"{index + 1}/{len(out_paths)}",end='\r') 46 | path = out_paths[index] 47 | name = path.split("/")[-1].split(".")[0] 48 | save_path = os.path.join(src_dir, name + ".jpg") 49 | # 调用函数进行转换并删除原始文件 50 | convert_png_to_jpg_and_delete(path, save_path) 51 | 52 | if __name__ == "__main__": 53 | processes = [] 54 | for i in range(num_processes): 55 | process = Process(target=main_process, args=(i,)) 56 | processes.append(process) 57 | process.start() 58 | 59 | # 等待所有进程完成 60 | for process in processes: 61 | process.join() 62 | 63 | print('任务完成') -------------------------------------------------------------------------------- /Bethany/py2step.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import numpy as np 4 | import argparse 5 | import sys 6 | sys.path.append(".") 7 | 8 | from lib.DataExchange import write_step_file 9 | from lib.visualize import create_CAD 10 | from lib.curves import Line, Arc, Circle 11 | from lib.sketch import Loop, Profile 12 | from lib.math_utils import * 13 | from lib.extrude import CoordSystem, Extrude, CADSequence 14 | 15 | # Curves 16 | def add_line(start, end): 17 | start = np.array(start) 18 | end = np.array(end) 19 | return Line(start, end) 20 | 21 | def add_arc(start, end, mid): 22 | # get radius and center point 23 | start = np.array(start) 24 | end = np.array(end) 25 | mid = np.array(mid) 26 | center, radius = find_circle_center_and_radius(start, end, mid) 27 | def get_angles_counterclockwise(eps=1e-8): 28 | c2s_vec = (start - center) / (np.linalg.norm(start - center) + eps) 29 | c2m_vec = (mid - center) / (np.linalg.norm(mid - center) + eps) 30 | c2e_vec = (end - center) / (np.linalg.norm(end - center) + eps) 31 | angle_s, angle_m, angle_e = angle_from_vector_to_x(c2s_vec), angle_from_vector_to_x(c2m_vec), \ 32 | angle_from_vector_to_x(c2e_vec) 33 | angle_s, angle_e = min(angle_s, angle_e), max(angle_s, angle_e) 34 | if not angle_s < angle_m < angle_e: 35 | angle_s, angle_e = angle_e - np.pi * 2, angle_s 36 | return angle_s, angle_e 37 | angle_s, angle_e = get_angles_counterclockwise() 38 | return Arc(start, end, center, radius, start_angle=angle_s, end_angle=angle_e, mid_point=mid) 39 | 40 | def add_circle(center, radius): 41 | center = np.array(center) 42 | return Circle(center, radius) 43 | 44 | # Loops 45 | def add_loop(curves): 46 | res = Loop(curves) 47 | res.reorder() 48 | def autofix(loop): 49 | if len(loop.children) <= 1: 50 | return 51 | if isinstance(loop.children[0], Circle): 52 | return 53 | for i in range(0, len(loop.children) - 1): 54 | if not np.allclose(loop.children[i].end_point, loop.children[i+1].start_point): 55 | loop.children[i+1].start_point = loop.children[i].end_point 56 | print("warning: fixing loop") 57 | if not np.allclose(loop.children[len(loop.children) - 1].end_point, loop.children[0].start_point): 58 | loop.children[len(loop.children) - 1].start_point = loop.children[0].end_point 59 | print("warning: fixing loop") 60 | 61 | autofix(res) 62 | return res 63 | 64 | # Sketch-Profile 65 | def add_profile(loops): 66 | return Profile(loops) 67 | 68 | def add_sketchplane(origin, normal, x_axis):#, y_axis): 69 | #print(origin, normal, x_axis) 70 | origin = np.array(origin) 71 | normal = np.array(normal) 72 | x_axis = np.array(x_axis) 73 | y_axis = find_n_from_x_and_y(normal, x_axis) 74 | # get theta and phi 75 | theta, phi, gamma = polar_parameterization(normal, x_axis) 76 | #print(normal_axis, x_axis) 77 | #print(theta, phi, gamma) 78 | return CoordSystem(origin, theta, phi, gamma, y_axis=cartesian2polar(y_axis)) 79 | 80 | def add_sketchplane_ref(extrude: Extrude, origin, type: str, line: Line = None, reverse = False, angle=0): 81 | origin = np.array(origin) 82 | types_dict = ["sameplane", "extent_one", "extent_two", "line"] 83 | """ 84 | sameplane: 参考Extrude的SketchPlane,angle是以normal为轴,向下看逆时针角度,原点是在SketchPlane内的2D相对坐标。reverse只在最后反转normal,其他参考并不反转。 85 | line:默认normal为y轴,line start to end 为x轴,它们的叉积为方向向量normal',原点是在该默认平面的2D相对坐标。angle是以normal'为轴,向下看逆时针角度。reverse只在最后反转normal',其他参考并不反转。 86 | """ 87 | if type not in types_dict: 88 | raise ValueError 89 | ref_plane = extrude.sketch_plane 90 | ref_x_axis = unit_vector(ref_plane.x_axis) 91 | ref_y_axis = unit_vector(ref_plane.y_axis) 92 | ref_n_axis = unit_vector(ref_plane.normal) 93 | ref_origin = ref_plane.origin 94 | if type == "sameplane": 95 | real_origin = map_2d_to_3d(ref_origin, ref_x_axis, ref_y_axis, origin) 96 | elif type == "extent_one": 97 | ref_origin_ = ref_origin + extrude.extent_one * ref_n_axis 98 | real_origin = map_2d_to_3d(ref_origin_, ref_x_axis, ref_y_axis, origin) 99 | elif type == "extent_two": 100 | ref_origin_ = ref_origin - extrude.extent_two * ref_n_axis 101 | real_origin = map_2d_to_3d(ref_origin_, ref_x_axis, ref_y_axis, origin) 102 | if type in types_dict[:3]: 103 | real_x_axis = rotate_vector(ref_x_axis, ref_n_axis, angle) 104 | return add_sketchplane(real_origin, ref_n_axis if not reverse else -ref_n_axis, real_x_axis) 105 | if type == "line": 106 | if line is None: 107 | raise TypeError 108 | start_point = map_2d_to_3d(ref_origin, ref_x_axis, ref_y_axis, line.start_point) 109 | end_point = map_2d_to_3d(ref_origin, ref_x_axis, ref_y_axis, line.end_point) 110 | default_x_axis = unit_vector(end_point - start_point) 111 | real_origin = map_2d_to_3d(start_point, default_x_axis, ref_n_axis, origin) # ref_n_axis is y axis of default plane 112 | default_normal = find_n_from_x_and_y(default_x_axis, ref_n_axis) 113 | real_x_axis = rotate_vector(default_x_axis, default_normal, angle) 114 | return add_sketchplane(real_origin, default_normal if not reverse else -default_normal, real_x_axis) 115 | raise ValueError 116 | 117 | class Sketch(object): 118 | def __init__(self, sketch_plane, profile, sketch_position, sketch_size): 119 | self.sketch_plane = sketch_plane 120 | self.profile = profile 121 | self.sketch_position = sketch_position 122 | self.sketch_size = sketch_size 123 | 124 | def add_sketch(sketch_plane, profile, sketch_position=[0.0,0.0,0.0], sketch_size=0): 125 | return Sketch(sketch_plane, profile, np.array(sketch_position), sketch_size) 126 | 127 | cad_seq = [] 128 | def add_extrude(sketch: Sketch, operation, type, extent_one, extent_two): 129 | res = Extrude( 130 | sketch.profile, 131 | sketch.sketch_plane, 132 | np.intc(operation), np.intc(type), np.double(extent_one), np.double(extent_two), 133 | np.double(sketch.sketch_position), 134 | np.double(sketch.sketch_size) 135 | ) 136 | cad_seq.append(res) 137 | return res 138 | 139 | from OCC.Core.TDocStd import TDocStd_Document 140 | from OCC.Core.TCollection import TCollection_ExtendedString 141 | 142 | def _process(path, path_o): 143 | global cad_seq 144 | cad_seq.clear() 145 | with open(path, 'r') as file: 146 | codes = file.read() 147 | # 执行读取到的代码 148 | 149 | codes = codes.split("\n") 150 | i = 0 151 | last_curves_list, last_loops_list = None, None 152 | while i < len(codes): 153 | code = codes[i] 154 | last_curve_name, last_loop_name = None, None 155 | if codes[i].startswith('Curves'): 156 | last_curves_list = codes[i].split('=', 1)[0].split()[0] 157 | elif codes[i].startswith('Loops'): 158 | last_loops_list = codes[i].split('=', 1)[0].split()[0] 159 | elif codes[i].startswith('Arc') or codes[i].startswith('Line') or codes[i].startswith('Circle'): 160 | last_curve_name = codes[i].split('=', 1)[0].split()[0] 161 | elif codes[i].startswith('Loop'): 162 | last_loop_name = codes[i].split('=', 1)[0].split()[0] 163 | for j in range(i + 1, len(codes)): 164 | if codes[j].startswith('\t') or codes[j].startswith(' '): 165 | code += '\n' + codes[j] 166 | i += 1 167 | else: 168 | break 169 | exec(code) 170 | if last_curve_name is not None: 171 | exec(f"{last_curves_list}.append({last_curve_name})") 172 | elif last_loop_name is not None: 173 | exec(f"{last_loops_list}.append({last_loop_name})") 174 | 175 | i += 1 176 | doc_name = TCollection_ExtendedString("pythonocc-doc") 177 | doc = TDocStd_Document(doc_name) 178 | cad = CADSequence(cad_seq) 179 | #print(cad) 180 | out_shape = create_CAD(doc, cad) 181 | 182 | write_step_file(doc, path_o) 183 | 184 | error_list = [] 185 | 186 | from lib.file_utils import ensure_dir 187 | 188 | parser = argparse.ArgumentParser() 189 | parser.add_argument('--src', type=str, required=True, help="source folder") 190 | parser.add_argument('-o', '--outputs', type=str, default=None, help="save folder") 191 | args = parser.parse_args() 192 | 193 | src_dir = args.src 194 | print(src_dir) 195 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("py")))) 196 | save_dir = args.src + "_step" if args.outputs is None else args.outputs 197 | ensure_dir(save_dir) 198 | 199 | import traceback 200 | 201 | for path in out_paths: 202 | name = path.split("/")[-1].split(".")[0] 203 | try: 204 | save_path = os.path.join(save_dir, name + ".step") 205 | _process(path, save_path) 206 | 207 | except Exception as e: 208 | print("load and create failed.") 209 | traceback.print_exc() 210 | print(name) 211 | error_list.append(name) 212 | 213 | for error in error_list: 214 | print(error) -------------------------------------------------------------------------------- /Bethany/requirements.txt: -------------------------------------------------------------------------------- 1 | matplotlib==3.9.1 2 | numpy==1.26.4 3 | Pillow==8.3.2 4 | trimesh==3.23.5 5 | count-tokens==0.7.0 6 | opencv-python==4.10.0.84 -------------------------------------------------------------------------------- /Bethany/step2img.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import os 4 | import glob 5 | import json 6 | sys.path.append(".") 7 | from lib.file_utils import ensure_dir 8 | import random 9 | 10 | from OCC.Core.STEPControl import STEPControl_Reader 11 | from OCC.Core.IFSelect import IFSelect_RetDone 12 | from OCC.Display.SimpleGui import init_display 13 | from OCC.Core.Bnd import Bnd_Box 14 | from OCC.Core.BRepBndLib import brepbndlib_Add 15 | 16 | from OCC.Extend.DataExchange import read_step_file_with_names_colors 17 | from OCC.Core.Quantity import Quantity_Color, Quantity_TOC_RGB 18 | 19 | from OCC.Core.AIS import AIS_Shape 20 | from OCC.Core.Aspect import Aspect_TOL_SOLID, Aspect_TOL_DASH, Aspect_TOL_DOT 21 | from OCC.Core.Prs3d import Prs3d_Drawer, Prs3d_LineAspect, Prs3d_Root 22 | 23 | from PIL import Image 24 | 25 | import traceback 26 | 27 | os.environ["PYTHONOCC_OFFSCREEN_RENDERER"] = "1" 28 | 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument('--src', type=str, required=True, help="source folder") 31 | parser.add_argument('--idx', type=int, default=0, help="export n files starting from idx.") 32 | parser.add_argument('--num', type=int, default=-1, help="number of shapes to export. -1 exports all shapes.") 33 | parser.add_argument('-o', '--outputs', type=str, default=None, help="save folder") 34 | parser.add_argument('--mode', type=str, default="default", help="mode of generation") 35 | args = parser.parse_args() 36 | 37 | src_dir = args.src 38 | print(src_dir) 39 | out_paths = sorted(glob.glob(os.path.join(src_dir, "*.{}".format("step")))) 40 | if args.num != -1: 41 | out_paths = out_paths[args.idx:args.idx+args.num] 42 | save_dir = args.src + "_images" if args.outputs is None else args.outputs 43 | ensure_dir(save_dir) 44 | 45 | from lib.timeout import timeout_decorator 46 | 47 | from multiprocessing import Process, cpu_count 48 | 49 | num_processes = 4 #cpu_count() 50 | 51 | def main_process(process_id): 52 | 53 | HasTarget = False 54 | for index in range(process_id, len(out_paths), num_processes): 55 | HasTarget = True 56 | break 57 | if not HasTarget: 58 | return 59 | 60 | # 初始化显示窗口 61 | display, start_display, add_menu, add_function_to_menu = init_display() 62 | display.View.SetBgGradientColors(Quantity_Color(1.0, 1.0, 1.0, Quantity_TOC_RGB), Quantity_Color(1.0, 1.0, 1.0, Quantity_TOC_RGB), 2, True) 63 | 64 | # 加载3D模型 65 | def load_step_file(filename): 66 | step_reader = STEPControl_Reader() 67 | status = step_reader.ReadFile(filename) 68 | if status == IFSelect_RetDone: 69 | step_reader.TransferRoots() 70 | shape = step_reader.OneShape() 71 | shapes_labels_colors = read_step_file_with_names_colors(filename) 72 | return shapes_labels_colors, shape 73 | else: 74 | raise IOError("Error: cannot read file.") 75 | 76 | # 计算模型包围盒 77 | def compute_bounding_box(shape): 78 | box = Bnd_Box() 79 | brepbndlib_Add(shape, box) 80 | return box 81 | 82 | # 渲染并保存图片 83 | def render_and_save(display, shapes_labels_colors, view_name): 84 | #display.DisplayShape(shape, update=True) 85 | for shpt_lbl_color in shapes_labels_colors: 86 | label, c = shapes_labels_colors[shpt_lbl_color] 87 | 88 | if args.mode == "segment": 89 | color = Quantity_Color(c.Red() , c.Green() , c.Blue() , Quantity_TOC_RGB) 90 | ais_shape = AIS_Shape(shpt_lbl_color) 91 | ais_shape.SetColor(color) 92 | if color == Quantity_Color(1.0 , 0.1 , 0.1 , Quantity_TOC_RGB): 93 | display.Context.SetTransparency(ais_shape, 0.0, False) 94 | else: 95 | display.Context.SetTransparency(ais_shape, 1.0, False) 96 | display.Context.Display(ais_shape, False) 97 | elif args.mode == "transparent": 98 | ais_shape = AIS_Shape(shpt_lbl_color) 99 | color = Quantity_Color(c.Red() * 0.25, c.Green() * 0.25, c.Blue() * 0.25, Quantity_TOC_RGB) 100 | ais_shape.SetColor(color) 101 | 102 | display.Context.SetTransparency(ais_shape, 0.25, False) 103 | display.Context.Display(ais_shape, False) 104 | 105 | ais_shape = AIS_Shape(shpt_lbl_color) 106 | ais_shape.SetWidth(2.0) 107 | display.Context.SetTransparency(ais_shape, 1.0, False) 108 | display.Context.Display(ais_shape, False) 109 | else: 110 | ais_shape = AIS_Shape(shpt_lbl_color) 111 | color = Quantity_Color(c.Red() * 0.25, c.Green() * 0.25, c.Blue() * 0.25, Quantity_TOC_RGB) 112 | ais_shape.SetColor(color) 113 | 114 | display.Context.SetTransparency(ais_shape, 0.0, False) 115 | display.Context.Display(ais_shape, False) 116 | 117 | ais_shape = AIS_Shape(shpt_lbl_color) 118 | ais_shape.SetWidth(2.0) 119 | display.Context.SetTransparency(ais_shape, 1.0, False) 120 | display.Context.Display(ais_shape, False) 121 | 122 | #display.DisplayColoredShape( 123 | # shpt_lbl_color, 124 | # color=Quantity_Color(c.Red() * 0.25, c.Green() * 0.25, c.Blue() * 0.25, Quantity_TOC_RGB), 125 | #) 126 | display.FitAll() 127 | display.View.Dump(view_name) 128 | 129 | # 设置视角并保存图片 130 | def save_views_from_angles(shapes_labels_colors, proj_directions, output_dir, name): 131 | if not os.path.exists(output_dir): 132 | os.makedirs(output_dir) 133 | 134 | image_paths = [] 135 | for proj_direction in proj_directions: 136 | x, y, z = proj_direction 137 | display.View_Front() 138 | #display.View_Iso() 139 | display.View.SetProj(x, y, z) 140 | display.View.FitAll() 141 | if args.mode == "orthographic": 142 | if x * y * z > 0: d = 'o' 143 | elif x > 0: d = 'x' 144 | elif y > 0: d = 'y' 145 | else: d = 'z' 146 | view_name = os.path.join(output_dir, f'{name}_{d}.jpg') 147 | else: 148 | view_name = os.path.join(output_dir, f'{name}.jpg') 149 | render_and_save(display, shapes_labels_colors, view_name) 150 | image_paths.append(view_name) 151 | 152 | return image_paths 153 | 154 | def merge_images(image_paths, output_path): 155 | # 打开所有图片 156 | images = [Image.open(image_path) for image_path in image_paths] 157 | 158 | # 获取单张图片的宽度和高度(假设所有图片大小相同) 159 | width, height = images[0].size 160 | 161 | # 创建一个新图像,大小为 2x2 布局的总大小 162 | merged_image = Image.new('RGB', (2 * width, 2 * height)) 163 | 164 | # 将每张图片粘贴到新图像的正确位置 165 | positions = [(0, 0), (width, 0), (0, height), (width, height)] 166 | for pos, img in zip(positions, images): 167 | merged_image.paste(img, pos) 168 | 169 | # 保存拼接后的图像 170 | merged_image.save(output_path) 171 | 172 | # 删除原图 173 | for image_path in image_paths: 174 | os.remove(image_path) 175 | 176 | for index in range(process_id, len(out_paths), num_processes): 177 | step_file = out_paths[index] 178 | 179 | @timeout_decorator 180 | def main__(): 181 | name = step_file.split("/")[-1].split(".")[0] 182 | view_name = os.path.join(save_dir, f'{name}.jpg') 183 | if os.path.isfile(view_name): return 184 | # 加载模型 185 | shapes_labels_colors, shape = load_step_file(step_file) 186 | # 计算包围盒 187 | bounding_box = compute_bounding_box(shape) 188 | # 定义方向矢量 189 | if args.mode == "default": 190 | proj_directions = [ 191 | (random.random() * 0.25 + 0.75, random.random() * 0.25 + 0.75, random.random() * 0.25 + 0.75) 192 | ] 193 | elif args.mode == "orthographic": 194 | proj_directions = [ 195 | (1, 1, 1), 196 | (1, 0, 0), 197 | (0, 1, 0), 198 | (0, 0, 1) 199 | ] 200 | else: 201 | proj_directions = [ 202 | (1, 1, 1) 203 | ] 204 | 205 | # 保存多角度视图 206 | image_paths = save_views_from_angles(shapes_labels_colors, proj_directions, save_dir, name=name) 207 | if args.mode == "orthographic": 208 | output_path = os.path.join(save_dir, f'{name}.jpg') 209 | merge_images(image_paths, output_path) 210 | # 结束显示 211 | display.EraseAll() 212 | #display.FitAll() 213 | #start_display() 214 | 215 | sub_folder = os.path.join(src_dir, name) 216 | sub_output_dir = os.path.join(save_dir, name) 217 | if os.path.exists(sub_folder) and args.mode == "segment": 218 | ensure_dir(sub_output_dir) 219 | sub_steps = sorted(glob.glob(os.path.join(sub_folder, "*.{}".format("step")))) 220 | for sub_step in sub_steps: 221 | sub_shapes_labels_colors, sub_shape = load_step_file(sub_step) 222 | bounding_box = compute_bounding_box(sub_shape) 223 | sub_proj_directions = [(1, 1, 1)] 224 | sub_name = sub_step.split("/")[-1].split(".")[0] + "." + sub_step.split("/")[-1].split(".")[1] 225 | image_paths = save_views_from_angles(sub_shapes_labels_colors, sub_proj_directions, sub_output_dir, name=sub_name) 226 | display.EraseAll() 227 | try: 228 | main__() 229 | display.EraseAll() 230 | except Exception as e: 231 | print("load and create failed.") 232 | traceback.print_exc() 233 | display.EraseAll() 234 | continue 235 | 236 | 237 | if __name__ == "__main__": 238 | processes = [] 239 | for i in range(num_processes): 240 | process = Process(target=main_process, args=(i,)) 241 | processes.append(process) 242 | process.start() 243 | 244 | # 等待所有进程完成 245 | for process in processes: 246 | process.join() 247 | 248 | print('任务完成') -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Yuki-Kokomi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Media/hand-drawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuki-Kokomi/OpenECAD_Project/a65d6611282dc16001799f966f9c327c3c015479/Media/hand-drawn.png -------------------------------------------------------------------------------- /Media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yuki-Kokomi/OpenECAD_Project/a65d6611282dc16001799f966f9c327c3c015479/Media/logo.png -------------------------------------------------------------------------------- /OpenECAD_TinyLLaVA.patch: -------------------------------------------------------------------------------- 1 | diff --git a/OpenECADv2_0.55B_finetune.sh b/OpenECADv2_0.55B_finetune.sh 2 | new file mode 100644 3 | index 0000000..4539b99 4 | --- /dev/null 5 | +++ b/OpenECADv2_0.55B_finetune.sh 6 | @@ -0,0 +1,45 @@ 7 | +DATA_PATH="/root/autodl-tmp/OpenECADv2_100k_directout_1k/data.json" 8 | +IMAGE_PATH="/root/autodl-tmp/OpenECADv2_100k_directout_1k/images" 9 | +MODEL_MAX_LENGTH=2048 10 | +OUTPUT_DIR="/root/autodl-tmp/OpenECADv2-0.55B-lora" 11 | + 12 | +deepspeed --include localhost:0,1 --master_port 29501 tinyllava/train/custom_finetune.py \ 13 | + --deepspeed ./scripts/zero2.json \ 14 | + --data_path $DATA_PATH \ 15 | + --image_folder $IMAGE_PATH \ 16 | + --is_multimodal True \ 17 | + --conv_version llama \ 18 | + --mm_vision_select_layer -2 \ 19 | + --image_aspect_ratio square \ 20 | + --fp16 True \ 21 | + --training_recipe lora \ 22 | + --tune_type_llm lora \ 23 | + --tune_type_vision_tower lora \ 24 | + --tune_vision_tower_from_layer 0 \ 25 | + --tune_type_connector full \ 26 | + --lora_r 128 \ 27 | + --lora_alpha 256 \ 28 | + --group_by_modality_length False \ 29 | + --pretrained_model_path "/root/autodl-tmp/TinyLLaVA-OpenELM-450M-CLIP-0.55B" \ 30 | + --output_dir $OUTPUT_DIR \ 31 | + --num_train_epochs 1 \ 32 | + --per_device_train_batch_size 2 \ 33 | + --per_device_eval_batch_size 2 \ 34 | + --gradient_accumulation_steps 2 \ 35 | + --evaluation_strategy "no" \ 36 | + --save_strategy "steps" \ 37 | + --save_steps 1250 \ 38 | + --save_total_limit 1 \ 39 | + --learning_rate 1e-4 \ 40 | + --weight_decay 0. \ 41 | + --warmup_ratio 0.03 \ 42 | + --lr_scheduler_type "cosine" \ 43 | + --logging_steps 1 \ 44 | + --tf32 False \ 45 | + --model_max_length $MODEL_MAX_LENGTH \ 46 | + --gradient_checkpointing True \ 47 | + --dataloader_num_workers 8 \ 48 | + --lazy_preprocess True \ 49 | + --report_to tensorboard \ 50 | + --tokenizer_use_fast False \ 51 | + --run_name OpenECADv2-0.55B-lora 52 | diff --git a/OpenECADv2_0.89B_finetune.sh b/OpenECADv2_0.89B_finetune.sh 53 | new file mode 100644 54 | index 0000000..33ae295 55 | --- /dev/null 56 | +++ b/OpenECADv2_0.89B_finetune.sh 57 | @@ -0,0 +1,45 @@ 58 | +DATA_PATH="/root/autodl-tmp/OpenECADv2_100k_directout_1k/data.json" 59 | +IMAGE_PATH="/root/autodl-tmp/OpenECADv2_100k_directout_1k/images" 60 | +MODEL_MAX_LENGTH=2048 61 | +OUTPUT_DIR="/root/autodl-tmp/OpenECADv2-0.89B-lora" 62 | + 63 | +deepspeed --include localhost:0,1 --master_port 29501 tinyllava/train/custom_finetune.py \ 64 | + --deepspeed ./scripts/zero2.json \ 65 | + --data_path $DATA_PATH \ 66 | + --image_folder $IMAGE_PATH \ 67 | + --is_multimodal True \ 68 | + --conv_version llama \ 69 | + --mm_vision_select_layer -2 \ 70 | + --image_aspect_ratio square \ 71 | + --fp16 True \ 72 | + --training_recipe lora \ 73 | + --tune_type_llm lora \ 74 | + --tune_type_vision_tower lora \ 75 | + --tune_vision_tower_from_layer 0 \ 76 | + --tune_type_connector full \ 77 | + --lora_r 128 \ 78 | + --lora_alpha 256 \ 79 | + --group_by_modality_length False \ 80 | + --pretrained_model_path "/root/autodl-tmp/TinyLLaVA-OpenELM-450M-SigLIP-0.89B" \ 81 | + --output_dir $OUTPUT_DIR \ 82 | + --num_train_epochs 1 \ 83 | + --per_device_train_batch_size 2 \ 84 | + --per_device_eval_batch_size 2 \ 85 | + --gradient_accumulation_steps 2 \ 86 | + --evaluation_strategy "no" \ 87 | + --save_strategy "steps" \ 88 | + --save_steps 1250 \ 89 | + --save_total_limit 1 \ 90 | + --learning_rate 1e-4 \ 91 | + --weight_decay 0. \ 92 | + --warmup_ratio 0.03 \ 93 | + --lr_scheduler_type "cosine" \ 94 | + --logging_steps 1 \ 95 | + --tf32 False \ 96 | + --model_max_length $MODEL_MAX_LENGTH \ 97 | + --gradient_checkpointing True \ 98 | + --dataloader_num_workers 8 \ 99 | + --lazy_preprocess True \ 100 | + --report_to tensorboard \ 101 | + --tokenizer_use_fast False \ 102 | + --run_name OpenECADv2-0.89B-lora 103 | \ No newline at end of file 104 | diff --git a/OpenECADv2_2.4B_finetune.sh b/OpenECADv2_2.4B_finetune.sh 105 | new file mode 100644 106 | index 0000000..efb0150 107 | --- /dev/null 108 | +++ b/OpenECADv2_2.4B_finetune.sh 109 | @@ -0,0 +1,45 @@ 110 | +DATA_PATH="/root/autodl-tmp/OpenECADv2_150k_directout_2k/data.json" 111 | +IMAGE_PATH="/root/autodl-tmp/OpenECADv2_150k_directout_2k/images" 112 | +MODEL_MAX_LENGTH=2048 113 | +OUTPUT_DIR="/root/autodl-tmp/OpenECADv2-2.4B-lora" 114 | + 115 | +deepspeed --include localhost:0 --master_port 29501 tinyllava/train/custom_finetune.py \ 116 | + --deepspeed ./scripts/zero2.json \ 117 | + --data_path $DATA_PATH \ 118 | + --image_folder $IMAGE_PATH \ 119 | + --is_multimodal True \ 120 | + --conv_version gemma \ 121 | + --mm_vision_select_layer -2 \ 122 | + --image_aspect_ratio square \ 123 | + --fp16 True \ 124 | + --training_recipe lora \ 125 | + --tune_type_llm lora \ 126 | + --tune_type_vision_tower lora \ 127 | + --tune_vision_tower_from_layer 0 \ 128 | + --tune_type_connector full \ 129 | + --lora_r 128 \ 130 | + --lora_alpha 256 \ 131 | + --group_by_modality_length False \ 132 | + --pretrained_model_path "/root/autodl-fs/TinyLLaVA-Gemma-SigLIP-2.4B" \ 133 | + --output_dir $OUTPUT_DIR \ 134 | + --num_train_epochs 1 \ 135 | + --per_device_train_batch_size 2 \ 136 | + --per_device_eval_batch_size 2 \ 137 | + --gradient_accumulation_steps 2 \ 138 | + --evaluation_strategy "no" \ 139 | + --save_strategy "steps" \ 140 | + --save_steps 1250 \ 141 | + --save_total_limit 1 \ 142 | + --learning_rate 1e-4 \ 143 | + --weight_decay 0. \ 144 | + --warmup_ratio 0.03 \ 145 | + --lr_scheduler_type "cosine" \ 146 | + --logging_steps 1 \ 147 | + --tf32 False \ 148 | + --model_max_length $MODEL_MAX_LENGTH \ 149 | + --gradient_checkpointing True \ 150 | + --dataloader_num_workers 8 \ 151 | + --lazy_preprocess True \ 152 | + --report_to tensorboard \ 153 | + --tokenizer_use_fast False \ 154 | + --run_name OpenECADv2-2.4B-lora 155 | \ No newline at end of file 156 | diff --git a/OpenECADv2_3.1B_finetune.sh b/OpenECADv2_3.1B_finetune.sh 157 | new file mode 100644 158 | index 0000000..1b8c75b 159 | --- /dev/null 160 | +++ b/OpenECADv2_3.1B_finetune.sh 161 | @@ -0,0 +1,45 @@ 162 | +DATA_PATH="/root/autodl-tmp/OpenECADv2_200k_directout_3k/data.json" 163 | +IMAGE_PATH="/root/autodl-tmp/OpenECADv2_200k_directout_3k/images" 164 | +MODEL_MAX_LENGTH=3072 165 | +OUTPUT_DIR="/root/autodl-tmp/OpenECADv2-3.1B-lora" 166 | + 167 | +deepspeed --include localhost:0,1 --master_port 29501 tinyllava/train/custom_finetune.py \ 168 | + --deepspeed ./scripts/zero2.json \ 169 | + --data_path $DATA_PATH \ 170 | + --image_folder $IMAGE_PATH \ 171 | + --is_multimodal True \ 172 | + --conv_version phi \ 173 | + --mm_vision_select_layer -2 \ 174 | + --image_aspect_ratio square \ 175 | + --fp16 True \ 176 | + --training_recipe lora \ 177 | + --tune_type_llm lora \ 178 | + --tune_type_vision_tower lora \ 179 | + --tune_vision_tower_from_layer 0 \ 180 | + --tune_type_connector full \ 181 | + --lora_r 128 \ 182 | + --lora_alpha 256 \ 183 | + --group_by_modality_length False \ 184 | + --pretrained_model_path "/root/autodl-fs/TinyLLaVA-Phi-2-SigLIP-3.1B" \ 185 | + --output_dir $OUTPUT_DIR \ 186 | + --num_train_epochs 1 \ 187 | + --per_device_train_batch_size 2 \ 188 | + --per_device_eval_batch_size 2 \ 189 | + --gradient_accumulation_steps 2 \ 190 | + --evaluation_strategy "no" \ 191 | + --save_strategy "steps" \ 192 | + --save_steps 1250 \ 193 | + --save_total_limit 1 \ 194 | + --learning_rate 1e-4 \ 195 | + --weight_decay 0. \ 196 | + --warmup_ratio 0.03 \ 197 | + --lr_scheduler_type "cosine" \ 198 | + --logging_steps 1 \ 199 | + --tf32 False \ 200 | + --model_max_length $MODEL_MAX_LENGTH \ 201 | + --gradient_checkpointing True \ 202 | + --dataloader_num_workers 8 \ 203 | + --lazy_preprocess True \ 204 | + --report_to tensorboard \ 205 | + --tokenizer_use_fast False \ 206 | + --run_name OpenECADv2-3.1B-lora 207 | diff --git a/eval.py b/eval.py 208 | new file mode 100644 209 | index 0000000..985945a 210 | --- /dev/null 211 | +++ b/eval.py 212 | @@ -0,0 +1,24 @@ 213 | +from tinyllava.eval.run_tiny_llava import eval_model 214 | + 215 | +model_path = "" 216 | +prompt = "This image is a view of a 3D model from a certain angle. Please try to use Python-style APIs to render this model." 217 | +image_file = "" 218 | +conv_mode = "llama" # or llama, gemma, etc 219 | +# for OpenECAD 0.55B & 0.89B, use llama 220 | +# for OpenECAD 2.4B, use gemma 221 | +# for OpenECAD 3.1B, use phi 222 | + 223 | +args = type('Args', (), { 224 | + "model_path": model_path, 225 | + "model_base": None, 226 | + "query": prompt, 227 | + "conv_mode": conv_mode, 228 | + "image_file": image_file, 229 | + "sep": ",", 230 | + "temperature": 0, 231 | + "top_p": None, 232 | + "num_beams": 1, 233 | + "max_new_tokens": 1024 # Please edit this value if code gen unfinish 234 | +})() 235 | + 236 | +eval_model(args) 237 | \ No newline at end of file 238 | diff --git a/merge_lora_weights.py b/merge_lora_weights.py 239 | new file mode 100644 240 | index 0000000..2a8931b 241 | --- /dev/null 242 | +++ b/merge_lora_weights.py 243 | @@ -0,0 +1,27 @@ 244 | +import argparse 245 | +from tinyllava.model.load_model import load_pretrained_model 246 | + 247 | +def get_model_name_from_path(model_path): 248 | + model_path = model_path.strip("/") 249 | + model_paths = model_path.split("/") 250 | + if model_paths[-1].startswith('checkpoint-'): 251 | + return model_paths[-2] + "_" + model_paths[-1] 252 | + else: 253 | + return model_paths[-1] 254 | + 255 | +def merge_lora(args): 256 | + model_name = get_model_name_from_path(args.model_path) 257 | + model, tokenizer, image_processor, context_len = load_pretrained_model(args.model_path) 258 | + 259 | + model.save_pretrained(args.save_path) 260 | + tokenizer.save_pretrained(args.save_path) 261 | + 262 | + 263 | +if __name__ == "__main__": 264 | + parser = argparse.ArgumentParser() 265 | + parser.add_argument("--model-path", type=str, required=True) 266 | + parser.add_argument("--save-path", type=str, required=True) 267 | + 268 | + args = parser.parse_args() 269 | + 270 | + merge_lora(args) 271 | \ No newline at end of file 272 | diff --git a/tinyllava/data/template/gemma_template.py b/tinyllava/data/template/gemma_template.py 273 | index 3e062eb..6b1eb33 100644 274 | --- a/tinyllava/data/template/gemma_template.py 275 | +++ b/tinyllava/data/template/gemma_template.py 276 | @@ -12,7 +12,7 @@ from transformers import PreTrainedTokenizer 277 | import torch 278 | import tokenizers 279 | 280 | -system = "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions." 281 | +system = "A conversation between a curious user and an artificial intelligence assistant. The user wants to create a 3D CAD project but only has a picture of the desired 3D geometry. The assistant, an expert mechanical part designer, provides detailed answers to the user's questions. The CAD project should be represented using OpenECAD style code in Python 3. Additionally, note that Curve (including Arc, Line, Circle) and Loop will automatically append to the Curves and Loops lists, respectively." 282 | 283 | @register_template('gemma') 284 | @dataclass 285 | diff --git a/tinyllava/data/template/llama_template.py b/tinyllava/data/template/llama_template.py 286 | index 6705add..ad835bb 100644 287 | --- a/tinyllava/data/template/llama_template.py 288 | +++ b/tinyllava/data/template/llama_template.py 289 | @@ -14,7 +14,7 @@ import tokenizers 290 | 291 | IS_TOKENIZER_GREATER_THAN_0_14 = version.parse(tokenizers.__version__) >= version.parse('0.14') 292 | 293 | -system = "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions." 294 | +system = "A conversation between a curious user and an artificial intelligence assistant. The user wants to create a 3D CAD project but only has a picture of the desired 3D geometry. The assistant, an expert mechanical part designer, provides detailed answers to the user's questions. The CAD project should be represented using OpenECAD style code in Python 3. Additionally, note that Curve (including Arc, Line, Circle) and Loop will automatically append to the Curves and Loops lists, respectively." 295 | 296 | @register_template('llama') 297 | @dataclass 298 | diff --git a/tinyllava/data/template/phi_template.py b/tinyllava/data/template/phi_template.py 299 | index e8aa45f..bedf41e 100644 300 | --- a/tinyllava/data/template/phi_template.py 301 | +++ b/tinyllava/data/template/phi_template.py 302 | @@ -9,7 +9,7 @@ from . import register_template 303 | from transformers import PreTrainedTokenizer 304 | import torch 305 | 306 | -system = "A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user's questions." 307 | +system = "A conversation between a curious user and an artificial intelligence assistant. The user wants to create a 3D CAD project but only has a picture of the desired 3D geometry. The assistant, an expert mechanical part designer, provides detailed answers to the user's questions. The CAD project should be represented using OpenECAD style code in Python 3. Additionally, note that Curve (including Arc, Line, Circle) and Loop will automatically append to the Curves and Loops lists, respectively." 308 | 309 | @register_template('phi') 310 | @dataclass 311 | diff --git a/tinyllava/model/load_model.py b/tinyllava/model/load_model.py 312 | index 7024247..01f8c0d 100644 313 | --- a/tinyllava/model/load_model.py 314 | +++ b/tinyllava/model/load_model.py 315 | @@ -42,6 +42,7 @@ def load_pretrained_model(model_name_or_path, load_type='hf', load_8bit=False, l 316 | model = TinyLlavaForConditionalGeneration(model_config) 317 | language_model_ckp_path = os.path.join(model_name_or_path, 'language_model/pytorch_model.bin') 318 | language_model_ckp = load_base_ckp_for_lora(language_model_ckp_path) 319 | + #language_model_ckp['lm_head.weight'] = language_model_ckp['model.embed_tokens.weight'] 320 | model.language_model.load_state_dict(language_model_ckp) 321 | vision_tower_ckp_path = os.path.join(model_name_or_path, 'vision_tower/pytorch_model.bin') 322 | vision_tower_ckp = load_base_ckp_for_lora(vision_tower_ckp_path) 323 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenECAD: An Efficient Visual Language Model for Editable 3D-CAD Design 2 | The project contains the source code used in the paper "OpenECAD: An Efficient Visual Language Model for Editable 3D-CAD Design." 3 | 4 | This project primarily utilizes modified DeepCAD libraries to convert the DeepCAD dataset into the OpenECAD dataset. The dataset is in LLaVA format, and the TinyLLaVA Factory is used to fine-tune the pre-trained Vision Language Model, resulting in the OpenECAD model weights. Finally, the OpenECAD model weights are run using the TinyLLaVA Factory. CUDA is need in this project. 5 | 6 | If you want to start from the training set we generated, please begin from Step 2. 7 | 8 | If you want to start from the model weights we trained, please begin from Step 3. 9 | 10 | The dataset and model weights can be accessed at https://huggingface.co/collections/Yuki-Kokomi/openecadv2-66b2d793cb885dff986cf967. 11 | 12 | To generate the OpenECAD dataset and train using other CAD datasets, please start from scratch (this project uses DeepCAD as an example). 13 | 14 | If you would like to directly try out our model's generation capabilities, you can run `aioscript.sh` and follow the step-by-step instructions provided. 15 | 16 | ## Step 1. Use the Bethany tool to convert the DeepCAD dataset into the OpenECAD dataset. 17 | First, download all the JSON files from the DeepCAD dataset and place them in the same directory. (Download link: http://www.cs.columbia.edu/cg/deepcad/data.tar) 18 | 19 | Then, create an environment in Linux using Conda: 20 | 21 | ```shell 22 | conda create -n Bethany python=3.10 -y 23 | conda activate Bethany 24 | pip install -r requirements.txt 25 | conda install -c conda-forge pythonocc-core=7.5.1 26 | ``` 27 | 28 | Next, modify the `JSON_SOURCE` and `OUT_DIR` in create_datasets.sh to the actual paths. 29 | 30 | Alternatively, you can manually execute the commands to generate the desired dataset: 31 | 32 | ```shell 33 | python json2step.py --src $JSON_SOURCE -o $OUT_DIR/steps_default/ --mode default 34 | # Converting DeepCAD JSON Files to STEP Files 35 | 36 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_default/ --mode default 37 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_transparent/ --mode transparent 38 | python step2img.py --src $OUT_DIR/steps_default/ -o $OUT_DIR/images_orthographic/ --mode orthographic 39 | 40 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_default_directout_3k.json --ignore True --mode default --token 3072 41 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_transparent_directout_3k.json --ignore True --mode transparent --token 3072 42 | python json2dataset.py --src $JSON_SOURCE -o $OUT_DIR/code_orthographic_directout_3k.json --ignore True --mode orthographic --token 3072 43 | 44 | # Generate datasets for the three types of views (see the paper for details) and limit the number of tokens. 45 | ``` 46 | 47 | If you want to create a mixed dataset with the three types of views, you can write a script to accomplish this after generating the datasets mentioned above. 48 | 49 | If you encounter issues running in a non-GUI environment, you can refer to: https://github.com/tpaviot/pythonocc-core/issues/708 50 | 51 | ## Step 2. Use the TinyLLaVA training framework to fine-tune the pre-trained Small Vision Language Model to obtain the OpenECAD model. 52 | 53 | First, pull a specific version of the TinyLLaVA repository (the latest version may work, but its compatibility is not guaranteed): 54 | 55 | ```shell 56 | git submodule update --init --recursive 57 | ``` 58 | 59 | Next, apply our patch file to the TinyLLaVA project: 60 | 61 | ```shell 62 | cd TinyLLaVA_Factory 63 | git apply ../OpenECAD_TinyLLaVA.patch 64 | ``` 65 | 66 | Then, set up the environment according to the TinyLLaVA README file: 67 | 68 | ```shell 69 | conda create -n tinyllava_factory python=3.10 -y 70 | conda activate tinyllava_factory 71 | pip install --upgrade pip # enable PEP 660 support 72 | pip install -e . 73 | pip install flash-attn --no-build-isolation # for training 74 | 75 | # If you encounter errors in this part, please try referring to https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/15863 76 | # Downgrade setuptools to below version 70.0.0 77 | ``` 78 | 79 | Then, refer to the link in the TinyLLaVA README to download the TinyLLaVA pre-trained weights. 80 | 81 | Finally, modify the dataset JSON file, image folder, pre-trained weights, and output paths in `OpenECADv2_{X}B_finetune.sh`. You can then start training, keeping in mind the correspondence between the script and the pre-trained weights. 82 | 83 | After training is complete, you can use `merge_lora_weights.py` to merge the Lora weights with the pre-trained weights. If you encounter issues merging Gemma-based models, refer to https://github.com/TinyLLaVA/TinyLLaVA_Factory/issues/88. 84 | 85 | ## Step 3. Use the OpenECAD model weights to generate OpenECAD code. 86 | 87 | If the environment is not installed, please first follow the steps mentioned in Step 2 to install the TinyLLaVA environment and apply our patch. 88 | 89 | You can use the model with the WebUI provided by TinyLLaVA: 90 | 91 | ```shell 92 | # You can download our models using git-lfs. 93 | # git lfs install 94 | # git clone https://huggingface.co/Yuki-Kokomi/OpenECADv2-SigLIP-2.4B 95 | 96 | python tinyllava/serve/app.py --model-path 97 | ``` 98 | 99 | If you want to batch process, you can refer to `eval.py`. 100 | 101 | Depending on the type of input images, you can use the following prompts: 102 | 103 | - "This image shows 4 views of a 3D model from specific angles. Please use Python-style APIs to render this model." 104 | - "This image shows a view of a 3D model from a certain angle. Please use Python-style APIs to render this model." 105 | 106 | 107 | ## Step 4. Convert the OpenECAD code to STEP files. 108 | 109 | If you have already installed the Bethany environment, you can use Bethany directly. Otherwise, you can follow the steps below to install it: 110 | 111 | ```shell 112 | git submodule update --init --recursive 113 | cd OpenECADv2toSTEP 114 | conda create -n OpenECADv2toSTEP python=3.10 -y 115 | conda activate OpenECADv2toSTEP 116 | pip install -r requirements.txt 117 | conda install -c conda-forge pythonocc-core=7.5.1 118 | ``` 119 | 120 | Afterwards, you can convert the OpenECAD Python files to STEP files. Before using them, make sure to remove any non-code parts generated by the model. 121 | 122 | ```shell 123 | python py2step.py --src -o 124 | ``` 125 | 126 | We provide test cases at https://huggingface.co/datasets/Yuki-Kokomi/OpenECADv2-Datasets/blob/main/OpenECADv2_EvaluationExamples.zip, which include input images, corresponding reference code, the code results generated by us, and a Python script used to compare the reference code with the generated code and assign scores. To get the score: 127 | 128 | ```shell 129 | python check_scores.py --gen --ans --output 130 | ``` 131 | -------------------------------------------------------------------------------- /aioscript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Define the conda initialization script 4 | CONDA_INIT_SCRIPT="$(conda info --base)/etc/profile.d/conda.sh" 5 | 6 | # Initialize conda if the script is found 7 | if [ -f "$CONDA_INIT_SCRIPT" ]; then 8 | source "$CONDA_INIT_SCRIPT" 9 | else 10 | echo "Error: Cannot find conda initialization script." 11 | exit 1 12 | fi 13 | 14 | # Step 1: Clone the Project 15 | clone_project() { 16 | echo "Cloning the project..." 17 | git submodule update --init --recursive 18 | } 19 | 20 | # Step 2: Install TinyLlava Environment 21 | install_tinyllava() { 22 | echo "Installing TinyLlava environment..." 23 | cd TinyLLaVA_Factory 24 | git apply ../OpenECAD_TinyLLaVA.patch 25 | 26 | conda create -n tinyllava_factory python=3.10 -y 27 | conda activate tinyllava_factory 28 | pip install --upgrade pip # enable PEP 660 support 29 | pip install -e . 30 | pip install "numpy<2.0.0" 31 | ##pip install flash-attn --no-build-isolation # for training 32 | conda deactivate 33 | cd .. 34 | 35 | cd Bethany 36 | conda create -n Bethany python=3.10 -y 37 | conda activate Bethany 38 | pip install -r requirements.txt 39 | conda install -c conda-forge pythonocc-core=7.5.1 40 | conda deactivate 41 | cd .. 42 | } 43 | 44 | # Step 3: Download Trained Weight 45 | download_weights() { 46 | echo "Downloading trained weights..." 47 | # sudo apt install git-lfs 48 | git lfs install 49 | git clone https://huggingface.co/Yuki-Kokomi/OpenECADv2-SigLIP-2.4B 50 | } 51 | 52 | # Step 4: Download Test Images 53 | download_test_images() { 54 | echo "Downloading test images..." 55 | # sudo apt install unzip 56 | wget https://huggingface.co/datasets/Yuki-Kokomi/OpenECADv2-Datasets/resolve/main/OpenECADv2_EvaluationExamples.zip 57 | unzip OpenECADv2_EvaluationExamples.zip 58 | rm -r "OpenECADv2_EvaluationExamples/Sample Output Codes" 59 | } 60 | 61 | # Step 5: Run and Get Results 62 | run_and_get_results() { 63 | echo "Running and getting results (this may take a long time)..." 64 | conda activate tinyllava_factory 65 | # export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH 66 | python run_model.py --model_path ./OpenECADv2-SigLIP-2.4B \ 67 | --src "./OpenECADv2_EvaluationExamples/Input Pictures and Reference Codes" \ 68 | --out "./OpenECADv2_EvaluationExamples/Output/Codes" 69 | conda deactivate 70 | } 71 | 72 | # Step 6: Transfer Py to Step 73 | transfer_py_to_step() { 74 | echo "Transferring py to step..." 75 | cd Bethany 76 | conda activate Bethany 77 | python py2step.py --src ../OpenECADv2_EvaluationExamples/Output/Codes/Default \ 78 | -o ../OpenECADv2_EvaluationExamples/Output/Steps/Default 79 | python step2img.py --src ../OpenECADv2_EvaluationExamples/Output/Steps/Default \ 80 | -o ../OpenECADv2_EvaluationExamples/Output/Images/Default --mode default --num -1 81 | conda deactivate 82 | cd .. 83 | } 84 | 85 | # Function to prompt user for step choice 86 | choose_step() { 87 | echo "You need to install conda unzip cuda-11-8 git-lfs first." 88 | echo "You should use huggingface-cli login first." 89 | echo "Choose the step to run:" 90 | echo "1. Clone Project" 91 | echo "2. Install Environment" 92 | echo "3. Download Trained Weight" 93 | echo "4. Download Test Images" 94 | echo "5. Run and Get Results" 95 | echo "6. Transfer Py to Step" 96 | echo "7. Exit" 97 | 98 | read -p "Enter step number: " step_choice 99 | 100 | case $step_choice in 101 | 1) 102 | clone_project 103 | ;; 104 | 2) 105 | install_tinyllava 106 | ;; 107 | 3) 108 | download_weights 109 | ;; 110 | 4) 111 | download_test_images 112 | ;; 113 | 5) 114 | run_and_get_results 115 | ;; 116 | 6) 117 | transfer_py_to_step 118 | ;; 119 | 7) 120 | echo "Exiting script." 121 | exit 0 122 | ;; 123 | *) 124 | echo "Invalid option. Please choose a valid step number." 125 | choose_step 126 | ;; 127 | esac 128 | } 129 | 130 | # Start the script by asking the user which step to begin with 131 | choose_step 132 | -------------------------------------------------------------------------------- /run_model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from tinyllava.eval.run_tiny_llava import *\n", 10 | "\n", 11 | "## Change the model path here\n", 12 | "## You need to change the type of the model\n", 13 | "## 0.55B, 0.89B: llama\n", 14 | "## 2.4B: gemma\n", 15 | "## 3.1B: phi\n", 16 | "model_path = \"\"\n", 17 | "conv_mode = \"llama\" # or llama, gemma, phi\n", 18 | "\n", 19 | "## You need to change the \"max_new_tokens\" if the model can't deal with long tokens.\n", 20 | "## possible values: 1024, 1152, 1536, 2048, 3072\n", 21 | "args = type('Args', (), {\n", 22 | " \"model_path\": model_path,\n", 23 | " \"model_base\": None,\n", 24 | " \"conv_mode\": conv_mode,\n", 25 | " \"sep\": \",\",\n", 26 | " \"temperature\": 0,\n", 27 | " \"top_p\": None,\n", 28 | " \"num_beams\": 1,\n", 29 | " \"max_new_tokens\": 2048\n", 30 | "})()\n", 31 | "\n", 32 | "# Model\n", 33 | "disable_torch_init()\n", 34 | "\n", 35 | "if args.model_path is not None:\n", 36 | " model, tokenizer, image_processor, context_len = load_pretrained_model(args.model_path)\n", 37 | "else:\n", 38 | " assert args.model is not None, 'model_path or model must be provided'\n", 39 | " model = args.model\n", 40 | " if hasattr(model.config, \"max_sequence_length\"):\n", 41 | " context_len = model.config.max_sequence_length\n", 42 | " else:\n", 43 | " context_len = 2048\n", 44 | " tokenizer = model.tokenizer\n", 45 | " image_processor = model.vision_tower._image_processor\n", 46 | "\n", 47 | "\n", 48 | "text_processor = TextPreprocess(tokenizer, args.conv_mode)\n", 49 | "data_args = model.config\n", 50 | "image_processor = ImagePreprocess(image_processor, data_args)\n", 51 | "model.cuda()" 52 | ] 53 | }, 54 | { 55 | "cell_type": "code", 56 | "execution_count": null, 57 | "metadata": {}, 58 | "outputs": [], 59 | "source": [ 60 | "import os\n", 61 | "\n", 62 | "def ensure_dir(path):\n", 63 | " \"\"\"\n", 64 | " create path by first checking its existence,\n", 65 | " :param paths: path\n", 66 | " :return:\n", 67 | " \"\"\"\n", 68 | " if not os.path.exists(path):\n", 69 | " os.makedirs(path)\n", 70 | "\n", 71 | "\n", 72 | "import signal\n", 73 | "\n", 74 | "class TimeoutException(Exception):\n", 75 | " pass\n", 76 | "\n", 77 | "def handler(signum, frame):\n", 78 | " raise TimeoutException()\n", 79 | "\n", 80 | "# Set timeout (unit: s)\n", 81 | "timeout = 300\n", 82 | "\n", 83 | "def timeout_decorator(func):\n", 84 | " def wrapper(*args, **kwargs):\n", 85 | " signal.signal(signal.SIGALRM, handler)\n", 86 | " signal.alarm(timeout)\n", 87 | " try:\n", 88 | " result = func(*args, **kwargs)\n", 89 | " except TimeoutException:\n", 90 | " print(\"Function timed out!\")\n", 91 | " raise TimeoutException\n", 92 | " result = None\n", 93 | " finally:\n", 94 | " signal.alarm(0)\n", 95 | " return result\n", 96 | " return wrapper" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": null, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "@timeout_decorator\n", 106 | "def process_image(qs, path):\n", 107 | " qs = DEFAULT_IMAGE_TOKEN + \"\\n\" + qs\n", 108 | "\n", 109 | "\n", 110 | " msg = Message()\n", 111 | " msg.add_message(qs)\n", 112 | "\n", 113 | " result = text_processor(msg.messages, mode='eval')\n", 114 | " input_ids = result['input_ids']\n", 115 | " prompt = result['prompt']\n", 116 | " input_ids = input_ids.unsqueeze(0).cuda()\n", 117 | " \n", 118 | "\n", 119 | " image_files = [path]\n", 120 | " images = load_images(image_files)[0]\n", 121 | " images_tensor = image_processor(images)\n", 122 | " images_tensor = images_tensor.unsqueeze(0).half().cuda()\n", 123 | "\n", 124 | " stop_str = text_processor.template.separator.apply()[1]\n", 125 | " keywords = [stop_str]\n", 126 | " stopping_criteria = KeywordsStoppingCriteria(keywords, tokenizer, input_ids)\n", 127 | "\n", 128 | " with torch.inference_mode():\n", 129 | " output_ids = model.generate(\n", 130 | " input_ids,\n", 131 | " images=images_tensor,\n", 132 | " do_sample=True if args.temperature > 0 else False,\n", 133 | " temperature=args.temperature,\n", 134 | " top_p=args.top_p,\n", 135 | " num_beams=args.num_beams,\n", 136 | " pad_token_id=tokenizer.pad_token_id,\n", 137 | " max_new_tokens=args.max_new_tokens,\n", 138 | " use_cache=True,\n", 139 | " stopping_criteria=[stopping_criteria],\n", 140 | " )\n", 141 | "\n", 142 | " outputs = tokenizer.batch_decode(\n", 143 | " output_ids, skip_special_tokens=True\n", 144 | " )[0]\n", 145 | " outputs = outputs.strip()\n", 146 | " if outputs.endswith(stop_str):\n", 147 | " outputs = outputs[: -len(stop_str)]\n", 148 | " outputs = outputs.strip()\n", 149 | " return outputs\n", 150 | "\n", 151 | "import os\n", 152 | "import glob\n", 153 | "import traceback\n", 154 | "errors = []\n", 155 | "\n", 156 | "ts = [\"Default\", \"Transparent\", \"Orthographic\"]\n", 157 | "# Change the src_b to the path of \"Input Pictures and Reference Codes\" in \n", 158 | "src_b = \"\"\n", 159 | "out_b = \"\"\n", 160 | "for index in range(len(ts)):\n", 161 | " tt = ts[index]\n", 162 | " src = src_b + tt\n", 163 | " out = out_b + tt\n", 164 | " ensure_dir(out)\n", 165 | " out_paths = sorted(glob.glob(os.path.join(src, \"*.{}\".format(\"jpg\"))))\n", 166 | " if tt == \"Orthographic\" :\n", 167 | " qs = \"This image is 4 views of a 3D model from certain angles. Please try to use Python-style APIs to render this model.\"\n", 168 | " else: \n", 169 | " qs = \"This image is a view of a 3D model from a certain angle. Please try to use Python-style APIs to render this model.\"\n", 170 | "\n", 171 | " for i in range(len(out_paths)):\n", 172 | " path = out_paths[i]\n", 173 | " print(f\"{tt}: {i + 1}/{len(out_paths)}\", end='\\r')\n", 174 | " name = path.split(\"/\")[-1].split(\".\")[0]\n", 175 | " save_path = os.path.join(out, f'{name}.py')\n", 176 | " if os.path.isfile(save_path): continue\n", 177 | " try:\n", 178 | " outputs = process_image(qs, path)\n", 179 | " with open(save_path, 'w', encoding='utf-8') as file:\n", 180 | " file.write(outputs)\n", 181 | " file.close()\n", 182 | " except:\n", 183 | " errors.append(f\"{tt}: {name}\")\n", 184 | " print(f\"gen error: {name}\")\n", 185 | " traceback.print_exc()\n", 186 | " print()\n", 187 | "\n", 188 | "print(errors)\n" 189 | ] 190 | } 191 | ], 192 | "metadata": { 193 | "kernelspec": { 194 | "display_name": "tinyllava_factory", 195 | "language": "python", 196 | "name": "python3" 197 | }, 198 | "language_info": { 199 | "codemirror_mode": { 200 | "name": "ipython", 201 | "version": 3 202 | }, 203 | "file_extension": ".py", 204 | "mimetype": "text/x-python", 205 | "name": "python", 206 | "nbconvert_exporter": "python", 207 | "pygments_lexer": "ipython3", 208 | "version": "3.10.14" 209 | } 210 | }, 211 | "nbformat": 4, 212 | "nbformat_minor": 2 213 | } -------------------------------------------------------------------------------- /run_model.py: -------------------------------------------------------------------------------- 1 | from tinyllava.eval.run_tiny_llava import * 2 | import argparse 3 | 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('--model_path', type=str, required=True, help="Path to the model.") 6 | parser.add_argument('--src', type=str, required=True, help="Path of Input Pictures and Reference Codes.") 7 | parser.add_argument('--out', type=str, required=True, help="Any output path you like.") 8 | 9 | # 解析命令行参数 10 | args = parser.parse_args() 11 | 12 | # 将命令行输入的值传递给相应的变量 13 | model_path = args.model_path 14 | src_base = args.src 15 | out_base = args.out 16 | 17 | input_types = ["Default"]#["Default", "Transparent", "Orthographic"] 18 | conv_mode = "gemma" # or llama, gemma, phi 19 | 20 | ## You need to change the "max_new_tokens" if the model can't deal with long tokens. 21 | ## possible values: 1024, 1152, 1536, 2048, 3072 22 | args = type('Args', (), { 23 | "model_path": model_path, 24 | "model_base": None, 25 | "conv_mode": conv_mode, 26 | "sep": ",", 27 | "temperature": 0, 28 | "top_p": None, 29 | "num_beams": 1, 30 | "max_new_tokens": 2048 31 | })() 32 | 33 | # Model 34 | disable_torch_init() 35 | 36 | if args.model_path is not None: 37 | model, tokenizer, image_processor, context_len = load_pretrained_model(args.model_path) 38 | else: 39 | assert args.model is not None, 'model_path or model must be provided' 40 | model = args.model 41 | if hasattr(model.config, "max_sequence_length"): 42 | context_len = model.config.max_sequence_length 43 | else: 44 | context_len = 2048 45 | tokenizer = model.tokenizer 46 | image_processor = model.vision_tower._image_processor 47 | 48 | 49 | text_processor = TextPreprocess(tokenizer, args.conv_mode) 50 | data_args = model.config 51 | image_processor = ImagePreprocess(image_processor, data_args) 52 | model.cuda() 53 | 54 | import os 55 | 56 | def ensure_dir(path): 57 | """ 58 | create path by first checking its existence, 59 | :param paths: path 60 | :return: 61 | """ 62 | if not os.path.exists(path): 63 | os.makedirs(path) 64 | 65 | 66 | import signal 67 | 68 | class TimeoutException(Exception): 69 | pass 70 | 71 | def handler(signum, frame): 72 | raise TimeoutException() 73 | 74 | # Set timeout (unit: s) 75 | timeout = 300 76 | 77 | def timeout_decorator(func): 78 | def wrapper(*args, **kwargs): 79 | signal.signal(signal.SIGALRM, handler) 80 | signal.alarm(timeout) 81 | try: 82 | result = func(*args, **kwargs) 83 | except TimeoutException: 84 | print("Function timed out!") 85 | raise TimeoutException 86 | result = None 87 | finally: 88 | signal.alarm(0) 89 | return result 90 | return wrapper 91 | 92 | @timeout_decorator 93 | def process_image(qs, path): 94 | qs = DEFAULT_IMAGE_TOKEN + "\n" + qs 95 | 96 | 97 | msg = Message() 98 | msg.add_message(qs) 99 | 100 | result = text_processor(msg.messages, mode='eval') 101 | input_ids = result['input_ids'] 102 | prompt = result['prompt'] 103 | input_ids = input_ids.unsqueeze(0).cuda() 104 | 105 | 106 | image_files = [path] 107 | images = load_images(image_files)[0] 108 | images_tensor = image_processor(images) 109 | images_tensor = images_tensor.unsqueeze(0).half().cuda() 110 | 111 | stop_str = text_processor.template.separator.apply()[1] 112 | keywords = [stop_str] 113 | stopping_criteria = KeywordsStoppingCriteria(keywords, tokenizer, input_ids) 114 | 115 | with torch.inference_mode(): 116 | output_ids = model.generate( 117 | input_ids, 118 | images=images_tensor, 119 | do_sample=True if args.temperature > 0 else False, 120 | temperature=args.temperature, 121 | top_p=args.top_p, 122 | num_beams=args.num_beams, 123 | pad_token_id=tokenizer.pad_token_id, 124 | max_new_tokens=args.max_new_tokens, 125 | use_cache=True, 126 | stopping_criteria=[stopping_criteria], 127 | ) 128 | 129 | outputs = tokenizer.batch_decode( 130 | output_ids, skip_special_tokens=True 131 | )[0] 132 | outputs = outputs.strip() 133 | if outputs.endswith(stop_str): 134 | outputs = outputs[: -len(stop_str)] 135 | outputs = outputs.strip() 136 | return outputs 137 | 138 | import re 139 | 140 | def extract_python_code(input_str): 141 | # 匹配以```python开头,```结束的内容 142 | match = re.search(r'```python(.*?)```', input_str, re.DOTALL) 143 | 144 | if match: 145 | # 如果找到匹配的内容,返回```python和```之间的内容 146 | return match.group(1) 147 | else: 148 | # 如果没有```python```包裹的内容,返回```python后面的所有内容 149 | # 找到```python的位置并返回从该位置到字符串末尾的所有内容 150 | match = re.search(r'```python(.*)', input_str, re.DOTALL) 151 | if match: 152 | return match.group(1) 153 | else: 154 | return input_str 155 | 156 | import os 157 | import glob 158 | import traceback 159 | errors = [] 160 | for index in range(len(input_types)): 161 | cur_type = input_types[index] 162 | src = os.path.join(src_base, cur_type) 163 | out = os.path.join(out_base, cur_type) 164 | ensure_dir(out) 165 | out_paths = sorted(glob.glob(os.path.join(src, "*.{}".format("jpg")))) 166 | if cur_type == "Orthographic" : 167 | qs = "This image is 4 views of a 3D model from certain angles. Please try to use Python-style APIs to render this model." 168 | else: 169 | qs = "This image is a view of a 3D model from a certain angle. Please try to use Python-style APIs to render this model." 170 | 171 | for i in range(len(out_paths)): 172 | path = out_paths[i] 173 | print(f"{cur_type}: {i + 1}/{len(out_paths)}", end='\r') 174 | name = path.split("/")[-1].split(".")[0] 175 | save_path = os.path.join(out, f'{name}.py') 176 | if os.path.isfile(save_path): continue 177 | try: 178 | outputs = process_image(qs, path) 179 | outputs = extract_python_code(outputs) 180 | with open(save_path, 'w', encoding='utf-8') as file: 181 | file.write(outputs) 182 | file.close() 183 | except: 184 | errors.append(f"{cur_type}: {name}") 185 | print(f"gen error: {name}") 186 | traceback.print_exc() 187 | print() 188 | 189 | print("Can't Generate these inputs:") 190 | print(errors) 191 | --------------------------------------------------------------------------------