dsh-cad 0.7.0 → 0.8.0

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.
@@ -37,7 +37,10 @@ function assertUsable(shape, what, hint) {
37
37
  }
38
38
  }
39
39
 
40
- /** bodyId → { shape, name } — the live document. */
40
+ /** bodyId → { shape, name, step? } — the live document. A body whose solid
41
+ * was produced by an occt.ts-only op (shell/draft) is HOSTED: `step` carries
42
+ * the STEP bytes as the source of truth and `shape` is null, because
43
+ * opencascade.js in this build cannot read geometry back. */
41
44
  const bodies = new Map()
42
45
  let nextBodyNumber = 1
43
46
 
@@ -98,8 +101,7 @@ async function applyOp(op) {
98
101
  return { bodyId, name: bodies.get(bodyId).name, mesh: meshOf(shape, bodies.get(bodyId).name) }
99
102
  }
100
103
  case 'chamfer': {
101
- const body = bodies.get(op.target)
102
- if (body === undefined) throw new Error(`unknown body: ${op.target}`)
104
+ const body = requireShape(op.target, 'chamfer')
103
105
  const shape = adapter.chamferAll(body.shape, op.distance)
104
106
  assertUsable(shape, 'chamfer', '距离可能超出相邻面')
105
107
  body.shape = shape
@@ -153,14 +155,31 @@ async function applyOp(op) {
153
155
  bodies.set(bodyId, { shape, name: op.name ?? bodyId })
154
156
  return { bodyId, name: bodies.get(bodyId).name, mesh: meshOf(shape, bodies.get(bodyId).name) }
155
157
  }
158
+ case 'shell': {
159
+ const body = bodies.get(op.target)
160
+ if (body === undefined) throw new Error(`unknown body: ${op.target}`)
161
+ const stepIn = body.step ?? adapter.exportFile(body.shape, 'step')
162
+ const bridge = await getOcctBridge()
163
+ if (bridge === null) throw new Error('shell requires the occt.ts kernel (>= 0.3.0) — reinstall dsh-cad so the dependency pulls it')
164
+ const out = bridge.hostedSolidOp('shell', stepIn, { thickness: op.thickness, openNormals: op.openNormals, faces: op.faces, name: body.name })
165
+ body.step = out.step
166
+ body.shape = null
167
+ return { bodyId: op.target, name: body.name, volume: out.volume, mesh: out.mesh }
168
+ }
169
+ case 'draft': {
170
+ const body = bodies.get(op.target)
171
+ if (body === undefined) throw new Error(`unknown body: ${op.target}`)
172
+ const stepIn = body.step ?? adapter.exportFile(body.shape, 'step')
173
+ const bridge = await getOcctBridge()
174
+ if (bridge === null) throw new Error('draft requires the occt.ts kernel (>= 0.3.0) — reinstall dsh-cad so the dependency pulls it')
175
+ const out = bridge.hostedSolidOp('draft', stepIn, { angle: op.angle, direction: op.direction, faces: op.faces, name: body.name })
176
+ body.step = out.step
177
+ body.shape = null
178
+ return { bodyId: op.target, name: body.name, volume: out.volume, mesh: out.mesh }
179
+ }
156
180
  case 'boolean': {
157
- const target = bodies.get(op.target)
158
- if (target === undefined) throw new Error(`unknown body: ${op.target}`)
159
- const tools = op.tools.map((id) => {
160
- const tool = bodies.get(id)
161
- if (tool === undefined) throw new Error(`unknown body: ${id}`)
162
- return tool.shape
163
- })
181
+ const target = requireShape(op.target, 'boolean')
182
+ const tools = op.tools.map((id) => requireShape(id, 'boolean').shape)
164
183
  const shape = adapter.boolean(op.op, target.shape, tools)
165
184
  target.shape = shape
166
185
  // Consumed tool bodies are removed from the document (CAD convention).
@@ -171,15 +190,13 @@ async function applyOp(op) {
171
190
  return { bodyId: op.target, name: target.name, removed, mesh: meshOf(shape, target.name) }
172
191
  }
173
192
  case 'fillet': {
174
- const body = bodies.get(op.target)
175
- if (body === undefined) throw new Error(`unknown body: ${op.target}`)
193
+ const body = requireShape(op.target, 'fillet')
176
194
  const { shape, edges } = adapter.filletAll(body.shape, op.radius ?? 1)
177
195
  body.shape = shape
178
196
  return { bodyId: op.target, name: body.name, edges, mesh: meshOf(shape, body.name) }
179
197
  }
180
198
  case 'transform': {
181
- const body = bodies.get(op.target)
182
- if (body === undefined) throw new Error(`unknown body: ${op.target}`)
199
+ const body = requireShape(op.target, 'transform')
183
200
  const shape = adapter.transform(body.shape, {
184
201
  translate: op.translate,
185
202
  rotate: op.rotate,
@@ -191,6 +208,7 @@ async function applyOp(op) {
191
208
  case 'tessellate_all': {
192
209
  const meshes = []
193
210
  for (const [bodyId, body] of bodies) {
211
+ if (body.shape === null || body.shape === undefined) continue // hosted bodies re-mesh via their ops
194
212
  meshes.push({ bodyId, name: body.name, ...meshOf(body.shape, body.name) })
195
213
  }
196
214
  return { meshes }
@@ -198,6 +216,11 @@ async function applyOp(op) {
198
216
  case 'export': {
199
217
  const body = bodies.get(op.target)
200
218
  if (body === undefined) throw new Error(`unknown body: ${op.target}`)
219
+ if (body.step !== undefined) {
220
+ // Hosted body: the STEP bytes ARE the parametric exchange form.
221
+ if (op.format !== 'step') throw new Error('stl export of a hosted body requires tessellation — export .step, or re-export before shelling')
222
+ return { bytes: body.step.buffer.slice(body.step.byteOffset, body.step.byteOffset + body.step.byteLength) }
223
+ }
201
224
  const bytes = adapter.exportFile(body.shape, op.format)
202
225
  return { bytes: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) }
203
226
  }
@@ -258,7 +281,13 @@ async function applyOp(op) {
258
281
  case 'volume': {
259
282
  const body = bodies.get(op.target)
260
283
  if (body === undefined) throw new Error(`unknown body: ${op.target}`)
261
- return { volume: adapter.volume(body.shape) }
284
+ if (body.step !== undefined) {
285
+ // Hosted body: ask the occt.ts kernel directly.
286
+ const bridge = await getOcctBridge()
287
+ if (bridge === null) throw new Error('volume on a hosted body requires the occt.ts kernel')
288
+ return { volume: bridge.volumeOf(body.step) }
289
+ }
290
+ return { volume: Math.abs(adapter.volume(body.shape)) }
262
291
  }
263
292
  case 'delete': {
264
293
  if (!bodies.delete(op.target)) throw new Error(`unknown body: ${op.target}`)
@@ -306,6 +335,16 @@ async function getOcctBridge() {
306
335
  return occtBridge
307
336
  }
308
337
 
338
+ /** Reject ops that need an opencascade.js BRep on a hosted body. */
339
+ function requireShape(bodyId, what) {
340
+ const body = bodies.get(bodyId)
341
+ if (body === undefined) throw new Error(`unknown body: ${bodyId}`)
342
+ if (body.shape === undefined || body.shape === null) {
343
+ throw new Error(`${bodyId} is hosted on the occt.ts kernel (created by shell/draft); ${what} needs the opencascade.js BRep and cannot read hosted geometry back — model the feature before shelling, or reconstruct the body`)
344
+ }
345
+ return body
346
+ }
347
+
309
348
  async function drawingViews(op) {
310
349
  const body = bodies.get(op.target)
311
350
  if (body === undefined) throw new Error(`unknown body: ${op.target}`)
@@ -143,6 +143,149 @@ async function hiddenLineViews(mod, stepBytes, views) {
143
143
  }
144
144
  }
145
145
 
146
+
147
+ // ── occt.ts-hosted solid operations (shell / draft / boolean / transform) ────
148
+ //
149
+ // opencascade.js cannot read geometry BACK (STEPControl_Reader and BRepTools
150
+ // readers are opaque no-method shells in that build), so a solid produced by
151
+ // an occt.ts-only op cannot return to the opencascade.js modeling session.
152
+ // Instead the result stays hosted as STEP bytes on the occt.ts side: every
153
+ // hosted op re-reads the STEP, operates, and writes it back, while meshes for
154
+ // display/volume come from the occt.ts tessellator. opencascade.js-exclusive
155
+ // ops (fillet/chamfer/loft/sweep/revolve) refuse hosted bodies with a clear
156
+ // error instead of silently operating on stale geometry.
157
+
158
+ /** Read STEP bytes into an occt.ts shape (caller deletes). */
159
+ function readShape(mod, stepBytes) {
160
+ const ptr = mod._malloc(stepBytes.length)
161
+ try {
162
+ mod.HEAPU8.set(stepBytes, ptr)
163
+ const shape = mod.readStep(ptr, stepBytes.length, 'mm')
164
+ if (shape.isNull()) throw new Error(`occt.ts readStep: ${mod.lastError()}`)
165
+ return shape
166
+ } finally {
167
+ mod._free(ptr)
168
+ }
169
+ }
170
+
171
+ /** Write an occt.ts shape to STEP bytes. */
172
+ function toStepBytes(mod, shape) {
173
+ const text = mod.writeStep(shape, 'mm')
174
+ return Buffer.from(text, 'utf8')
175
+ }
176
+
177
+ /** Tessellate an occt.ts shape into the worker mesh format. */
178
+ function tessellateMesh(mod, shape, name, linearDeflection = 0.3) {
179
+ const data = mod.tessellate(shape, linearDeflection, 20, false)
180
+ try {
181
+ const positions = new Float32Array(mod.HEAPU8.buffer, data.positionsPtr(), data.positionCount() * 3).slice()
182
+ const indices = new Uint32Array(mod.HEAPU8.buffer, data.indicesPtr(), data.indexCount()).slice()
183
+ const triangles = indices.length / 3
184
+ // Flat per-triangle normals (the mechanical-CAD look the viewer expects).
185
+ const normals = new Float32Array(positions.length)
186
+ for (let i = 0; i + 2 < indices.length; i += 3) {
187
+ const a = indices[i] * 3, b = indices[i + 1] * 3, c = indices[i + 2] * 3
188
+ const ux = positions[c] - positions[a], uy = positions[c + 1] - positions[a + 1], uz = positions[c + 2] - positions[a + 2]
189
+ const vx = positions[b] - positions[a], vy = positions[b + 1] - positions[a + 1], vz = positions[b + 2] - positions[a + 2]
190
+ let nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx
191
+ const len = Math.hypot(nx, ny, nz) || 1
192
+ nx /= len; ny /= len; nz /= len
193
+ for (const corner of [a, b, c]) {
194
+ normals[corner] = nx; normals[corner + 1] = ny; normals[corner + 2] = nz
195
+ }
196
+ }
197
+ return { name, positions, normals, indices, vertexCount: positions.length / 3, triangleCount: triangles }
198
+ } finally {
199
+ data.delete()
200
+ }
201
+ }
202
+
203
+ /** Faces (1-based indices) whose outward plane-normal matches a target list. */
204
+ function matchFaces(mod, shape, openNormals) {
205
+ if (!Array.isArray(openNormals) || openNormals.length === 0) return []
206
+ const desc = JSON.parse(mod.describe(shape))
207
+ const faces = Array.isArray(desc.faces) ? desc.faces : []
208
+ const picked = []
209
+ for (const target of openNormals) {
210
+ const tlen = Math.hypot(target[0], target[1], target[2])
211
+ if (tlen < 1e-12) continue
212
+ const tn = [target[0] / tlen, target[1] / tlen, target[2] / tlen]
213
+ let best = null
214
+ let bestScore = 0.9
215
+ for (const face of faces) {
216
+ if (face.surface !== 'plane' || !Array.isArray(face.normal)) continue
217
+ const dot = face.normal[0] * tn[0] + face.normal[1] * tn[1] + face.normal[2] * tn[2]
218
+ // |dot| ≈ 1 means parallel; the OUTER face along ±target has the larger
219
+ // origin·target projection (a box carries both ±Z faces).
220
+ if (Math.abs(dot) < 0.9) continue
221
+ const outward = Math.sign(dot) >= 0 ? 1 : -1
222
+ const proj = (face.origin[0] * tn[0] + face.origin[1] * tn[1] + face.origin[2] * tn[2]) * outward
223
+ const score = Math.abs(dot) + proj * 1e-3
224
+ if (score > bestScore) { bestScore = score; best = face.index }
225
+ }
226
+ if (best !== null) picked.push(best)
227
+ }
228
+ return picked
229
+ }
230
+
231
+ /** int32 face-index array into wasm memory; returns {ptr, count} or null. */
232
+ function faceListPtr(mod, faces) {
233
+ if (!Array.isArray(faces) || faces.length === 0) return { ptr: 0, count: 0 }
234
+ const ptr = mod._malloc(faces.length * 4)
235
+ for (let i = 0; i < faces.length; i++) {
236
+ mod.HEAPU32[ptr / 4 + i] = faces[i]
237
+ }
238
+ return { ptr, count: faces.length }
239
+ }
240
+
241
+ /**
242
+ * Run one occt.ts-hosted solid operation on STEP bytes:
243
+ * shell(stepBytes, thickness, openNormals) → { step, mesh, volume, faces }
244
+ * draft(stepBytes, angleDegrees, direction, faces?) → { step, mesh, volume }
245
+ * The STEP bytes of the RESULT are the hosted body's new source of truth.
246
+ */
247
+ function hostedSolidOp(mod, kind, stepBytes, params) {
248
+ const shape = readShape(mod, stepBytes)
249
+ try {
250
+ let result
251
+ if (kind === 'shell') {
252
+ const faces = Array.isArray(params.faces) && params.faces.length > 0
253
+ ? params.faces
254
+ : matchFaces(mod, shape, params.openNormals)
255
+ const list = faceListPtr(mod, faces)
256
+ try {
257
+ result = mod.shell(shape, Math.abs(params.thickness), list.ptr, list.count)
258
+ } finally {
259
+ if (list.ptr !== 0) mod._free(list.ptr)
260
+ }
261
+ if (result.isNull()) throw new Error(`shell failed: ${mod.lastError()}`)
262
+ } else if (kind === 'draft') {
263
+ const faces = Array.isArray(params.faces) && params.faces.length > 0 ? params.faces : []
264
+ const list = faceListPtr(mod, faces)
265
+ const d = params.direction ?? [0, 0, 1]
266
+ try {
267
+ result = mod.draft(shape, (params.angle * Math.PI) / 180, d[0], d[1], d[2], 0, 0, 1, list.ptr, list.count, faces.length === 0)
268
+ } finally {
269
+ if (list.ptr !== 0) mod._free(list.ptr)
270
+ }
271
+ if (result.isNull()) throw new Error(`draft failed: ${mod.lastError()}`)
272
+ } else {
273
+ throw new Error(`unknown hosted op: ${kind}`)
274
+ }
275
+ try {
276
+ return {
277
+ step: toStepBytes(mod, result),
278
+ mesh: tessellateMesh(mod, result, params.name ?? 'hosted'),
279
+ volume: Math.abs(result.volume()),
280
+ }
281
+ } finally {
282
+ result.delete()
283
+ }
284
+ } finally {
285
+ shape.delete()
286
+ }
287
+ }
288
+
146
289
  /**
147
290
  * Create the bridge, or null when the dist is absent/unloadable (the caller
148
291
  * falls back to the mesh HLR). The heavy wasm load is lazy and cached.
@@ -154,11 +297,25 @@ async function createOcctBridge(options = {}) {
154
297
  if (typeof mod.hiddenLines !== 'function' || typeof mod.readStep !== 'function') {
155
298
  throw new Error(`occt.ts dist at ${distDir} lacks hiddenLines/readStep`)
156
299
  }
300
+ for (const required of ['shell', 'draft', 'describe', 'writeStep', 'tessellate']) {
301
+ if (typeof mod[required] !== 'function') {
302
+ throw new Error(`occt.ts dist at ${distDir} lacks ${required} (need occt.ts >= 0.3.0)`)
303
+ }
304
+ }
157
305
  return {
158
306
  kernel: 'occt.ts',
159
307
  distDir,
160
308
  occtVersion: mod.occtVersion(),
161
309
  hiddenLineViews: (stepBytes, views) => hiddenLineViews(mod, stepBytes, views),
310
+ hostedSolidOp: (kind, stepBytes, params) => hostedSolidOp(mod, kind, stepBytes, params),
311
+ volumeOf: (stepBytes) => {
312
+ const shape = readShape(mod, stepBytes)
313
+ try {
314
+ return Math.abs(shape.volume())
315
+ } finally {
316
+ shape.delete()
317
+ }
318
+ },
162
319
  }
163
320
  }
164
321
 
@@ -597,6 +597,70 @@ export function createModelTools(deps) {
597
597
  presentCall: (args) => ({ card: 'generic', title: `CAD chamfer ${String(args.target)}`, kind: 'other' }),
598
598
  presentResult: () => ({ card: 'generic', title: 'CAD chamfer' }),
599
599
  });
600
+ const cadShell = defineTool({
601
+ name: 'cad_shell',
602
+ description: 'Shell (抽壳): hollow a solid into a wall of `thickness` mm (the wall grows inward, the outer skin is preserved). `open` lists outward NORMALS [x,y,z] whose faces become openings (e.g. [[0,0,1]] opens the top of a box; empty = sealed hollow). ' +
603
+ 'Runs on the occt.ts kernel (>= 0.3.0): the result becomes a HOSTED body — volume/export(.step)/display keep working, but opencascade.js-only edits (fillet/chamfer/loft/sweep/revolve/boolean) on it are refused, so do those before shelling.',
604
+ parameters: {
605
+ target: bodyTarget,
606
+ thickness: { type: 'number', required: true, description: 'Wall thickness (mm, positive).' },
607
+ open: { type: 'array', items: { type: 'array', items: { type: 'number' } }, description: 'Outward normals of the faces to open, e.g. [[0,0,1]].' },
608
+ },
609
+ output: {
610
+ schema: {
611
+ type: 'object',
612
+ additionalProperties: false,
613
+ properties: {
614
+ bodyId: { type: 'string', required: true },
615
+ ...requiredCounts,
616
+ ...commonOptional,
617
+ },
618
+ },
619
+ render: (_args, value) => [{ type: 'text', text: renderModel(value) }],
620
+ presentationMeta: (_args, value) => metaOf(value),
621
+ },
622
+ isConcurrencySafe: () => false,
623
+ async execute(args, exec) {
624
+ await resolveDoc(exec);
625
+ const op = { kind: 'shell', target: args.target, thickness: args.thickness, ...(Array.isArray(args.open) ? { openNormals: args.open } : {}) };
626
+ const result = await runModelOp(op);
627
+ return syncScene(op, result);
628
+ },
629
+ presentCall: (args) => ({ card: 'generic', title: `CAD shell ${String(args.target)}`, kind: 'other' }),
630
+ presentResult: () => ({ card: 'generic', title: 'CAD shell' }),
631
+ });
632
+ const cadDraft = defineTool({
633
+ name: 'cad_draft',
634
+ description: 'Draft (拔模): tilt the walls of a solid by `angle` degrees for mold release — faces pivot about a neutral plane, dimensions there unchanged. `direction` is the pull direction (default +Z); by default the faces parallel to it (the walls) are drafted. ' +
635
+ 'Runs on the occt.ts kernel (>= 0.3.0): the result becomes a HOSTED body (same caveats as cad_shell).',
636
+ parameters: {
637
+ target: bodyTarget,
638
+ angle: { type: 'number', required: true, description: 'Draft angle in degrees (positive opens the walls toward the pull direction).' },
639
+ direction: { type: 'array', items: { type: 'number' }, description: 'Pull direction [x,y,z] (default +Z).' },
640
+ },
641
+ output: {
642
+ schema: {
643
+ type: 'object',
644
+ additionalProperties: false,
645
+ properties: {
646
+ bodyId: { type: 'string', required: true },
647
+ ...requiredCounts,
648
+ ...commonOptional,
649
+ },
650
+ },
651
+ render: (_args, value) => [{ type: 'text', text: renderModel(value) }],
652
+ presentationMeta: (_args, value) => metaOf(value),
653
+ },
654
+ isConcurrencySafe: () => false,
655
+ async execute(args, exec) {
656
+ await resolveDoc(exec);
657
+ const op = { kind: 'draft', target: args.target, angle: args.angle, ...(Array.isArray(args.direction) ? { direction: args.direction } : {}) };
658
+ const result = await runModelOp(op);
659
+ return syncScene(op, result);
660
+ },
661
+ presentCall: (args) => ({ card: 'generic', title: `CAD draft ${String(args.target)} ${String(args.angle ?? '')}°`, kind: 'other' }),
662
+ presentResult: () => ({ card: 'generic', title: 'CAD draft' }),
663
+ });
600
664
  const cadPattern = defineTool({
601
665
  name: 'cad_pattern',
602
666
  description: 'Pattern (阵列): replicate a body into `count` total placements. Linear: `delta` [dx,dy,dz] spacing per copy. Circular: rotate copies around a principal axis (+X/+Y/+Z) through `at` over `angle` degrees (default 360). Creates new bodies (target·1 … target·(count−1)); fuse afterwards for a single body.',
@@ -1303,6 +1367,8 @@ export function createModelTools(deps) {
1303
1367
  cadExtrude,
1304
1368
  cadRevolve,
1305
1369
  cadChamfer,
1370
+ cadShell,
1371
+ cadDraft,
1306
1372
  cadPattern,
1307
1373
  cadLoft,
1308
1374
  cadSweep,
@@ -1329,6 +1395,8 @@ export const MODEL_TOOL_NAMES = [
1329
1395
  'cad_extrude_profile',
1330
1396
  'cad_revolve',
1331
1397
  'cad_chamfer',
1398
+ 'cad_shell',
1399
+ 'cad_draft',
1332
1400
  'cad_pattern',
1333
1401
  'cad_loft',
1334
1402
  'cad_sweep',
@@ -81,6 +81,18 @@ export type ModelOp = {
81
81
  kind: 'chamfer';
82
82
  target: string;
83
83
  distance: number;
84
+ } | {
85
+ kind: 'shell';
86
+ target: string;
87
+ thickness: number;
88
+ openNormals?: Array<[number, number, number]>;
89
+ faces?: number[];
90
+ } | {
91
+ kind: 'draft';
92
+ target: string;
93
+ angle: number;
94
+ direction?: [number, number, number];
95
+ faces?: number[];
84
96
  } | {
85
97
  kind: 'pattern';
86
98
  target: string;
@@ -13,4 +13,4 @@ export interface ModelToolDeps {
13
13
  }
14
14
  /** Build the whole tool family over the document registry + worker + stores. */
15
15
  export declare function createModelTools(deps: ModelToolDeps): ToolDefinition[];
16
- export declare const MODEL_TOOL_NAMES: readonly ["cad_create_prim", "cad_extrude_profile", "cad_revolve", "cad_chamfer", "cad_pattern", "cad_loft", "cad_sweep", "cad_boolean", "cad_fillet", "cad_transform", "cad_export", "cad_delete", "cad_volume", "cad_drawing", "cad_assembly_insert", "cad_assembly_move", "cad_assembly_remove", "cad_docs", "cad_doc_new", "cad_doc_open", "cad_doc_rename", "cad_doc_delete", "cad_constraint", "cad_solve", "cad_motion"];
16
+ export declare const MODEL_TOOL_NAMES: readonly ["cad_create_prim", "cad_extrude_profile", "cad_revolve", "cad_chamfer", "cad_shell", "cad_draft", "cad_pattern", "cad_loft", "cad_sweep", "cad_boolean", "cad_fillet", "cad_transform", "cad_export", "cad_delete", "cad_volume", "cad_drawing", "cad_assembly_insert", "cad_assembly_move", "cad_assembly_remove", "cad_docs", "cad_doc_new", "cad_doc_open", "cad_doc_rename", "cad_doc_delete", "cad_constraint", "cad_solve", "cad_motion"];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-cad",
3
3
  "description": "CAD visualization plugin for DeepSeek Harness: cad_view / cad_info tools with an embedded 3D (STL/OBJ/STEP/IGES) and 2D (DXF/SVG) viewer card in the Web UI",
4
- "version": "0.7.0",
4
+ "version": "0.8.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "LAU-MARS",
@@ -70,7 +70,7 @@
70
70
  "ansatz-wasm": "^0.2.0",
71
71
  "dxf-parser": "^1.1.2",
72
72
  "occt-import-js": "^0.0.23",
73
- "occt.ts": "^0.1.0",
73
+ "occt.ts": "^0.3.0",
74
74
  "opencascade.js": "^1.1.1",
75
75
  "three": "^0.185.1"
76
76
  },