fluidcad 0.0.26 → 0.0.28

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.
Files changed (93) hide show
  1. package/lib/dist/common/scene-object.d.ts +45 -0
  2. package/lib/dist/common/scene-object.js +121 -0
  3. package/lib/dist/common/shape-factory.d.ts +1 -1
  4. package/lib/dist/common/shape-history-tracker.d.ts +35 -0
  5. package/lib/dist/common/shape-history-tracker.js +114 -0
  6. package/lib/dist/common/shape.js +7 -1
  7. package/lib/dist/common/shapes.d.ts +0 -1
  8. package/lib/dist/common/shapes.js +0 -1
  9. package/lib/dist/common/solid.js +5 -1
  10. package/lib/dist/core/extrude.d.ts +12 -13
  11. package/lib/dist/core/extrude.js +19 -1
  12. package/lib/dist/core/part.d.ts +2 -1
  13. package/lib/dist/core/part.js +4 -1
  14. package/lib/dist/core/sketch.d.ts +4 -3
  15. package/lib/dist/core/sketch.js +4 -1
  16. package/lib/dist/features/chamfer.js +12 -6
  17. package/lib/dist/features/extrude-base.d.ts +43 -1
  18. package/lib/dist/features/extrude-base.js +141 -36
  19. package/lib/dist/features/extrude-to-face.d.ts +1 -1
  20. package/lib/dist/features/extrude-to-face.js +42 -19
  21. package/lib/dist/features/extrude-two-distances.d.ts +1 -1
  22. package/lib/dist/features/extrude-two-distances.js +41 -15
  23. package/lib/dist/features/extrude.d.ts +1 -1
  24. package/lib/dist/features/extrude.js +75 -20
  25. package/lib/dist/features/fillet.js +3 -4
  26. package/lib/dist/features/fuse.js +14 -0
  27. package/lib/dist/features/infinite-extrude.d.ts +1 -0
  28. package/lib/dist/features/infinite-extrude.js +33 -4
  29. package/lib/dist/features/loft.js +18 -5
  30. package/lib/dist/features/mirror-shape.d.ts +1 -3
  31. package/lib/dist/features/mirror-shape.js +2 -1
  32. package/lib/dist/features/revolve.js +17 -4
  33. package/lib/dist/features/rotate.js +1 -0
  34. package/lib/dist/features/simple-extruder.js +5 -0
  35. package/lib/dist/features/sweep.js +13 -2
  36. package/lib/dist/features/translate.js +3 -1
  37. package/lib/dist/filters/face/face-filter.d.ts +12 -0
  38. package/lib/dist/filters/face/face-filter.js +21 -0
  39. package/lib/dist/filters/face/torus-filter.d.ts +19 -0
  40. package/lib/dist/filters/face/torus-filter.js +38 -0
  41. package/lib/dist/helpers/scene-helpers.d.ts +10 -2
  42. package/lib/dist/helpers/scene-helpers.js +278 -10
  43. package/lib/dist/index.d.ts +1 -0
  44. package/lib/dist/oc/boolean-ops.d.ts +32 -4
  45. package/lib/dist/oc/boolean-ops.js +122 -11
  46. package/lib/dist/oc/color-transfer.d.ts +37 -0
  47. package/lib/dist/oc/color-transfer.js +135 -0
  48. package/lib/dist/oc/extrude-ops.js +25 -3
  49. package/lib/dist/oc/face-ops.d.ts +0 -1
  50. package/lib/dist/oc/face-ops.js +0 -13
  51. package/lib/dist/oc/face-query.d.ts +2 -0
  52. package/lib/dist/oc/face-query.js +30 -0
  53. package/lib/dist/oc/fillet-ops.d.ts +5 -3
  54. package/lib/dist/oc/fillet-ops.js +107 -70
  55. package/lib/dist/oc/intersection.js +6 -3
  56. package/lib/dist/oc/mesh.d.ts +25 -2
  57. package/lib/dist/oc/mesh.js +112 -35
  58. package/lib/dist/oc/shape-ops.d.ts +25 -20
  59. package/lib/dist/oc/shape-ops.js +129 -113
  60. package/lib/dist/rendering/mesh-transform.js +17 -1
  61. package/lib/dist/rendering/render-solid.js +19 -6
  62. package/lib/dist/rendering/render-wire.js +2 -0
  63. package/lib/dist/rendering/render.d.ts +12 -2
  64. package/lib/dist/rendering/render.js +195 -220
  65. package/lib/dist/scene-manager.d.ts +2 -0
  66. package/lib/dist/scene-manager.js +4 -3
  67. package/lib/dist/tests/common/scene-object-history.test.d.ts +1 -0
  68. package/lib/dist/tests/common/scene-object-history.test.js +274 -0
  69. package/lib/dist/tests/common/shape-history-tracker.test.d.ts +1 -0
  70. package/lib/dist/tests/common/shape-history-tracker.test.js +110 -0
  71. package/lib/dist/tests/features/2d/project-regression.test.d.ts +1 -0
  72. package/lib/dist/tests/features/2d/project-regression.test.js +69 -0
  73. package/lib/dist/tests/features/2d/project-user-regression.test.d.ts +1 -0
  74. package/lib/dist/tests/features/2d/project-user-regression.test.js +37 -0
  75. package/lib/dist/tests/features/color-lineage.test.d.ts +1 -0
  76. package/lib/dist/tests/features/color-lineage.test.js +213 -0
  77. package/lib/dist/tests/features/cut-symmetric-through-all.test.d.ts +1 -0
  78. package/lib/dist/tests/features/cut-symmetric-through-all.test.js +32 -0
  79. package/lib/dist/tests/features/extrude-history.test.d.ts +1 -0
  80. package/lib/dist/tests/features/extrude-history.test.js +248 -0
  81. package/lib/dist/tests/features/extrude.test.js +71 -0
  82. package/lib/dist/tests/features/fillet2d.test.js +16 -1
  83. package/lib/dist/tests/features/peer-ops-history.test.d.ts +1 -0
  84. package/lib/dist/tests/features/peer-ops-history.test.js +119 -0
  85. package/lib/dist/tests/features/select.test.js +50 -0
  86. package/lib/dist/tests/features/subtract.test.js +21 -1
  87. package/lib/dist/tests/setup.js +3 -2
  88. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  89. package/package.json +3 -3
  90. package/ui/dist/assets/{index-BeLxRMCv.js → index-BrW_x4uc.js} +37 -37
  91. package/ui/dist/index.html +1 -1
  92. package/lib/dist/common/solid-face.d.ts +0 -9
  93. package/lib/dist/common/solid-face.js +0 -22
