cyclecad 3.0.0 → 3.2.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/BILLING-IMPLEMENTATION-SUMMARY.md +425 -0
- package/BILLING-INDEX.md +293 -0
- package/BILLING-INTEGRATION-GUIDE.md +414 -0
- package/COLLABORATION-INDEX.md +440 -0
- package/COLLABORATION-SYSTEM-SUMMARY.md +548 -0
- package/DOCKER-BUILD-MANIFEST.txt +483 -0
- package/DOCKER-FILES-REFERENCE.md +440 -0
- package/DOCKER-INFRASTRUCTURE.md +475 -0
- package/DOCKER-README.md +435 -0
- package/Dockerfile +33 -55
- package/PWA-FILES-CREATED.txt +350 -0
- package/QUICK-START-TESTING.md +126 -0
- package/STEP-IMPORT-QUICKSTART.md +347 -0
- package/STEP-IMPORT-SYSTEM-SUMMARY.md +502 -0
- package/app/css/mobile.css +1074 -0
- package/app/icons/generate-icons.js +203 -0
- package/app/index.html +93 -0
- package/app/js/billing-ui.js +990 -0
- package/app/js/brep-kernel.js +933 -981
- package/app/js/collab-client.js +750 -0
- package/app/js/mobile-nav.js +623 -0
- package/app/js/mobile-toolbar.js +476 -0
- package/app/js/modules/billing-module.js +724 -0
- package/app/js/modules/step-module-enhanced.js +938 -0
- package/app/js/offline-manager.js +705 -0
- package/app/js/responsive-init.js +360 -0
- package/app/js/touch-handler.js +429 -0
- package/app/manifest.json +211 -0
- package/app/offline.html +508 -0
- package/app/sw.js +571 -0
- package/app/tests/billing-tests.html +779 -0
- package/app/tests/brep-tests.html +980 -0
- package/app/tests/collab-tests.html +743 -0
- package/app/tests/mobile-tests.html +1299 -0
- package/app/tests/pwa-tests.html +1134 -0
- package/app/tests/step-tests.html +1042 -0
- package/app/tests/test-agent-v3.html +719 -0
- package/docker-compose.yml +225 -0
- package/docs/BILLING-HELP.json +260 -0
- package/docs/BILLING-README.md +639 -0
- package/docs/BILLING-TUTORIAL.md +736 -0
- package/docs/BREP-HELP.json +326 -0
- package/docs/BREP-TUTORIAL.md +802 -0
- package/docs/COLLABORATION-HELP.json +228 -0
- package/docs/COLLABORATION-TUTORIAL.md +818 -0
- package/docs/DOCKER-HELP.json +224 -0
- package/docs/DOCKER-TUTORIAL.md +974 -0
- package/docs/MOBILE-HELP.json +243 -0
- package/docs/MOBILE-RESPONSIVE-README.md +378 -0
- package/docs/MOBILE-TUTORIAL.md +747 -0
- package/docs/PWA-HELP.json +228 -0
- package/docs/PWA-README.md +662 -0
- package/docs/PWA-TUTORIAL.md +757 -0
- package/docs/STEP-HELP.json +481 -0
- package/docs/STEP-IMPORT-TUTORIAL.md +824 -0
- package/docs/TESTING-GUIDE.md +528 -0
- package/docs/TESTING-HELP.json +182 -0
- package/fusion-vs-cyclecad.html +1771 -0
- package/nginx.conf +237 -0
- package/package.json +1 -1
- package/server/Dockerfile.converter +51 -0
- package/server/Dockerfile.signaling +28 -0
- package/server/billing-server.js +487 -0
- package/server/converter-enhanced.py +528 -0
- package/server/requirements-converter.txt +29 -0
- package/server/signaling-server.js +801 -0
- package/tests/docker-tests.sh +389 -0
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# B-Rep (Boundary Representation) Modeling in cycleCAD
|
|
2
|
+
|
|
3
|
+
A comprehensive tutorial on real solid modeling using OpenCascade.js WASM in the browser.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [What is B-Rep and Why It Matters](#what-is-brep-and-why-it-matters)
|
|
8
|
+
2. [How OpenCascade.js Works in the Browser](#how-opencascadejs-works-in-the-browser)
|
|
9
|
+
3. [Getting Started with the B-Rep Kernel](#getting-started-with-the-brep-kernel)
|
|
10
|
+
4. [Step-by-Step: Your First Solid](#step-by-step-your-first-solid)
|
|
11
|
+
5. [Boolean Operations](#boolean-operations)
|
|
12
|
+
6. [Advanced Operations](#advanced-operations)
|
|
13
|
+
7. [STEP Import and Export](#step-import-and-export)
|
|
14
|
+
8. [Analyzing Your Models](#analyzing-your-models)
|
|
15
|
+
9. [Performance and Optimization](#performance-and-optimization)
|
|
16
|
+
10. [Troubleshooting](#troubleshooting)
|
|
17
|
+
11. [Complete API Reference](#complete-api-reference)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## What is B-Rep and Why It Matters
|
|
22
|
+
|
|
23
|
+
### B-Rep Definition
|
|
24
|
+
|
|
25
|
+
**B-Rep** (Boundary Representation) is a solid modeling technique that represents 3D objects using their boundary surfaces. Unlike voxel grids or point clouds, B-Rep captures complete topological information:
|
|
26
|
+
|
|
27
|
+
- **Vertices** — 0D points (corners)
|
|
28
|
+
- **Edges** — 1D curves connecting vertices
|
|
29
|
+
- **Faces** — 2D surfaces bounded by edges
|
|
30
|
+
- **Shells** — Collections of faces forming closed volumes
|
|
31
|
+
- **Solids** — Complete 3D shapes with topology
|
|
32
|
+
|
|
33
|
+
### Why B-Rep is Powerful
|
|
34
|
+
|
|
35
|
+
1. **Precision** — Geometry is defined mathematically (NURBS curves, analytical surfaces) rather than approximations
|
|
36
|
+
2. **Topology-Aware** — Knows which edges and faces belong to which solid, enabling intelligent operations
|
|
37
|
+
3. **Manufacturing-Ready** — Compatible with CAM (CNC, 3D printing, laser cutting) because it captures exact geometry
|
|
38
|
+
4. **Editable** — Can fillet specific edges, chamfer specific faces, or cut holes at precise locations
|
|
39
|
+
5. **Interoperable** — STEP (ISO 10303) is the universal CAD format, and B-Rep is its native representation
|
|
40
|
+
|
|
41
|
+
### B-Rep vs. Mesh
|
|
42
|
+
|
|
43
|
+
| Feature | B-Rep | Mesh |
|
|
44
|
+
|---------|-------|------|
|
|
45
|
+
| Topology | Full (edges, faces, vertices tracked) | Implicit (just triangles) |
|
|
46
|
+
| Precision | Infinite (mathematical surfaces) | Limited (tessellation dependent) |
|
|
47
|
+
| Editable | Can modify specific features | Difficult to modify cleanly |
|
|
48
|
+
| File Size | Compact (few curves) | Large (many triangles) |
|
|
49
|
+
| STEP Compatible | Yes (native) | No (conversion required) |
|
|
50
|
+
| Manufacturing | Direct CAM export | Requires healing/repair |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## How OpenCascade.js Works in the Browser
|
|
55
|
+
|
|
56
|
+
### What is OpenCascade.js?
|
|
57
|
+
|
|
58
|
+
OpenCascade.js is a **WebAssembly (WASM) compilation** of the OpenCASCADE 3D geometry kernel — the same library used by FreeCAD, Salome, and many professional CAD systems.
|
|
59
|
+
|
|
60
|
+
### Architecture
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
┌─────────────────────────────────────────────────────┐
|
|
64
|
+
│ Your cycleCAD Code (JavaScript) │
|
|
65
|
+
├─────────────────────────────────────────────────────┤
|
|
66
|
+
│ BRepKernel (wrapper providing high-level API) │
|
|
67
|
+
├─────────────────────────────────────────────────────┤
|
|
68
|
+
│ OpenCascade.js (WASM module, ~50MB) │
|
|
69
|
+
├─────────────────────────────────────────────────────┤
|
|
70
|
+
│ Browser's WASM Runtime (V8, SpiderMonkey, etc.) │
|
|
71
|
+
├─────────────────────────────────────────────────────┤
|
|
72
|
+
│ System Libraries (POSIX, memory management) │
|
|
73
|
+
└─────────────────────────────────────────────────────┘
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Loading the WASM File
|
|
77
|
+
|
|
78
|
+
The first time you use the B-Rep kernel, it downloads the OpenCascade.js library:
|
|
79
|
+
|
|
80
|
+
1. **JavaScript** (~400KB) — loaded as a script tag, defines the Emscripten factory function
|
|
81
|
+
2. **WASM binary** (~50MB) — downloaded on demand, provides the actual geometry algorithms
|
|
82
|
+
3. **Initialization** — factory function creates an instance with proper memory setup
|
|
83
|
+
|
|
84
|
+
This happens **once per browser session** and is cached in IndexedDB if available.
|
|
85
|
+
|
|
86
|
+
### Why Lazy Loading?
|
|
87
|
+
|
|
88
|
+
Because 50MB is large, the kernel uses **lazy initialization**:
|
|
89
|
+
- If you only load a model and don't create new geometry, WASM is never downloaded
|
|
90
|
+
- If you start modeling, WASM loads automatically (users see a progress message)
|
|
91
|
+
- Subsequent operations are fast (WASM stays in memory)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Getting Started with the B-Rep Kernel
|
|
96
|
+
|
|
97
|
+
### Initialization
|
|
98
|
+
|
|
99
|
+
```javascript
|
|
100
|
+
import BRepKernel from '/app/js/brep-kernel.js';
|
|
101
|
+
|
|
102
|
+
// Create an instance
|
|
103
|
+
const kernel = new BRepKernel();
|
|
104
|
+
|
|
105
|
+
// Initialize (lazy — loads WASM only when needed)
|
|
106
|
+
await kernel.init();
|
|
107
|
+
|
|
108
|
+
// Or with progress feedback
|
|
109
|
+
await kernel.init((loaded, total, percent) => {
|
|
110
|
+
console.log(`Downloading: ${percent}%`);
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Basic Pattern
|
|
115
|
+
|
|
116
|
+
All B-Rep operations follow this pattern:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// Create a shape
|
|
120
|
+
const result = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
121
|
+
console.log('Shape ID:', result.id); // 'shape_0'
|
|
122
|
+
console.log('Shape object:', result.shape); // OCC TopoDS_Solid
|
|
123
|
+
|
|
124
|
+
// Apply an operation
|
|
125
|
+
const filleted = await kernel.fillet({
|
|
126
|
+
shapeId: result.id,
|
|
127
|
+
edgeIndices: [0, 1, 2, 3],
|
|
128
|
+
radius: 5
|
|
129
|
+
});
|
|
130
|
+
// filleted = {id: 'shape_1', shape: <OCC object>}
|
|
131
|
+
|
|
132
|
+
// Convert to Three.js for visualization
|
|
133
|
+
const geometry = await kernel.shapeToMesh(filleted.id);
|
|
134
|
+
const mesh = new THREE.Mesh(
|
|
135
|
+
geometry,
|
|
136
|
+
new THREE.MeshStandardMaterial({color: 0x888888})
|
|
137
|
+
);
|
|
138
|
+
scene.add(mesh);
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Key Concepts
|
|
142
|
+
|
|
143
|
+
1. **Shape IDs** — Every operation returns a new shape with a unique ID (`shape_0`, `shape_1`, etc.)
|
|
144
|
+
2. **Immutability** — Operations don't modify the original; they return new shapes
|
|
145
|
+
3. **Caching** — All shapes are cached internally for reuse and memory efficiency
|
|
146
|
+
4. **Async/Await** — All operations are asynchronous (WASM can be slow for large geometries)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Step-by-Step: Your First Solid
|
|
151
|
+
|
|
152
|
+
### 1. Create a Box and Add a Rounded Edge
|
|
153
|
+
|
|
154
|
+
```javascript
|
|
155
|
+
// Step 1: Create a box
|
|
156
|
+
const box = await kernel.makeBox({
|
|
157
|
+
width: 100, // X dimension
|
|
158
|
+
height: 50, // Y dimension
|
|
159
|
+
depth: 30 // Z dimension
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Step 2: Get the edges (to see what we can fillet)
|
|
163
|
+
const edges = await kernel.getEdges(box.id);
|
|
164
|
+
console.log(`Box has ${edges.length} edges`);
|
|
165
|
+
// Output: Box has 12 edges
|
|
166
|
+
|
|
167
|
+
// Step 3: Fillet the top 4 edges with a 5mm radius
|
|
168
|
+
const filleted = await kernel.fillet({
|
|
169
|
+
shapeId: box.id,
|
|
170
|
+
edgeIndices: [0, 1, 2, 3], // First 4 edges are typically the top edges
|
|
171
|
+
radius: 5 // 5mm radius
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Step 4: Visualize
|
|
175
|
+
const geometry = await kernel.shapeToMesh(filleted.id);
|
|
176
|
+
const material = new THREE.MeshStandardMaterial({
|
|
177
|
+
color: 0xcccccc,
|
|
178
|
+
metalness: 0.3,
|
|
179
|
+
roughness: 0.7
|
|
180
|
+
});
|
|
181
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
182
|
+
scene.add(mesh);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 2. Add a Beveled Edge
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
// Chamfer bottom edges with a 2mm bevel
|
|
189
|
+
const chamfered = await kernel.chamfer({
|
|
190
|
+
shapeId: filleted.id, // Use the filleted shape as input
|
|
191
|
+
edgeIndices: [8, 9, 10, 11], // Bottom edges
|
|
192
|
+
distance: 2
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 3. Create a Hollow Shell
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
// Remove the top face and create a hollow box with 2mm walls
|
|
200
|
+
const hollow = await kernel.shell({
|
|
201
|
+
shapeId: chamfered.id,
|
|
202
|
+
removeFaceIndices: [4], // Remove top face
|
|
203
|
+
thickness: 2 // 2mm wall thickness
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Check the hollow box properties
|
|
207
|
+
const props = await kernel.getMassProperties({
|
|
208
|
+
shapeId: hollow.id,
|
|
209
|
+
density: 2.7 // Aluminum: 2.7 g/cm³
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log(`Volume: ${props.volume} mm³`);
|
|
213
|
+
console.log(`Surface area: ${props.area} mm²`);
|
|
214
|
+
console.log(`Weight (aluminum): ${props.mass} grams`);
|
|
215
|
+
console.log(`Center of gravity:`, props.centerOfGravity);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Boolean Operations
|
|
221
|
+
|
|
222
|
+
Boolean operations combine two or more solids. They are the foundation of solid modeling.
|
|
223
|
+
|
|
224
|
+
### Union (Fuse)
|
|
225
|
+
|
|
226
|
+
Combine two solids into one:
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
// Create a base box
|
|
230
|
+
const box = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
231
|
+
|
|
232
|
+
// Create a cylindrical feature to add
|
|
233
|
+
const cyl = await kernel.makeCylinder({radius: 15, height: 40});
|
|
234
|
+
|
|
235
|
+
// Union them
|
|
236
|
+
const combined = await kernel.booleanUnion({
|
|
237
|
+
shapeA: box.id,
|
|
238
|
+
shapeB: cyl.id
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Visualize the combined shape
|
|
242
|
+
const geometry = await kernel.shapeToMesh(combined.id);
|
|
243
|
+
// ...render as before
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Cut (Difference)
|
|
247
|
+
|
|
248
|
+
Subtract one solid from another:
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
// Create a hole by subtracting a cylinder from the box
|
|
252
|
+
const withHole = await kernel.booleanCut({
|
|
253
|
+
shapeA: combined.id, // Base shape (box + cylinder)
|
|
254
|
+
shapeB: cyl.id // Tool shape (cylinder to subtract)
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Visualize
|
|
258
|
+
const geometry = await kernel.shapeToMesh(withHole.id);
|
|
259
|
+
// ...render
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Intersection (Common)
|
|
263
|
+
|
|
264
|
+
Keep only the overlapping region:
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// Find the intersection of two spheres
|
|
268
|
+
const sphere1 = await kernel.makeSphere({radius: 30});
|
|
269
|
+
const sphere2 = await kernel.makeSphere({radius: 30});
|
|
270
|
+
|
|
271
|
+
// Move sphere2 (would need translation if they're at same position)
|
|
272
|
+
const intersection = await kernel.booleanIntersect({
|
|
273
|
+
shapeA: sphere1.id,
|
|
274
|
+
shapeB: sphere2.id
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// This creates a lens-shaped solid where they overlap
|
|
278
|
+
const geometry = await kernel.shapeToMesh(intersection.id);
|
|
279
|
+
// ...render
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Error Recovery
|
|
283
|
+
|
|
284
|
+
Boolean operations can fail if shapes have:
|
|
285
|
+
- Tiny gaps (numerical precision issues)
|
|
286
|
+
- Overlapping surfaces
|
|
287
|
+
- Degenerate geometry
|
|
288
|
+
|
|
289
|
+
The B-Rep kernel uses **3-tier error recovery**:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
try {
|
|
293
|
+
const result = await kernel.booleanCut({
|
|
294
|
+
shapeA: box.id,
|
|
295
|
+
shapeB: tool.id
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (err.name === 'BRepError') {
|
|
299
|
+
console.error('Operation failed:', err.message);
|
|
300
|
+
console.error('Diagnostic:', err.diagnostic);
|
|
301
|
+
// The error message will tell you which recovery tier failed
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The three tiers are:
|
|
307
|
+
1. **Standard** — Normal operation without tolerance adjustments
|
|
308
|
+
2. **Fuzzy Tolerance** — Allows small gaps/overlaps (0.01mm)
|
|
309
|
+
3. **Shape Healing** — Repairs degenerate geometry before retrying
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Advanced Operations
|
|
314
|
+
|
|
315
|
+
### Sweep (Profile Along Path)
|
|
316
|
+
|
|
317
|
+
Sweep a 2D profile along a 3D path:
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// Create a circular profile
|
|
321
|
+
const profile = await kernel.makeSphere({radius: 5});
|
|
322
|
+
|
|
323
|
+
// Create a path (would be a wire in production)
|
|
324
|
+
// For this example, we'll use the axis of extrusion
|
|
325
|
+
const swept = await kernel.sweep({
|
|
326
|
+
profileId: profile.id,
|
|
327
|
+
pathId: lineWire.id // pathId should be a wire/curve
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Creates a shape that "drags" the profile along the path
|
|
331
|
+
const geometry = await kernel.shapeToMesh(swept.id);
|
|
332
|
+
// ...render
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Loft (Between Profiles)
|
|
336
|
+
|
|
337
|
+
Create a surface flowing between multiple 2D profiles:
|
|
338
|
+
|
|
339
|
+
```javascript
|
|
340
|
+
// Create profiles at different sizes
|
|
341
|
+
const base = await kernel.makeBox({width: 50, height: 50, depth: 1});
|
|
342
|
+
const mid = await kernel.makeBox({width: 40, height: 40, depth: 1});
|
|
343
|
+
const top = await kernel.makeBox({width: 20, height: 20, depth: 1});
|
|
344
|
+
|
|
345
|
+
// Loft between them
|
|
346
|
+
const lofted = await kernel.loft({
|
|
347
|
+
profileIds: [base.id, mid.id, top.id],
|
|
348
|
+
isSolid: true // Create a solid, not just a shell
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const geometry = await kernel.shapeToMesh(lofted.id);
|
|
352
|
+
// ...render
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### Mirroring
|
|
356
|
+
|
|
357
|
+
Reflect a shape across a plane:
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
// Mirror a box across the YZ plane
|
|
361
|
+
const mirrored = await kernel.mirror({
|
|
362
|
+
shapeId: box.id,
|
|
363
|
+
plane: {
|
|
364
|
+
originX: 0, // Plane passes through (0, 0, 0)
|
|
365
|
+
originY: 0,
|
|
366
|
+
originZ: 0,
|
|
367
|
+
normalX: 1, // Normal points in X direction (mirror across YZ)
|
|
368
|
+
normalY: 0,
|
|
369
|
+
normalZ: 0
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const geometry = await kernel.shapeToMesh(mirrored.id);
|
|
374
|
+
// ...render
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## STEP Import and Export
|
|
380
|
+
|
|
381
|
+
### Export to STEP
|
|
382
|
+
|
|
383
|
+
Save your model as a standard STEP file (readable by any CAD software):
|
|
384
|
+
|
|
385
|
+
```javascript
|
|
386
|
+
// Export a single shape
|
|
387
|
+
const box = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
388
|
+
const stepData = await kernel.exportSTEP([box.id], {
|
|
389
|
+
fileName: 'mybox.stp',
|
|
390
|
+
writeAscii: false // Binary format (smaller file)
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Download the file
|
|
394
|
+
const blob = new Blob([stepData], {type: 'model/step'});
|
|
395
|
+
const url = URL.createObjectURL(blob);
|
|
396
|
+
const link = document.createElement('a');
|
|
397
|
+
link.href = url;
|
|
398
|
+
link.download = 'mybox.stp';
|
|
399
|
+
link.click();
|
|
400
|
+
URL.revokeObjectURL(url);
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Export Multiple Shapes
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
// Export an assembly of multiple shapes
|
|
407
|
+
const box1 = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
408
|
+
const box2 = await kernel.makeBox({width: 50, height: 50, depth: 50});
|
|
409
|
+
|
|
410
|
+
const stepData = await kernel.exportSTEP([box1.id, box2.id], {
|
|
411
|
+
fileName: 'assembly.stp'
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// ...download as shown above
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Import from STEP
|
|
418
|
+
|
|
419
|
+
Load a STEP file into cycleCAD:
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// User selects a .stp file
|
|
423
|
+
const fileInput = document.createElement('input');
|
|
424
|
+
fileInput.type = 'file';
|
|
425
|
+
fileInput.accept = '.stp,.step';
|
|
426
|
+
fileInput.onchange = async (event) => {
|
|
427
|
+
const file = event.target.files[0];
|
|
428
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
429
|
+
|
|
430
|
+
// Import the shape
|
|
431
|
+
const imported = await kernel.importSTEP(new Uint8Array(arrayBuffer));
|
|
432
|
+
console.log('Imported shape:', imported.id);
|
|
433
|
+
|
|
434
|
+
// Visualize it
|
|
435
|
+
const geometry = await kernel.shapeToMesh(imported.id);
|
|
436
|
+
const material = new THREE.MeshStandardMaterial({color: 0x888888});
|
|
437
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
438
|
+
scene.add(mesh);
|
|
439
|
+
};
|
|
440
|
+
fileInput.click();
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
### Workflow: Edit a CAD Model
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
// 1. Import a model from STEP
|
|
447
|
+
const imported = await kernel.importSTEP(stepData);
|
|
448
|
+
|
|
449
|
+
// 2. Analyze it
|
|
450
|
+
const props = await kernel.getMassProperties({shapeId: imported.id, density: 7.85});
|
|
451
|
+
console.log('Original weight:', props.mass, 'grams');
|
|
452
|
+
|
|
453
|
+
// 3. Modify it (e.g., add fillets)
|
|
454
|
+
const edges = await kernel.getEdges(imported.id);
|
|
455
|
+
const improved = await kernel.fillet({
|
|
456
|
+
shapeId: imported.id,
|
|
457
|
+
edgeIndices: edges.slice(0, 4).map(e => e.index),
|
|
458
|
+
radius: 2
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// 4. Analyze the result
|
|
462
|
+
const newProps = await kernel.getMassProperties({shapeId: improved.id, density: 7.85});
|
|
463
|
+
console.log('Improved weight:', newProps.mass, 'grams');
|
|
464
|
+
|
|
465
|
+
// 5. Export back to STEP
|
|
466
|
+
const outputData = await kernel.exportSTEP([improved.id], {fileName: 'improved.stp'});
|
|
467
|
+
// ...download
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Analyzing Your Models
|
|
473
|
+
|
|
474
|
+
### Mass Properties
|
|
475
|
+
|
|
476
|
+
Calculate volume, area, weight, center of gravity, and moments of inertia:
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
const shape = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
480
|
+
|
|
481
|
+
const props = await kernel.getMassProperties({
|
|
482
|
+
shapeId: shape.id,
|
|
483
|
+
density: 2.7 // g/cm³ (aluminum)
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
console.log(`Volume: ${props.volume.toFixed(2)} mm³`);
|
|
487
|
+
console.log(`Surface area: ${props.area.toFixed(2)} mm²`);
|
|
488
|
+
console.log(`Weight: ${props.mass.toFixed(2)} grams (${(props.mass / 1000).toFixed(2)} kg)`);
|
|
489
|
+
console.log(`Center of gravity:`, {
|
|
490
|
+
x: props.centerOfGravity.x.toFixed(2),
|
|
491
|
+
y: props.centerOfGravity.y.toFixed(2),
|
|
492
|
+
z: props.centerOfGravity.z.toFixed(2)
|
|
493
|
+
});
|
|
494
|
+
console.log(`Moments of inertia (kg·mm²):`, {
|
|
495
|
+
Ixx: props.momentOfInertia.xx.toFixed(0),
|
|
496
|
+
Iyy: props.momentOfInertia.yy.toFixed(0),
|
|
497
|
+
Izz: props.momentOfInertia.zz.toFixed(0)
|
|
498
|
+
});
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Bounding Box
|
|
502
|
+
|
|
503
|
+
Get the extents of a shape:
|
|
504
|
+
|
|
505
|
+
```javascript
|
|
506
|
+
const bbox = await kernel.getBoundingBox(shape.id);
|
|
507
|
+
|
|
508
|
+
console.log(`Extents:`);
|
|
509
|
+
console.log(` X: ${bbox.minX.toFixed(2)} to ${bbox.maxX.toFixed(2)} (width: ${bbox.width.toFixed(2)} mm)`);
|
|
510
|
+
console.log(` Y: ${bbox.minY.toFixed(2)} to ${bbox.maxY.toFixed(2)} (height: ${bbox.height.toFixed(2)} mm)`);
|
|
511
|
+
console.log(` Z: ${bbox.minZ.toFixed(2)} to ${bbox.maxZ.toFixed(2)} (depth: ${bbox.depth.toFixed(2)} mm)`);
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
### Topology Analysis
|
|
515
|
+
|
|
516
|
+
Examine the structure of a shape:
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
const edges = await kernel.getEdges(shape.id);
|
|
520
|
+
const faces = await kernel.getFaces(shape.id);
|
|
521
|
+
|
|
522
|
+
console.log(`Shape has ${edges.length} edges and ${faces.length} faces`);
|
|
523
|
+
|
|
524
|
+
// Iterate over edges
|
|
525
|
+
edges.forEach((edgeObj) => {
|
|
526
|
+
console.log(`Edge ${edgeObj.index}: ${edgeObj.name}`);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
// Iterate over faces
|
|
530
|
+
faces.forEach((faceObj) => {
|
|
531
|
+
console.log(`Face ${faceObj.index}: ${faceObj.name}`);
|
|
532
|
+
});
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## Performance and Optimization
|
|
538
|
+
|
|
539
|
+
### Mesh Fineness
|
|
540
|
+
|
|
541
|
+
The `linearDeflection` parameter controls how fine the tessellation is:
|
|
542
|
+
|
|
543
|
+
```javascript
|
|
544
|
+
// Fine mesh (slower, more triangles)
|
|
545
|
+
const fineMesh = await kernel.shapeToMesh(shape.id, 0.01); // 0.01mm deflection
|
|
546
|
+
|
|
547
|
+
// Coarse mesh (faster, fewer triangles)
|
|
548
|
+
const coarseMesh = await kernel.shapeToMesh(shape.id, 1.0); // 1.0mm deflection
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
For visualization, `0.1mm` is usually a good balance. For 3D printing, use `0.01mm`.
|
|
552
|
+
|
|
553
|
+
### Memory Management
|
|
554
|
+
|
|
555
|
+
The kernel caches all shapes. For long sessions, clean up:
|
|
556
|
+
|
|
557
|
+
```javascript
|
|
558
|
+
// Get cache statistics
|
|
559
|
+
const stats = kernel.getCacheStats();
|
|
560
|
+
console.log(`Cached shapes: ${stats.shapeCount}`);
|
|
561
|
+
|
|
562
|
+
// Delete a specific shape
|
|
563
|
+
kernel.deleteShape(shape.id);
|
|
564
|
+
|
|
565
|
+
// Clear all shapes
|
|
566
|
+
kernel.clearCache(); // WARNING: invalidates all shape IDs
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### Async Best Practices
|
|
570
|
+
|
|
571
|
+
```javascript
|
|
572
|
+
// DON'T wait for each operation sequentially
|
|
573
|
+
const box = await kernel.makeBox({width: 100, height: 50, depth: 30});
|
|
574
|
+
const cyl = await kernel.makeCylinder({radius: 15, height: 60});
|
|
575
|
+
const sphere = await kernel.makeSphere({radius: 25});
|
|
576
|
+
|
|
577
|
+
// DO create them in parallel
|
|
578
|
+
const [box, cyl, sphere] = await Promise.all([
|
|
579
|
+
kernel.makeBox({width: 100, height: 50, depth: 30}),
|
|
580
|
+
kernel.makeCylinder({radius: 15, height: 60}),
|
|
581
|
+
kernel.makeSphere({radius: 25})
|
|
582
|
+
]);
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
---
|
|
586
|
+
|
|
587
|
+
## Troubleshooting
|
|
588
|
+
|
|
589
|
+
### WASM Loading Fails
|
|
590
|
+
|
|
591
|
+
**Problem**: "Failed to load OpenCascade.js from CDN"
|
|
592
|
+
|
|
593
|
+
**Solutions**:
|
|
594
|
+
1. Check internet connection
|
|
595
|
+
2. Verify CDN is not blocked by firewall/proxy
|
|
596
|
+
3. Check browser console for 404 errors
|
|
597
|
+
4. Try a different CDN or host locally
|
|
598
|
+
5. Use browser DevTools to debug network issues
|
|
599
|
+
|
|
600
|
+
### Boolean Operation Fails
|
|
601
|
+
|
|
602
|
+
**Problem**: "Cut failed at all recovery levels"
|
|
603
|
+
|
|
604
|
+
**Causes & Solutions**:
|
|
605
|
+
1. **Shapes don't overlap** — Verify the shapes actually intersect with bounding box check
|
|
606
|
+
2. **Self-intersecting faces** — Use shape healing: `kernel.shell()` or `kernel.fillet()` can clean up
|
|
607
|
+
3. **Tolerance issues** — Try scaling the model larger (1000mm instead of 1mm)
|
|
608
|
+
4. **Coplanar faces** — Ensure faces aren't exactly coplanar; translate one shape slightly
|
|
609
|
+
|
|
610
|
+
### Mesh Rendering is Slow
|
|
611
|
+
|
|
612
|
+
**Problem**: Visualization stutters when displaying large tessellations
|
|
613
|
+
|
|
614
|
+
**Solutions**:
|
|
615
|
+
1. Use coarser mesh deflection (0.5-1.0mm instead of 0.1mm)
|
|
616
|
+
2. Use instanced rendering for many identical shapes
|
|
617
|
+
3. Reduce lighting quality (fewer lights, lower shadow resolution)
|
|
618
|
+
4. Use THREE.js LOD (Level of Detail) system
|
|
619
|
+
5. Profile with Chrome DevTools to find bottleneck
|
|
620
|
+
|
|
621
|
+
### Fillet/Chamfer Produces Empty Shape
|
|
622
|
+
|
|
623
|
+
**Problem**: Operation returns a valid shape ID but mesh is empty
|
|
624
|
+
|
|
625
|
+
**Causes & Solutions**:
|
|
626
|
+
1. **Fillet radius too large** — Radius must be smaller than local edge curvature
|
|
627
|
+
2. **Wrong edge indices** — Verify edge count with `kernel.getEdges()`
|
|
628
|
+
3. **Invalid shape** — Check that input shape is valid solid (not a wire or shell)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Complete API Reference
|
|
633
|
+
|
|
634
|
+
### Initialization
|
|
635
|
+
|
|
636
|
+
| Method | Returns | Description |
|
|
637
|
+
|--------|---------|-------------|
|
|
638
|
+
| `init([onProgress])` | `Promise<Object>` | Load WASM from CDN. Call once per session. |
|
|
639
|
+
|
|
640
|
+
### Primitives
|
|
641
|
+
|
|
642
|
+
| Method | Parameters | Returns | Description |
|
|
643
|
+
|--------|-----------|---------|-------------|
|
|
644
|
+
| `makeBox(options)` | `{width, height, depth, x?, y?, z?}` | `{id, shape}` | Create rectangular box |
|
|
645
|
+
| `makeCylinder(options)` | `{radius, height, angle?}` | `{id, shape}` | Create cylinder (360° or wedge) |
|
|
646
|
+
| `makeSphere(options)` | `{radius}` | `{id, shape}` | Create sphere |
|
|
647
|
+
| `makeCone(options)` | `{radius1, radius2, height}` | `{id, shape}` | Create cone or frustum |
|
|
648
|
+
| `makeTorus(options)` | `{majorRadius, minorRadius}` | `{id, shape}` | Create torus |
|
|
649
|
+
|
|
650
|
+
### Transformations
|
|
651
|
+
|
|
652
|
+
| Method | Parameters | Returns | Description |
|
|
653
|
+
|--------|-----------|---------|-------------|
|
|
654
|
+
| `extrude(options)` | `{shapeId, dirX, dirY, dirZ, depth}` | `{id, shape}` | Extrude profile along vector |
|
|
655
|
+
| `revolve(options)` | `{shapeId, axisX, axisY, axisZ, dirX, dirY, dirZ, angle?}` | `{id, shape}` | Revolve profile around axis |
|
|
656
|
+
| `sweep(options)` | `{profileId, pathId}` | `{id, shape}` | Sweep profile along path |
|
|
657
|
+
| `loft(options)` | `{profileIds, isSolid?}` | `{id, shape}` | Loft between profiles |
|
|
658
|
+
| `mirror(options)` | `{shapeId, plane}` | `{id, shape}` | Mirror across plane |
|
|
659
|
+
|
|
660
|
+
### Boolean Operations
|
|
661
|
+
|
|
662
|
+
| Method | Parameters | Returns | Description |
|
|
663
|
+
|--------|-----------|---------|-------------|
|
|
664
|
+
| `booleanUnion(options)` | `{shapeA, shapeB}` | `{id, shape}` | Union of two shapes (3-tier recovery) |
|
|
665
|
+
| `booleanCut(options)` | `{shapeA, shapeB}` | `{id, shape}` | Cut shapeB from shapeA (3-tier recovery) |
|
|
666
|
+
| `booleanIntersect(options)` | `{shapeA, shapeB}` | `{id, shape}` | Intersection of two shapes (3-tier recovery) |
|
|
667
|
+
|
|
668
|
+
### Modifiers
|
|
669
|
+
|
|
670
|
+
| Method | Parameters | Returns | Description |
|
|
671
|
+
|--------|-----------|---------|-------------|
|
|
672
|
+
| `fillet(options)` | `{shapeId, edgeIndices, radius}` | `{id, shape}` | Round edges with real B-Rep |
|
|
673
|
+
| `chamfer(options)` | `{shapeId, edgeIndices, distance}` | `{id, shape}` | Bevel edges with sharp angle |
|
|
674
|
+
| `shell(options)` | `{shapeId, removeFaceIndices?, thickness}` | `{id, shape}` | Create hollow shell |
|
|
675
|
+
|
|
676
|
+
### Topology
|
|
677
|
+
|
|
678
|
+
| Method | Parameters | Returns | Description |
|
|
679
|
+
|--------|-----------|---------|-------------|
|
|
680
|
+
| `getEdges(shapeId)` | `shapeId: string` | `Promise<Array>` | Get all edges with indices |
|
|
681
|
+
| `getFaces(shapeId)` | `shapeId: string` | `Promise<Array>` | Get all faces with indices |
|
|
682
|
+
|
|
683
|
+
### Visualization & Analysis
|
|
684
|
+
|
|
685
|
+
| Method | Parameters | Returns | Description |
|
|
686
|
+
|--------|-----------|---------|-------------|
|
|
687
|
+
| `shapeToMesh(shapeId, deflection?)` | `shapeId, deflection?` | `THREE.BufferGeometry` | Tessellate to Three.js mesh |
|
|
688
|
+
| `getMassProperties(options)` | `{shapeId, density?}` | `{volume, area, mass, cog, moi}` | Compute mass properties |
|
|
689
|
+
| `getBoundingBox(shapeId)` | `shapeId: string` | `{minX, minY, minZ, maxX, maxY, maxZ, width, height, depth}` | Get bounds |
|
|
690
|
+
|
|
691
|
+
### File I/O
|
|
692
|
+
|
|
693
|
+
| Method | Parameters | Returns | Description |
|
|
694
|
+
|--------|-----------|---------|-------------|
|
|
695
|
+
| `exportSTEP(shapeIds, options?)` | `[shapeIds], {fileName?, writeAscii?}` | `Uint8Array` | Export to STEP format |
|
|
696
|
+
| `importSTEP(stepData)` | `ArrayBuffer \| Uint8Array` | `{id, shape, count}` | Import from STEP |
|
|
697
|
+
|
|
698
|
+
### Utility
|
|
699
|
+
|
|
700
|
+
| Method | Parameters | Returns | Description |
|
|
701
|
+
|--------|-----------|---------|-------------|
|
|
702
|
+
| `getShapeInfo(shapeId)` | `shapeId: string` | `Object \| null` | Get cached metadata |
|
|
703
|
+
| `deleteShape(shapeId)` | `shapeId: string` | `boolean` | Remove shape from cache |
|
|
704
|
+
| `clearCache()` | (none) | (none) | Clear all cached shapes |
|
|
705
|
+
| `getCacheStats()` | (none) | `{shapeCount, memoryEstimate}` | Get cache statistics |
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Examples
|
|
710
|
+
|
|
711
|
+
### Example 1: Manufacturing a Bracket
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
// 1. Create the base plate
|
|
715
|
+
const plate = await kernel.makeBox({width: 100, height: 50, depth: 10});
|
|
716
|
+
|
|
717
|
+
// 2. Create mounting holes
|
|
718
|
+
const hole1 = await kernel.makeCylinder({radius: 5, height: 15});
|
|
719
|
+
const hole2 = await kernel.makeCylinder({radius: 5, height: 15});
|
|
720
|
+
|
|
721
|
+
// 3. Subtract holes from plate
|
|
722
|
+
let bracket = await kernel.booleanCut({shapeA: plate.id, shapeB: hole1.id});
|
|
723
|
+
bracket = await kernel.booleanCut({shapeA: bracket.id, shapeB: hole2.id});
|
|
724
|
+
|
|
725
|
+
// 4. Round edges for safety
|
|
726
|
+
const edges = await kernel.getEdges(bracket.id);
|
|
727
|
+
bracket = await kernel.fillet({
|
|
728
|
+
shapeId: bracket.id,
|
|
729
|
+
edgeIndices: [0, 1, 2, 3], // Top edges
|
|
730
|
+
radius: 2
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// 5. Analyze
|
|
734
|
+
const props = await kernel.getMassProperties({
|
|
735
|
+
shapeId: bracket.id,
|
|
736
|
+
density: 2.7 // Aluminum
|
|
737
|
+
});
|
|
738
|
+
console.log(`Weight: ${(props.mass / 1000).toFixed(2)} kg`);
|
|
739
|
+
|
|
740
|
+
// 6. Export for machining
|
|
741
|
+
const stepData = await kernel.exportSTEP([bracket.id]);
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
### Example 2: Organic Shape via Loft
|
|
745
|
+
|
|
746
|
+
```javascript
|
|
747
|
+
// 1. Create profiles at different Z heights
|
|
748
|
+
const base = await kernel.makeBox({width: 50, height: 50, depth: 1});
|
|
749
|
+
const mid = await kernel.makeBox({width: 40, height: 40, depth: 1});
|
|
750
|
+
const top = await kernel.makeBox({width: 20, height: 20, depth: 1});
|
|
751
|
+
|
|
752
|
+
// 2. Loft between them
|
|
753
|
+
const organic = await kernel.loft({
|
|
754
|
+
profileIds: [base.id, mid.id, top.id],
|
|
755
|
+
isSolid: true
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
// 3. Visualize with smooth shading
|
|
759
|
+
const geometry = await kernel.shapeToMesh(organic.id, 0.1);
|
|
760
|
+
const material = new THREE.MeshStandardMaterial({
|
|
761
|
+
color: 0x6fa3d0,
|
|
762
|
+
metalness: 0.2,
|
|
763
|
+
roughness: 0.8
|
|
764
|
+
});
|
|
765
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
766
|
+
scene.add(mesh);
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
---
|
|
770
|
+
|
|
771
|
+
## Performance Tips
|
|
772
|
+
|
|
773
|
+
1. **Lazy load** — WASM only loads when first geometry op happens
|
|
774
|
+
2. **Batch operations** — Use `Promise.all()` for parallel shape creation
|
|
775
|
+
3. **Cache meshes** — Don't regenerate THREE.js geometries; cache them
|
|
776
|
+
4. **Reduce precision** — Use 0.5-1.0mm deflection for viewport, 0.1mm for export
|
|
777
|
+
5. **Clean up** — Call `kernel.deleteShape()` or `kernel.clearCache()` in long sessions
|
|
778
|
+
6. **Monitor memory** — Use DevTools to watch for memory leaks
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## Next Steps
|
|
783
|
+
|
|
784
|
+
1. **Try the test suite** — Run `/app/tests/brep-tests.html` to see all features
|
|
785
|
+
2. **Integrate into cycleCAD** — Wire `BRepKernel` into the main CAD app
|
|
786
|
+
3. **Build importers** — Parse Inventor .ipt files and convert to B-Rep shapes
|
|
787
|
+
4. **Implement CAM export** — Generate G-code from B-Rep shapes for 3D printing/CNC
|
|
788
|
+
5. **Add collaboration** — Share B-Rep models via WebSockets or IndexedDB
|
|
789
|
+
|
|
790
|
+
---
|
|
791
|
+
|
|
792
|
+
## Resources
|
|
793
|
+
|
|
794
|
+
- **OpenCascade Documentation**: https://dev.opencascade.org/cdoc
|
|
795
|
+
- **OpenCascade.js GitHub**: https://github.com/CadQuery/opencascade.js
|
|
796
|
+
- **STEP Format (ISO 10303)**: https://en.wikipedia.org/wiki/ISO_10303
|
|
797
|
+
- **B-Rep Theory**: *An Introduction to NURBS* by Rogers & Adams
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
**Last Updated**: 2026-03-31
|
|
802
|
+
**B-Rep Kernel Version**: 3.0.0
|