@code3d/core 0.0.1-alpha.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/LICENSE +22 -0
- package/README.md +824 -0
- package/THIRD_PARTY.md +28 -0
- package/bld/library/alignment-geometry.d.ts +50 -0
- package/bld/library/alignment-geometry.d.ts.map +1 -0
- package/bld/library/alignment-geometry.js +322 -0
- package/bld/library/alignment-geometry.js.map +1 -0
- package/bld/library/bound-solver.d.ts +33 -0
- package/bld/library/bound-solver.d.ts.map +1 -0
- package/bld/library/bound-solver.js +252 -0
- package/bld/library/bound-solver.js.map +1 -0
- package/bld/library/extrude.d.ts +8 -0
- package/bld/library/extrude.d.ts.map +1 -0
- package/bld/library/extrude.js +28 -0
- package/bld/library/extrude.js.map +1 -0
- package/bld/library/index.d.ts +7 -0
- package/bld/library/index.d.ts.map +1 -0
- package/bld/library/index.js +3 -0
- package/bld/library/index.js.map +1 -0
- package/bld/library/kernel-artifact-codec.d.ts +3 -0
- package/bld/library/kernel-artifact-codec.d.ts.map +1 -0
- package/bld/library/kernel-artifact-codec.js +148 -0
- package/bld/library/kernel-artifact-codec.js.map +1 -0
- package/bld/library/kernel-cache.d.ts +63 -0
- package/bld/library/kernel-cache.d.ts.map +1 -0
- package/bld/library/kernel-cache.js +245 -0
- package/bld/library/kernel-cache.js.map +1 -0
- package/bld/library/kernel-shapes.d.ts +20 -0
- package/bld/library/kernel-shapes.d.ts.map +1 -0
- package/bld/library/kernel-shapes.js +108 -0
- package/bld/library/kernel-shapes.js.map +1 -0
- package/bld/library/loft.d.ts +7 -0
- package/bld/library/loft.d.ts.map +1 -0
- package/bld/library/loft.js +161 -0
- package/bld/library/loft.js.map +1 -0
- package/bld/library/material.d.ts +12 -0
- package/bld/library/material.d.ts.map +1 -0
- package/bld/library/material.js +176 -0
- package/bld/library/material.js.map +1 -0
- package/bld/library/model-color.d.ts +8 -0
- package/bld/library/model-color.d.ts.map +1 -0
- package/bld/library/model-color.js +36 -0
- package/bld/library/model-color.js.map +1 -0
- package/bld/library/open-cascade-error.d.ts +2 -0
- package/bld/library/open-cascade-error.d.ts.map +1 -0
- package/bld/library/open-cascade-error.js +22 -0
- package/bld/library/open-cascade-error.js.map +1 -0
- package/bld/library/relation-solver.d.ts +29 -0
- package/bld/library/relation-solver.d.ts.map +1 -0
- package/bld/library/relation-solver.js +258 -0
- package/bld/library/relation-solver.js.map +1 -0
- package/bld/library/replicad.d.ts +17 -0
- package/bld/library/replicad.d.ts.map +1 -0
- package/bld/library/replicad.js +24 -0
- package/bld/library/replicad.js.map +1 -0
- package/bld/library/retained-memory.d.ts +7 -0
- package/bld/library/retained-memory.d.ts.map +1 -0
- package/bld/library/retained-memory.js +27 -0
- package/bld/library/retained-memory.js.map +1 -0
- package/bld/library/runtime.d.ts +775 -0
- package/bld/library/runtime.d.ts.map +1 -0
- package/bld/library/runtime.js +2954 -0
- package/bld/library/runtime.js.map +1 -0
- package/bld/library/shell.d.ts +7 -0
- package/bld/library/shell.d.ts.map +1 -0
- package/bld/library/shell.js +221 -0
- package/bld/library/shell.js.map +1 -0
- package/bld/library/sketch-curve-intersections.d.ts +13 -0
- package/bld/library/sketch-curve-intersections.d.ts.map +1 -0
- package/bld/library/sketch-curve-intersections.js +117 -0
- package/bld/library/sketch-curve-intersections.js.map +1 -0
- package/bld/library/sketch-curves.d.ts +31 -0
- package/bld/library/sketch-curves.d.ts.map +1 -0
- package/bld/library/sketch-curves.js +89 -0
- package/bld/library/sketch-curves.js.map +1 -0
- package/bld/library/sketch-drag-rules.d.ts +35 -0
- package/bld/library/sketch-drag-rules.d.ts.map +1 -0
- package/bld/library/sketch-drag-rules.js +522 -0
- package/bld/library/sketch-drag-rules.js.map +1 -0
- package/bld/library/sketch-face.d.ts +4 -0
- package/bld/library/sketch-face.d.ts.map +1 -0
- package/bld/library/sketch-face.js +72 -0
- package/bld/library/sketch-face.js.map +1 -0
- package/bld/library/sketch-incidence.d.ts +26 -0
- package/bld/library/sketch-incidence.d.ts.map +1 -0
- package/bld/library/sketch-incidence.js +114 -0
- package/bld/library/sketch-incidence.js.map +1 -0
- package/bld/library/sketch-precision.d.ts +19 -0
- package/bld/library/sketch-precision.d.ts.map +1 -0
- package/bld/library/sketch-precision.js +55 -0
- package/bld/library/sketch-precision.js.map +1 -0
- package/bld/library/sketch-regions.d.ts +12 -0
- package/bld/library/sketch-regions.d.ts.map +1 -0
- package/bld/library/sketch-regions.js +193 -0
- package/bld/library/sketch-regions.js.map +1 -0
- package/bld/library/sketch-solver.d.ts +97 -0
- package/bld/library/sketch-solver.d.ts.map +1 -0
- package/bld/library/sketch-solver.js +578 -0
- package/bld/library/sketch-solver.js.map +1 -0
- package/bld/library/sketch.d.ts +142 -0
- package/bld/library/sketch.d.ts.map +1 -0
- package/bld/library/sketch.js +650 -0
- package/bld/library/sketch.js.map +1 -0
- package/bld/library/spatial.d.ts +28 -0
- package/bld/library/spatial.d.ts.map +1 -0
- package/bld/library/spatial.js +195 -0
- package/bld/library/spatial.js.map +1 -0
- package/bld/library/three.d.ts +3 -0
- package/bld/library/three.d.ts.map +1 -0
- package/bld/library/three.js +3 -0
- package/bld/library/three.js.map +1 -0
- package/bld/library/topology-id.d.ts +24 -0
- package/bld/library/topology-id.d.ts.map +1 -0
- package/bld/library/topology-id.js +67 -0
- package/bld/library/topology-id.js.map +1 -0
- package/bld/library/topology-inspection.d.ts +64 -0
- package/bld/library/topology-inspection.d.ts.map +1 -0
- package/bld/library/topology-inspection.js +267 -0
- package/bld/library/topology-inspection.js.map +1 -0
- package/bld/library/topology.d.ts +94 -0
- package/bld/library/topology.d.ts.map +1 -0
- package/bld/library/topology.js +455 -0
- package/bld/library/topology.js.map +1 -0
- package/bld/node/index.d.ts +2 -0
- package/bld/node/index.d.ts.map +1 -0
- package/bld/node/index.js +14 -0
- package/bld/node/index.js.map +1 -0
- package/bld/node/replicad.d.ts +3 -0
- package/bld/node/replicad.d.ts.map +1 -0
- package/bld/node/replicad.js +3 -0
- package/bld/node/replicad.js.map +1 -0
- package/bld/tooling/index.d.ts +26 -0
- package/bld/tooling/index.d.ts.map +1 -0
- package/bld/tooling/index.js +19 -0
- package/bld/tooling/index.js.map +1 -0
- package/package.json +53 -0
- package/src/library/alignment-geometry.ts +431 -0
- package/src/library/bound-solver.ts +410 -0
- package/src/library/extrude.ts +38 -0
- package/src/library/index.ts +88 -0
- package/src/library/kernel-artifact-codec.ts +166 -0
- package/src/library/kernel-cache.ts +337 -0
- package/src/library/kernel-shapes.ts +135 -0
- package/src/library/loft.ts +196 -0
- package/src/library/material.ts +228 -0
- package/src/library/model-color.ts +47 -0
- package/src/library/open-cascade-error.ts +26 -0
- package/src/library/relation-solver.ts +479 -0
- package/src/library/replicad.ts +40 -0
- package/src/library/retained-memory.ts +23 -0
- package/src/library/runtime.ts +5146 -0
- package/src/library/shell.ts +281 -0
- package/src/library/sketch-curve-intersections.ts +154 -0
- package/src/library/sketch-curves.ts +161 -0
- package/src/library/sketch-drag-rules.ts +767 -0
- package/src/library/sketch-face.ts +89 -0
- package/src/library/sketch-incidence.ts +204 -0
- package/src/library/sketch-precision.ts +81 -0
- package/src/library/sketch-regions.ts +271 -0
- package/src/library/sketch-solver.ts +973 -0
- package/src/library/sketch.ts +977 -0
- package/src/library/spatial.ts +254 -0
- package/src/library/three.ts +2 -0
- package/src/library/topology-id.ts +81 -0
- package/src/library/topology-inspection.ts +368 -0
- package/src/library/topology.ts +776 -0
- package/src/library/tsconfig.json +11 -0
- package/src/node/index.ts +20 -0
- package/src/node/replicad.ts +3 -0
- package/src/node/tsconfig.json +16 -0
- package/src/tooling/index.ts +160 -0
- package/src/tooling/tsconfig.json +15 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import type {OpenCascadeInstance} from '@code3d/opencascade';
|
|
2
|
+
import {getOC} from 'replicad';
|
|
3
|
+
import {estimateRetainedBytes} from './retained-memory.js';
|
|
4
|
+
import {
|
|
5
|
+
decodeKernelArtifact,
|
|
6
|
+
encodeKernelArtifact,
|
|
7
|
+
} from './kernel-artifact-codec.js';
|
|
8
|
+
|
|
9
|
+
/** The host opens storage before synchronous evaluation and owns its lifetime. */
|
|
10
|
+
export interface KernelArtifactStore {
|
|
11
|
+
get(id: string): Uint8Array | undefined;
|
|
12
|
+
set(id: string, bytes: Uint8Array): void;
|
|
13
|
+
touch(id: string): boolean;
|
|
14
|
+
delete(id: string): void;
|
|
15
|
+
flush(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type KernelKeyPart =
|
|
19
|
+
string | number | boolean | null | readonly KernelKeyPart[];
|
|
20
|
+
|
|
21
|
+
export type KernelArtifact<Value> = Readonly<{
|
|
22
|
+
id: string;
|
|
23
|
+
value: Value;
|
|
24
|
+
}>;
|
|
25
|
+
|
|
26
|
+
export type KernelValueLifecycle<Value> = Readonly<{
|
|
27
|
+
estimateBytes(value: Value): number;
|
|
28
|
+
retain(value: Value): Value;
|
|
29
|
+
instantiate(retained: Value): Value;
|
|
30
|
+
release(value: Value): void;
|
|
31
|
+
}>;
|
|
32
|
+
|
|
33
|
+
type CacheEntry<Value> = Readonly<{
|
|
34
|
+
estimatedBytes: number;
|
|
35
|
+
signature: string;
|
|
36
|
+
value: Value;
|
|
37
|
+
instantiate(retained: Value): Value;
|
|
38
|
+
release(value: Value): void;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
export function createKernelOperationCache({
|
|
42
|
+
maximumBytes = 2 * 1024 ** 3,
|
|
43
|
+
nativeAllocatedBytes,
|
|
44
|
+
}: {
|
|
45
|
+
maximumBytes?: number;
|
|
46
|
+
nativeAllocatedBytes: () => number;
|
|
47
|
+
}) {
|
|
48
|
+
const entries = new Map<string, CacheEntry<unknown>>();
|
|
49
|
+
const historicalEntries = new Map<string, CacheEntry<unknown>>();
|
|
50
|
+
let retainedEvaluation = new Set<string>();
|
|
51
|
+
let currentEvaluation:
|
|
52
|
+
{used: Set<string>; checkCancelled?: () => void} | undefined;
|
|
53
|
+
let hits = 0;
|
|
54
|
+
let misses = 0;
|
|
55
|
+
let estimatedJavaScriptBytes = 0;
|
|
56
|
+
let store: KernelArtifactStore | undefined;
|
|
57
|
+
let persistentHits = 0;
|
|
58
|
+
let persistentWrites = 0;
|
|
59
|
+
let persistenceErrors = 0;
|
|
60
|
+
const persisted = new Set<string>();
|
|
61
|
+
let externalBytes = 0;
|
|
62
|
+
|
|
63
|
+
/** The host accounts for in-flight inputs and all auxiliary native heaps. */
|
|
64
|
+
function setKernelExternalBytes(bytes: number): void {
|
|
65
|
+
externalBytes = bytes;
|
|
66
|
+
evictHistoricalEntries();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setKernelArtifactStore(next: KernelArtifactStore | undefined): void {
|
|
70
|
+
store = next;
|
|
71
|
+
persisted.clear();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function accessStore<Result>(
|
|
75
|
+
action: (store: KernelArtifactStore) => Result,
|
|
76
|
+
): Result | undefined {
|
|
77
|
+
if (!store) return undefined;
|
|
78
|
+
try {
|
|
79
|
+
return action(store);
|
|
80
|
+
} catch {
|
|
81
|
+
// Cache storage is optional. Its failures must not change model execution.
|
|
82
|
+
persistenceErrors += 1;
|
|
83
|
+
store = undefined;
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Keep a serial evaluation's complete working set, including snapshot queries.
|
|
90
|
+
* The previous working set stays available until this evaluation finishes so
|
|
91
|
+
* a changed prefix cannot evict unchanged operations that execute later.
|
|
92
|
+
* Outside evaluations, only the bounded historical LRU admits new entries.
|
|
93
|
+
*/
|
|
94
|
+
function beginKernelOperationEvaluation(
|
|
95
|
+
checkCancelled?: () => void,
|
|
96
|
+
): () => void {
|
|
97
|
+
const used = new Set<string>();
|
|
98
|
+
currentEvaluation = {used, checkCancelled};
|
|
99
|
+
return () => {
|
|
100
|
+
for (const id of retainedEvaluation) {
|
|
101
|
+
if (!used.has(id)) historicalEntries.set(id, entries.get(id)!);
|
|
102
|
+
}
|
|
103
|
+
retainedEvaluation = used;
|
|
104
|
+
currentEvaluation = undefined;
|
|
105
|
+
evictHistoricalEntries();
|
|
106
|
+
accessStore(store => store.flush());
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Reuses one complete, deterministic kernel operation. Arguments describe
|
|
112
|
+
* scalar semantics; inputs carry the content identities of prior operations.
|
|
113
|
+
* The lifecycle keeps the retained value independent from each disposable use.
|
|
114
|
+
*/
|
|
115
|
+
function evaluateKernelOperation<Value>(
|
|
116
|
+
operation: string,
|
|
117
|
+
arguments_: readonly KernelKeyPart[],
|
|
118
|
+
inputs: readonly KernelArtifact<unknown>[],
|
|
119
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
120
|
+
compute: () => Value,
|
|
121
|
+
): KernelArtifact<Value> {
|
|
122
|
+
const key = kernelOperationKey(operation, arguments_, inputs);
|
|
123
|
+
const cached = findKernelOperation(key, lifecycle);
|
|
124
|
+
if (cached) return cached;
|
|
125
|
+
const value = compute();
|
|
126
|
+
acceptKernelOperation(key, lifecycle, value);
|
|
127
|
+
return {id: key.id, value};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findKernelOperation<Value>(
|
|
131
|
+
key: KernelOperationKey,
|
|
132
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
133
|
+
): KernelArtifact<Value> | undefined {
|
|
134
|
+
currentEvaluation?.checkCancelled?.();
|
|
135
|
+
const {id, signature} = key;
|
|
136
|
+
let cached = entries.get(id) as CacheEntry<Value> | undefined;
|
|
137
|
+
if (!cached) {
|
|
138
|
+
const bytes = accessStore(store => store.get(id));
|
|
139
|
+
if (bytes) {
|
|
140
|
+
let restored: Value;
|
|
141
|
+
try {
|
|
142
|
+
restored = decodeKernelArtifact<Value>(bytes, signature);
|
|
143
|
+
} catch {
|
|
144
|
+
persistenceErrors += 1;
|
|
145
|
+
accessStore(store => store.delete(id));
|
|
146
|
+
misses += 1;
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
cached = retainEntry(key, lifecycle, restored);
|
|
150
|
+
persistentHits += 1;
|
|
151
|
+
persisted.add(id);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!cached) {
|
|
155
|
+
misses += 1;
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
if (cached.signature !== signature)
|
|
159
|
+
throw new Error(`Kernel operation cache identity collision: ${id}`);
|
|
160
|
+
hits += 1;
|
|
161
|
+
const value = cached.instantiate(cached.value);
|
|
162
|
+
persist(key, cached.value);
|
|
163
|
+
touchEntry(id, cached as CacheEntry<unknown>);
|
|
164
|
+
return {id, value};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Accept completed work even when cancellation arrived during computation. */
|
|
168
|
+
function acceptKernelOperation<Value>(
|
|
169
|
+
key: KernelOperationKey,
|
|
170
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
171
|
+
value: Value,
|
|
172
|
+
): void {
|
|
173
|
+
const existing = entries.get(key.id);
|
|
174
|
+
if (existing) {
|
|
175
|
+
if (existing.signature !== key.signature)
|
|
176
|
+
throw new Error(`Kernel operation cache identity collision: ${key.id}`);
|
|
177
|
+
touchEntry(key.id, existing);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let retained: Value;
|
|
181
|
+
try {
|
|
182
|
+
retained = lifecycle.retain(value);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
lifecycle.release(value);
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
const entry = retainEntry(key, lifecycle, retained);
|
|
188
|
+
persist(key, retained);
|
|
189
|
+
touchEntry(key.id, entry as CacheEntry<unknown>);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function persist(key: KernelOperationKey, value: unknown): void {
|
|
193
|
+
if (!store || persisted.has(key.id)) return;
|
|
194
|
+
accessStore(store => {
|
|
195
|
+
if (!store.touch(key.id)) {
|
|
196
|
+
store.set(key.id, encodeKernelArtifact(key.signature, value));
|
|
197
|
+
persistentWrites += 1;
|
|
198
|
+
}
|
|
199
|
+
persisted.add(key.id);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function retainEntry<Value>(
|
|
204
|
+
key: KernelOperationKey,
|
|
205
|
+
lifecycle: KernelValueLifecycle<Value>,
|
|
206
|
+
retained: Value,
|
|
207
|
+
): CacheEntry<Value> {
|
|
208
|
+
const entry: CacheEntry<Value> = {
|
|
209
|
+
estimatedBytes:
|
|
210
|
+
256 +
|
|
211
|
+
estimateRetainedBytes(key.signature) +
|
|
212
|
+
lifecycle.estimateBytes(retained),
|
|
213
|
+
signature: key.signature,
|
|
214
|
+
value: retained,
|
|
215
|
+
instantiate: lifecycle.instantiate,
|
|
216
|
+
release: lifecycle.release,
|
|
217
|
+
};
|
|
218
|
+
entries.set(key.id, entry as CacheEntry<unknown>);
|
|
219
|
+
estimatedJavaScriptBytes += entry.estimatedBytes;
|
|
220
|
+
return entry;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function touchEntry(id: string, entry: CacheEntry<unknown>): void {
|
|
224
|
+
historicalEntries.delete(id);
|
|
225
|
+
if (currentEvaluation) {
|
|
226
|
+
currentEvaluation.used.delete(id);
|
|
227
|
+
currentEvaluation.used.add(id);
|
|
228
|
+
} else if (!retainedEvaluation.has(id)) {
|
|
229
|
+
historicalEntries.set(id, entry);
|
|
230
|
+
}
|
|
231
|
+
evictHistoricalEntries();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function clearKernelOperationCache(): void {
|
|
235
|
+
for (const entry of entries.values()) {
|
|
236
|
+
entry.release(entry.value);
|
|
237
|
+
}
|
|
238
|
+
entries.clear();
|
|
239
|
+
historicalEntries.clear();
|
|
240
|
+
retainedEvaluation.clear();
|
|
241
|
+
currentEvaluation?.used.clear();
|
|
242
|
+
hits = 0;
|
|
243
|
+
misses = 0;
|
|
244
|
+
estimatedJavaScriptBytes = 0;
|
|
245
|
+
persistentHits = 0;
|
|
246
|
+
persistentWrites = 0;
|
|
247
|
+
persistenceErrors = 0;
|
|
248
|
+
persisted.clear();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function kernelOperationCacheStats() {
|
|
252
|
+
return {
|
|
253
|
+
entries: entries.size,
|
|
254
|
+
hits,
|
|
255
|
+
misses,
|
|
256
|
+
historicalEntries: historicalEntries.size,
|
|
257
|
+
estimatedJavaScriptBytes,
|
|
258
|
+
nativeAllocatedBytes: nativeAllocatedBytes(),
|
|
259
|
+
maximumBytes,
|
|
260
|
+
externalBytes,
|
|
261
|
+
persistentHits,
|
|
262
|
+
persistentWrites,
|
|
263
|
+
persistenceErrors,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function evictHistoricalEntries(): void {
|
|
268
|
+
while (
|
|
269
|
+
historicalEntries.size &&
|
|
270
|
+
nativeAllocatedBytes() + estimatedJavaScriptBytes + externalBytes >
|
|
271
|
+
maximumBytes
|
|
272
|
+
) {
|
|
273
|
+
const [id, entry] = historicalEntries.entries().next().value!;
|
|
274
|
+
historicalEntries.delete(id);
|
|
275
|
+
entries.delete(id);
|
|
276
|
+
estimatedJavaScriptBytes -= entry.estimatedBytes;
|
|
277
|
+
entry.release(entry.value);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
beginKernelOperationEvaluation,
|
|
283
|
+
evaluateKernelOperation,
|
|
284
|
+
clearKernelOperationCache,
|
|
285
|
+
kernelOperationCacheStats,
|
|
286
|
+
setKernelArtifactStore,
|
|
287
|
+
findKernelOperation,
|
|
288
|
+
acceptKernelOperation,
|
|
289
|
+
setKernelExternalBytes,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export const {
|
|
294
|
+
beginKernelOperationEvaluation,
|
|
295
|
+
evaluateKernelOperation,
|
|
296
|
+
clearKernelOperationCache,
|
|
297
|
+
kernelOperationCacheStats,
|
|
298
|
+
setKernelArtifactStore,
|
|
299
|
+
findKernelOperation,
|
|
300
|
+
acceptKernelOperation,
|
|
301
|
+
setKernelExternalBytes,
|
|
302
|
+
} = createKernelOperationCache({
|
|
303
|
+
nativeAllocatedBytes: () =>
|
|
304
|
+
(getOC() as OpenCascadeInstance).Code3dMemory.AllocatedBytes(),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
export type KernelOperationKey = Readonly<{id: string; signature: string}>;
|
|
308
|
+
|
|
309
|
+
export function kernelOperationKey(
|
|
310
|
+
operation: string,
|
|
311
|
+
arguments_: readonly KernelKeyPart[],
|
|
312
|
+
inputs: readonly Pick<KernelArtifact<unknown>, 'id'>[],
|
|
313
|
+
): KernelOperationKey {
|
|
314
|
+
const signature = JSON.stringify([
|
|
315
|
+
operation,
|
|
316
|
+
arguments_,
|
|
317
|
+
inputs.map(input => input.id),
|
|
318
|
+
]);
|
|
319
|
+
return {id: contentId(signature), signature};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function contentId(value: string): string {
|
|
323
|
+
let first = 0x811c9dc5;
|
|
324
|
+
let second = 0x9e3779b9;
|
|
325
|
+
let third = 0x85ebca6b;
|
|
326
|
+
let fourth = 0xc2b2ae35;
|
|
327
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
328
|
+
const code = value.charCodeAt(index);
|
|
329
|
+
first = Math.imul(first ^ code, 0x01000193);
|
|
330
|
+
second = Math.imul(second ^ code, 0x27d4eb2d);
|
|
331
|
+
third = Math.imul(third ^ code, 0x165667b1);
|
|
332
|
+
fourth = Math.imul(fourth ^ code, 0x85ebca77);
|
|
333
|
+
}
|
|
334
|
+
return [first, second, third, fourth]
|
|
335
|
+
.map(part => (part >>> 0).toString(16).padStart(8, '0'))
|
|
336
|
+
.join('');
|
|
337
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cast,
|
|
3
|
+
getOC,
|
|
4
|
+
type AnyShape,
|
|
5
|
+
type Edge,
|
|
6
|
+
type Face,
|
|
7
|
+
type Shape3D,
|
|
8
|
+
type Vertex,
|
|
9
|
+
type Wire,
|
|
10
|
+
} from 'replicad';
|
|
11
|
+
import type {
|
|
12
|
+
NCollection_List_TopoDS_Shape,
|
|
13
|
+
TopoDS_Shape,
|
|
14
|
+
gp_Trsf,
|
|
15
|
+
} from 'replicad-opencascadejs';
|
|
16
|
+
|
|
17
|
+
export function centeredBoxShape(x: number, y: number, z: number): Shape3D {
|
|
18
|
+
const oc = getOC();
|
|
19
|
+
const corner = new oc.gp_Pnt(-x / 2, -y / 2, -z / 2);
|
|
20
|
+
try {
|
|
21
|
+
const builder = new oc.BRepPrimAPI_MakeBox(corner, x, y, z);
|
|
22
|
+
try {
|
|
23
|
+
return castOwnedShape3D(builder.Shape());
|
|
24
|
+
} finally {
|
|
25
|
+
builder.delete();
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
corner.delete();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Consumes a raw handle; Replicad's cast creates a separate native handle. */
|
|
33
|
+
export function castOwnedShape(shape: TopoDS_Shape): AnyShape {
|
|
34
|
+
try {
|
|
35
|
+
return cast(shape);
|
|
36
|
+
} finally {
|
|
37
|
+
shape.delete();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function castOwnedShape3D(shape: TopoDS_Shape): Shape3D {
|
|
42
|
+
const result = castOwnedShape(shape);
|
|
43
|
+
try {
|
|
44
|
+
return result.asShape3D();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
result.delete();
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type Subshapes = {vertex: Vertex; edge: Edge; face: Face; wire: Wire};
|
|
52
|
+
|
|
53
|
+
/** Returns owned, distinct subshapes in kernel traversal order. */
|
|
54
|
+
export function shapeSubshapes<Kind extends keyof Subshapes>(
|
|
55
|
+
shape: AnyShape,
|
|
56
|
+
kind: Kind,
|
|
57
|
+
): Subshapes[Kind][] {
|
|
58
|
+
const oc = getOC();
|
|
59
|
+
const kinds = {
|
|
60
|
+
vertex: oc.TopAbs_ShapeEnum.TopAbs_VERTEX,
|
|
61
|
+
edge: oc.TopAbs_ShapeEnum.TopAbs_EDGE,
|
|
62
|
+
face: oc.TopAbs_ShapeEnum.TopAbs_FACE,
|
|
63
|
+
wire: oc.TopAbs_ShapeEnum.TopAbs_WIRE,
|
|
64
|
+
};
|
|
65
|
+
const explorer = new oc.TopExp_Explorer(
|
|
66
|
+
shape.wrapped,
|
|
67
|
+
kinds[kind],
|
|
68
|
+
oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
|
|
69
|
+
);
|
|
70
|
+
const values: Subshapes[Kind][] = [];
|
|
71
|
+
try {
|
|
72
|
+
while (explorer.More()) {
|
|
73
|
+
const raw = explorer.Current();
|
|
74
|
+
try {
|
|
75
|
+
if (!values.some(value => value.wrapped.IsSame(raw))) {
|
|
76
|
+
values.push(cast(raw) as Subshapes[Kind]);
|
|
77
|
+
}
|
|
78
|
+
} finally {
|
|
79
|
+
// Current() owns a handle even for duplicate topology occurrences.
|
|
80
|
+
raw.delete();
|
|
81
|
+
}
|
|
82
|
+
explorer.Next();
|
|
83
|
+
}
|
|
84
|
+
return values;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
values.forEach(value => value.delete());
|
|
87
|
+
throw error;
|
|
88
|
+
} finally {
|
|
89
|
+
explorer.delete();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Consumes a native list and transfers its owned element handles to the caller. */
|
|
94
|
+
export function consumeShapeList(
|
|
95
|
+
list: NCollection_List_TopoDS_Shape,
|
|
96
|
+
): TopoDS_Shape[] {
|
|
97
|
+
const shapes: TopoDS_Shape[] = [];
|
|
98
|
+
try {
|
|
99
|
+
while (!list.IsEmpty()) {
|
|
100
|
+
shapes.push(list.First());
|
|
101
|
+
list.RemoveFirst();
|
|
102
|
+
}
|
|
103
|
+
return shapes;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
shapes.forEach(shape => shape.delete());
|
|
106
|
+
throw error;
|
|
107
|
+
} finally {
|
|
108
|
+
list.delete();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Borrows the input and owns every builder/returned native handle. */
|
|
113
|
+
export function transformShape<Shape extends AnyShape>(
|
|
114
|
+
source: Shape,
|
|
115
|
+
configure: (transform: gp_Trsf) => void,
|
|
116
|
+
): Shape {
|
|
117
|
+
const oc = getOC();
|
|
118
|
+
const transform = new oc.gp_Trsf();
|
|
119
|
+
try {
|
|
120
|
+
configure(transform);
|
|
121
|
+
const builder = new oc.BRepBuilderAPI_Transform(
|
|
122
|
+
source.wrapped,
|
|
123
|
+
transform,
|
|
124
|
+
true,
|
|
125
|
+
false,
|
|
126
|
+
);
|
|
127
|
+
try {
|
|
128
|
+
return castOwnedShape(builder.ModifiedShape(source.wrapped)) as Shape;
|
|
129
|
+
} finally {
|
|
130
|
+
builder.delete();
|
|
131
|
+
}
|
|
132
|
+
} finally {
|
|
133
|
+
transform.delete();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
cast,
|
|
3
|
+
getOC,
|
|
4
|
+
type AnyShape,
|
|
5
|
+
type Face,
|
|
6
|
+
type Wire,
|
|
7
|
+
type Shape3D,
|
|
8
|
+
} from 'replicad';
|
|
9
|
+
import type {TopoDS_Shape} from 'replicad-opencascadejs';
|
|
10
|
+
import {
|
|
11
|
+
castOwnedShape,
|
|
12
|
+
castOwnedShape3D,
|
|
13
|
+
consumeShapeList,
|
|
14
|
+
shapeSubshapes,
|
|
15
|
+
} from './kernel-shapes.js';
|
|
16
|
+
import {
|
|
17
|
+
transferShapeTopology,
|
|
18
|
+
booleanWithTopology,
|
|
19
|
+
type TopologyInput,
|
|
20
|
+
type ShapeTopology,
|
|
21
|
+
} from './topology.js';
|
|
22
|
+
|
|
23
|
+
export function loftWithTopology(
|
|
24
|
+
sections: readonly TopologyInput[],
|
|
25
|
+
spine: Wire | undefined,
|
|
26
|
+
ruled: boolean,
|
|
27
|
+
): Readonly<{shape: Shape3D; topology: ShapeTopology}> {
|
|
28
|
+
const holes: Wire[][] = [];
|
|
29
|
+
let outer: {shape: Shape3D; topology: ShapeTopology} | undefined;
|
|
30
|
+
let inner: {shape: Shape3D; topology: ShapeTopology} | undefined;
|
|
31
|
+
try {
|
|
32
|
+
for (const section of sections) {
|
|
33
|
+
const boundary = (section.shape as Face).clone().outerWire();
|
|
34
|
+
try {
|
|
35
|
+
const wires = shapeSubshapes(section.shape, 'wire');
|
|
36
|
+
holes.push(
|
|
37
|
+
wires.filter(wire => {
|
|
38
|
+
if (!wire.isSame(boundary)) return true;
|
|
39
|
+
wire.delete();
|
|
40
|
+
return false;
|
|
41
|
+
}),
|
|
42
|
+
);
|
|
43
|
+
} finally {
|
|
44
|
+
boundary.delete();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (holes.some(wires => wires.length !== holes[0].length))
|
|
48
|
+
throw new Error('Loft sections must have matching hole counts.');
|
|
49
|
+
if (holes[0].length > 1)
|
|
50
|
+
throw new Error(
|
|
51
|
+
'Loft currently supports at most one hole per section; multiple holes need explicit correspondence.',
|
|
52
|
+
);
|
|
53
|
+
outer = loftContoursWithTopology(sections, spine, ruled);
|
|
54
|
+
if (!holes[0].length) {
|
|
55
|
+
const result = outer;
|
|
56
|
+
outer = undefined;
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
inner = loftContoursWithTopology(
|
|
60
|
+
sections,
|
|
61
|
+
spine,
|
|
62
|
+
ruled,
|
|
63
|
+
holes.map(wires => wires[0]),
|
|
64
|
+
);
|
|
65
|
+
return booleanWithTopology(outer, inner, 'cut');
|
|
66
|
+
} finally {
|
|
67
|
+
inner?.shape.delete();
|
|
68
|
+
outer?.shape.delete();
|
|
69
|
+
holes.flat().forEach(wire => wire.delete());
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function loftContoursWithTopology(
|
|
74
|
+
sections: readonly TopologyInput[],
|
|
75
|
+
spine: Wire | undefined,
|
|
76
|
+
ruled: boolean,
|
|
77
|
+
holes?: readonly Wire[],
|
|
78
|
+
): Readonly<{shape: Shape3D; topology: ShapeTopology}> {
|
|
79
|
+
const oc = getOC();
|
|
80
|
+
const builder = spine
|
|
81
|
+
? new oc.BRepOffsetAPI_MakePipeShell(spine.wrapped)
|
|
82
|
+
: new oc.BRepOffsetAPI_ThruSections(true, ruled, 1e-6);
|
|
83
|
+
const wires: Wire[] = [];
|
|
84
|
+
let result: Shape3D | undefined;
|
|
85
|
+
const caps: (Face | undefined)[] = [];
|
|
86
|
+
try {
|
|
87
|
+
for (const [index, section] of sections.entries())
|
|
88
|
+
wires.push(
|
|
89
|
+
holes
|
|
90
|
+
? holes[index].clone()
|
|
91
|
+
: (section.shape as Face).clone().outerWire(),
|
|
92
|
+
);
|
|
93
|
+
if (builder instanceof oc.BRepOffsetAPI_MakePipeShell) {
|
|
94
|
+
builder.SetMode(false);
|
|
95
|
+
for (const wire of wires) builder.Add(wire.wrapped, false, false);
|
|
96
|
+
if (!builder.IsReady())
|
|
97
|
+
throw new Error(
|
|
98
|
+
'The loft sections could not be associated with the spine.',
|
|
99
|
+
);
|
|
100
|
+
builder.Build();
|
|
101
|
+
if (!builder.IsDone() || !builder.MakeSolid())
|
|
102
|
+
throw new Error('Could not construct a solid loft along the spine.');
|
|
103
|
+
} else {
|
|
104
|
+
builder.SetMutableInput(false);
|
|
105
|
+
for (const wire of wires) builder.AddWire(wire.wrapped);
|
|
106
|
+
builder.Build();
|
|
107
|
+
}
|
|
108
|
+
result = castOwnedShape3D(builder.Shape());
|
|
109
|
+
caps[0] = castOwnedShape(builder.FirstShape()) as Face;
|
|
110
|
+
caps[sections.length - 1] = castOwnedShape(builder.LastShape()) as Face;
|
|
111
|
+
const topology = transferShapeTopology(
|
|
112
|
+
sections,
|
|
113
|
+
result,
|
|
114
|
+
(input, kind, index) => {
|
|
115
|
+
const cap = caps[index];
|
|
116
|
+
// The builder consumes wires, so the profile faces need explicit cap history.
|
|
117
|
+
if (kind === 'surface')
|
|
118
|
+
return cap && !holes ? [copyShapeHandle(cap.wrapped)] : [];
|
|
119
|
+
const modified = consumeShapeList(builder.Modified(input));
|
|
120
|
+
if (!cap && kind === 'vertex') return modified;
|
|
121
|
+
let generated: TopoDS_Shape[] = [];
|
|
122
|
+
let capParts: AnyShape[] = [];
|
|
123
|
+
let source: AnyShape | undefined;
|
|
124
|
+
try {
|
|
125
|
+
generated = consumeShapeList(builder.Generated(input));
|
|
126
|
+
if (cap) capParts = shapeSubshapes(cap, kind);
|
|
127
|
+
else source = cast(input);
|
|
128
|
+
// Sweeps generate side faces from edges and side edges from vertices.
|
|
129
|
+
// Their intersection with this section's cap identifies its descendants,
|
|
130
|
+
// including splits, without relying on traversal or geometric proximity.
|
|
131
|
+
for (const raw of generated) {
|
|
132
|
+
const shape = cast(raw);
|
|
133
|
+
try {
|
|
134
|
+
const parts = shapeSubshapes(shape, kind);
|
|
135
|
+
try {
|
|
136
|
+
for (const part of capParts) {
|
|
137
|
+
if (parts.some(candidate => candidate.isSame(part)))
|
|
138
|
+
modified.push(copyShapeHandle(part.wrapped));
|
|
139
|
+
}
|
|
140
|
+
// Ruled lofts can copy intermediate profile edges while
|
|
141
|
+
// retaining their vertices. Generated side faces plus the
|
|
142
|
+
// exact endpoint identities establish the copied edge history.
|
|
143
|
+
if (source)
|
|
144
|
+
for (const part of parts)
|
|
145
|
+
if (sameVertices(source, part))
|
|
146
|
+
modified.push(copyShapeHandle(part.wrapped));
|
|
147
|
+
} finally {
|
|
148
|
+
parts.forEach(part => part.delete());
|
|
149
|
+
}
|
|
150
|
+
} finally {
|
|
151
|
+
shape.delete();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return modified;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
modified.forEach(shape => shape.delete());
|
|
157
|
+
throw error;
|
|
158
|
+
} finally {
|
|
159
|
+
generated.forEach(shape => shape.delete());
|
|
160
|
+
capParts.forEach(part => part.delete());
|
|
161
|
+
source?.delete();
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
return {shape: result, topology};
|
|
166
|
+
} catch (error) {
|
|
167
|
+
result?.delete();
|
|
168
|
+
throw error;
|
|
169
|
+
} finally {
|
|
170
|
+
caps.forEach(cap => cap?.delete());
|
|
171
|
+
wires.forEach(wire => wire.delete());
|
|
172
|
+
builder.delete();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function copyShapeHandle(shape: TopoDS_Shape): TopoDS_Shape {
|
|
177
|
+
return shape.Oriented(shape.Orientation());
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function sameVertices(left: AnyShape, right: AnyShape): boolean {
|
|
181
|
+
const leftVertices = shapeSubshapes(left, 'vertex');
|
|
182
|
+
let rightVertices: AnyShape[] = [];
|
|
183
|
+
try {
|
|
184
|
+
rightVertices = shapeSubshapes(right, 'vertex');
|
|
185
|
+
return (
|
|
186
|
+
leftVertices.length > 0 &&
|
|
187
|
+
leftVertices.length === rightVertices.length &&
|
|
188
|
+
leftVertices.every(vertex =>
|
|
189
|
+
rightVertices.some(candidate => candidate.isSame(vertex)),
|
|
190
|
+
)
|
|
191
|
+
);
|
|
192
|
+
} finally {
|
|
193
|
+
leftVertices.forEach(vertex => vertex.delete());
|
|
194
|
+
rightVertices.forEach(vertex => vertex.delete());
|
|
195
|
+
}
|
|
196
|
+
}
|