@@ -1,4 +1,7 @@
1
1
  import { getOC } from "./init.js";
2
+ // Flip to false to benchmark single-threaded meshing.
3
+ const DEFAULT_LIN_DEFLECTION = 0.1;
4
+ const DEFAULT_ANG_DEFLECTION = 0.5;
2
5
  export class Mesh {
3
6
  // Wrapper methods (public API for external callers)
4
7
  static triangulateFace(face, vertexOffset = 0) {
@@ -7,23 +10,33 @@ export class Mesh {
7
10
  static discretizeEdge(edge) {
8
11
  return Mesh.discretizeEdgeRaw(edge.getShape());
9
12
  }
10
- static premeshShape(shape) {
13
+ /**
14
+ * Triangulates `shape` only if it doesn't already have an up-to-date
15
+ * triangulation at the requested deflection. Returns true when a fresh
16
+ * mesh was built, false when the stored one was reused.
17
+ */
18
+ static ensureTriangulated(shape, opts = {}) {
11
19
  const oc = getOC();
12
- const inc = new oc.BRepMesh_IncrementalMesh(shape, 0.3, false, 0.3, true);
20
+ const linDefl = opts.linDefl ?? DEFAULT_LIN_DEFLECTION;
21
+ const angDefl = opts.angDefl ?? DEFAULT_ANG_DEFLECTION;
22
+ const relative = opts.relative ?? false;
23
+ const checkFreeEdges = opts.checkFreeEdges ?? true;
24
+ if (oc.BRepTools.Triangulation(shape, linDefl, checkFreeEdges)) {
25
+ return false;
26
+ }
27
+ const inc = new oc.BRepMesh_IncrementalMesh(shape, linDefl, relative, angDefl, true);
13
28
  inc.delete();
29
+ return true;
14
30
  }
15
31
  // Raw methods (for oc-internal use)
16
32
  static triangulateFaceRaw(face, vertexOffset = 0) {
17
- const oc = getOC();
18
- let inc;
19
33
  try {
20
- inc = new oc.BRepMesh_IncrementalMesh(face, 0.3, false, 0.3, true);
34
+ Mesh.ensureTriangulated(face);
21
35
  }
22
36
  catch (e) {
23
37
  console.error("Face mesh failed", e);
24
38
  return null;
25
39
  }
26
- inc.delete();
27
40
  return Mesh.extractFaceTriangulationRaw(face, vertexOffset);
28
41
  }
29
42
  static extractFaceTriangulationRaw(face, vertexOffset = 0) {
@@ -80,39 +93,103 @@ export class Mesh {
80
93
  aLocation.delete();
81
94
  return { vertices, normals, indices, count: nbNodes };
82
95
  }
96
+ /**
97
+ * Reads the polyline stored for `edge` as a polygon-on-triangulation of
98
+ * `face`. Node indices point into the face's triangulation, so the edge
99
+ * samples coincide exactly with the face mesh vertices (watertight).
100
+ */
101
+ static discretizeEdgeOnFace(edge, face) {
102
+ const oc = getOC();
103
+ if (oc.BRep_Tool.Degenerated(edge)) {
104
+ return null;
105
+ }
106
+ const loc = new oc.TopLoc_Location();
107
+ const triHandle = oc.BRep_Tool.Triangulation(face, loc, 0);
108
+ if (triHandle.IsNull()) {
109
+ triHandle.delete();
110
+ loc.delete();
111
+ return null;
112
+ }
113
+ const polyHandle = oc.BRep_Tool.PolygonOnTriangulation(edge, triHandle, loc);
114
+ if (polyHandle.IsNull()) {
115
+ polyHandle.delete();
116
+ triHandle.delete();
117
+ loc.delete();
118
+ return null;
119
+ }
120
+ const tri = triHandle.get();
121
+ const poly = polyHandle.get();
122
+ const nbNodes = poly.NbNodes();
123
+ const tx = loc.Transformation();
124
+ const vertices = new Array(nbNodes * 3);
125
+ for (let i = 1; i <= nbNodes; i++) {
126
+ const nodeIdx = poly.Node(i);
127
+ const p = tri.Node(nodeIdx);
128
+ const pT = p.Transformed(tx);
129
+ const base = (i - 1) * 3;
130
+ vertices[base] = pT.X();
131
+ vertices[base + 1] = pT.Y();
132
+ vertices[base + 2] = pT.Z();
133
+ p.delete();
134
+ pT.delete();
135
+ }
136
+ const indices = new Array((nbNodes - 1) * 2);
137
+ for (let i = 0; i < nbNodes - 1; i++) {
138
+ indices[i * 2] = i;
139
+ indices[i * 2 + 1] = i + 1;
140
+ }
141
+ tx.delete();
142
+ polyHandle.delete();
143
+ triHandle.delete();
144
+ loc.delete();
145
+ return { vertices, normals: [], indices };
146
+ }
147
+ /**
148
+ * Reads the stored 3D polygon for a free edge (one not attached to a
149
+ * meshed face). Caller must have already run `ensureTriangulated` on the
150
+ * edge or its parent wire.
151
+ */
83
152
  static discretizeEdgeRaw(edge) {
84
153
  const oc = getOC();
85
154
  const ocEdge = oc.TopoDS.Edge(edge);
86
- const adaptor = new oc.BRepAdaptor_Curve(ocEdge);
87
- const type = adaptor.GetType();
88
- const first = adaptor.FirstParameter();
89
- const last = adaptor.LastParameter();
90
- const points = [];
91
- if (type === oc.GeomAbs_CurveType.GeomAbs_Line) {
92
- const startPnt = adaptor.Value(first);
93
- const endPnt = adaptor.Value(last);
94
- points.push(startPnt.X(), startPnt.Y(), startPnt.Z());
95
- points.push(endPnt.X(), endPnt.Y(), endPnt.Z());
96
- startPnt.delete();
97
- endPnt.delete();
98
- }
99
- else {
100
- const numSegments = Math.max(1, Math.floor((last - first) / 0.01));
101
- for (let i = 0; i <= numSegments; i++) {
102
- const t = first + ((last - first) * i) / numSegments;
103
- const pnt = adaptor.Value(t);
104
- points.push(pnt.X(), pnt.Y(), pnt.Z());
105
- pnt.delete();
106
- }
155
+ if (oc.BRep_Tool.Degenerated(ocEdge)) {
156
+ ocEdge.delete();
157
+ return { vertices: [], normals: [], indices: [] };
107
158
  }
108
- adaptor.delete();
109
- ocEdge.delete();
110
- const pointCount = points.length / 3;
111
- const indices = [];
112
- for (let i = 0; i < pointCount - 1; i++) {
113
- indices[2 * i] = i;
114
- indices[2 * i + 1] = i + 1;
159
+ Mesh.ensureTriangulated(edge);
160
+ const loc = new oc.TopLoc_Location();
161
+ const polyHandle = oc.BRep_Tool.Polygon3D(ocEdge, loc);
162
+ if (polyHandle.IsNull()) {
163
+ polyHandle.delete();
164
+ loc.delete();
165
+ ocEdge.delete();
166
+ console.warn("Edge has no stored Polygon3D after meshing; returning empty polyline.");
167
+ return { vertices: [], normals: [], indices: [] };
115
168
  }
116
- return { vertices: points, normals: [], indices };
169
+ const poly = polyHandle.get();
170
+ const nbNodes = poly.NbNodes();
171
+ const nodes = poly.Nodes();
172
+ const tx = loc.Transformation();
173
+ const vertices = new Array(nbNodes * 3);
174
+ for (let i = 1; i <= nbNodes; i++) {
175
+ const p = nodes.Value(i);
176
+ const pT = p.Transformed(tx);
177
+ const base = (i - 1) * 3;
178
+ vertices[base] = pT.X();
179
+ vertices[base + 1] = pT.Y();
180
+ vertices[base + 2] = pT.Z();
181
+ p.delete();
182
+ pT.delete();
183
+ }
184
+ const indices = new Array((nbNodes - 1) * 2);
185
+ for (let i = 0; i < nbNodes - 1; i++) {
186
+ indices[i * 2] = i;
187
+ indices[i * 2 + 1] = i + 1;
188
+ }
189
+ tx.delete();
190
+ polyHandle.delete();
191
+ loc.delete();
192
+ ocEdge.delete();
193
+ return { vertices, normals: [], indices };
117
194
  }
118
195
  }
@@ -1,11 +1,26 @@
1
- import type { TopTools_ListOfShape, TopoDS_Edge, TopoDS_Face, TopoDS_Shape, TopoDS_Solid } from "occjs-wrapper";
1
+ import type { TopTools_ListOfShape, TopoDS_Shape } from "occjs-wrapper";
2
2
  import { Matrix4 } from "../math/matrix4.js";
3
- import { Vector3d } from "../math/vector3d.js";
4
3
  import { Shape } from "../common/shape.js";
5
4
  import { Face } from "../common/face.js";
6
- import { Solid } from "../common/solid.js";
5
+ import { Edge } from "../common/edge.js";
7
6
  import { BoundingBox } from "../helpers/types.js";
8
- import { Axis } from "../math/axis.js";
7
+ /**
8
+ * A cleanShape result that preserves UnifySameDomain lineage so callers can
9
+ * chain pre-clean → post-clean face/edge remapping. `remapFace(pf)` returns
10
+ * the post-clean face(s) corresponding to a pre-clean face, or `null` if the
11
+ * cleanup didn't process it. Caller must invoke `dispose()` exactly once.
12
+ *
13
+ * When the post-clean shape fails validation and ShapeFix_Shape has to run,
14
+ * the UnifySameDomain history is discarded (ShapeFix_Shape creates more new
15
+ * TShapes without recording lineage). In that case remap returns `[face]`
16
+ * for any face the cleanup saw, which is best-effort.
17
+ */
18
+ export type CleanShapeLineage = {
19
+ shape: Shape;
20
+ remapFace: (face: Face) => Face[] | null;
21
+ remapEdge: (edge: Edge) => Edge[] | null;
22
+ dispose: () => void;
23
+ };
9
24
  export declare class ShapeOps {
10
25
  static transform(shape: Shape, matrix: Matrix4): Shape;
11
26
  static getBoundingBox(shape: Shape | TopoDS_Shape): BoundingBox;
@@ -13,22 +28,12 @@ export declare class ShapeOps {
13
28
  static makeCompound(shapes: Shape[]): Shape;
14
29
  static makeCompoundRaw(shapes: TopoDS_Shape[]): import("occjs-wrapper").TopoDS_Compound;
15
30
  static cleanShape(shape: Shape): Shape;
31
+ /**
32
+ * Variant of `cleanShape` that preserves UnifySameDomain lineage via
33
+ * `BRepTools_History`. Caller must call `dispose()` exactly once to free
34
+ * the OC wrappers.
35
+ */
36
+ static cleanShapeWithLineage(shape: Shape): CleanShapeLineage;
16
37
  static cleanShapeRaw(shape: TopoDS_Shape): TopoDS_Shape;
17
- static getSolidOutwardNormal(face: Face, solid: Solid): Vector3d;
18
- static getSolidOutwardNormalRaw(face: TopoDS_Face, solid: TopoDS_Solid): Vector3d;
19
- static mirrorShape(shape: Shape, mirrorPoint: {
20
- x: number;
21
- y: number;
22
- z: number;
23
- }): Shape;
24
- static mirrorShapeRaw(shape: TopoDS_Shape, mirrorPoint: {
25
- x: number;
26
- y: number;
27
- z: number;
28
- }): TopoDS_Shape;
29
- static translateShape(shape: Shape, direction: Vector3d): Shape;
30
- static translateShapeRaw(shape: TopoDS_Shape, direction: Vector3d): TopoDS_Shape;
31
- static rotateShape(shape: TopoDS_Shape, axis: Axis, angle: number): TopoDS_Shape;
32
38
  static shapeListToArray(list: TopTools_ListOfShape): TopoDS_Shape[];
33
- static edgeMiddlePoint(edge: TopoDS_Edge): import("occjs-wrapper").gp_Pnt;
34
39
  }
@@ -1,8 +1,10 @@
1
1
  import { getOC } from "./init.js";
2
2
  import { Convert } from "./convert.js";
3
- import { Vector3d } from "../math/vector3d.js";
4
3
  import { Shape } from "../common/shape.js";
5
4
  import { ShapeFactory } from "../common/shape-factory.js";
5
+ import { Face } from "../common/face.js";
6
+ import { Edge } from "../common/edge.js";
7
+ import { Explorer } from "./explorer.js";
6
8
  export class ShapeOps {
7
9
  static transform(shape, matrix) {
8
10
  const oc = getOC();
@@ -71,13 +73,124 @@ export class ShapeOps {
71
73
  static cleanShape(shape) {
72
74
  return ShapeFactory.fromShape(ShapeOps.cleanShapeRaw(shape.getShape()));
73
75
  }
74
- static cleanShapeRaw(shape) {
76
+ /**
77
+ * Variant of `cleanShape` that preserves UnifySameDomain lineage via
78
+ * `BRepTools_History`. Caller must call `dispose()` exactly once to free
79
+ * the OC wrappers.
80
+ */
81
+ static cleanShapeWithLineage(shape) {
75
82
  const oc = getOC();
76
- // Full unification: merge redundant edges AND co-surface faces
77
- const unify = new oc.ShapeUpgrade_UnifySameDomain(shape, false, true, false);
83
+ const FACE = oc.TopAbs_ShapeEnum.TopAbs_FACE;
84
+ const EDGE = oc.TopAbs_ShapeEnum.TopAbs_EDGE;
85
+ const unify = new oc.ShapeUpgrade_UnifySameDomain(shape.getShape(), false, true, false);
78
86
  unify.Build();
79
- let cleaned = unify.Shape();
80
- unify.delete();
87
+ const cleanedRaw = unify.Shape();
88
+ // Pre-compute which faces/edges this cleanup saw so the remap can
89
+ // distinguish "didn't know about this shape" (return null) from
90
+ // "saw but didn't modify" (return [original]).
91
+ const knownFaces = new oc.TopTools_MapOfShape();
92
+ const knownEdges = new oc.TopTools_MapOfShape();
93
+ for (const raw of Explorer.findShapes(shape.getShape(), FACE)) {
94
+ knownFaces.Add(raw);
95
+ }
96
+ for (const raw of Explorer.findShapes(shape.getShape(), EDGE)) {
97
+ knownEdges.Add(raw);
98
+ }
99
+ const checker = new oc.BRepCheck_Analyzer(cleanedRaw, true, true);
100
+ const valid = checker.IsValid();
101
+ checker.delete();
102
+ if (!valid) {
103
+ // ShapeFix_Shape creates new TShapes without recording history.
104
+ // Lineage is lost here — remap returns [face] best-effort for
105
+ // faces the cleanup saw, null otherwise.
106
+ unify.delete();
107
+ const fixer = new oc.ShapeFix_Shape(cleanedRaw);
108
+ const progress = new oc.Message_ProgressRange();
109
+ fixer.Perform(progress);
110
+ const fixed = fixer.Shape();
111
+ fixer.delete();
112
+ progress.delete();
113
+ const wrapped = ShapeFactory.fromShape(fixed);
114
+ let disposed = false;
115
+ const dispose = () => {
116
+ if (disposed) {
117
+ return;
118
+ }
119
+ disposed = true;
120
+ knownFaces.delete();
121
+ knownEdges.delete();
122
+ };
123
+ return {
124
+ shape: wrapped,
125
+ remapFace: (face) => (knownFaces.Contains(face.getShape()) ? [face] : null),
126
+ remapEdge: (edge) => (knownEdges.Contains(edge.getShape()) ? [edge] : null),
127
+ dispose,
128
+ };
129
+ }
130
+ const historyHandle = unify.History();
131
+ const history = historyHandle.get();
132
+ let disposed = false;
133
+ const dispose = () => {
134
+ if (disposed) {
135
+ return;
136
+ }
137
+ disposed = true;
138
+ historyHandle.delete();
139
+ unify.delete();
140
+ knownFaces.delete();
141
+ knownEdges.delete();
142
+ };
143
+ return {
144
+ shape: ShapeFactory.fromShape(cleanedRaw),
145
+ remapFace: (face) => {
146
+ const raw = face.getShape();
147
+ if (!knownFaces.Contains(raw)) {
148
+ return null;
149
+ }
150
+ if (history.IsRemoved(raw)) {
151
+ return [];
152
+ }
153
+ const list = ShapeOps.shapeListToArray(history.Modified(raw))
154
+ .filter(s => s.ShapeType() === FACE);
155
+ if (list.length === 0) {
156
+ return [face];
157
+ }
158
+ return list.map(r => Face.fromTopoDSFace(Explorer.toFace(r)));
159
+ },
160
+ remapEdge: (edge) => {
161
+ const raw = edge.getShape();
162
+ if (!knownEdges.Contains(raw)) {
163
+ return null;
164
+ }
165
+ if (history.IsRemoved(raw)) {
166
+ return [];
167
+ }
168
+ const list = ShapeOps.shapeListToArray(history.Modified(raw))
169
+ .filter(s => s.ShapeType() === EDGE);
170
+ if (list.length === 0) {
171
+ return [edge];
172
+ }
173
+ return list.map(r => Edge.fromTopoDSEdge(Explorer.toEdge(r)));
174
+ },
175
+ dispose,
176
+ };
177
+ }
178
+ static cleanShapeRaw(shape) {
179
+ const oc = getOC();
180
+ // Full unification: merge redundant edges AND co-surface faces.
181
+ // UnifySameDomain can throw on shapes with subtle topology issues
182
+ // (e.g. boolean output from a profile with reversed face normal).
183
+ // Fall back to the input shape on failure rather than aborting.
184
+ let cleaned;
185
+ try {
186
+ const unify = new oc.ShapeUpgrade_UnifySameDomain(shape, false, true, false);
187
+ unify.Build();
188
+ cleaned = unify.Shape();
189
+ unify.delete();
190
+ }
191
+ catch {
192
+ return shape;
193
+ }
81
194
  // Validate — UnifySameDomain can corrupt periodic surfaces (e.g. cylinders)
82
195
  const checker = new oc.BRepCheck_Analyzer(cleaned, true, true);
83
196
  if (checker.IsValid()) {
@@ -86,105 +199,18 @@ export class ShapeOps {
86
199
  }
87
200
  checker.delete();
88
201
  // Repair with ShapeFix_Shape (fixes seam edges, wire orientation, SameParameter)
89
- const fixer = new oc.ShapeFix_Shape(cleaned);
90
- const progress = new oc.Message_ProgressRange();
91
- fixer.Perform(progress);
92
- const fixed = fixer.Shape();
93
- fixer.delete();
94
- progress.delete();
95
- return fixed;
96
- }
97
- static getSolidOutwardNormal(face, solid) {
98
- return ShapeOps.getSolidOutwardNormalRaw(face.getShape(), solid.getShape());
99
- }
100
- static getSolidOutwardNormalRaw(face, solid) {
101
- const oc = getOC();
102
- const surfaceAdaptor = new oc.BRepAdaptor_Surface(face, true);
103
- const type = surfaceAdaptor.GetType();
104
- if (type !== oc.GeomAbs_SurfaceType.GeomAbs_Plane) {
105
- surfaceAdaptor.delete();
106
- throw new Error("Non-planar faces not supported for normal calculation");
107
- }
108
- const uFirst = surfaceAdaptor.FirstUParameter();
109
- const uLast = surfaceAdaptor.LastUParameter();
110
- const vFirst = surfaceAdaptor.FirstVParameter();
111
- const vLast = surfaceAdaptor.LastVParameter();
112
- const u = (uFirst + uLast) / 2.0;
113
- const v = (vFirst + vLast) / 2.0;
114
- const testPoint = new oc.gp_Pnt();
115
- const du = new oc.gp_Vec();
116
- const dv = new oc.gp_Vec();
117
- surfaceAdaptor.D1(u, v, testPoint, du, dv);
118
- const geometricNormal = surfaceAdaptor.Plane().Position().Direction();
119
- if (face.Orientation() === oc.TopAbs_Orientation.TopAbs_REVERSED) {
120
- geometricNormal.Reverse();
202
+ try {
203
+ const fixer = new oc.ShapeFix_Shape(cleaned);
204
+ const progress = new oc.Message_ProgressRange();
205
+ fixer.Perform(progress);
206
+ const fixed = fixer.Shape();
207
+ fixer.delete();
208
+ progress.delete();
209
+ return fixed;
121
210
  }
122
- const offset = 1e-6;
123
- const testPointOffset = new oc.gp_Pnt(testPoint.X() + geometricNormal.X() * offset, testPoint.Y() + geometricNormal.Y() * offset, testPoint.Z() + geometricNormal.Z() * offset);
124
- const classifier = new oc.BRepClass3d_SolidClassifier(solid, testPointOffset, oc.Precision.Confusion());
125
- const state = classifier.State();
126
- let result;
127
- if (state === oc.TopAbs_State.TopAbs_IN) {
128
- result = new Vector3d(-geometricNormal.X(), -geometricNormal.Y(), -geometricNormal.Z());
129
- }
130
- else {
131
- result = new Vector3d(geometricNormal.X(), geometricNormal.Y(), geometricNormal.Z());
211
+ catch {
212
+ return cleaned;
132
213
  }
133
- classifier.delete();
134
- testPointOffset.delete();
135
- geometricNormal.delete();
136
- du.delete();
137
- dv.delete();
138
- testPoint.delete();
139
- surfaceAdaptor.delete();
140
- return result;
141
- }
142
- static mirrorShape(shape, mirrorPoint) {
143
- const result = ShapeOps.mirrorShapeRaw(shape.getShape(), mirrorPoint);
144
- return ShapeFactory.fromShape(result);
145
- }
146
- static mirrorShapeRaw(shape, mirrorPoint) {
147
- const oc = getOC();
148
- const point = new oc.gp_Pnt(mirrorPoint.x, mirrorPoint.y, mirrorPoint.z);
149
- const trsf = new oc.gp_Trsf();
150
- trsf.SetMirror(point);
151
- const transformer = new oc.BRepBuilderAPI_Transform(trsf);
152
- transformer.Perform(shape, true);
153
- const result = transformer.Shape();
154
- transformer.delete();
155
- trsf.delete();
156
- point.delete();
157
- return result;
158
- }
159
- static translateShape(shape, direction) {
160
- const result = ShapeOps.translateShapeRaw(shape.getShape(), direction);
161
- return ShapeFactory.fromShape(result);
162
- }
163
- static translateShapeRaw(shape, direction) {
164
- const oc = getOC();
165
- const [vec, disposeVec] = Convert.toGpVec(direction);
166
- const trsf = new oc.gp_Trsf();
167
- trsf.SetTranslation(vec);
168
- const transformer = new oc.BRepBuilderAPI_Transform(trsf);
169
- transformer.Perform(shape, false);
170
- const result = transformer.Shape();
171
- transformer.delete();
172
- trsf.delete();
173
- disposeVec();
174
- return result;
175
- }
176
- static rotateShape(shape, axis, angle) {
177
- const oc = getOC();
178
- const trsf = new oc.gp_Trsf();
179
- const [gpAxis, disposeGpAxis] = Convert.toGpAx1(axis);
180
- trsf.SetRotation(gpAxis, angle);
181
- const transformer = new oc.BRepBuilderAPI_Transform(trsf);
182
- transformer.Perform(shape, false);
183
- const result = transformer.Shape();
184
- transformer.delete();
185
- trsf.delete();
186
- disposeGpAxis();
187
- return result;
188
214
  }
189
215
  static shapeListToArray(list) {
190
216
  let res = [];
@@ -195,14 +221,4 @@ export class ShapeOps {
195
221
  list.delete();
196
222
  return res;
197
223
  }
198
- static edgeMiddlePoint(edge) {
199
- const oc = getOC();
200
- const curveAdaptor = new oc.BRepAdaptor_Curve(oc.TopoDS.Edge(edge));
201
- const curve = curveAdaptor.Curve();
202
- const midParam = (curve.FirstParameter() + curve.LastParameter()) / 2.0;
203
- const midPoint = curve.Value(midParam);
204
- const result = new oc.gp_Pnt(midPoint.X(), midPoint.Y(), midPoint.Z());
205
- curveAdaptor.delete();
206
- return result;
207
- }
208
224
  }
@@ -1,5 +1,12 @@
1
1
  export function transformMeshes(meshes, matrix) {
2
2
  const m = matrix.elements;
3
+ // Determinant of the 3x3 linear part. Negative for mirror/reflection
4
+ // transforms — those flip triangle winding, so we have to swap indices
5
+ // to keep faces front-facing after the vertex transform.
6
+ const det3 = m[0] * (m[5] * m[10] - m[9] * m[6]) -
7
+ m[4] * (m[1] * m[10] - m[9] * m[2]) +
8
+ m[8] * (m[1] * m[6] - m[5] * m[2]);
9
+ const flipWinding = det3 < 0;
3
10
  return meshes.map(mesh => {
4
11
  const srcV = mesh.vertices;
5
12
  const srcN = mesh.normals;
@@ -17,6 +24,15 @@ export function transformMeshes(meshes, matrix) {
17
24
  newN[i + 1] = m[1] * nx + m[5] * ny + m[9] * nz;
18
25
  newN[i + 2] = m[2] * nx + m[6] * ny + m[10] * nz;
19
26
  }
20
- return { ...mesh, vertices: newV, normals: newN };
27
+ let newIndices = mesh.indices;
28
+ if (flipWinding && mesh.indices.length % 3 === 0) {
29
+ newIndices = new Array(mesh.indices.length);
30
+ for (let i = 0; i < mesh.indices.length; i += 3) {
31
+ newIndices[i] = mesh.indices[i];
32
+ newIndices[i + 1] = mesh.indices[i + 2];
33
+ newIndices[i + 2] = mesh.indices[i + 1];
34
+ }
35
+ }
36
+ return { ...mesh, vertices: newV, normals: newN, indices: newIndices };
21
37
  });
22
38
  }
@@ -1,23 +1,36 @@
1
- import { renderEdge } from "./render-edge.js";
2
1
  import { Explorer } from "../oc/explorer.js";
3
2
  import { Mesh } from "../oc/mesh.js";
3
+ import { getOC } from "../oc/init.js";
4
4
  export function renderSolid(shapeObj) {
5
- Mesh.premeshShape(shapeObj.getShape());
5
+ Mesh.ensureTriangulated(shapeObj.getShape());
6
6
  const facesMeshes = getFacesMesh(shapeObj);
7
7
  const edgesMesh = getEdgesMesh(shapeObj);
8
8
  return [...facesMeshes, ...edgesMesh];
9
9
  }
10
10
  function getEdgesMesh(shapeObj) {
11
+ const oc = getOC();
11
12
  const result = [];
13
+ const edgeToFaces = new oc.TopTools_IndexedDataMapOfShapeListOfShape();
14
+ oc.TopExp.MapShapesAndAncestors(shapeObj.getShape(), oc.TopAbs_ShapeEnum.TopAbs_EDGE, oc.TopAbs_ShapeEnum.TopAbs_FACE, edgeToFaces);
12
15
  const edges = Explorer.findEdgesWrapped(shapeObj);
13
16
  for (let edgeIdx = 0; edgeIdx < edges.length; edgeIdx++) {
14
- const edgeResult = renderEdge(edges[edgeIdx]);
17
+ const edgeShape = edges[edgeIdx].getShape();
18
+ const parents = edgeToFaces.Seek(edgeShape);
19
+ if (!parents || parents.Size() === 0) {
20
+ continue;
21
+ }
22
+ const parentFace = oc.TopoDS.Face(parents.First());
23
+ const edgeResult = Mesh.discretizeEdgeOnFace(edgeShape, parentFace);
24
+ parentFace.delete();
15
25
  if (edgeResult) {
16
- edgeResult.label = 'solid-edges';
17
- edgeResult.edgeIndex = edgeIdx;
18
- result.push(edgeResult);
26
+ result.push({
27
+ ...edgeResult,
28
+ label: 'solid-edges',
29
+ edgeIndex: edgeIdx,
30
+ });
19
31
  }
20
32
  }
33
+ edgeToFaces.delete();
21
34
  return result;
22
35
  }
23
36
  function getFacesMesh(shapeObj) {
@@ -1,8 +1,10 @@
1
1
  import { renderEdge } from "./render-edge.js";
2
2
  import { Edge } from "../common/edge.js";
3
3
  import { Explorer } from "../oc/explorer.js";
4
+ import { Mesh } from "../oc/mesh.js";
4
5
  export function renderWire(shapeObj) {
5
6
  const shape = shapeObj.getShape();
7
+ Mesh.ensureTriangulated(shape);
6
8
  const edges = Explorer.findShapes(shape, Explorer.getOcShapeType("edge"));
7
9
  const allVertices = [];
8
10
  const allNormals = [];
@@ -1,3 +1,13 @@
1
1
  import { Scene } from "./scene.js";
2
- export declare function renderSceneRollback(scene: Scene, rollbackIndex: number): Scene;
3
- export declare function renderScene(scene: Scene): Scene;
2
+ export declare class SceneRenderer {
3
+ private readonly meshBuilder;
4
+ render(scene: Scene): Scene;
5
+ renderRollback(scene: Scene, rollbackIndex: number): Scene;
6
+ private renderObject;
7
+ private buildObject;
8
+ private getOrBuildMeshes;
9
+ private toRenderedShape;
10
+ private computeVisibility;
11
+ private aggregateContainerDurations;
12
+ private emitRendered;
13
+ }