dsh-cad 0.8.0 → 0.9.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.
- package/README.md +11 -8
- package/README.zh-CN.md +9 -7
- package/lib/client.js +21 -21
- package/lib/client.js.map +3 -3
- package/lib/modeling/modeling-worker.cjs +91 -54
- package/lib/modeling/occt-adapter.cjs +1 -1
- package/lib/modeling/occt-bridge.cjs +70 -4
- package/lib/tools/cad-model.js +3 -2
- package/package.json +2 -2
|
@@ -13,7 +13,8 @@ const path = require('node:path')
|
|
|
13
13
|
const fs = require('node:fs')
|
|
14
14
|
const { createRequire } = require('node:module')
|
|
15
15
|
const { createAdapter } = require('./occt-adapter.cjs')
|
|
16
|
-
const {
|
|
16
|
+
const { createOcctTsAdapter } = require('./occtts-adapter.cjs')
|
|
17
|
+
const { createOcctBridge, loadSharedOcctModule } = require('./occt-bridge.cjs')
|
|
17
18
|
|
|
18
19
|
const require3 = createRequire(__filename)
|
|
19
20
|
// The ES6 emscripten build reads a global __dirname when its factory runs.
|
|
@@ -23,8 +24,12 @@ const loaderPath = require3.resolve('opencascade.js/dist/opencascade.wasm.js')
|
|
|
23
24
|
const loaderModule = require3(loaderPath)
|
|
24
25
|
const wasmBinary = fs.readFileSync(path.join(path.dirname(loaderPath), 'opencascade.wasm.wasm'))
|
|
25
26
|
|
|
27
|
+
// Kernel selection: occt.ts is the PRIMARY backend (single-kernel modeling,
|
|
28
|
+
// true B-splines, shell/draft native, direct HLR); opencascade.js stays as
|
|
29
|
+
// the fallback when occt.ts cannot load. `adapter` carries whichever.
|
|
26
30
|
let adapter = null
|
|
27
31
|
let occt = null
|
|
32
|
+
let kernelName = 'occt.ts'
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Reject a degenerate result BEFORE registering it: the kernel cannot
|
|
@@ -156,26 +161,21 @@ async function applyOp(op) {
|
|
|
156
161
|
return { bodyId, name: bodies.get(bodyId).name, mesh: meshOf(shape, bodies.get(bodyId).name) }
|
|
157
162
|
}
|
|
158
163
|
case 'shell': {
|
|
159
|
-
const body =
|
|
160
|
-
if (
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
body.
|
|
166
|
-
body.shape = null
|
|
167
|
-
return { bodyId: op.target, name: body.name, volume: out.volume, mesh: out.mesh }
|
|
164
|
+
const body = requireShape(op.target, 'shell')
|
|
165
|
+
if (typeof adapter.shell !== 'function') throw new Error('shell requires the occt.ts kernel backend — it is unavailable, reinstall dsh-cad')
|
|
166
|
+
const faces = Array.isArray(op.faces) && op.faces.length > 0 ? op.faces : matchFacesByNormal(body.shape, op.openNormals)
|
|
167
|
+
const shape = adapter.shell(body.shape, op.thickness, faces)
|
|
168
|
+
assertUsable(shape, 'shell', '检查壁厚是否超出几何允许')
|
|
169
|
+
body.shape = shape
|
|
170
|
+
return { bodyId: op.target, name: body.name, volume: Math.abs(adapter.volume(shape)), mesh: meshOf(shape, body.name) }
|
|
168
171
|
}
|
|
169
172
|
case 'draft': {
|
|
170
|
-
const body =
|
|
171
|
-
if (
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
body.step = out.step
|
|
177
|
-
body.shape = null
|
|
178
|
-
return { bodyId: op.target, name: body.name, volume: out.volume, mesh: out.mesh }
|
|
173
|
+
const body = requireShape(op.target, 'draft')
|
|
174
|
+
if (typeof adapter.draft !== 'function') throw new Error('draft requires the occt.ts kernel backend — it is unavailable, reinstall dsh-cad')
|
|
175
|
+
const shape = adapter.draft(body.shape, op.angle, op.direction)
|
|
176
|
+
assertUsable(shape, 'draft', '检查拔模角度是否超出几何允许')
|
|
177
|
+
body.shape = shape
|
|
178
|
+
return { bodyId: op.target, name: body.name, volume: Math.abs(adapter.volume(shape)), mesh: meshOf(shape, body.name) }
|
|
179
179
|
}
|
|
180
180
|
case 'boolean': {
|
|
181
181
|
const target = requireShape(op.target, 'boolean')
|
|
@@ -191,9 +191,10 @@ async function applyOp(op) {
|
|
|
191
191
|
}
|
|
192
192
|
case 'fillet': {
|
|
193
193
|
const body = requireShape(op.target, 'fillet')
|
|
194
|
-
const
|
|
194
|
+
const shape = adapter.filletAll(body.shape, op.radius ?? 1)
|
|
195
|
+
assertUsable(shape, 'fillet', '半径可能超出相邻面')
|
|
195
196
|
body.shape = shape
|
|
196
|
-
return { bodyId: op.target, name: body.name,
|
|
197
|
+
return { bodyId: op.target, name: body.name, mesh: meshOf(shape, body.name) }
|
|
197
198
|
}
|
|
198
199
|
case 'transform': {
|
|
199
200
|
const body = requireShape(op.target, 'transform')
|
|
@@ -214,13 +215,7 @@ async function applyOp(op) {
|
|
|
214
215
|
return { meshes }
|
|
215
216
|
}
|
|
216
217
|
case 'export': {
|
|
217
|
-
const body =
|
|
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
|
-
}
|
|
218
|
+
const body = requireShape(op.target, 'export')
|
|
224
219
|
const bytes = adapter.exportFile(body.shape, op.format)
|
|
225
220
|
return { bytes: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) }
|
|
226
221
|
}
|
|
@@ -261,17 +256,20 @@ async function applyOp(op) {
|
|
|
261
256
|
}
|
|
262
257
|
case 'export_assembly': {
|
|
263
258
|
if (instances.size === 0) throw new Error('the assembly is empty')
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
259
|
+
// occt.ts has no compound binding — fuse the placed instances into one
|
|
260
|
+
// solid (the STEP carries the union; instance separation stays in the
|
|
261
|
+
// assembly scene/document). The legacy backend kept separate solids via
|
|
262
|
+
// a TopoDS_Compound; both exports open identically in CAD tools.
|
|
263
|
+
let compound = null
|
|
267
264
|
let added = 0
|
|
268
265
|
for (const instance of instances.values()) {
|
|
269
266
|
const body = bodies.get(instance.bodyId)
|
|
270
267
|
if (body === undefined) continue // stale instance of a consumed body
|
|
271
|
-
|
|
268
|
+
const placed = adapter.transform(body.shape, {
|
|
272
269
|
translate: instance.translate,
|
|
273
270
|
rotate: instance.rotate,
|
|
274
|
-
})
|
|
271
|
+
})
|
|
272
|
+
compound = compound === null ? placed : adapter.boolean('fuse', compound, [placed])
|
|
275
273
|
added++
|
|
276
274
|
}
|
|
277
275
|
if (added === 0) throw new Error('no instance references a live body')
|
|
@@ -279,15 +277,12 @@ async function applyOp(op) {
|
|
|
279
277
|
return { bytes: bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength), instances: instancesList() }
|
|
280
278
|
}
|
|
281
279
|
case 'volume': {
|
|
282
|
-
const body =
|
|
283
|
-
|
|
284
|
-
if (
|
|
285
|
-
|
|
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) }
|
|
280
|
+
const body = requireShape(op.target, 'volume')
|
|
281
|
+
const result = { volume: Math.abs(adapter.volume(body.shape)) }
|
|
282
|
+
if (typeof adapter.centroid === 'function') {
|
|
283
|
+
try { result.centroid = adapter.centroid(body.shape) } catch { /* centroid optional */ }
|
|
289
284
|
}
|
|
290
|
-
return
|
|
285
|
+
return result
|
|
291
286
|
}
|
|
292
287
|
case 'delete': {
|
|
293
288
|
if (!bodies.delete(op.target)) throw new Error(`unknown body: ${op.target}`)
|
|
@@ -335,12 +330,39 @@ async function getOcctBridge() {
|
|
|
335
330
|
return occtBridge
|
|
336
331
|
}
|
|
337
332
|
|
|
338
|
-
/**
|
|
333
|
+
/** Faces (1-based) whose outward plane-normal matches the target normals. */
|
|
334
|
+
function matchFacesByNormal(shape, openNormals) {
|
|
335
|
+
if (!Array.isArray(openNormals) || openNormals.length === 0) return []
|
|
336
|
+
if (typeof adapter.describe !== 'function') throw new Error('face matching by normal requires the occt.ts kernel backend')
|
|
337
|
+
const desc = adapter.describe(shape)
|
|
338
|
+
const faces = Array.isArray(desc.faces) ? desc.faces : []
|
|
339
|
+
const picked = []
|
|
340
|
+
for (const target of openNormals) {
|
|
341
|
+
const tlen = Math.hypot(target[0], target[1], target[2])
|
|
342
|
+
if (tlen < 1e-12) continue
|
|
343
|
+
const tn = [target[0] / tlen, target[1] / tlen, target[2] / tlen]
|
|
344
|
+
let best = null
|
|
345
|
+
let bestScore = -Infinity
|
|
346
|
+
for (const face of faces) {
|
|
347
|
+
if (face.surface !== 'plane' || !Array.isArray(face.normal)) continue
|
|
348
|
+
const dot = face.normal[0] * tn[0] + face.normal[1] * tn[1] + face.normal[2] * tn[2]
|
|
349
|
+
if (Math.abs(dot) < 0.9) continue
|
|
350
|
+
const outward = dot >= 0 ? 1 : -1
|
|
351
|
+
const proj = (face.origin[0] * tn[0] + face.origin[1] * tn[1] + face.origin[2] * tn[2]) * outward
|
|
352
|
+
const score = Math.abs(dot) + proj * 1e-3
|
|
353
|
+
if (score > bestScore) { bestScore = score; best = face.index }
|
|
354
|
+
}
|
|
355
|
+
if (best !== null) picked.push(best)
|
|
356
|
+
}
|
|
357
|
+
return picked
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Resolve a body's live shape (single-kernel: always present). */
|
|
339
361
|
function requireShape(bodyId, what) {
|
|
340
362
|
const body = bodies.get(bodyId)
|
|
341
363
|
if (body === undefined) throw new Error(`unknown body: ${bodyId}`)
|
|
342
|
-
if (body.shape ===
|
|
343
|
-
throw new Error(`${bodyId}
|
|
364
|
+
if (body.shape === null || body.shape === undefined) {
|
|
365
|
+
throw new Error(`${bodyId} has no live shape (${what})`)
|
|
344
366
|
}
|
|
345
367
|
return body
|
|
346
368
|
}
|
|
@@ -358,21 +380,34 @@ async function drawingViews(op) {
|
|
|
358
380
|
return { views: projected.views }
|
|
359
381
|
}
|
|
360
382
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
383
|
+
async function boot() {
|
|
384
|
+
const mod = await loadSharedOcctModule()
|
|
385
|
+
if (mod !== null) {
|
|
386
|
+
adapter = createOcctTsAdapter(mod)
|
|
387
|
+
kernelName = 'occt.ts'
|
|
388
|
+
} else {
|
|
389
|
+
console.warn('[dsh-cad] occt.ts unavailable — falling back to the opencascade.js backend')
|
|
390
|
+
const initOpenCascade = loaderModule.default ?? loaderModule
|
|
391
|
+
occt = await initOpenCascade({ wasmBinary })
|
|
392
|
+
adapter = createAdapter(occt)
|
|
393
|
+
kernelName = 'opencascade.js'
|
|
394
|
+
}
|
|
395
|
+
parentPort.postMessage({ jobId: 0, ok: true, result: { ready: true, kernel: kernelName } })
|
|
396
|
+
parentPort.on('message', runMessage)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
async function runMessage(message) {
|
|
400
|
+
{
|
|
366
401
|
const transfers = []
|
|
367
402
|
try {
|
|
368
403
|
const result = await applyOp(message.op)
|
|
369
|
-
// Collect transferable mesh buffers.
|
|
370
404
|
const collect = (mesh) => {
|
|
371
405
|
if (mesh === undefined) return
|
|
372
406
|
transfers.push(mesh.positions.buffer, mesh.indices.buffer, mesh.normals.buffer)
|
|
373
407
|
}
|
|
374
408
|
if (result.mesh !== undefined) collect(result.mesh)
|
|
375
409
|
for (const mesh of result.meshes ?? []) collect(mesh)
|
|
410
|
+
if (result.created !== undefined) for (const entry of result.created) if (entry.mesh !== undefined) collect(entry.mesh)
|
|
376
411
|
if (result.bytes !== undefined) transfers.push(result.bytes)
|
|
377
412
|
parentPort.postMessage({ jobId: message.jobId, ok: true, result }, transfers)
|
|
378
413
|
} catch (error) {
|
|
@@ -382,8 +417,10 @@ initOpenCascade({ wasmBinary }).then((instance) => {
|
|
|
382
417
|
error: error instanceof Error ? error.message : String(error),
|
|
383
418
|
})
|
|
384
419
|
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
boot().catch((error) => {
|
|
424
|
+
parentPort.postMessage({ jobId: 0, ok: false, error: `modeling kernel init failed: ${error instanceof Error ? error.message : String(error)}` })
|
|
389
425
|
})
|
|
426
|
+
|
|
@@ -127,7 +127,7 @@ function createAdapter(occt) {
|
|
|
127
127
|
if (edges === 0) throw new Error('no edges to fillet')
|
|
128
128
|
algo.Build()
|
|
129
129
|
if (!algo.IsDone()) throw new Error('fillet failed (radius may exceed the adjacent faces)')
|
|
130
|
-
return
|
|
130
|
+
return algo.Shape()
|
|
131
131
|
}
|
|
132
132
|
|
|
133
133
|
function castEdge(shape) {
|
|
@@ -54,6 +54,24 @@ function resolveDistDir(explicit) {
|
|
|
54
54
|
return null
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/** Shared wasm-module singleton: the worker's modeling adapter, the HLR path
|
|
58
|
+
* and the hosted ops all reuse ONE occt.ts instance per process. */
|
|
59
|
+
let sharedModule = null
|
|
60
|
+
|
|
61
|
+
/** Load (once) and return the shared occt.ts raw module, or null when absent. */
|
|
62
|
+
async function loadSharedOcctModule() {
|
|
63
|
+
if (sharedModule !== null && sharedModule !== undefined) return sharedModule
|
|
64
|
+
const distDir = resolveDistDir()
|
|
65
|
+
if (distDir === null) return null
|
|
66
|
+
try {
|
|
67
|
+
sharedModule = await loadModule(distDir)
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.warn(`[dsh-cad] occt.ts kernel failed to load: ${error instanceof Error ? error.message : error}`)
|
|
70
|
+
sharedModule = null
|
|
71
|
+
}
|
|
72
|
+
return sharedModule
|
|
73
|
+
}
|
|
74
|
+
|
|
57
75
|
async function loadModule(distDir) {
|
|
58
76
|
const imported = await import(pathToFileURL(path.join(distDir, 'wasm', 'occtjs.js')).href)
|
|
59
77
|
const factory = imported.default
|
|
@@ -81,7 +99,7 @@ async function hiddenLineViews(mod, stepBytes, views) {
|
|
|
81
99
|
const ptr = mod._malloc(stepBytes.length)
|
|
82
100
|
try {
|
|
83
101
|
mod.HEAPU8.set(stepBytes, ptr)
|
|
84
|
-
const shape = mod.readStep(ptr, stepBytes.length)
|
|
102
|
+
const shape = mod.readStep(ptr, stepBytes.length, 'mm')
|
|
85
103
|
if (shape.isNull()) throw new Error(`occt.ts readStep: ${mod.lastError()}`)
|
|
86
104
|
try {
|
|
87
105
|
return {
|
|
@@ -286,14 +304,61 @@ function hostedSolidOp(mod, kind, stepBytes, params) {
|
|
|
286
304
|
}
|
|
287
305
|
}
|
|
288
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Hidden-line views of a shape that ALREADY lives in this occt.ts instance
|
|
309
|
+
* (the single-kernel path — no STEP hop). Same frame/remap contract as
|
|
310
|
+
* hiddenLineViews.
|
|
311
|
+
*/
|
|
312
|
+
function hiddenLineViewsOfShape(mod, shape, views) {
|
|
313
|
+
return {
|
|
314
|
+
views: views.map((view) => {
|
|
315
|
+
const w = norm(view.dir)
|
|
316
|
+
const u = norm(view.xDir)
|
|
317
|
+
const v = norm(cross(w, u))
|
|
318
|
+
const dv = dot(w, v)
|
|
319
|
+
let x = [v[0] - w[0] * dv, v[1] - w[1] * dv, v[2] - w[2] * dv]
|
|
320
|
+
if (Math.hypot(x[0], x[1], x[2]) < 1e-9) x = Math.abs(w[2]) < 0.9 ? [0, 0, 1] : [1, 0, 0]
|
|
321
|
+
x = norm(x)
|
|
322
|
+
const y = norm(cross(w, x))
|
|
323
|
+
const raw = mod.hiddenLines(shape, w[0], w[1], w[2], x[0], x[1], x[2], 0.1)
|
|
324
|
+
const read = (ptr2, count) => new Float32Array(mod.HEAPU8.buffer, ptr2, count * 3).slice()
|
|
325
|
+
let visible
|
|
326
|
+
let hidden
|
|
327
|
+
try {
|
|
328
|
+
visible = read(raw.visiblePtr(), raw.visiblePointCount())
|
|
329
|
+
hidden = read(raw.hiddenPtr(), raw.hiddenPointCount())
|
|
330
|
+
} finally {
|
|
331
|
+
raw.delete()
|
|
332
|
+
}
|
|
333
|
+
const a = dot(x, u)
|
|
334
|
+
const b = dot(y, u)
|
|
335
|
+
const c = dot(x, v)
|
|
336
|
+
const d = dot(y, v)
|
|
337
|
+
const remap = (arr) => {
|
|
338
|
+
const xyz = []
|
|
339
|
+
for (let i = 0; i + 2 < arr.length; i += 3) xyz.push(arr[i] * a + arr[i + 1] * b, arr[i] * c + arr[i + 1] * d, 0)
|
|
340
|
+
return xyz
|
|
341
|
+
}
|
|
342
|
+
const toPolylines = (xyz) =>
|
|
343
|
+
chainSegments(
|
|
344
|
+
Array.from({ length: xyz.length / 6 }, (_, si) => [xyz[si * 6], xyz[si * 6 + 1], xyz[si * 6 + 3], xyz[si * 6 + 4]]),
|
|
345
|
+
).map((chain) => simplify(chain, 0.02)).filter((chain) => chain.length >= 4)
|
|
346
|
+
const visiblePolylines = toPolylines(remap(visible))
|
|
347
|
+
const visibleKeys = new Set(visiblePolylines.map(polylineKey))
|
|
348
|
+
const hiddenPolylines = toPolylines(remap(hidden)).filter((chain) => !visibleKeys.has(polylineKey(chain)))
|
|
349
|
+
return { name: view.name, visible: visiblePolylines, hidden: hiddenPolylines }
|
|
350
|
+
}),
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
289
354
|
/**
|
|
290
355
|
* Create the bridge, or null when the dist is absent/unloadable (the caller
|
|
291
356
|
* falls back to the mesh HLR). The heavy wasm load is lazy and cached.
|
|
292
357
|
*/
|
|
293
358
|
async function createOcctBridge(options = {}) {
|
|
359
|
+
const mod = options.mod ?? await loadSharedOcctModule()
|
|
360
|
+
if (mod === null) return null
|
|
294
361
|
const distDir = resolveDistDir(options.distDir)
|
|
295
|
-
if (distDir === null) return null
|
|
296
|
-
const mod = await loadModule(distDir)
|
|
297
362
|
if (typeof mod.hiddenLines !== 'function' || typeof mod.readStep !== 'function') {
|
|
298
363
|
throw new Error(`occt.ts dist at ${distDir} lacks hiddenLines/readStep`)
|
|
299
364
|
}
|
|
@@ -307,6 +372,7 @@ async function createOcctBridge(options = {}) {
|
|
|
307
372
|
distDir,
|
|
308
373
|
occtVersion: mod.occtVersion(),
|
|
309
374
|
hiddenLineViews: (stepBytes, views) => hiddenLineViews(mod, stepBytes, views),
|
|
375
|
+
hiddenLineViewsOf: (shape, views) => hiddenLineViewsOfShape(mod, shape, views),
|
|
310
376
|
hostedSolidOp: (kind, stepBytes, params) => hostedSolidOp(mod, kind, stepBytes, params),
|
|
311
377
|
volumeOf: (stepBytes) => {
|
|
312
378
|
const shape = readShape(mod, stepBytes)
|
|
@@ -404,4 +470,4 @@ function simplify(points, epsilon) {
|
|
|
404
470
|
return out
|
|
405
471
|
}
|
|
406
472
|
|
|
407
|
-
module.exports = { createOcctBridge, resolveDistDir }
|
|
473
|
+
module.exports = { createOcctBridge, resolveDistDir, loadSharedOcctModule }
|
package/lib/tools/cad-model.js
CHANGED
|
@@ -372,7 +372,8 @@ export function createModelTools(deps) {
|
|
|
372
372
|
const cadCreatePrim = defineTool({
|
|
373
373
|
name: 'cad_create_prim',
|
|
374
374
|
description: 'Create a parametric primitive in the shared modeling document (mm, Z-up). Kinds: box (dx,dy,dz), cylinder (radius,height), sphere (radius), cone (radius1,radius2,height), torus (majorRadius,minorRadius). ' +
|
|
375
|
-
'`at`
|
|
375
|
+
'`at` is the anchor point with per-kind semantics: box → min corner (the solid spans at .. at+[dx,dy,dz] along +X/+Y/+Z); cylinder/cone → base-circle center (the solid extends `height` from it along `axis`); sphere/torus → geometric center. ' +
|
|
376
|
+
'`axis` orients cylinder/cone/torus (default +Z). Returns the bodyId other CAD tools reference. The viewer card updates after every call.',
|
|
376
377
|
parameters: {
|
|
377
378
|
kind: { type: 'string', required: true, enum: ['box', 'cylinder', 'sphere', 'cone', 'torus'], description: 'Primitive kind.' },
|
|
378
379
|
dx: numberParam('box: size X (mm).'),
|
|
@@ -384,7 +385,7 @@ export function createModelTools(deps) {
|
|
|
384
385
|
height: numberParam('cylinder/cone: height (mm).'),
|
|
385
386
|
majorRadius: numberParam('torus: center radius (mm).'),
|
|
386
387
|
minorRadius: numberParam('torus: tube radius (mm).'),
|
|
387
|
-
at: pointParam('
|
|
388
|
+
at: pointParam('anchor [x,y,z] (mm): box = min corner, cylinder/cone = base-circle center, sphere/torus = geometric center.'),
|
|
388
389
|
axis: pointParam('axis direction [x,y,z] for cylinder/cone/torus.'),
|
|
389
390
|
name: { type: 'string', description: 'Optional display name.' },
|
|
390
391
|
},
|
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.
|
|
4
|
+
"version": "0.9.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.
|
|
73
|
+
"occt.ts": "^0.5.0",
|
|
74
74
|
"opencascade.js": "^1.1.1",
|
|
75
75
|
"three": "^0.185.1"
|
|
76
76
|
},
|