fluidcad 0.0.21 → 0.0.22

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 (41) hide show
  1. package/README.md +7 -2
  2. package/lib/dist/common/shape.d.ts +7 -0
  3. package/lib/dist/common/shape.js +7 -0
  4. package/lib/dist/core/copy.js +1 -1
  5. package/lib/dist/core/draft.d.ts +16 -0
  6. package/lib/dist/core/draft.js +29 -0
  7. package/lib/dist/core/index.d.ts +2 -3
  8. package/lib/dist/core/index.js +1 -1
  9. package/lib/dist/core/interfaces.d.ts +2 -0
  10. package/lib/dist/core/part.d.ts +2 -6
  11. package/lib/dist/core/part.js +12 -22
  12. package/lib/dist/features/2d/sketch.d.ts +1 -0
  13. package/lib/dist/features/2d/sketch.js +21 -4
  14. package/lib/dist/features/copy-circular.js +1 -0
  15. package/lib/dist/features/copy-circular2d.js +1 -0
  16. package/lib/dist/features/copy-linear.js +4 -5
  17. package/lib/dist/features/copy-linear2d.js +4 -5
  18. package/lib/dist/features/draft.d.ts +15 -0
  19. package/lib/dist/features/draft.js +88 -0
  20. package/lib/dist/oc/draft-ops.d.ts +5 -0
  21. package/lib/dist/oc/draft-ops.js +51 -0
  22. package/lib/dist/oc/mesh.d.ts +2 -0
  23. package/lib/dist/oc/mesh.js +14 -6
  24. package/lib/dist/rendering/mesh-transform.d.ts +3 -0
  25. package/lib/dist/rendering/mesh-transform.js +22 -0
  26. package/lib/dist/rendering/render-solid.js +3 -2
  27. package/lib/dist/rendering/render.js +25 -4
  28. package/lib/dist/tests/features/draft.test.d.ts +1 -0
  29. package/lib/dist/tests/features/draft.test.js +147 -0
  30. package/lib/dist/tests/features/part.test.js +69 -114
  31. package/lib/dist/tsconfig.tsbuildinfo +1 -1
  32. package/package.json +1 -1
  33. package/server/dist/fluidcad-server.d.ts +2 -0
  34. package/server/dist/fluidcad-server.js +10 -0
  35. package/server/dist/routes/actions.js +20 -0
  36. package/server/dist/vite-manager.js +7 -1
  37. package/ui/dist/assets/{index-B1LkrBga.js → index-C0JwQ8Bk.js} +9 -5
  38. package/ui/dist/assets/{index-BfcNNxXr.css → index-gPoNOiIs.css} +1 -1
  39. package/ui/dist/index.html +2 -2
  40. package/lib/dist/core/use.d.ts +0 -5
  41. package/lib/dist/core/use.js +0 -22
package/README.md CHANGED
@@ -71,7 +71,7 @@ Re-apply modeling features based on matrix transformations. Move, rotate, or mir
71
71
 
