circuit-json-to-step 0.0.2 → 0.0.4
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/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +77 -0
- package/.github/workflows/bun-test.yml +31 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/README.md +1 -3
- package/bunfig.toml +2 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +465 -40
- package/lib/index.ts +33 -5
- package/lib/mesh-generation.ts +29 -1
- package/lib/step-model-merger/excluded-entity-types.ts +27 -0
- package/lib/step-model-merger/read-step-file.ts +23 -0
- package/lib/step-model-merger/types.ts +46 -0
- package/lib/step-model-merger/vector-utils.ts +74 -0
- package/lib/step-model-merger.ts +394 -0
- package/lib/step-text-utils.ts +6 -0
- package/package.json +8 -3
- package/test/basics/basics01/__snapshots__/basics01.snap.png +0 -0
- package/test/basics/basics01/basics01.test.ts +3 -2
- package/test/basics/basics02/__snapshots__/basics02.snap.png +0 -0
- package/test/basics/basics02/basics02.test.ts +3 -2
- package/test/basics/basics03/__snapshots__/basics03.snap.png +0 -0
- package/test/basics/basics03/basics03.test.ts +3 -2
- package/test/basics/basics04/__snapshots__/basics04.snap.png +0 -0
- package/test/basics/basics04/basics04.test.ts +3 -2
- package/test/basics/basics05/__snapshots__/basics05.snap.png +0 -0
- package/test/basics/basics05/basics05.json +40 -0
- package/test/basics/basics05/basics05.test.ts +55 -0
- package/test/fixtures/kicad-models/Panasonic_EVQPUJ_EVQPUA.step +3093 -0
- package/test/fixtures/kicad-models/R_0603_1608Metric.step +1049 -0
- package/test/fixtures/kicad-models/SW_Push_1P1T_NO_CK_KMR2.step +2916 -0
- package/test/fixtures/png-matcher.ts +173 -0
- package/test/fixtures/step-snapshot.ts +249 -0
- package/test/repros/kicad-step/__snapshots__/kicad-step-board.snap.png +0 -0
- package/test/repros/kicad-step/__snapshots__/resistor-fixture.snap.png +0 -0
- package/test/repros/kicad-step/__snapshots__/switch-fixture.snap.png +0 -0
- package/test/repros/kicad-step/kicad-step.json +163 -0
- package/test/repros/kicad-step/kicad-step.test.ts +211 -0
- package/test/repros/repro01/__snapshots__/repro01.snap.png +0 -0
- package/test/repros/repro01/repro01.test.ts +3 -2
- package/test/utils/load-step-files.ts +41 -0
- package/types/occt-import-js.d.ts +33 -0
- package/.claude/settings.local.json +0 -16
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// lib/index.ts
|
|
2
2
|
import {
|
|
3
|
-
Repository,
|
|
3
|
+
Repository as Repository2,
|
|
4
4
|
ApplicationContext,
|
|
5
5
|
ApplicationProtocolDefinition,
|
|
6
6
|
ProductContext,
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
ProductDefinitionFormation,
|
|
10
10
|
ProductDefinition,
|
|
11
11
|
ProductDefinitionShape,
|
|
12
|
-
Unknown,
|
|
13
|
-
CartesianPoint as
|
|
14
|
-
Direction as
|
|
12
|
+
Unknown as Unknown2,
|
|
13
|
+
CartesianPoint as CartesianPoint3,
|
|
14
|
+
Direction as Direction3,
|
|
15
15
|
Axis2Placement3D as Axis2Placement3D2,
|
|
16
16
|
Plane as Plane2,
|
|
17
17
|
CylindricalSurface,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
AdvancedFace as AdvancedFace2,
|
|
27
27
|
Circle,
|
|
28
28
|
ClosedShell as ClosedShell2,
|
|
29
|
-
ManifoldSolidBrep as
|
|
29
|
+
ManifoldSolidBrep as ManifoldSolidBrep3,
|
|
30
30
|
ColourRgb,
|
|
31
31
|
FillAreaStyleColour,
|
|
32
32
|
FillAreaStyle,
|
|
@@ -233,11 +233,25 @@ async function generateComponentMeshes(options) {
|
|
|
233
233
|
repo,
|
|
234
234
|
circuitJson,
|
|
235
235
|
boardThickness,
|
|
236
|
-
includeExternalMeshes = false
|
|
236
|
+
includeExternalMeshes = false,
|
|
237
|
+
excludeCadComponentIds,
|
|
238
|
+
excludePcbComponentIds
|
|
237
239
|
} = options;
|
|
238
240
|
const solids = [];
|
|
239
241
|
try {
|
|
240
|
-
const filteredCircuitJson = circuitJson.filter((e) =>
|
|
242
|
+
const filteredCircuitJson = circuitJson.filter((e) => {
|
|
243
|
+
if (e.type === "pcb_board") return false;
|
|
244
|
+
if (e.type === "cad_component" && e.cad_component_id && excludeCadComponentIds?.has(e.cad_component_id)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (e.type === "pcb_component" && e.pcb_component_id && excludePcbComponentIds?.has(e.pcb_component_id)) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (e.type === "cad_component" && e.model_step_url) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}).map((e) => {
|
|
241
255
|
if (!includeExternalMeshes && e.type === "cad_component") {
|
|
242
256
|
return {
|
|
243
257
|
...e,
|
|
@@ -298,9 +312,402 @@ async function generateComponentMeshes(options) {
|
|
|
298
312
|
return solids;
|
|
299
313
|
}
|
|
300
314
|
|
|
315
|
+
// lib/step-model-merger.ts
|
|
316
|
+
import {
|
|
317
|
+
CartesianPoint as CartesianPoint2,
|
|
318
|
+
Direction as Direction2,
|
|
319
|
+
ManifoldSolidBrep as ManifoldSolidBrep2,
|
|
320
|
+
Ref,
|
|
321
|
+
Unknown,
|
|
322
|
+
parseRepository
|
|
323
|
+
} from "stepts";
|
|
324
|
+
import { eid } from "stepts/lib/core/EntityId";
|
|
325
|
+
|
|
326
|
+
// lib/step-model-merger/excluded-entity-types.ts
|
|
327
|
+
var EXCLUDED_ENTITY_TYPES = /* @__PURE__ */ new Set([
|
|
328
|
+
"APPLICATION_CONTEXT",
|
|
329
|
+
"APPLICATION_PROTOCOL_DEFINITION",
|
|
330
|
+
"PRODUCT",
|
|
331
|
+
"PRODUCT_CONTEXT",
|
|
332
|
+
"PRODUCT_DEFINITION",
|
|
333
|
+
"PRODUCT_DEFINITION_FORMATION",
|
|
334
|
+
"PRODUCT_DEFINITION_CONTEXT",
|
|
335
|
+
"PRODUCT_DEFINITION_SHAPE",
|
|
336
|
+
"SHAPE_DEFINITION_REPRESENTATION",
|
|
337
|
+
"ADVANCED_BREP_SHAPE_REPRESENTATION",
|
|
338
|
+
"MECHANICAL_DESIGN_GEOMETRIC_PRESENTATION_REPRESENTATION",
|
|
339
|
+
"PRESENTATION_STYLE_ASSIGNMENT",
|
|
340
|
+
"SURFACE_STYLE_USAGE",
|
|
341
|
+
"SURFACE_SIDE_STYLE",
|
|
342
|
+
"SURFACE_STYLE_FILL_AREA",
|
|
343
|
+
"FILL_AREA_STYLE",
|
|
344
|
+
"FILL_AREA_STYLE_COLOUR",
|
|
345
|
+
"COLOUR_RGB",
|
|
346
|
+
"STYLED_ITEM",
|
|
347
|
+
"CURVE_STYLE",
|
|
348
|
+
"DRAUGHTING_PRE_DEFINED_CURVE_FONT",
|
|
349
|
+
"PRODUCT_RELATED_PRODUCT_CATEGORY",
|
|
350
|
+
"NEXT_ASSEMBLY_USAGE_OCCURRENCE",
|
|
351
|
+
"CONTEXT_DEPENDENT_SHAPE_REPRESENTATION",
|
|
352
|
+
"ITEM_DEFINED_TRANSFORMATION"
|
|
353
|
+
]);
|
|
354
|
+
|
|
355
|
+
// lib/step-model-merger/vector-utils.ts
|
|
356
|
+
function asVector3(value) {
|
|
357
|
+
return {
|
|
358
|
+
x: value?.x ?? 0,
|
|
359
|
+
y: value?.y ?? 0,
|
|
360
|
+
z: value?.z ?? 0
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function toRadians(rotation) {
|
|
364
|
+
const factor = Math.PI / 180;
|
|
365
|
+
return {
|
|
366
|
+
x: rotation.x * factor,
|
|
367
|
+
y: rotation.y * factor,
|
|
368
|
+
z: rotation.z * factor
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function transformPoint(point, rotation, translation) {
|
|
372
|
+
const rotated = rotateVector(point, rotation);
|
|
373
|
+
return [
|
|
374
|
+
rotated[0] + translation.x,
|
|
375
|
+
rotated[1] + translation.y,
|
|
376
|
+
rotated[2] + translation.z
|
|
377
|
+
];
|
|
378
|
+
}
|
|
379
|
+
function transformDirection(vector, rotation) {
|
|
380
|
+
return rotateVector(vector, rotation);
|
|
381
|
+
}
|
|
382
|
+
function rotateVector(vector, rotation) {
|
|
383
|
+
let [x, y, z] = vector;
|
|
384
|
+
if (rotation.x !== 0) {
|
|
385
|
+
const cosX = Math.cos(rotation.x);
|
|
386
|
+
const sinX = Math.sin(rotation.x);
|
|
387
|
+
const y1 = y * cosX - z * sinX;
|
|
388
|
+
const z1 = y * sinX + z * cosX;
|
|
389
|
+
y = y1;
|
|
390
|
+
z = z1;
|
|
391
|
+
}
|
|
392
|
+
if (rotation.y !== 0) {
|
|
393
|
+
const cosY = Math.cos(rotation.y);
|
|
394
|
+
const sinY = Math.sin(rotation.y);
|
|
395
|
+
const x1 = x * cosY + z * sinY;
|
|
396
|
+
const z1 = -x * sinY + z * cosY;
|
|
397
|
+
x = x1;
|
|
398
|
+
z = z1;
|
|
399
|
+
}
|
|
400
|
+
if (rotation.z !== 0) {
|
|
401
|
+
const cosZ = Math.cos(rotation.z);
|
|
402
|
+
const sinZ = Math.sin(rotation.z);
|
|
403
|
+
const x1 = x * cosZ - y * sinZ;
|
|
404
|
+
const y1 = x * sinZ + y * cosZ;
|
|
405
|
+
x = x1;
|
|
406
|
+
y = y1;
|
|
407
|
+
}
|
|
408
|
+
return [x, y, z];
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// lib/step-model-merger/read-step-file.ts
|
|
412
|
+
async function readStepFile(modelUrl) {
|
|
413
|
+
if (!/^https?:\/\//i.test(modelUrl)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`Only HTTP(S) URLs are supported. For local files, read the file content and pass it directly. Received: ${modelUrl}`
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const globalFetch = globalThis.fetch;
|
|
419
|
+
if (!globalFetch) {
|
|
420
|
+
throw new Error("fetch is not available in this environment");
|
|
421
|
+
}
|
|
422
|
+
const res = await globalFetch(modelUrl);
|
|
423
|
+
if (!res.ok) {
|
|
424
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
425
|
+
}
|
|
426
|
+
return await res.text();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// lib/step-model-merger.ts
|
|
430
|
+
async function mergeExternalStepModels(options) {
|
|
431
|
+
const { repo, circuitJson, boardThickness, fsMap } = options;
|
|
432
|
+
const cadComponents = circuitJson.filter(
|
|
433
|
+
(item) => item?.type === "cad_component" && typeof item.model_step_url === "string"
|
|
434
|
+
);
|
|
435
|
+
const pcbComponentMap = /* @__PURE__ */ new Map();
|
|
436
|
+
for (const item of circuitJson) {
|
|
437
|
+
if (item?.type === "pcb_component" && item.pcb_component_id) {
|
|
438
|
+
pcbComponentMap.set(item.pcb_component_id, item);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const solids = [];
|
|
442
|
+
const handledComponentIds = /* @__PURE__ */ new Set();
|
|
443
|
+
const handledPcbComponentIds = /* @__PURE__ */ new Set();
|
|
444
|
+
for (const component of cadComponents) {
|
|
445
|
+
const componentId = component.cad_component_id ?? "";
|
|
446
|
+
const stepUrl = component.model_step_url;
|
|
447
|
+
try {
|
|
448
|
+
const stepText = fsMap?.[stepUrl] ?? await readStepFile(stepUrl);
|
|
449
|
+
if (!stepText.trim()) {
|
|
450
|
+
throw new Error("STEP file is empty");
|
|
451
|
+
}
|
|
452
|
+
const pcbComponent = component.pcb_component_id ? pcbComponentMap.get(component.pcb_component_id) : void 0;
|
|
453
|
+
const layer = pcbComponent?.layer?.toLowerCase();
|
|
454
|
+
const transform = {
|
|
455
|
+
translation: asVector3(component.position),
|
|
456
|
+
rotation: asVector3(component.rotation)
|
|
457
|
+
};
|
|
458
|
+
const componentSolids = mergeSingleStepModel(repo, stepText, transform, {
|
|
459
|
+
layer,
|
|
460
|
+
boardThickness
|
|
461
|
+
});
|
|
462
|
+
if (componentSolids.length > 0) {
|
|
463
|
+
if (componentId) {
|
|
464
|
+
handledComponentIds.add(componentId);
|
|
465
|
+
}
|
|
466
|
+
const pcbComponentId = component.pcb_component_id;
|
|
467
|
+
if (pcbComponentId) {
|
|
468
|
+
handledPcbComponentIds.add(pcbComponentId);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
solids.push(...componentSolids);
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.warn(`Failed to merge STEP model from ${stepUrl}:`, error);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return { solids, handledComponentIds, handledPcbComponentIds };
|
|
477
|
+
}
|
|
478
|
+
function mergeSingleStepModel(targetRepo, stepText, transform, placement) {
|
|
479
|
+
const sourceRepo = parseRepository(stepText);
|
|
480
|
+
let entries = sourceRepo.entries().map(([id, entity]) => [Number(id), entity]).filter(([, entity]) => !EXCLUDED_ENTITY_TYPES.has(entity.type));
|
|
481
|
+
entries = pruneInvalidEntries(entries);
|
|
482
|
+
adjustTransformForPlacement(entries, transform, placement);
|
|
483
|
+
applyTransform(entries, transform);
|
|
484
|
+
const idMapping = allocateIds(targetRepo, entries);
|
|
485
|
+
remapReferences(entries, idMapping);
|
|
486
|
+
for (const [oldId, entity] of entries) {
|
|
487
|
+
const mappedId = idMapping.get(oldId);
|
|
488
|
+
if (mappedId === void 0) continue;
|
|
489
|
+
targetRepo.set(eid(mappedId), entity);
|
|
490
|
+
}
|
|
491
|
+
const solids = [];
|
|
492
|
+
for (const [oldId, entity] of entries) {
|
|
493
|
+
if (entity instanceof ManifoldSolidBrep2) {
|
|
494
|
+
const mappedId = idMapping.get(oldId);
|
|
495
|
+
if (mappedId !== void 0) {
|
|
496
|
+
solids.push(new Ref(eid(mappedId)));
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return solids;
|
|
501
|
+
}
|
|
502
|
+
function adjustTransformForPlacement(entries, transform, placement) {
|
|
503
|
+
if (!placement) return;
|
|
504
|
+
const points = [];
|
|
505
|
+
for (const [, entity] of entries) {
|
|
506
|
+
if (entity instanceof CartesianPoint2) {
|
|
507
|
+
points.push([entity.x, entity.y, entity.z]);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (!points.length) return;
|
|
511
|
+
const rotationRadians = toRadians(transform.rotation);
|
|
512
|
+
let minX = Infinity;
|
|
513
|
+
let minY = Infinity;
|
|
514
|
+
let minZ = Infinity;
|
|
515
|
+
let maxX = -Infinity;
|
|
516
|
+
let maxY = -Infinity;
|
|
517
|
+
let maxZ = -Infinity;
|
|
518
|
+
for (const point of points) {
|
|
519
|
+
const [x, y, z] = rotateVector(point, rotationRadians);
|
|
520
|
+
if (x < minX) minX = x;
|
|
521
|
+
if (y < minY) minY = y;
|
|
522
|
+
if (z < minZ) minZ = z;
|
|
523
|
+
if (x > maxX) maxX = x;
|
|
524
|
+
if (y > maxY) maxY = y;
|
|
525
|
+
if (z > maxZ) maxZ = z;
|
|
526
|
+
}
|
|
527
|
+
if (!Number.isFinite(minX)) return;
|
|
528
|
+
const center = {
|
|
529
|
+
x: (minX + maxX) / 2,
|
|
530
|
+
y: (minY + maxY) / 2,
|
|
531
|
+
z: (minZ + maxZ) / 2
|
|
532
|
+
};
|
|
533
|
+
const normalizedLayer = placement.layer?.toLowerCase() === "bottom" ? "bottom" : "top";
|
|
534
|
+
const boardThickness = placement.boardThickness ?? 0;
|
|
535
|
+
const halfThickness = boardThickness / 2;
|
|
536
|
+
const targetX = transform.translation.x;
|
|
537
|
+
const targetY = transform.translation.y;
|
|
538
|
+
const targetZ = transform.translation.z;
|
|
539
|
+
transform.translation.x = targetX - center.x;
|
|
540
|
+
transform.translation.y = targetY - center.y;
|
|
541
|
+
if (boardThickness > 0) {
|
|
542
|
+
const offsetZ = targetZ - halfThickness;
|
|
543
|
+
if (normalizedLayer === "bottom") {
|
|
544
|
+
transform.translation.z = -maxZ + offsetZ;
|
|
545
|
+
transform.rotation.x = normalizeDegrees(transform.rotation.x + 180);
|
|
546
|
+
} else {
|
|
547
|
+
transform.translation.z = boardThickness - minZ + offsetZ;
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
transform.translation.z = targetZ - center.z;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
function normalizeDegrees(value) {
|
|
554
|
+
const wrapped = value % 360;
|
|
555
|
+
return wrapped < 0 ? wrapped + 360 : wrapped;
|
|
556
|
+
}
|
|
557
|
+
function pruneInvalidEntries(entries) {
|
|
558
|
+
let remaining = entries.slice();
|
|
559
|
+
let remainingIds = new Set(remaining.map(([id]) => id));
|
|
560
|
+
let changed = true;
|
|
561
|
+
while (changed) {
|
|
562
|
+
changed = false;
|
|
563
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
564
|
+
for (const [entityId, entity] of remaining) {
|
|
565
|
+
const refs = collectReferencedIds(entity);
|
|
566
|
+
for (const refId of refs) {
|
|
567
|
+
if (!remainingIds.has(refId)) {
|
|
568
|
+
toRemove.add(entityId);
|
|
569
|
+
break;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (toRemove.size > 0) {
|
|
574
|
+
changed = true;
|
|
575
|
+
remaining = remaining.filter(([id]) => !toRemove.has(id));
|
|
576
|
+
remainingIds = new Set(remaining.map(([id]) => id));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
return remaining;
|
|
580
|
+
}
|
|
581
|
+
function collectReferencedIds(entity) {
|
|
582
|
+
const result = /* @__PURE__ */ new Set();
|
|
583
|
+
collectReferencedIdsRecursive(entity, result, /* @__PURE__ */ new Set());
|
|
584
|
+
return result;
|
|
585
|
+
}
|
|
586
|
+
function collectReferencedIdsRecursive(value, result, seen) {
|
|
587
|
+
if (!value) return;
|
|
588
|
+
if (value instanceof Ref) {
|
|
589
|
+
result.add(Number(value.id));
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (value instanceof Unknown) {
|
|
593
|
+
for (const arg of value.args) {
|
|
594
|
+
arg.replace(/#(\d+)/g, (_, num) => {
|
|
595
|
+
result.add(Number(num));
|
|
596
|
+
return _;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (Array.isArray(value)) {
|
|
602
|
+
for (const item of value) {
|
|
603
|
+
collectReferencedIdsRecursive(item, result, seen);
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (typeof value === "object") {
|
|
608
|
+
if (seen.has(value)) {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
seen.add(value);
|
|
612
|
+
for (const entry of Object.values(value)) {
|
|
613
|
+
collectReferencedIdsRecursive(entry, result, seen);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
function applyTransform(entries, transform) {
|
|
618
|
+
const rotation = toRadians(transform.rotation);
|
|
619
|
+
for (const [, entity] of entries) {
|
|
620
|
+
if (entity instanceof CartesianPoint2) {
|
|
621
|
+
const [x, y, z] = transformPoint(
|
|
622
|
+
[entity.x, entity.y, entity.z],
|
|
623
|
+
rotation,
|
|
624
|
+
transform.translation
|
|
625
|
+
);
|
|
626
|
+
entity.x = x;
|
|
627
|
+
entity.y = y;
|
|
628
|
+
entity.z = z;
|
|
629
|
+
} else if (entity instanceof Direction2) {
|
|
630
|
+
const [dx, dy, dz] = transformDirection(
|
|
631
|
+
[entity.dx, entity.dy, entity.dz],
|
|
632
|
+
rotation
|
|
633
|
+
);
|
|
634
|
+
const length = Math.hypot(dx, dy, dz);
|
|
635
|
+
if (length > 0) {
|
|
636
|
+
entity.dx = dx / length;
|
|
637
|
+
entity.dy = dy / length;
|
|
638
|
+
entity.dz = dz / length;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
function allocateIds(targetRepo, entries) {
|
|
644
|
+
let nextId = getNextEntityId(targetRepo);
|
|
645
|
+
const idMapping = /* @__PURE__ */ new Map();
|
|
646
|
+
for (const [oldId] of entries) {
|
|
647
|
+
idMapping.set(oldId, nextId);
|
|
648
|
+
nextId += 1;
|
|
649
|
+
}
|
|
650
|
+
return idMapping;
|
|
651
|
+
}
|
|
652
|
+
function getNextEntityId(repo) {
|
|
653
|
+
let maxId = 0;
|
|
654
|
+
for (const [id] of repo.entries()) {
|
|
655
|
+
const numericId = Number(id);
|
|
656
|
+
if (numericId > maxId) {
|
|
657
|
+
maxId = numericId;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return maxId + 1;
|
|
661
|
+
}
|
|
662
|
+
function remapReferences(entries, idMapping) {
|
|
663
|
+
for (const [, entity] of entries) {
|
|
664
|
+
remapValue(entity, idMapping, /* @__PURE__ */ new Set());
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function remapValue(value, idMapping, seen) {
|
|
668
|
+
if (!value) return;
|
|
669
|
+
if (value instanceof Ref) {
|
|
670
|
+
const mapped = idMapping.get(Number(value.id));
|
|
671
|
+
if (mapped !== void 0) {
|
|
672
|
+
value.id = eid(mapped);
|
|
673
|
+
}
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (value instanceof Unknown) {
|
|
677
|
+
value.args = value.args.map(
|
|
678
|
+
(arg) => arg.replace(/#(\d+)/g, (match, num) => {
|
|
679
|
+
const mapped = idMapping.get(Number(num));
|
|
680
|
+
return mapped !== void 0 ? `#${mapped}` : match;
|
|
681
|
+
})
|
|
682
|
+
);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
if (Array.isArray(value)) {
|
|
686
|
+
for (const item of value) {
|
|
687
|
+
remapValue(item, idMapping, seen);
|
|
688
|
+
}
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (typeof value === "object") {
|
|
692
|
+
if (seen.has(value)) return;
|
|
693
|
+
seen.add(value);
|
|
694
|
+
for (const key of Object.keys(value)) {
|
|
695
|
+
remapValue(value[key], idMapping, seen);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// lib/step-text-utils.ts
|
|
701
|
+
function normalizeStepNumericExponents(stepText) {
|
|
702
|
+
return stepText.replace(
|
|
703
|
+
/(-?(?:\d+\.\d*|\.\d+|\d+))e([+-]?\d+)/g,
|
|
704
|
+
(_match, mantissa, exponent) => `${mantissa}E${exponent}`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
301
708
|
// lib/index.ts
|
|
302
709
|
async function circuitJsonToStep(circuitJson, options = {}) {
|
|
303
|
-
const repo = new
|
|
710
|
+
const repo = new Repository2();
|
|
304
711
|
const pcbBoard = circuitJson.find((item) => item.type === "pcb_board");
|
|
305
712
|
const holes = circuitJson.filter(
|
|
306
713
|
(item) => item.type === "pcb_hole" || item.type === "pcb_plated_hole"
|
|
@@ -348,22 +755,22 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
348
755
|
new ProductDefinitionShape("", "", productDef)
|
|
349
756
|
);
|
|
350
757
|
const lengthUnit = repo.add(
|
|
351
|
-
new
|
|
758
|
+
new Unknown2("", [
|
|
352
759
|
"( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) )"
|
|
353
760
|
])
|
|
354
761
|
);
|
|
355
762
|
const angleUnit = repo.add(
|
|
356
|
-
new
|
|
763
|
+
new Unknown2("", [
|
|
357
764
|
"( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) )"
|
|
358
765
|
])
|
|
359
766
|
);
|
|
360
767
|
const solidAngleUnit = repo.add(
|
|
361
|
-
new
|
|
768
|
+
new Unknown2("", [
|
|
362
769
|
"( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() )"
|
|
363
770
|
])
|
|
364
771
|
);
|
|
365
772
|
const uncertainty = repo.add(
|
|
366
|
-
new
|
|
773
|
+
new Unknown2("UNCERTAINTY_MEASURE_WITH_UNIT", [
|
|
367
774
|
`LENGTH_MEASURE(1.E-07)`,
|
|
368
775
|
`${lengthUnit}`,
|
|
369
776
|
`'distance_accuracy_value'`,
|
|
@@ -371,7 +778,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
371
778
|
])
|
|
372
779
|
);
|
|
373
780
|
const geomContext = repo.add(
|
|
374
|
-
new
|
|
781
|
+
new Unknown2("", [
|
|
375
782
|
`( GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((${uncertainty})) GLOBAL_UNIT_ASSIGNED_CONTEXT((${lengthUnit},${angleUnit},${solidAngleUnit})) REPRESENTATION_CONTEXT('${productName}','3D') )`
|
|
376
783
|
])
|
|
377
784
|
);
|
|
@@ -383,7 +790,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
383
790
|
(point) => repo.add(
|
|
384
791
|
new VertexPoint2(
|
|
385
792
|
"",
|
|
386
|
-
repo.add(new
|
|
793
|
+
repo.add(new CartesianPoint3("", point.x, point.y, 0))
|
|
387
794
|
)
|
|
388
795
|
)
|
|
389
796
|
);
|
|
@@ -391,7 +798,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
391
798
|
(point) => repo.add(
|
|
392
799
|
new VertexPoint2(
|
|
393
800
|
"",
|
|
394
|
-
repo.add(new
|
|
801
|
+
repo.add(new CartesianPoint3("", point.x, point.y, boardThickness))
|
|
395
802
|
)
|
|
396
803
|
)
|
|
397
804
|
);
|
|
@@ -410,7 +817,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
410
817
|
];
|
|
411
818
|
const vertices = corners.map(
|
|
412
819
|
([x, y, z]) => repo.add(
|
|
413
|
-
new VertexPoint2("", repo.add(new
|
|
820
|
+
new VertexPoint2("", repo.add(new CartesianPoint3("", x, y, z)))
|
|
414
821
|
)
|
|
415
822
|
);
|
|
416
823
|
bottomVertices = [vertices[0], vertices[1], vertices[2], vertices[3]];
|
|
@@ -420,7 +827,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
420
827
|
const p1 = v1.resolve(repo).pnt.resolve(repo);
|
|
421
828
|
const p2 = v2.resolve(repo).pnt.resolve(repo);
|
|
422
829
|
const dir = repo.add(
|
|
423
|
-
new
|
|
830
|
+
new Direction3("", p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
|
|
424
831
|
);
|
|
425
832
|
const vec = repo.add(new Vector2("", dir, 1));
|
|
426
833
|
const line = repo.add(new Line2("", v1.resolve(repo).pnt, vec));
|
|
@@ -442,14 +849,14 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
442
849
|
for (let i = 0; i < bottomVertices.length; i++) {
|
|
443
850
|
verticalEdges.push(createEdge(bottomVertices[i], topVertices[i]));
|
|
444
851
|
}
|
|
445
|
-
const origin = repo.add(new
|
|
446
|
-
const xDir = repo.add(new
|
|
447
|
-
const zDir = repo.add(new
|
|
852
|
+
const origin = repo.add(new CartesianPoint3("", 0, 0, 0));
|
|
853
|
+
const xDir = repo.add(new Direction3("", 1, 0, 0));
|
|
854
|
+
const zDir = repo.add(new Direction3("", 0, 0, 1));
|
|
448
855
|
const bottomFrame = repo.add(
|
|
449
856
|
new Axis2Placement3D2(
|
|
450
857
|
"",
|
|
451
858
|
origin,
|
|
452
|
-
repo.add(new
|
|
859
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
453
860
|
xDir
|
|
454
861
|
)
|
|
455
862
|
);
|
|
@@ -467,18 +874,18 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
467
874
|
const holeX = typeof hole.x === "number" ? hole.x : hole.x.value;
|
|
468
875
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
469
876
|
const radius = hole.hole_diameter / 2;
|
|
470
|
-
const holeCenter = repo.add(new
|
|
877
|
+
const holeCenter = repo.add(new CartesianPoint3("", holeX, holeY, 0));
|
|
471
878
|
const holeVertex = repo.add(
|
|
472
879
|
new VertexPoint2(
|
|
473
880
|
"",
|
|
474
|
-
repo.add(new
|
|
881
|
+
repo.add(new CartesianPoint3("", holeX + radius, holeY, 0))
|
|
475
882
|
)
|
|
476
883
|
);
|
|
477
884
|
const holePlacement = repo.add(
|
|
478
885
|
new Axis2Placement3D2(
|
|
479
886
|
"",
|
|
480
887
|
holeCenter,
|
|
481
|
-
repo.add(new
|
|
888
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
482
889
|
xDir
|
|
483
890
|
)
|
|
484
891
|
);
|
|
@@ -503,7 +910,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
503
910
|
true
|
|
504
911
|
)
|
|
505
912
|
);
|
|
506
|
-
const topOrigin = repo.add(new
|
|
913
|
+
const topOrigin = repo.add(new CartesianPoint3("", 0, 0, boardThickness));
|
|
507
914
|
const topFrame = repo.add(new Axis2Placement3D2("", topOrigin, zDir, xDir));
|
|
508
915
|
const topPlane = repo.add(new Plane2("", topFrame));
|
|
509
916
|
const topLoop = repo.add(
|
|
@@ -520,13 +927,13 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
520
927
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
521
928
|
const radius = hole.hole_diameter / 2;
|
|
522
929
|
const holeCenter = repo.add(
|
|
523
|
-
new
|
|
930
|
+
new CartesianPoint3("", holeX, holeY, boardThickness)
|
|
524
931
|
);
|
|
525
932
|
const holeVertex = repo.add(
|
|
526
933
|
new VertexPoint2(
|
|
527
934
|
"",
|
|
528
935
|
repo.add(
|
|
529
|
-
new
|
|
936
|
+
new CartesianPoint3("", holeX + radius, holeY, boardThickness)
|
|
530
937
|
)
|
|
531
938
|
)
|
|
532
939
|
);
|
|
@@ -563,8 +970,8 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
563
970
|
y: bottomV2.y - bottomV1.y,
|
|
564
971
|
z: 0
|
|
565
972
|
};
|
|
566
|
-
const normalDir = repo.add(new
|
|
567
|
-
const refDir = repo.add(new
|
|
973
|
+
const normalDir = repo.add(new Direction3("", edgeDir.y, -edgeDir.x, 0));
|
|
974
|
+
const refDir = repo.add(new Direction3("", edgeDir.x, edgeDir.y, 0));
|
|
568
975
|
const sideFrame = repo.add(
|
|
569
976
|
new Axis2Placement3D2("", bottomV1Pnt, normalDir, refDir)
|
|
570
977
|
);
|
|
@@ -594,18 +1001,18 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
594
1001
|
const holeX = typeof hole.x === "number" ? hole.x : hole.x.value;
|
|
595
1002
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
596
1003
|
const radius = hole.hole_diameter / 2;
|
|
597
|
-
const bottomHoleCenter = repo.add(new
|
|
1004
|
+
const bottomHoleCenter = repo.add(new CartesianPoint3("", holeX, holeY, 0));
|
|
598
1005
|
const bottomHoleVertex = repo.add(
|
|
599
1006
|
new VertexPoint2(
|
|
600
1007
|
"",
|
|
601
|
-
repo.add(new
|
|
1008
|
+
repo.add(new CartesianPoint3("", holeX + radius, holeY, 0))
|
|
602
1009
|
)
|
|
603
1010
|
);
|
|
604
1011
|
const bottomHolePlacement = repo.add(
|
|
605
1012
|
new Axis2Placement3D2(
|
|
606
1013
|
"",
|
|
607
1014
|
bottomHoleCenter,
|
|
608
|
-
repo.add(new
|
|
1015
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
609
1016
|
xDir
|
|
610
1017
|
)
|
|
611
1018
|
);
|
|
@@ -622,13 +1029,13 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
622
1029
|
)
|
|
623
1030
|
);
|
|
624
1031
|
const topHoleCenter = repo.add(
|
|
625
|
-
new
|
|
1032
|
+
new CartesianPoint3("", holeX, holeY, boardThickness)
|
|
626
1033
|
);
|
|
627
1034
|
const topHoleVertex = repo.add(
|
|
628
1035
|
new VertexPoint2(
|
|
629
1036
|
"",
|
|
630
1037
|
repo.add(
|
|
631
|
-
new
|
|
1038
|
+
new CartesianPoint3("", holeX + radius, holeY, boardThickness)
|
|
632
1039
|
)
|
|
633
1040
|
)
|
|
634
1041
|
);
|
|
@@ -664,20 +1071,37 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
664
1071
|
}
|
|
665
1072
|
const allFaces = [bottomFace, topFace, ...sideFaces, ...holeCylindricalFaces];
|
|
666
1073
|
const shell = repo.add(new ClosedShell2("", allFaces));
|
|
667
|
-
const solid = repo.add(new
|
|
1074
|
+
const solid = repo.add(new ManifoldSolidBrep3(productName, shell));
|
|
668
1075
|
const allSolids = [solid];
|
|
1076
|
+
let handledComponentIds = /* @__PURE__ */ new Set();
|
|
1077
|
+
let handledPcbComponentIds = /* @__PURE__ */ new Set();
|
|
1078
|
+
if (options.includeComponents && options.includeExternalMeshes) {
|
|
1079
|
+
const mergeResult = await mergeExternalStepModels({
|
|
1080
|
+
repo,
|
|
1081
|
+
circuitJson,
|
|
1082
|
+
boardThickness,
|
|
1083
|
+
fsMap: options.fsMap
|
|
1084
|
+
});
|
|
1085
|
+
handledComponentIds = mergeResult.handledComponentIds;
|
|
1086
|
+
handledPcbComponentIds = mergeResult.handledPcbComponentIds;
|
|
1087
|
+
allSolids.push(...mergeResult.solids);
|
|
1088
|
+
}
|
|
669
1089
|
if (options.includeComponents) {
|
|
670
1090
|
const componentSolids = await generateComponentMeshes({
|
|
671
1091
|
repo,
|
|
672
1092
|
circuitJson,
|
|
673
1093
|
boardThickness,
|
|
674
|
-
includeExternalMeshes: options.includeExternalMeshes
|
|
1094
|
+
includeExternalMeshes: options.includeExternalMeshes,
|
|
1095
|
+
excludeCadComponentIds: handledComponentIds,
|
|
1096
|
+
excludePcbComponentIds: handledPcbComponentIds
|
|
675
1097
|
});
|
|
676
1098
|
allSolids.push(...componentSolids);
|
|
677
1099
|
}
|
|
678
1100
|
const styledItems = [];
|
|
679
|
-
|
|
680
|
-
const
|
|
1101
|
+
allSolids.forEach((solidRef, index) => {
|
|
1102
|
+
const isBoard = index === 0;
|
|
1103
|
+
const [r, g, b] = isBoard ? [0.2, 0.6, 0.2] : [0.75, 0.75, 0.75];
|
|
1104
|
+
const color = repo.add(new ColourRgb("", r, g, b));
|
|
681
1105
|
const fillColor = repo.add(new FillAreaStyleColour("", color));
|
|
682
1106
|
const fillStyle = repo.add(new FillAreaStyle("", [fillColor]));
|
|
683
1107
|
const surfaceFill = repo.add(new SurfaceStyleFillArea(fillStyle));
|
|
@@ -686,7 +1110,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
686
1110
|
const presStyle = repo.add(new PresentationStyleAssignment([surfaceUsage]));
|
|
687
1111
|
const styledItem = repo.add(new StyledItem("", [presStyle], solidRef));
|
|
688
1112
|
styledItems.push(styledItem);
|
|
689
|
-
}
|
|
1113
|
+
});
|
|
690
1114
|
repo.add(
|
|
691
1115
|
new MechanicalDesignGeometricPresentationRepresentation(
|
|
692
1116
|
"",
|
|
@@ -698,7 +1122,8 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
698
1122
|
new AdvancedBrepShapeRepresentation(productName, allSolids, geomContext)
|
|
699
1123
|
);
|
|
700
1124
|
repo.add(new ShapeDefinitionRepresentation(productDefShape, shapeRep));
|
|
701
|
-
|
|
1125
|
+
const stepText = repo.toPartFile({ name: productName });
|
|
1126
|
+
return normalizeStepNumericExponents(stepText);
|
|
702
1127
|
}
|
|
703
1128
|
export {
|
|
704
1129
|
circuitJsonToStep
|