@itwin/editor-frontend 3.0.0-dev.128 → 3.0.0-dev.133

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.
@@ -7,7 +7,7 @@
7
7
  */
8
8
  import { BooleanMode, ChamferMode, CutDepthMode, CutDirectionMode, EmbossDirectionMode, ProfileClosure, SubEntityType } from "@itwin/editor-common";
9
9
  import { ColorDef, ElementGeometry } from "@itwin/core-common";
10
- import { Geometry, LineString3d, Point3d, Vector3d } from "@itwin/core-geometry";
10
+ import { Angle, Geometry, LineString3d, Point3d, Vector3d } from "@itwin/core-geometry";
11
11
  import { AccuDrawHintBuilder, EventHandled, GraphicType } from "@itwin/core-frontend";
12
12
  import { computeChordToleranceFromPoint } from "./CreateElementTool";
13
13
  import { ElementGeometryCacheTool, isSameSubEntity, LocateSubEntityTool, SubEntityData } from "./ElementGeometryTool";
@@ -353,6 +353,13 @@ export class FaceLocationData {
353
353
  }
354
354
  /** @alpha Identify faces of solids and surfaces to offset. */
355
355
  export class OffsetFacesTool extends LocateSubEntityTool {
356
+ constructor() {
357
+ super(...arguments);
358
+ // TODO: Tool settings for offset distance...
359
+ this.useOffset = false;
360
+ this.offset = 0.1;
361
+ this.addSmoothFaces = false;
362
+ }
356
363
  get wantDynamics() { return true; }
357
364
  get wantAccuSnap() { return this.isDynamicsStarted; }
358
365
  get geometryCacheFilter() {
@@ -376,6 +383,15 @@ export class OffsetFacesTool extends LocateSubEntityTool {
376
383
  builder.addPointString([faceData.point]);
377
384
  context.addDecorationFromBuilder(builder);
378
385
  }
386
+ async getSmoothFaces(id, face) {
387
+ try {
388
+ // NOTE: For offset, include all smoothly connected faces, not just adjacent...
389
+ return await ElementGeometryCacheTool.callCommand("getConnectedSubEntities", id, face, SubEntityType.Face, { smoothFaces: true });
390
+ }
391
+ catch (err) {
392
+ return undefined;
393
+ }
394
+ }
379
395
  async applyAgendaOperation(ev, isAccept) {
380
396
  var _a;
381
397
  if (undefined === ev.viewport || this.agenda.isEmpty || !this.haveAcceptedSubEntities)
@@ -387,6 +403,8 @@ export class OffsetFacesTool extends LocateSubEntityTool {
387
403
  if (undefined === projPt)
388
404
  return undefined;
389
405
  const offsetDir = Vector3d.createStartEnd(faceData.point, projPt);
406
+ if (this.useOffset && undefined === offsetDir.scaleToLength(this.offset, offsetDir))
407
+ return undefined;
390
408
  let offset = offsetDir.magnitude();
391
409
  if (offset < Geometry.smallMetricDistance)
392
410
  return undefined;
@@ -394,14 +412,28 @@ export class OffsetFacesTool extends LocateSubEntityTool {
394
412
  offset = -offset;
395
413
  try {
396
414
  this._startedCmd = await this.startCommand();
397
- const params = { faces: this.getAcceptedSubEntities(), distances: offset };
415
+ const id = this.agenda.elements[0];
416
+ const faces = this.getAcceptedSubEntities();
417
+ if (this.addSmoothFaces) {
418
+ const allSmoothFaces = [];
419
+ for (const face of faces) {
420
+ const smoothFaces = await this.getSmoothFaces(id, face);
421
+ if (undefined !== smoothFaces)
422
+ allSmoothFaces.push(...smoothFaces);
423
+ }
424
+ for (const smooth of allSmoothFaces) {
425
+ if (undefined === faces.find((selected) => isSameSubEntity(selected, smooth)))
426
+ faces.unshift(smooth); // Preserve last selected entry as reference face...
427
+ }
428
+ }
429
+ const params = { faces, distances: offset };
398
430
  const opts = {
399
431
  wantGraphic: isAccept ? undefined : true,
400
432
  chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
401
- requestId: `${this.toolId}:${this.agenda.elements[0]}`,
433
+ requestId: `${this.toolId}:${id}`,
402
434
  writeChanges: isAccept ? true : undefined,
403
435
  };
404
- return await ElementGeometryCacheTool.callCommand("offsetFaces", this.agenda.elements[0], params, opts);
436
+ return await ElementGeometryCacheTool.callCommand("offsetFaces", id, params, opts);
405
437
  }
406
438
  catch (err) {
407
439
  return undefined;
@@ -468,6 +500,72 @@ export class HollowFacesTool extends LocateSubEntityTool {
468
500
  }
469
501
  HollowFacesTool.toolId = "HollowFaces";
470
502
  HollowFacesTool.iconSpec = "icon-move"; // TODO: Need better icon...
503
+ /** @alpha Identify faces or edges for removal by growing surrounding faces. */
504
+ export class DeleteSubEntitiesTool extends LocateSubEntityTool {
505
+ wantSubEntityType(type) {
506
+ switch (type) {
507
+ case SubEntityType.Face:
508
+ case SubEntityType.Edge:
509
+ // Choose all faces or all edges...
510
+ return (0 === this._acceptedSubEntities.length || this._acceptedSubEntities[0].props.type === type);
511
+ default:
512
+ return false;
513
+ }
514
+ }
515
+ getMaximumSubEntityHits(type) {
516
+ if (!this.wantSubEntityType(type))
517
+ return 0;
518
+ // Only return single closest edge, avoids having to test for redundant edges on reset...
519
+ return (SubEntityType.Edge === type ? 1 : 25);
520
+ }
521
+ get geometryCacheFilter() {
522
+ return { parts: true, curves: false, surfaces: true, solids: true, other: false };
523
+ }
524
+ async doPickSubEntities(id, ev) {
525
+ const hits = await super.doPickSubEntities(id, ev);
526
+ if (undefined === hits)
527
+ return hits;
528
+ // Don't allow reset to select a back edge...
529
+ if (SubEntityType.Face === hits[0].subEntity.type)
530
+ return hits.filter((hit) => { return SubEntityType.Face === hit.subEntity.type; });
531
+ try {
532
+ const accept = await ElementGeometryCacheTool.callCommand("isRedundantEdge", id, hits[0].subEntity);
533
+ if (accept)
534
+ return hits;
535
+ if (hits.length > 1 && SubEntityType.Face === hits[1].subEntity.type)
536
+ return hits.slice(1); // Accept face of rejected edge...
537
+ return undefined;
538
+ }
539
+ catch (err) {
540
+ return undefined;
541
+ }
542
+ }
543
+ async applyAgendaOperation(ev, isAccept) {
544
+ if (undefined === ev.viewport || this.agenda.isEmpty || !this.haveAcceptedSubEntities)
545
+ return undefined;
546
+ try {
547
+ this._startedCmd = await this.startCommand();
548
+ const params = { subEntities: this.getAcceptedSubEntities() };
549
+ const opts = {
550
+ wantGraphic: isAccept ? undefined : true,
551
+ chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
552
+ requestId: `${this.toolId}:${this.agenda.elements[0]}`,
553
+ writeChanges: isAccept ? true : undefined,
554
+ };
555
+ return await ElementGeometryCacheTool.callCommand("deleteSubEntities", this.agenda.elements[0], params, opts);
556
+ }
557
+ catch (err) {
558
+ return undefined;
559
+ }
560
+ }
561
+ async onRestartTool() {
562
+ const tool = new DeleteSubEntitiesTool();
563
+ if (!await tool.run())
564
+ return this.exitTool();
565
+ }
566
+ }
567
+ DeleteSubEntitiesTool.toolId = "DeleteSubEntities";
568
+ DeleteSubEntitiesTool.iconSpec = "icon-move"; // TODO: Need better icon...
471
569
  /** @alpha */
472
570
  export var ImprintSolidMethod;
473
571
  (function (ImprintSolidMethod) {
@@ -764,4 +862,205 @@ export class ChamferEdgesTool extends BlendEdgesTool {
764
862
  }
765
863
  ChamferEdgesTool.toolId = "ChamferEdges";
766
864
  ChamferEdgesTool.iconSpec = "icon-move"; // TODO: Need better icon...
865
+ /** @alpha Identify faces of solids and surfaces to translate. */
866
+ export class SweepFacesTool extends LocateSubEntityTool {
867
+ constructor() {
868
+ super(...arguments);
869
+ // TODO: Tool settings for sweep distance...
870
+ this.useDistance = false;
871
+ this.distance = 0.1;
872
+ this.addSmoothFaces = false;
873
+ }
874
+ get wantDynamics() { return true; }
875
+ get wantAccuSnap() { return this.isDynamicsStarted; }
876
+ // TODO: Sweep open path to surface, sweep surface to solid...
877
+ get geometryCacheFilter() {
878
+ return { parts: true, curves: false, surfaces: true, solids: true, other: false };
879
+ }
880
+ async createSubEntityData(id, hit) {
881
+ const data = await super.createSubEntityData(id, hit);
882
+ if (undefined !== hit.point && undefined !== hit.normal)
883
+ data.toolData = FaceLocationData.create(hit.point, hit.normal);
884
+ return data;
885
+ }
886
+ drawAcceptedSubEntities(context) {
887
+ var _a;
888
+ super.drawAcceptedSubEntities(context);
889
+ // Show pick point on last identified face, offset direction/distance will be computed from this location...
890
+ const faceData = (_a = this.getAcceptedSubEntityData()) === null || _a === void 0 ? void 0 : _a.toolData;
891
+ if (undefined === faceData)
892
+ return;
893
+ const builder = context.createGraphic({ type: GraphicType.WorldOverlay });
894
+ builder.setSymbology(context.viewport.getContrastToBackgroundColor(), ColorDef.black, 10);
895
+ builder.addPointString([faceData.point]);
896
+ context.addDecorationFromBuilder(builder);
897
+ }
898
+ async getSmoothFaces(id, face) {
899
+ try {
900
+ // NOTE: For sweep/translation, it makes sense to limit smooth to immediately adjacent...
901
+ return await ElementGeometryCacheTool.callCommand("getConnectedSubEntities", id, face, SubEntityType.Face, { smoothFaces: true, adjacentFaces: true });
902
+ }
903
+ catch (err) {
904
+ return undefined;
905
+ }
906
+ }
907
+ async applyAgendaOperation(ev, isAccept) {
908
+ var _a;
909
+ if (undefined === ev.viewport || this.agenda.isEmpty || !this.haveAcceptedSubEntities)
910
+ return undefined;
911
+ const faceData = (_a = this.getAcceptedSubEntityData()) === null || _a === void 0 ? void 0 : _a.toolData;
912
+ if (undefined === faceData)
913
+ return undefined;
914
+ const path = Vector3d.createStartEnd(faceData.point, ev.point);
915
+ if (this.useDistance && undefined === path.scaleToLength(this.distance, path))
916
+ return undefined;
917
+ if (path.magnitude() < Geometry.smallMetricDistance)
918
+ return undefined;
919
+ try {
920
+ this._startedCmd = await this.startCommand();
921
+ const id = this.agenda.elements[0];
922
+ const opts = {
923
+ wantGraphic: isAccept ? undefined : true,
924
+ chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
925
+ requestId: `${this.toolId}:${id}`,
926
+ writeChanges: isAccept ? true : undefined,
927
+ };
928
+ const faces = this.getAcceptedSubEntities();
929
+ if (this.addSmoothFaces) {
930
+ const allSmoothFaces = [];
931
+ for (const face of faces) {
932
+ const smoothFaces = await this.getSmoothFaces(id, face);
933
+ if (undefined !== smoothFaces)
934
+ allSmoothFaces.push(...smoothFaces);
935
+ }
936
+ for (const smooth of allSmoothFaces) {
937
+ if (undefined === faces.find((selected) => isSameSubEntity(selected, smooth)))
938
+ faces.unshift(smooth); // Preserve last selected entry as reference face...
939
+ }
940
+ }
941
+ const params = { faces, path };
942
+ return await ElementGeometryCacheTool.callCommand("sweepFaces", id, params, opts);
943
+ }
944
+ catch (err) {
945
+ return undefined;
946
+ }
947
+ }
948
+ setupAccuDraw() {
949
+ var _a;
950
+ if (!this.haveAcceptedSubEntities)
951
+ return;
952
+ const faceData = (_a = this.getAcceptedSubEntityData()) === null || _a === void 0 ? void 0 : _a.toolData;
953
+ if (undefined === faceData)
954
+ return;
955
+ const hints = new AccuDrawHintBuilder();
956
+ hints.setOriginFixed = true;
957
+ hints.setLockY = true;
958
+ hints.setLockZ = true;
959
+ hints.setModeRectangular();
960
+ hints.setOrigin(faceData.point);
961
+ hints.setXAxis2(faceData.normal);
962
+ hints.sendHints(false);
963
+ }
964
+ async onRestartTool() {
965
+ const tool = new SweepFacesTool();
966
+ if (!await tool.run())
967
+ return this.exitTool();
968
+ }
969
+ }
970
+ SweepFacesTool.toolId = "SweepFaces";
971
+ SweepFacesTool.iconSpec = "icon-move"; // TODO: Need better icon...
972
+ /** @alpha Identify faces of solids and surfaces to revolve. */
973
+ export class SpinFacesTool extends LocateSubEntityTool {
974
+ constructor() {
975
+ super(...arguments);
976
+ this.points = [];
977
+ // TODO: Tool settings for sweep angle...
978
+ this.sweep = Angle.piOver2Radians;
979
+ }
980
+ get wantDynamics() { return true; }
981
+ get wantAccuSnap() { return this.isDynamicsStarted || (this.haveAcceptedSubEntities && !this.isControlDown); }
982
+ // TODO: Spin open path to surface, spin surface to solid...
983
+ get geometryCacheFilter() {
984
+ return { parts: true, curves: false, surfaces: true, solids: true, other: false };
985
+ }
986
+ getSubEntityFilter() {
987
+ return { nonPlanarFaces: true };
988
+ }
989
+ async createSubEntityData(id, hit) {
990
+ const data = await super.createSubEntityData(id, hit);
991
+ if (undefined !== hit.point && undefined !== hit.normal)
992
+ data.toolData = FaceLocationData.create(hit.point, hit.normal);
993
+ return data;
994
+ }
995
+ async applyAgendaOperation(ev, isAccept) {
996
+ if (undefined === ev.viewport || this.agenda.isEmpty || !this.haveAcceptedSubEntities || this.points.length < (isAccept ? 2 : 1))
997
+ return undefined;
998
+ const direction = Vector3d.createStartEnd(this.points[0], isAccept ? this.points[1] : ev.point);
999
+ if (direction.magnitude() < Geometry.smallMetricDistance)
1000
+ return undefined;
1001
+ const origin = this.points[0];
1002
+ const angle = Angle.createRadians(this.sweep);
1003
+ try {
1004
+ this._startedCmd = await this.startCommand();
1005
+ const id = this.agenda.elements[0];
1006
+ const opts = {
1007
+ wantGraphic: isAccept ? undefined : true,
1008
+ chordTolerance: computeChordToleranceFromPoint(ev.viewport, ev.point),
1009
+ requestId: `${this.toolId}:${id}`,
1010
+ writeChanges: isAccept ? true : undefined,
1011
+ };
1012
+ const faces = this.getAcceptedSubEntities();
1013
+ const params = { faces, origin, direction, angle };
1014
+ let result = await ElementGeometryCacheTool.callCommand("spinFaces", id, params, opts);
1015
+ // Spun face can be used to create a pocket...retry with negative sweep...
1016
+ if (undefined === result) {
1017
+ angle.setRadians(-angle.radians);
1018
+ result = await ElementGeometryCacheTool.callCommand("spinFaces", id, params, opts);
1019
+ }
1020
+ return result;
1021
+ }
1022
+ catch (err) {
1023
+ return undefined;
1024
+ }
1025
+ }
1026
+ onDynamicFrame(ev, context) {
1027
+ if (0 === this.points.length)
1028
+ return;
1029
+ const pts = this.points.slice();
1030
+ pts.push(ev.point.clone());
1031
+ const builder = context.createGraphic({ type: GraphicType.WorldOverlay });
1032
+ builder.setSymbology(context.viewport.getContrastToBackgroundColor(), ColorDef.black, 3);
1033
+ builder.addLineString(pts);
1034
+ context.addGraphic(builder.finish());
1035
+ super.onDynamicFrame(ev, context);
1036
+ }
1037
+ async gatherInput(ev) {
1038
+ if (this.haveAcceptedSubEntities)
1039
+ this.points.push(ev.point.clone());
1040
+ return super.gatherInput(ev);
1041
+ }
1042
+ get wantAdditionalInput() {
1043
+ return super.wantAdditionalInput || this.points.length < 2;
1044
+ }
1045
+ setupAccuDraw() {
1046
+ var _a;
1047
+ if (!this.haveAcceptedSubEntities || 0 !== this.points.length)
1048
+ return;
1049
+ const faceData = (_a = this.getAcceptedSubEntityData()) === null || _a === void 0 ? void 0 : _a.toolData;
1050
+ if (undefined === faceData)
1051
+ return;
1052
+ const hints = new AccuDrawHintBuilder();
1053
+ hints.setModeRectangular();
1054
+ hints.setOrigin(faceData.point);
1055
+ hints.setNormal(faceData.normal);
1056
+ hints.sendHints(false);
1057
+ }
1058
+ async onRestartTool() {
1059
+ const tool = new SpinFacesTool();
1060
+ if (!await tool.run())
1061
+ return this.exitTool();
1062
+ }
1063
+ }
1064
+ SpinFacesTool.toolId = "SpinFaces";
1065
+ SpinFacesTool.iconSpec = "icon-move"; // TODO: Need better icon...
767
1066
  //# sourceMappingURL=SolidModelingTools.js.map