72
72
  ```javascript
73
73
  sketch("xy", () => {
74
- rect(200, 100).center()
74
+ rect(200, 100).centered()
75
75
  })
76
76
 
77
77
  const e1 = extrude(20)
@@ -155,7 +155,12 @@ Step-by-step tutorials from simple shapes to exam-level parts. [Browse all tutor
155
155
  <strong>CSWP Sample Exam</strong>
156
156
  </a>
157
157
  </td>
158
- <td width="33%"></td>
158
+ <td align="center" width="33%">
159
+ <a href="https://fluidcad.io/docs/tutorials/hinge-bracket">
160
+ <img src="https://fluidcad.io/assets/images/hinge-bracket-final-137547b475db21736d78b5b13f8db48b.png" alt="Hinge Bracket" height="180" /><br />
161
+ <strong>Hinge Bracket</strong>
162
+ </a>
163
+ </td>
159
164
  </tr>
160
165
  </table>
161
166
 
@@ -1,6 +1,7 @@
1
1
  import type { TopoDS_Shape } from "occjs-wrapper";
2
2
  import { ShapeType } from "./shape-type.js";
3
3
  import { SceneObjectMesh } from "../rendering/scene.js";
4
+ import { Matrix4 } from "../math/matrix4.js";
4
5
  export interface ShapeFilter {
5
6
  excludeMeta?: boolean;
6
7
  excludeGuide?: boolean;
@@ -17,6 +18,7 @@ export declare abstract class Shape<T extends TopoDS_Shape = TopoDS_Shape> {
17
18
  color: string;
18
19
  }>;
19
20
  private meshes;
21
+ private _meshSource;
20
22
  constructor(shape: T);
21
23
  abstract getType(): ShapeType;
22
24
  getShape(): T;
@@ -36,6 +38,11 @@ export declare abstract class Shape<T extends TopoDS_Shape = TopoDS_Shape> {
36
38
  isGuideShape(): boolean;
37
39
  getMeshes(): SceneObjectMesh[];
38
40
  setMeshes(meshes: SceneObjectMesh[]): void;
41
+ setMeshSource(source: Shape, matrix: Matrix4): void;
42
+ getMeshSource(): {
43
+ shape: Shape;
44
+ matrix: Matrix4;
45
+ } | null;
39
46
  setColor(face: TopoDS_Shape, color: string): void;
40
47
  getColor(face: TopoDS_Shape): string | undefined;
41
48
  hasColors(): boolean;
@@ -8,6 +8,7 @@ export class Shape {
8
8
  id;
9
9
  colorMap = [];
10
10
  meshes;
11
+ _meshSource = null;
11
12
  constructor(shape) {
12
13
  this.shape = shape;
13
14
  this.id = randomUUID();
@@ -70,6 +71,12 @@ export class Shape {
70
71
  setMeshes(meshes) {
71
72
  this.meshes = meshes;
72
73
  }
74
+ setMeshSource(source, matrix) {
75
+ this._meshSource = { shape: source, matrix };
76
+ }
77
+ getMeshSource() {
78
+ return this._meshSource;
79
+ }
73
80
  setColor(face, color) {
74
81
  if (this.isEdge()) {
75
82
  throw new Error("Cannot set color on edge shape");
@@ -16,7 +16,7 @@ function build(context) {
16
16
  const restObjects = args.slice(3);
17
17
  const objects = restObjects.length > 0
18
18
  ? restObjects
19
- : [context.getSceneObjects().at(-1)];
19
+ : null;
20
20
  if (type === 'linear') {
21
21
  const axisArg = args[1];
22
22
  const axes = Array.isArray(axisArg)
@@ -0,0 +1,16 @@
1
+ import { ISceneObject, IDraft } from "./interfaces.js";
2
+ interface DraftFunction {
3
+ /**
4
+ * Applies a draft angle to the last selected faces.
5
+ * @param angle - The draft angle in degrees
6
+ */
7
+ (angle: number): IDraft;
8
+ /**
9
+ * Applies a draft angle to the given face selections.
10
+ * @param angle - The draft angle in degrees
11
+ * @param selections - The face selections to draft
12
+ */
13
+ (angle: number, ...selections: ISceneObject[]): IDraft;
14
+ }
15
+ declare const _default: DraftFunction;
16
+ export default _default;
@@ -0,0 +1,29 @@
1
+ import { Draft } from "../features/draft.js";
2
+ import { SceneObject } from "../common/scene-object.js";
3
+ import { registerBuilder } from "../index.js";
4
+ import { rad } from "../helpers/math-helpers.js";
5
+ function build(context) {
6
+ return function draft() {
7
+ const args = Array.from(arguments);
8
+ const selections = [];
9
+ while (args.length > 0 && args[args.length - 1] instanceof SceneObject) {
10
+ selections.unshift(args.pop());
11
+ }
12
+ if (selections.length === 0) {
13
+ const implicit = context.getLastSelection() || undefined;
14
+ if (implicit) {
15
+ selections.push(implicit);
16
+ }
17
+ }
18
+ const angleDeg = (args.length >= 1 && typeof args[0] === 'number')
19
+ ? args[0]
20
+ : 5;
21
+ for (const sel of selections) {
22
+ context.addSceneObject(sel);
23
+ }
24
+ const draft = new Draft(rad(angleDeg), selections);
25
+ context.addSceneObject(draft);
26
+ return draft;
27
+ };
28
+ }
29
+ export default registerBuilder(build);
@@ -1,4 +1,4 @@
1
- export type { ISceneObject, IFuseable, IPlane, IAxis, ISelect, IGeometry, IExtrudableGeometry, IRect, ISlot, IPolygon, ITwoObjectsTangentLine, ITangentArcTwoObjects, IExtrude, ICut, ICommon, ISweep, ILoft, IRevolve } from "./interfaces.js";
1
+ export type { ISceneObject, IFuseable, IPlane, IAxis, ISelect, IGeometry, IExtrudableGeometry, IRect, ISlot, IPolygon, ITwoObjectsTangentLine, ITangentArcTwoObjects, IExtrude, ICut, ICommon, ISweep, ILoft, IRevolve, IDraft } from "./interfaces.js";
2
2
  export { default as axis } from "./axis.js";
3
3
  export { default as plane } from "./plane.js";
4
4
  export { default as sketch } from "./sketch.js";
@@ -23,11 +23,10 @@ export { default as load } from "./load.js";
23
23
  export { default as loft } from "./loft.js";
24
24
  export { default as sweep } from "./sweep.js";
25
25
  export { default as color } from "./color.js";
26
+ export { default as draft } from "./draft.js";
26
27
  export { default as remove } from "./remove.js";
27
28
  export { default as split } from "./split.js";
28
29
  export { default as trim } from "./trim.js";
29
30
  export { default as part } from "./part.js";
30
- export { default as use } from "./use.js";
31
- export type { PartHandle } from "./part.js";
32
31
  export * from "./2d/index.js";
33
32
  export { breakpoint } from "./breakpoint.js";
@@ -22,10 +22,10 @@ export { default as load } from "./load.js";
22
22
  export { default as loft } from "./loft.js";
23
23
  export { default as sweep } from "./sweep.js";
24
24
  export { default as color } from "./color.js";
25
+ export { default as draft } from "./draft.js";
25
26
  export { default as remove } from "./remove.js";
26
27
  export { default as split } from "./split.js";
27
28
  export { default as trim } from "./trim.js";
28
29
  export { default as part } from "./part.js";
29
- export { default as use } from "./use.js";
30
30
  export * from "./2d/index.js";
31
31
  export { breakpoint } from "./breakpoint.js";
@@ -562,6 +562,8 @@ export interface ISweep extends IFuseable {
562
562
  */
563
563
  capEdges(...args: (number | EdgeFilterBuilder)[]): ISceneObject;
564
564
  }
565
+ export interface IDraft extends ISceneObject {
566
+ }
565
567
  export interface IShell extends ISceneObject {
566
568
  /**
567
569
  * Selects the inner wall faces created by the shell operation (from thickness removal).
@@ -1,7 +1,3 @@
1
- export type PartHandle<T = any> = {
2
- __fluidcad_part: true;
3
- name: string;
4
- _callback: (options: T) => void;
5
- };
6
- declare function part<T = any>(name: string, callback: (options: T) => void): PartHandle<T>;
1
+ import { ISceneObject } from "./interfaces.js";
2
+ declare function part(name: string, callback: () => void): ISceneObject;
7
3
  export default part;
@@ -1,29 +1,19 @@
1
1
  import { captureSourceLocation } from "../index.js";
2
- import { getCurrentScene, getCurrentFile } from "../scene-manager.js";
2
+ import { getCurrentScene } from "../scene-manager.js";
3
3
  import { Part } from "../features/part.js";
4
4
  function part(name, callback) {
5
- const handle = {
6
- __fluidcad_part: true,
7
- name,
8
- _callback: callback,
9
- };
5
+ const scene = getCurrentScene();
6
+ if (!scene) {
7
+ throw new Error("part() must be called within a scene context");
8
+ }
10
9
  const sourceLocation = captureSourceLocation();
11
- const currentFile = getCurrentFile();
12
- const isDirectEdit = sourceLocation
13
- && currentFile
14
- && sourceLocation.filePath === currentFile;
15
- if (isDirectEdit) {
16
- const scene = getCurrentScene();
17
- if (scene) {
18
- const partObj = new Part(name);
19
- if (sourceLocation) {
20
- partObj.setSourceLocation(sourceLocation);
21
- }
22
- scene.startProgressiveContainer(partObj);
23
- callback(undefined);
24
- scene.endProgressiveContainer();
25
- }
10
+ const partObj = new Part(name);
11
+ if (sourceLocation) {
12
+ partObj.setSourceLocation(sourceLocation);
26
13
  }
27
- return handle;
14
+ scene.startProgressiveContainer(partObj);
15
+ callback();
16
+ scene.endProgressiveContainer();
17
+ return partObj;
28
18
  }
29
19
  export default part;
@@ -18,6 +18,7 @@ export declare class Sketch extends SceneObject implements Extrudable {
18
18
  build(context?: BuildSceneObjectContext): void;
19
19
  getEdges(): Edge[];
20
20
  getEdgesWithOwner(): Map<Edge, GeometrySceneObject>;
21
+ getAllEdges(): Edge[];
21
22
  getGeometriesWithOwner(): Map<Edge, GeometrySceneObject>;
22
23
  getGeometries(): Edge[];
23
24
  getDependencies(): SceneObject[];
@@ -83,7 +83,7 @@ export class Sketch extends SceneObject {
83
83
  const source = this.getCloneSource();
84
84
  const transform = context?.getTransform();
85
85
  if (source instanceof Sketch && transform) {
86
- const originalEdges = source.getEdges();
86
+ const originalEdges = source.getAllEdges();
87
87
  const transformedEdges = originalEdges.map(edge => ShapeOps.transform(edge, transform));
88
88
  this.setState('cloned-edges', transformedEdges);
89
89
  }
@@ -98,7 +98,24 @@ export class Sketch extends SceneObject {
98
98
  getEdgesWithOwner() {
99
99
  const children = this.getChildren();
100
100
  const result = new Map();
101
- // get edges and filter out the ones that were removed by non-siblings
101
+ for (const child of children) {
102
+ const shapes = child.getShapes();
103
+ for (const shape of shapes) {
104
+ if (shape instanceof Edge) {
105
+ result.set(shape, child);
106
+ }
107
+ else if (shape instanceof Wire) {
108
+ for (const edge of shape.getEdges()) {
109
+ result.set(edge, child);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ return result;
115
+ }
116
+ getAllEdges() {
117
+ const children = this.getChildren();
118
+ const result = [];
102
119
  for (const child of children) {
103
120
  const shapes = child.getAddedShapes();
104
121
  const removedShapes = child.getRemovedShapes();
@@ -111,11 +128,11 @@ export class Sketch extends SceneObject {
111
128
  continue;
112
129
  }
113
130
  if (shape instanceof Edge) {
114
- result.set(shape, child);
131
+ result.push(shape);
115
132
  }
116
133
  else if (shape instanceof Wire) {
117
134
  for (const edge of shape.getEdges()) {
118
- result.set(edge, child);
135
+ result.push(edge);
119
136
  }
120
137
  }
121
138
  }
@@ -34,6 +34,7 @@ export class CopyCircular extends SceneObject {
34
34
  for (const obj of objects) {
35
35
  for (const shape of obj.getShapes()) {
36
36
  const transformed = ShapeOps.transform(shape, matrix);
37
+ transformed.setMeshSource(shape, matrix);
37
38
  this.addShape(transformed);
38
39
  }
39
40
  }
@@ -41,6 +41,7 @@ export class CopyCircular2D extends GeometrySceneObject {
41
41
  for (const obj of objects) {
42
42
  for (const shape of obj.getShapes()) {
43
43
  const transformed = ShapeOps.transform(shape, matrix);
44
+ transformed.setMeshSource(shape, matrix);
44
45
  this.addShape(transformed);
45
46
  }
46
47
  }
@@ -34,6 +34,7 @@ export class CopyLinear extends SceneObject {
34
34
  const axisCount = counts[a];
35
35
  return axisCount > 1 ? len / (axisCount - 1) : 0;
36
36
  });
37
+ const centerIndices = this.axes.map((_, a) => centered ? Math.floor(counts[a] / 2) : 0);
37
38
  // Build grid positions as cartesian product of per-axis indices (0..counts[a]-1)
38
39
  let positions = [[]];
39
40
  for (let a = 0; a < this.axes.length; a++) {
@@ -46,23 +47,21 @@ export class CopyLinear extends SceneObject {
46
47
  positions = next;
47
48
  }
48
49
  for (const pos of positions) {
49
- if (pos.every(idx => idx === 0))
50
+ if (pos.every((idx, a) => idx === centerIndices[a]))
50
51
  continue;
51
52
  if (skip?.some(coord => coord.every((v, a) => v === pos[a]))) {
52
53
  continue;
53
54
  }
54
55
  let matrix = Matrix4.identity();
55
56
  for (let a = 0; a < this.axes.length; a++) {
56
- const axisCount = counts[a];
57
- const axisOffset = axisOffsets[a];
58
- const startOffset = centered ? -(axisCount * axisOffset) / 2 : 0;
59
- const distance = startOffset + axisOffset * pos[a];
57
+ const distance = (pos[a] - centerIndices[a]) * axisOffsets[a];
60
58
  const translation = this.axes[a].direction.multiply(distance);
61
59
  matrix = matrix.multiply(Matrix4.fromTranslationVector(translation));
62
60
  }
63
61
  for (const obj of objects) {
64
62
  for (const shape of obj.getShapes()) {
65
63
  const transformed = ShapeOps.transform(shape, matrix);
64
+ transformed.setMeshSource(shape, matrix);
66
65
  this.addShape(transformed);
67
66
  }
68
67
  }
@@ -38,6 +38,7 @@ export class CopyLinear2D extends GeometrySceneObject {
38
38
  const axisCount = counts[a];
39
39
  return axisCount > 1 ? len / (axisCount - 1) : 0;
40
40
  });
41
+ const centerIndices = this.axes.map((_, a) => centered ? Math.floor(counts[a] / 2) : 0);
41
42
  // Build grid positions as cartesian product of per-axis indices (0..counts[a]-1)
42
43
  let positions = [[]];
43
44
  for (let a = 0; a < this.axes.length; a++) {
@@ -50,23 +51,21 @@ export class CopyLinear2D extends GeometrySceneObject {
50
51
  positions = next;
51
52
  }
52
53
  for (const pos of positions) {
53
- if (pos.every(idx => idx === 0))
54
+ if (pos.every((idx, a) => idx === centerIndices[a]))
54
55
  continue;
55
56
  if (skip?.some(coord => coord.every((v, a) => v === pos[a]))) {
56
57
  continue;
57
58
  }
58
59
  let matrix = Matrix4.identity();
59
60
  for (let a = 0; a < this.axes.length; a++) {
60
- const axisCount = counts[a];
61
- const axisOffset = axisOffsets[a];
62
- const startOffset = centered ? -(axisCount * axisOffset) / 2 : 0;
63
- const distance = startOffset + axisOffset * pos[a];
61
+ const distance = (pos[a] - centerIndices[a]) * axisOffsets[a];
64
62
  const translation = this.axes[a].direction.multiply(distance);
65
63
  matrix = matrix.multiply(Matrix4.fromTranslationVector(translation));
66
64
  }
67
65
  for (const obj of objects) {
68
66
  for (const shape of obj.getShapes()) {
69
67
  const transformed = ShapeOps.transform(shape, matrix);
68
+ transformed.setMeshSource(shape, matrix);
70
69
  this.addShape(transformed);
71
70
  }
72
71
  }
@@ -0,0 +1,15 @@
1
+ import { BuildSceneObjectContext, SceneObject } from "../common/scene-object.js";
2
+ import { IDraft } from "../core/interfaces.js";
3
+ export declare class Draft extends SceneObject implements IDraft {
4
+ private angle;
5
+ private _selections;
6
+ constructor(angle: number, selections: SceneObject[]);
7
+ build(context: BuildSceneObjectContext): void;
8
+ compareTo(other: SceneObject): boolean;
9
+ getDependencies(): SceneObject[];
10
+ createCopy(remap: Map<SceneObject, SceneObject>): SceneObject;
11
+ getType(): string;
12
+ serialize(): {
13
+ angle: number;
14
+ };
15
+ }
@@ -0,0 +1,88 @@
1
+ import { SceneObject } from "../common/scene-object.js";
2
+ import { DraftOps } from "../oc/draft-ops.js";
3
+ export class Draft extends SceneObject {
4
+ angle;
5
+ _selections = [];
6
+ constructor(angle, selections) {
7
+ super();
8
+ this.angle = angle;
9
+ this._selections = selections;
10
+ }
11
+ build(context) {
12
+ const shapeObjMap = new Map();
13
+ for (const obj of context.getSceneObjects()) {
14
+ if (obj.id === this.parentId) {
15
+ continue;
16
+ }
17
+ const shapes = obj.getShapes({ excludeMeta: false }, 'solid');
18
+ for (const shape of shapes) {
19
+ shapeObjMap.set(shape, obj);
20
+ }
21
+ }
22
+ if (!shapeObjMap.size) {
23
+ return;
24
+ }
25
+ const allFaceShapes = [];
26
+ for (const sel of this._selections) {
27
+ allFaceShapes.push(...sel.getShapes());
28
+ }
29
+ const selectionFaces = allFaceShapes;
30
+ const selectionFaceRaws = selectionFaces.map(f => f.getShape());
31
+ const newShapes = [];
32
+ const allTargetShapes = Array.from(shapeObjMap.keys());
33
+ for (const shape of allTargetShapes) {
34
+ try {
35
+ const result = DraftOps.applyDraft(shape, selectionFaceRaws, this.angle);
36
+ if (!result) {
37
+ continue;
38
+ }
39
+ newShapes.push(result);
40
+ const originalObj = shapeObjMap.get(shape);
41
+ originalObj.removeShape(shape, this);
42
+ }
43
+ catch (e) {
44
+ newShapes.push(shape);
45
+ console.warn("Draft: Failed to apply draft angle.", e);
46
+ }
47
+ }
48
+ for (const sel of this._selections) {
49
+ sel.removeShapes(this);
50
+ }
51
+ this.addShapes(newShapes);
52
+ }
53
+ compareTo(other) {
54
+ if (!(other instanceof Draft)) {
55
+ return false;
56
+ }
57
+ if (!super.compareTo(other)) {
58
+ return false;
59
+ }
60
+ if (this.angle !== other.angle) {
61
+ return false;
62
+ }
63
+ if (this._selections.length !== other._selections.length) {
64
+ return false;
65
+ }
66
+ for (let i = 0; i < this._selections.length; i++) {
67
+ if (!this._selections[i].compareTo(other._selections[i])) {
68
+ return false;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+ getDependencies() {
74
+ return [...this._selections];
75
+ }
76
+ createCopy(remap) {
77
+ const selections = this._selections.map(sel => remap.get(sel) || sel);
78
+ return new Draft(this.angle, selections);
79
+ }
80
+ getType() {
81
+ return 'draft';
82
+ }
83
+ serialize() {
84
+ return {
85
+ angle: this.angle
86
+ };
87
+ }
88
+ }
@@ -0,0 +1,5 @@
1
+ import type { TopoDS_Shape } from "occjs-wrapper";
2
+ import { Shape } from "../common/shape.js";
3
+ export declare class DraftOps {
4
+ static applyDraft(solid: Shape, faceRaws: TopoDS_Shape[], angle: number): Shape;
5
+ }
@@ -0,0 +1,51 @@
1
+ import { getOC } from "./init.js";
2
+ import { Convert } from "./convert.js";
3
+ import { ShapeFactory } from "../common/shape-factory.js";
4
+ import { ShapeOps } from "./shape-ops.js";
5
+ import { Vector3d } from "../math/vector3d.js";
6
+ import { Plane } from "../math/plane.js";
7
+ import { Point } from "../math/point.js";
8
+ export class DraftOps {
9
+ static applyDraft(solid, faceRaws, angle) {
10
+ const oc = getOC();
11
+ const solidRaw = solid.getShape();
12
+ const bbox = ShapeOps.getBoundingBox(solid);
13
+ const neutralPlane = new Plane(new Point(0, 0, bbox.minZ), new Vector3d(1, 0, 0), new Vector3d(0, 0, 1));
14
+ const [dir, disposeDir] = Convert.toGpDir(neutralPlane.normal);
15
+ const [pln, disposePln] = Convert.toGpPln(neutralPlane);
16
+ try {
17
+ const draftMaker = new oc.BRepOffsetAPI_DraftAngle(solidRaw);
18
+ let addedCount = 0;
19
+ const explorer = new oc.TopExp_Explorer(solidRaw, oc.TopAbs_ShapeEnum.TopAbs_FACE, oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
20
+ while (explorer.More()) {
21
+ const currentShape = explorer.Current();
22
+ const isSelected = faceRaws.some(sel => sel.IsSame(currentShape));
23
+ if (isSelected) {
24
+ const face = oc.TopoDS.Face(currentShape);
25
+ draftMaker.Add(face, dir, -angle, pln, true);
26
+ addedCount++;
27
+ }
28
+ explorer.Next();
29
+ }
30
+ explorer.delete();
31
+ if (addedCount === 0) {
32
+ draftMaker.delete();
33
+ return null;
34
+ }
35
+ const progress = new oc.Message_ProgressRange();
36
+ draftMaker.Build(progress);
37
+ progress.delete();
38
+ if (!draftMaker.IsDone()) {
39
+ draftMaker.delete();
40
+ throw new Error("Failed to apply draft angle.");
41
+ }
42
+ const result = draftMaker.Shape();
43
+ draftMaker.delete();
44
+ return ShapeFactory.fromShape(result);
45
+ }
46
+ finally {
47
+ disposeDir();
48
+ disposePln();
49
+ }
50
+ }
51
+ }
@@ -10,6 +10,8 @@ export interface MeshData {
10
10
  export declare class Mesh {
11
11
  static triangulateFace(face: Face, vertexOffset?: number): MeshData | null;
12
12
  static discretizeEdge(edge: Shape): MeshData;
13
+ static premeshShape(shape: TopoDS_Shape): void;
13
14
  static triangulateFaceRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
15
+ static extractFaceTriangulationRaw(face: TopoDS_Face, vertexOffset?: number): MeshData | null;
14
16
  static discretizeEdgeRaw(edge: TopoDS_Shape): MeshData;
15
17
  }
@@ -7,12 +7,14 @@ export class Mesh {
7
7
  static discretizeEdge(edge) {
8
8
  return Mesh.discretizeEdgeRaw(edge.getShape());
9
9
  }
10
+ static premeshShape(shape) {
11
+ const oc = getOC();
12
+ const inc = new oc.BRepMesh_IncrementalMesh(shape, 0.3, false, 0.3, true);
13
+ inc.delete();
14
+ }
10
15
  // Raw methods (for oc-internal use)
11
16
  static triangulateFaceRaw(face, vertexOffset = 0) {
12
17
  const oc = getOC();
13
- const vertices = [];
14
- const normals = [];
15
- const indices = [];
16
18
  let inc;
17
19
  try {
18
20
  inc = new oc.BRepMesh_IncrementalMesh(face, 0.3, false, 0.3, true);
@@ -21,12 +23,19 @@ export class Mesh {
21
23
  console.error("Face mesh failed", e);
22
24
  return null;
23
25
  }
26
+ inc.delete();
27
+ return Mesh.extractFaceTriangulationRaw(face, vertexOffset);
28
+ }
29
+ static extractFaceTriangulationRaw(face, vertexOffset = 0) {
30
+ const oc = getOC();
31
+ const vertices = [];
32
+ const normals = [];
33
+ const indices = [];
24
34
  const aLocation = new oc.TopLoc_Location();
25
35
  const myT = oc.BRep_Tool.Triangulation(face, aLocation, 0);
26
36
  if (myT.IsNull()) {
27
37
  aLocation.delete();
28
- inc.delete();
29
- throw new Error("No triangulation for face");
38
+ return null;
30
39
  }
31
40
  const pc = new oc.Poly_Connect(myT);
32
41
  const triangulation = myT.get();
@@ -69,7 +78,6 @@ export class Mesh {
69
78
  triangles.delete();
70
79
  myT.delete();
71
80
  aLocation.delete();
72
- inc.delete();
73
81
  return { vertices, normals, indices, count: nbNodes };
74
82
  }
75
83
  static discretizeEdgeRaw(edge) {
@@ -0,0 +1,3 @@
1
+ import { SceneObjectMesh } from "./scene.js";
2
+ import { Matrix4 } from "../math/matrix4.js";
3
+ export declare function transformMeshes(meshes: SceneObjectMesh[], matrix: Matrix4): SceneObjectMesh[];
@@ -0,0 +1,22 @@
1
+ export function transformMeshes(meshes, matrix) {
2
+ const m = matrix.elements;
3
+ return meshes.map(mesh => {
4
+ const srcV = mesh.vertices;
5
+ const srcN = mesh.normals;
6
+ const newV = new Array(srcV.length);
7
+ const newN = new Array(srcN.length);
8
+ for (let i = 0; i < srcV.length; i += 3) {
9
+ const x = srcV[i], y = srcV[i + 1], z = srcV[i + 2];
10
+ newV[i] = m[0] * x + m[4] * y + m[8] * z + m[12];
11
+ newV[i + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];
12
+ newV[i + 2] = m[2] * x + m[6] * y + m[10] * z + m[14];
13
+ }
14
+ for (let i = 0; i < srcN.length; i += 3) {
15
+ const nx = srcN[i], ny = srcN[i + 1], nz = srcN[i + 2];
16
+ newN[i] = m[0] * nx + m[4] * ny + m[8] * nz;
17
+ newN[i + 1] = m[1] * nx + m[5] * ny + m[9] * nz;
18
+ newN[i + 2] = m[2] * nx + m[6] * ny + m[10] * nz;
19
+ }
20
+ return { ...mesh, vertices: newV, normals: newN };
21
+ });
22
+ }
@@ -1,7 +1,8 @@
1
- import { renderFace } from "./render-face.js";
2
1
  import { renderEdge } from "./render-edge.js";
3
2
  import { Explorer } from "../oc/explorer.js";
3
+ import { Mesh } from "../oc/mesh.js";
4
4
  export function renderSolid(shapeObj) {
5
+ Mesh.premeshShape(shapeObj.getShape());
5
6
  const facesMeshes = getFacesMesh(shapeObj);
6
7
  const edgesMesh = getEdgesMesh(shapeObj);
7
8
  return [...facesMeshes, ...edgesMesh];
@@ -25,7 +26,7 @@ function getFacesMesh(shapeObj) {
25
26
  for (let faceIdx = 0; faceIdx < faces.length; faceIdx++) {
26
27
  const face = faces[faceIdx];
27
28
  const color = shapeObj.getColor(face.getShape());
28
- const faceResult = renderFace(face, 0);
29
+ const faceResult = Mesh.extractFaceTriangulationRaw(face.getShape(), 0);
29
30
  if (faceResult) {
30
31
  if (!groups.has(color)) {
31
32
  groups.set(color, { vertices: [], normals: [], indices: [], faceMapping: [], vertexOffset: 0 });