three-cad-viewer 2.2.4 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/viewer.js CHANGED
@@ -72,7 +72,7 @@ class Viewer {
72
72
  this.scene = null;
73
73
  this.camera = null;
74
74
  this.orthographicCamera = null;
75
- this.orthographicScene = null;
75
+ // this.orthographicScene = null;
76
76
  this.gridHelper = null;
77
77
  this.axesHelper = null;
78
78
  this.controls = null;
@@ -100,6 +100,7 @@ class Viewer {
100
100
  this.renderer = new THREE.WebGLRenderer({
101
101
  alpha: !this.dark,
102
102
  antialias: true,
103
+ stencil: true,
103
104
  });
104
105
  this.renderer.setPixelRatio(window.devicePixelRatio);
105
106
  this.renderer.setSize(this.cadWidth, this.height);
@@ -109,6 +110,14 @@ class Viewer {
109
110
  this.lastNotification = {};
110
111
  this.lastBbox = null;
111
112
 
113
+ // measure supporting exploded shapes and compact shapes
114
+ this.explodedGroup = null;
115
+ this.explodedTree = null;
116
+ this.explodedStates = null;
117
+ this.compactGroup = null;
118
+ this.compactTree = null;
119
+ this.compactStates = null;
120
+
112
121
  // If fromSolid is true, this means the selected object is from the solid
113
122
  // This is the obj that has been picked but the actual selected obj is the solid
114
123
  // Since we cannot directly pick a solid this is the solution
@@ -233,6 +242,8 @@ class Viewer {
233
242
  this.position = null;
234
243
  this.quaternion = null;
235
244
  this.target = null;
245
+ this.measureTools = true;
246
+ this.newTreeBehaviour = true;
236
247
 
237
248
  this.zoom = 1;
238
249
 
@@ -316,6 +327,12 @@ class Viewer {
316
327
  this.roughness,
317
328
  this.normalLen,
318
329
  );
330
+ if (shapes.bb) {
331
+ this.bbox = new BoundingBox(
332
+ new THREE.Vector3(shapes.bb.xmin, shapes.bb.ymin, shapes.bb.zmin),
333
+ new THREE.Vector3(shapes.bb.xmax, shapes.bb.ymax, shapes.bb.zmax),
334
+ );
335
+ }
319
336
  nestedGroup.render(states);
320
337
  return nestedGroup;
321
338
  }
@@ -374,10 +391,35 @@ class Viewer {
374
391
  name: "faces",
375
392
  id: `${part.id}/faces`,
376
393
  };
394
+ var triangles;
377
395
  const vertices = shape.vertices;
378
396
  const normals = shape.normals;
379
- for (j = 0; j < shape.triangles.length; j++) {
380
- var triangles = shape.triangles[j];
397
+ const num = shape.triangles_per_face
398
+ ? shape.triangles_per_face.length
399
+ : shape.triangles.length;
400
+ var current = 0;
401
+ for (j = 0; j < num; j++) {
402
+ if (shape.triangles_per_face) {
403
+ triangles = shape.triangles.subarray(
404
+ current,
405
+ current + 3 * shape.triangles_per_face[j],
406
+ );
407
+ current += 3 * shape.triangles_per_face[j];
408
+ } else {
409
+ triangles = shape.triangles[j];
410
+ }
411
+
412
+ var vecs = new Float32Array(triangles.length * 3);
413
+ var norms = new Float32Array(triangles.length * 3);
414
+ for (var i = 0; i < triangles.length; i++) {
415
+ var s = triangles[i];
416
+ vecs[3 * i] = vertices[3 * s];
417
+ vecs[3 * i + 1] = vertices[3 * s + 1];
418
+ vecs[3 * i + 2] = vertices[3 * s + 2];
419
+ norms[3 * i] = normals[3 * s];
420
+ norms[3 * i + 1] = normals[3 * s + 1];
421
+ norms[3 * i + 2] = normals[3 * s + 2];
422
+ }
381
423
  var new_shape = {
382
424
  loc: [
383
425
  [0, 0, 0],
@@ -395,20 +437,8 @@ class Viewer {
395
437
  subtype: part.subtype,
396
438
  shape: {
397
439
  triangles: [...Array(triangles.length).keys()],
398
- vertices: triangles
399
- .map((s) => [
400
- vertices[3 * s],
401
- vertices[3 * s + 1],
402
- vertices[3 * s + 2],
403
- ])
404
- .flat(),
405
- normals: triangles
406
- .map((s) => [
407
- normals[3 * s],
408
- normals[3 * s + 1],
409
- normals[3 * s + 2],
410
- ])
411
- .flat(),
440
+ vertices: vecs,
441
+ normals: norms,
412
442
  edges: [],
413
443
  },
414
444
  };
@@ -433,8 +463,22 @@ class Viewer {
433
463
  const multiColor =
434
464
  Array.isArray(part.color) && part.color.length == shape.edges.length;
435
465
  var color;
436
- for (j = 0; j < shape.edges.length; j++) {
437
- const edge = shape.edges[j];
466
+
467
+ const num = shape.segments_per_edge
468
+ ? shape.segments_per_edge.length
469
+ : shape.edges.length;
470
+ current = 0;
471
+ var edge;
472
+ for (j = 0; j < num; j++) {
473
+ if (shape.segments_per_edge) {
474
+ edge = shape.edges.subarray(
475
+ current,
476
+ current + 6 * shape.segments_per_edge[j],
477
+ );
478
+ current += 6 * shape.segments_per_edge[j];
479
+ } else {
480
+ edge = shape.edges[j];
481
+ }
438
482
  color = multiColor ? part.color[j] : part.color;
439
483
  new_shape = {
440
484
  loc: [
@@ -514,44 +558,44 @@ class Viewer {
514
558
  * @param {RenderOptions} options - the options for rendering
515
559
  * @returns {THREE.Group} A nested THREE.Group object.
516
560
  */
517
- renderTessellatedShapes(shapes, states, options) {
518
- this.setRenderDefaults(options);
519
- const _render = (shapes, states, measureTools) => {
520
- var part, shape;
521
- if (shapes.version == 2) {
522
- if (measureTools) {
523
- var i, tmp;
524
- let parts = [];
525
- for (i = 0; i < shapes.parts.length; i++) {
526
- part = shapes.parts[i];
527
- if (part.parts != null) {
528
- tmp = _render(part, states, options);
529
- parts.push(tmp);
530
- } else {
531
- parts.push(this._decompose(part, states));
532
- }
533
- }
534
- shapes.parts = parts;
535
- } else {
536
- for (i = 0; i < shapes.parts.length; i++) {
537
- part = shapes.parts[i];
538
- shape = part.shape;
539
- if (part.type == "shapes") {
540
- shape.triangles = shape.triangles.flat();
541
- shape.edges = shape.edges.flat();
542
- } else if (part.type == "edges" || part.type == "shapes") {
543
- shape.edges = shape.edges.flat();
544
- }
561
+ renderTessellatedShapes(exploded, shapes, states) {
562
+ const _render = (shapes, states) => {
563
+ var part;
564
+ if (shapes.version == 2 || shapes.version == 3) {
565
+ var i, tmp;
566
+ let parts = [];
567
+ for (i = 0; i < shapes.parts.length; i++) {
568
+ part = shapes.parts[i];
569
+ if (part.parts != null) {
570
+ tmp = _render(part, states);
571
+ parts.push(tmp);
572
+ } else {
573
+ parts.push(this._decompose(part, states));
545
574
  }
546
575
  }
576
+ shapes.parts = parts;
547
577
  }
548
578
  return shapes;
549
579
  };
550
- shapes = _render(shapes, states, options.measureTools);
551
- return [
552
- this._renderTessellatedShapes(shapes, states),
553
- this._getTree(shapes, states),
554
- ];
580
+
581
+ var exploded_states = structuredClone(states);
582
+ var exploded_shapes;
583
+ if (exploded) {
584
+ exploded_shapes = _render(structuredClone(shapes), exploded_states);
585
+ } else {
586
+ exploded_shapes = structuredClone(shapes);
587
+ }
588
+ var nested_group = this._renderTessellatedShapes(
589
+ exploded_shapes,
590
+ exploded_states,
591
+ );
592
+ var rendered_tree = this._getTree(exploded_shapes, exploded_states);
593
+
594
+ return {
595
+ group: nested_group,
596
+ tree: rendered_tree,
597
+ states: exploded_states,
598
+ };
555
599
  }
556
600
 
557
601
  // - - - - - - - - - - - - - - - - - - - - - - - -
@@ -837,41 +881,101 @@ class Viewer {
837
881
  // Rendering
838
882
  // - - - - - - - - - - - - - - - - - - - - - - - -
839
883
 
884
+ /**
885
+ * Toggle the two version of the NestedGroup
886
+ * @param exploded - whether to render the exploded or compact version
887
+ */
888
+ toggleGroup(exploded) {
889
+ var _config = () => {
890
+ this.nestedGroup.setTransparent(this.transparent);
891
+ this.nestedGroup.setBlackEdges(this.blackEdges);
892
+ this.nestedGroup.setMetalness(this.metalness);
893
+ this.nestedGroup.setRoughness(this.roughness);
894
+ this.nestedGroup.setPolygonOffset(2);
895
+ };
896
+ this.setRenderDefaults(this.renderOptions);
897
+
898
+ var result;
899
+ if (exploded) {
900
+ if (this.explodedGroup == null) {
901
+ result = this.renderTessellatedShapes(
902
+ exploded,
903
+ this.shapes,
904
+ this.states,
905
+ );
906
+ this.nestedGroup = result["group"];
907
+ _config();
908
+ this.explodedStates = result["states"];
909
+ this.explodedTree = result["tree"];
910
+ this.explodedGroup = this.nestedGroup.render(result["states"]);
911
+ }
912
+ } else {
913
+ if (this.compactGroup == null) {
914
+ result = this.renderTessellatedShapes(
915
+ exploded,
916
+ this.shapes,
917
+ this.states,
918
+ );
919
+ this.nestedGroup = result["group"];
920
+ _config();
921
+ this.compactStates = result["states"];
922
+ this.compactTree = result["tree"];
923
+ this.compactGroup = this.nestedGroup.render(this.states);
924
+ }
925
+ }
926
+
927
+ this.states = exploded ? this.explodedStates : this.compactStates;
928
+ this.tree = exploded ? this.explodedTree : this.compactTree;
929
+ this.scene.children[0] = exploded ? this.explodedGroup : this.compactGroup;
930
+
931
+ this.treeview = new TreeView(
932
+ structuredClone(this.states),
933
+ this.tree,
934
+ this.setObjects,
935
+ this.handlePick,
936
+ this.theme,
937
+ this.newTreeBehavior,
938
+ );
939
+
940
+ this.update(true, true);
941
+
942
+ this.display.clearCadTree();
943
+ this.display.addCadTree(this.treeview.render(this.collapse));
944
+ this.display.selectTabByName("tree");
945
+
946
+ this.display.toggleClippingTab(!exploded);
947
+ }
948
+
840
949
  /**
841
950
  * Render a CAD object and build the navigation tree
842
- * @param {NestedGroup} nestedgroup - the shapes of the CAD object to be rendered
843
- * @param {NavTree} tree - The navigation tree object
951
+ * @param {Shapes} shapes - the Shapes object representing the tessellated CAD object
844
952
  * @param {States} states - the visibility state of meshes and edges
845
- * @param {ViewerOptions} options - the Viewer options
953
+ * @param {ViewerOptions} viewerOptions - the viewer options
954
+ * @param {RenderOptions} renderOptions - the render options
846
955
  */
847
- render(group, tree, states, options) {
848
- this.setViewerDefaults(options);
956
+ render(shapes, states, renderOptions, viewerOptions) {
957
+ this.shapes = shapes;
958
+ this.states = states;
959
+ this.renderOptions = renderOptions;
960
+ this.setViewerDefaults(viewerOptions);
849
961
 
850
962
  this.animation.cleanBackup();
851
963
 
852
964
  const timer = new Timer("viewer", this.timeit);
853
965
 
854
- this.states = states;
855
966
  this.scene = new THREE.Scene();
856
- this.orthographicScene = new THREE.Scene();
967
+ // this.orthographicScene = new THREE.Scene();
857
968
 
858
969
  //
859
- // render the input assembly
970
+ // add shapes and cad tree
860
971
  //
861
- this.lastBbox = null;
862
-
863
- this.nestedGroup = group;
864
- this.scene.add(this.nestedGroup.render(states));
865
-
866
- this.nestedGroup.setTransparent(this.transparent);
867
- this.nestedGroup.setBlackEdges(this.blackEdges);
868
- this.nestedGroup.setMetalness(this.metalness);
869
- this.nestedGroup.setRoughness(this.roughness);
870
- this.nestedGroup.setPolygonOffset(2);
871
972
 
872
- timer.split("rendered nested group");
973
+ this.toggleGroup(false);
974
+ timer.split("scene and tree done");
873
975
 
874
- this.bbox = this.nestedGroup.boundingBox();
976
+ if (!this.bbox) {
977
+ this.bbox = this.nestedGroup.boundingBox();
978
+ }
875
979
  const center = new THREE.Vector3();
876
980
  this.bbox.getCenter(center);
877
981
  this.bb_max = this.bbox.max_dist_from_center();
@@ -894,9 +998,9 @@ class Viewer {
894
998
  this.cadWidth,
895
999
  this.height,
896
1000
  this.bb_radius,
897
- options.target == null ? this.bbox.center() : options.target,
1001
+ viewerOptions.target == null ? this.bbox.center() : viewerOptions.target,
898
1002
  this.ortho,
899
- options.up,
1003
+ viewerOptions.up,
900
1004
  );
901
1005
 
902
1006
  // this.orthographicCamera = new THREE.OrthographicCamera(
@@ -923,7 +1027,7 @@ class Viewer {
923
1027
  this.controls = new Controls(
924
1028
  this.control,
925
1029
  this.camera.getCamera(),
926
- options.target == null ? this.bbox.center() : options.target,
1030
+ viewerOptions.target == null ? this.bbox.center() : viewerOptions.target,
927
1031
  this.renderer.domElement,
928
1032
  this.rotateSpeed,
929
1033
  this.zoomSpeed,
@@ -935,12 +1039,17 @@ class Viewer {
935
1039
  this.controls.controls.screenSpacePanning = true;
936
1040
 
937
1041
  // this needs to happen after the controls have been established
938
- if (options.position == null && options.quaternion == null) {
1042
+ if (viewerOptions.position == null && viewerOptions.quaternion == null) {
939
1043
  this.presetCamera("iso", this.zoom);
940
1044
  this.display.highlightButton("iso");
941
- } else if (options.position != null) {
942
- this.setCamera(false, options.position, options.quaternion, this.zoom);
943
- if (options.quaternion == null) {
1045
+ } else if (viewerOptions.position != null) {
1046
+ this.setCamera(
1047
+ false,
1048
+ viewerOptions.position,
1049
+ viewerOptions.quaternion,
1050
+ this.zoom,
1051
+ );
1052
+ if (viewerOptions.quaternion == null) {
944
1053
  this.camera.lookAtTarget();
945
1054
  }
946
1055
  } else {
@@ -985,7 +1094,7 @@ class Viewer {
985
1094
  this.centerGrid,
986
1095
  this.axes0,
987
1096
  this.grid,
988
- options.up == "Z",
1097
+ viewerOptions.up == "Z",
989
1098
  this.theme,
990
1099
  );
991
1100
  this.gridHelper.computeGrid();
@@ -1044,22 +1153,30 @@ class Viewer {
1044
1153
 
1045
1154
  this.display.setSliderLimits(this.gridSize / 2, this.bbox.center());
1046
1155
 
1047
- this.setClipNormal(0, options.clipNormal0, true);
1048
- this.setClipNormal(1, options.clipNormal1, true);
1049
- this.setClipNormal(2, options.clipNormal2, true);
1050
-
1051
- this.clipSlider0 = (options.clipSlider0 != null) ? (options.clipSlider0) : this.gridSize / 2;
1052
- this.clipSlider1 = (options.clipSlider1 != null) ? (options.clipSlider1) : this.gridSize / 2;
1053
- this.clipSlider2 = (options.clipSlider2 != null) ? (options.clipSlider2) : this.gridSize / 2;
1156
+ this.setClipNormal(0, viewerOptions.clipNormal0, true);
1157
+ this.setClipNormal(1, viewerOptions.clipNormal1, true);
1158
+ this.setClipNormal(2, viewerOptions.clipNormal2, true);
1159
+
1160
+ this.clipSlider0 =
1161
+ viewerOptions.clipSlider0 != null
1162
+ ? viewerOptions.clipSlider0
1163
+ : this.gridSize / 2;
1164
+ this.clipSlider1 =
1165
+ viewerOptions.clipSlider1 != null
1166
+ ? viewerOptions.clipSlider1
1167
+ : this.gridSize / 2;
1168
+ this.clipSlider2 =
1169
+ viewerOptions.clipSlider2 != null
1170
+ ? viewerOptions.clipSlider2
1171
+ : this.gridSize / 2;
1054
1172
 
1055
1173
  this.setClipSlider(0, this.clipSlider0, true);
1056
1174
  this.setClipSlider(1, this.clipSlider1, true);
1057
1175
  this.setClipSlider(2, this.clipSlider2, true);
1058
1176
 
1059
-
1060
- this.setClipIntersection(options.clipIntersection, true);
1061
- this.setClipObjectColorCaps(options.clipObjectColors, true);
1062
- this.setClipPlaneHelpersCheck(options.clipPlaneHelpers, true);
1177
+ this.setClipIntersection(viewerOptions.clipIntersection, true);
1178
+ this.setClipObjectColorCaps(viewerOptions.clipObjectColors, true);
1179
+ this.setClipPlaneHelpersCheck(viewerOptions.clipPlaneHelpers, true);
1063
1180
 
1064
1181
  this.scene.add(this.clipping.planeHelpers);
1065
1182
  this.nestedGroup.setClipPlanes(this.clipping.clipPlanes);
@@ -1075,8 +1192,8 @@ class Viewer {
1075
1192
 
1076
1193
  const theme =
1077
1194
  this.theme === "dark" ||
1078
- (this.theme === "browser" &&
1079
- window.matchMedia("(prefers-color-scheme: dark)").matches)
1195
+ (this.theme === "browser" &&
1196
+ window.matchMedia("(prefers-color-scheme: dark)").matches)
1080
1197
  ? "dark"
1081
1198
  : "light";
1082
1199
 
@@ -1092,25 +1209,6 @@ class Viewer {
1092
1209
  );
1093
1210
  this.orientationMarker.create();
1094
1211
 
1095
- //
1096
- // build tree view
1097
- //
1098
-
1099
- this.tree = tree;
1100
- this.treeview = new TreeView(
1101
- clone(this.states),
1102
- this.tree,
1103
- this.setObjects,
1104
- this.handlePick,
1105
- theme,
1106
- this.newTreeBehavior,
1107
- );
1108
-
1109
- this.display.addCadTree(this.treeview.render(options.collapse));
1110
- this.display.selectTabByName("tree");
1111
-
1112
- timer.split("scene done");
1113
-
1114
1212
  //
1115
1213
  // update UI elements
1116
1214
  //
@@ -1124,19 +1222,19 @@ class Viewer {
1124
1222
  this.tools,
1125
1223
  this.glass,
1126
1224
  );
1127
-
1225
+ timer.split("ui updated");
1128
1226
  this.display.autoCollapse();
1129
1227
 
1130
1228
  // ensure all for all deselected objects the stencil planes are invisible
1131
1229
  this.setObjects(this.states, true, true);
1132
-
1230
+ timer.split("stencil done");
1133
1231
  //
1134
1232
  // show the rendering
1135
1233
  //
1136
1234
 
1137
1235
  this.toggleAnimationLoop(this.hasAnimationLoop);
1138
1236
 
1139
- this.display.showMeasureTools(options.measureTools);
1237
+ this.display.showMeasureTools(viewerOptions.measureTools);
1140
1238
 
1141
1239
  this.ready = true;
1142
1240
  this.info.readyMsg(this.gridHelper.ticks, this.control);
@@ -1144,7 +1242,7 @@ class Viewer {
1144
1242
  //
1145
1243
  // notify calculated results
1146
1244
  //
1147
-
1245
+ timer.split("show done");
1148
1246
  if (this.notifyCallback) {
1149
1247
  this.notifyCallback({
1150
1248
  tab: { old: null, new: this.display.activeTab },
@@ -1155,8 +1253,10 @@ class Viewer {
1155
1253
  clip_normal_2: { old: null, new: this.clipNormal2 },
1156
1254
  });
1157
1255
  }
1158
- this.update(true, false);
1256
+ timer.split("notification done");
1159
1257
 
1258
+ this.update(true, false);
1259
+ timer.split("update done");
1160
1260
  timer.stop();
1161
1261
  }
1162
1262
 
@@ -1495,7 +1595,7 @@ class Viewer {
1495
1595
  this.bb_max / 30,
1496
1596
  this.scene.children.slice(0, 1),
1497
1597
  // eslint-disable-next-line no-unused-vars
1498
- (ev) => { },
1598
+ (ev) => {},
1499
1599
  );
1500
1600
  raycaster.init();
1501
1601
  raycaster.onPointerMove(e);
@@ -2172,7 +2272,9 @@ class Viewer {
2172
2272
  for (var capPlane of child.children) {
2173
2273
  if (flag) {
2174
2274
  capPlane.material.clippingPlanes =
2175
- this.clipping.reverseClipPlanes.filter((_, j) => j !== capPlane.index);
2275
+ this.clipping.reverseClipPlanes.filter(
2276
+ (_, j) => j !== capPlane.index,
2277
+ );
2176
2278
  } else {
2177
2279
  capPlane.material.clippingPlanes = this.clipping.clipPlanes.filter(
2178
2280
  (_, j) => j !== capPlane.index,
@@ -2187,7 +2289,9 @@ class Viewer {
2187
2289
  for (var helper of child.children) {
2188
2290
  if (flag) {
2189
2291
  helper.material.clippingPlanes =
2190
- this.clipping.reverseClipPlanes.filter((_, j) => j !== helper.index);
2292
+ this.clipping.reverseClipPlanes.filter(
2293
+ (_, j) => j !== helper.index,
2294
+ );
2191
2295
  } else {
2192
2296
  helper.material.clippingPlanes = this.clipping.clipPlanes.filter(
2193
2297
  (_, j) => j !== helper.index,