circuit-json-to-step 0.0.28 → 0.0.30
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/dist/index.js +136 -63
- package/lib/index.ts +13 -3
- package/lib/step-model-merger/types.ts +2 -2
- package/lib/step-model-merger.ts +139 -16
- package/package.json +1 -1
- package/test/basics/basics06/__snapshots__/basics06.snap.png +0 -0
- package/test/repros/kicad-step/__snapshots__/kicad-step-board.snap.png +0 -0
- package/test/repros/kicad-step/kicad-step.test.ts +16 -2
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
Unknown as Unknown2,
|
|
13
13
|
CartesianPoint as CartesianPoint4,
|
|
14
14
|
Direction as Direction4,
|
|
15
|
-
Axis2Placement3D as
|
|
15
|
+
Axis2Placement3D as Axis2Placement3D4,
|
|
16
16
|
Plane as Plane3,
|
|
17
17
|
CylindricalSurface as CylindricalSurface2,
|
|
18
18
|
VertexPoint as VertexPoint3,
|
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
ClosedShell as ClosedShell2,
|
|
29
29
|
ManifoldSolidBrep as ManifoldSolidBrep4,
|
|
30
30
|
MechanicalDesignGeometricPresentationRepresentation,
|
|
31
|
-
AdvancedBrepShapeRepresentation,
|
|
31
|
+
AdvancedBrepShapeRepresentation as AdvancedBrepShapeRepresentation2,
|
|
32
|
+
ShapeRepresentation,
|
|
32
33
|
ShapeDefinitionRepresentation
|
|
33
34
|
} from "stepts";
|
|
34
35
|
|
|
@@ -373,12 +374,16 @@ async function generateComponentMeshes(options) {
|
|
|
373
374
|
|
|
374
375
|
// lib/step-model-merger.ts
|
|
375
376
|
import {
|
|
377
|
+
AdvancedBrepShapeRepresentation,
|
|
378
|
+
Axis2Placement3D as Axis2Placement3D2,
|
|
376
379
|
CartesianPoint as CartesianPoint2,
|
|
377
380
|
Direction as Direction2,
|
|
381
|
+
Entity,
|
|
378
382
|
ManifoldSolidBrep as ManifoldSolidBrep3,
|
|
379
383
|
Ref,
|
|
380
384
|
Unknown,
|
|
381
|
-
parseRepository
|
|
385
|
+
parseRepository,
|
|
386
|
+
stepStr
|
|
382
387
|
} from "stepts";
|
|
383
388
|
import { eid } from "stepts";
|
|
384
389
|
|
|
@@ -416,14 +421,6 @@ function toRadians(rotation) {
|
|
|
416
421
|
z: rotation.z * factor
|
|
417
422
|
};
|
|
418
423
|
}
|
|
419
|
-
function transformPoint(point, rotation, translation) {
|
|
420
|
-
const rotated = rotateVector(point, rotation);
|
|
421
|
-
return [
|
|
422
|
-
rotated[0] + translation.x,
|
|
423
|
-
rotated[1] + translation.y,
|
|
424
|
-
rotated[2] + translation.z
|
|
425
|
-
];
|
|
426
|
-
}
|
|
427
424
|
function transformDirection(vector, rotation) {
|
|
428
425
|
return rotateVector(vector, rotation);
|
|
429
426
|
}
|
|
@@ -489,13 +486,19 @@ async function mergeExternalStepModels(options) {
|
|
|
489
486
|
const solids = [];
|
|
490
487
|
const handledComponentIds = /* @__PURE__ */ new Set();
|
|
491
488
|
const handledPcbComponentIds = /* @__PURE__ */ new Set();
|
|
489
|
+
const importedModels = /* @__PURE__ */ new Map();
|
|
492
490
|
for (const component of cadComponents) {
|
|
493
491
|
const componentId = component.cad_component_id ?? "";
|
|
494
492
|
const stepUrl = component.model_step_url;
|
|
495
493
|
try {
|
|
496
|
-
|
|
497
|
-
if (!
|
|
498
|
-
|
|
494
|
+
let importedModel = importedModels.get(stepUrl);
|
|
495
|
+
if (!importedModel) {
|
|
496
|
+
const stepText = fsMap?.[stepUrl] ?? await readStepFile(stepUrl);
|
|
497
|
+
if (!stepText.trim()) {
|
|
498
|
+
throw new Error("STEP file is empty");
|
|
499
|
+
}
|
|
500
|
+
importedModel = importStepModelOnce(repo, stepText, stepUrl);
|
|
501
|
+
importedModels.set(stepUrl, importedModel);
|
|
499
502
|
}
|
|
500
503
|
const pcbComponent = component.pcb_component_id ? pcbComponentMap.get(component.pcb_component_id) : void 0;
|
|
501
504
|
const layer = pcbComponent?.layer?.toLowerCase();
|
|
@@ -503,9 +506,14 @@ async function mergeExternalStepModels(options) {
|
|
|
503
506
|
translation: asVector3(component.position),
|
|
504
507
|
rotation: asVector3(component.rotation)
|
|
505
508
|
};
|
|
506
|
-
const componentSolids =
|
|
507
|
-
|
|
508
|
-
|
|
509
|
+
const componentSolids = createMappedStepModelInstance({
|
|
510
|
+
repo,
|
|
511
|
+
importedModel,
|
|
512
|
+
transform,
|
|
513
|
+
placement: {
|
|
514
|
+
layer,
|
|
515
|
+
boardThickness
|
|
516
|
+
}
|
|
509
517
|
});
|
|
510
518
|
if (componentSolids.length > 0) {
|
|
511
519
|
if (componentId) {
|
|
@@ -523,12 +531,38 @@ async function mergeExternalStepModels(options) {
|
|
|
523
531
|
}
|
|
524
532
|
return { solids, handledComponentIds, handledPcbComponentIds };
|
|
525
533
|
}
|
|
526
|
-
|
|
534
|
+
var RepresentationMap = class extends Entity {
|
|
535
|
+
constructor(mappingOrigin, mappedRepresentation) {
|
|
536
|
+
super();
|
|
537
|
+
this.mappingOrigin = mappingOrigin;
|
|
538
|
+
this.mappedRepresentation = mappedRepresentation;
|
|
539
|
+
}
|
|
540
|
+
mappingOrigin;
|
|
541
|
+
mappedRepresentation;
|
|
542
|
+
type = "REPRESENTATION_MAP";
|
|
543
|
+
toStep() {
|
|
544
|
+
return `REPRESENTATION_MAP(${this.mappingOrigin},${this.mappedRepresentation})`;
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
var MappedItem = class extends Entity {
|
|
548
|
+
constructor(name, mappingSource, mappingTarget) {
|
|
549
|
+
super();
|
|
550
|
+
this.name = name;
|
|
551
|
+
this.mappingSource = mappingSource;
|
|
552
|
+
this.mappingTarget = mappingTarget;
|
|
553
|
+
}
|
|
554
|
+
name;
|
|
555
|
+
mappingSource;
|
|
556
|
+
mappingTarget;
|
|
557
|
+
type = "MAPPED_ITEM";
|
|
558
|
+
toStep() {
|
|
559
|
+
return `MAPPED_ITEM(${stepStr(this.name)},${this.mappingSource},${this.mappingTarget})`;
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
function importStepModelOnce(targetRepo, stepText, modelName) {
|
|
527
563
|
const sourceRepo = parseRepository(stepText);
|
|
528
564
|
let entries = sourceRepo.entries().map(([id, entity]) => [Number(id), entity]).filter(([, entity]) => !EXCLUDED_ENTITY_TYPES.has(entity.type));
|
|
529
565
|
entries = pruneInvalidEntries(entries);
|
|
530
|
-
adjustTransformForPlacement(entries, transform, placement);
|
|
531
|
-
applyTransform(entries, transform);
|
|
532
566
|
const idMapping = allocateIds(targetRepo, entries);
|
|
533
567
|
remapReferences(entries, idMapping);
|
|
534
568
|
for (const [oldId, entity] of entries) {
|
|
@@ -545,7 +579,65 @@ function mergeSingleStepModel(targetRepo, stepText, transform, placement) {
|
|
|
545
579
|
}
|
|
546
580
|
}
|
|
547
581
|
}
|
|
548
|
-
|
|
582
|
+
const originPoint = targetRepo.add(new CartesianPoint2("", 0, 0, 0));
|
|
583
|
+
const zDirection = targetRepo.add(new Direction2("", 0, 0, 1));
|
|
584
|
+
const xDirection = targetRepo.add(new Direction2("", 1, 0, 0));
|
|
585
|
+
const mappingOrigin = targetRepo.add(
|
|
586
|
+
new Axis2Placement3D2("", originPoint, zDirection, xDirection)
|
|
587
|
+
);
|
|
588
|
+
const representation = targetRepo.add(
|
|
589
|
+
new AdvancedBrepShapeRepresentation(
|
|
590
|
+
modelName,
|
|
591
|
+
solids,
|
|
592
|
+
getGeomContext(targetRepo)
|
|
593
|
+
)
|
|
594
|
+
);
|
|
595
|
+
const representationMap = targetRepo.add(
|
|
596
|
+
new RepresentationMap(mappingOrigin, representation)
|
|
597
|
+
);
|
|
598
|
+
return { entries, representationMap };
|
|
599
|
+
}
|
|
600
|
+
function createMappedStepModelInstance({
|
|
601
|
+
repo,
|
|
602
|
+
importedModel,
|
|
603
|
+
transform,
|
|
604
|
+
placement
|
|
605
|
+
}) {
|
|
606
|
+
adjustTransformForPlacement(importedModel.entries, transform, placement);
|
|
607
|
+
const placementTarget = createPlacementTarget(repo, transform);
|
|
608
|
+
const mappedItem = repo.add(
|
|
609
|
+
new MappedItem("", importedModel.representationMap, placementTarget)
|
|
610
|
+
);
|
|
611
|
+
return [mappedItem];
|
|
612
|
+
}
|
|
613
|
+
function createPlacementTarget(repo, transform) {
|
|
614
|
+
const rotation = toRadians(transform.rotation);
|
|
615
|
+
const [refX, refY, refZ] = transformDirection([1, 0, 0], rotation);
|
|
616
|
+
const [axisX, axisY, axisZ] = transformDirection([0, 0, 1], rotation);
|
|
617
|
+
const axis = repo.add(new Direction2("", axisX, axisY, axisZ));
|
|
618
|
+
const refDirection = repo.add(new Direction2("", refX, refY, refZ));
|
|
619
|
+
const origin = repo.add(
|
|
620
|
+
new CartesianPoint2(
|
|
621
|
+
"",
|
|
622
|
+
transform.translation.x,
|
|
623
|
+
transform.translation.y,
|
|
624
|
+
transform.translation.z
|
|
625
|
+
)
|
|
626
|
+
);
|
|
627
|
+
return repo.add(new Axis2Placement3D2("", origin, axis, refDirection));
|
|
628
|
+
}
|
|
629
|
+
function getGeomContext(repo) {
|
|
630
|
+
for (const [id, entity] of repo.entries()) {
|
|
631
|
+
if (entity.type === "GEOMETRIC_REPRESENTATION_CONTEXT") {
|
|
632
|
+
return new Ref(id);
|
|
633
|
+
}
|
|
634
|
+
if (entity instanceof Unknown && entity.args.some(
|
|
635
|
+
(arg) => arg.includes("GEOMETRIC_REPRESENTATION_CONTEXT")
|
|
636
|
+
)) {
|
|
637
|
+
return new Ref(id);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
throw new Error("GEOMETRIC_REPRESENTATION_CONTEXT is missing");
|
|
549
641
|
}
|
|
550
642
|
function adjustTransformForPlacement(entries, transform, placement) {
|
|
551
643
|
if (!placement) return;
|
|
@@ -667,32 +759,6 @@ function collectReferencedIdsRecursive(value, result, seen) {
|
|
|
667
759
|
}
|
|
668
760
|
}
|
|
669
761
|
}
|
|
670
|
-
function applyTransform(entries, transform) {
|
|
671
|
-
const rotation = toRadians(transform.rotation);
|
|
672
|
-
for (const [, entity] of entries) {
|
|
673
|
-
if (entity instanceof CartesianPoint2) {
|
|
674
|
-
const [x, y, z] = transformPoint(
|
|
675
|
-
[entity.x, entity.y, entity.z],
|
|
676
|
-
rotation,
|
|
677
|
-
transform.translation
|
|
678
|
-
);
|
|
679
|
-
entity.x = x;
|
|
680
|
-
entity.y = y;
|
|
681
|
-
entity.z = z;
|
|
682
|
-
} else if (entity instanceof Direction2) {
|
|
683
|
-
const [dx, dy, dz] = transformDirection(
|
|
684
|
-
[entity.dx, entity.dy, entity.dz],
|
|
685
|
-
rotation
|
|
686
|
-
);
|
|
687
|
-
const length = Math.hypot(dx, dy, dz);
|
|
688
|
-
if (length > 0) {
|
|
689
|
-
entity.dx = dx / length;
|
|
690
|
-
entity.dy = dy / length;
|
|
691
|
-
entity.dz = dz / length;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
762
|
function allocateIds(targetRepo, entries) {
|
|
697
763
|
let nextId = getNextEntityId(targetRepo);
|
|
698
764
|
const idMapping = /* @__PURE__ */ new Map();
|
|
@@ -762,7 +828,7 @@ function normalizeStepNumericExponents(stepText) {
|
|
|
762
828
|
var package_default = {
|
|
763
829
|
name: "circuit-json-to-step",
|
|
764
830
|
main: "dist/index.js",
|
|
765
|
-
version: "0.0.
|
|
831
|
+
version: "0.0.28",
|
|
766
832
|
type: "module",
|
|
767
833
|
scripts: {
|
|
768
834
|
"pull-reference": `git clone https://github.com/tscircuit/circuit-json.git && find circuit-json/tests -name '*.test.ts' -exec bash -c 'mv "$0" "\${0%.test.ts}.ts"' {} \\; && git clone https://github.com/tscircuit/stepts.git && find stepts/tests -name '*.test.ts' -exec bash -c 'mv "$0" "\${0%.test.ts}.ts"' {} \\;`,
|
|
@@ -805,7 +871,7 @@ var VERSION = package_default.version;
|
|
|
805
871
|
// lib/pill-geometry.ts
|
|
806
872
|
import {
|
|
807
873
|
AdvancedFace as AdvancedFace2,
|
|
808
|
-
Axis2Placement3D as
|
|
874
|
+
Axis2Placement3D as Axis2Placement3D3,
|
|
809
875
|
CartesianPoint as CartesianPoint3,
|
|
810
876
|
Circle,
|
|
811
877
|
CylindricalSurface,
|
|
@@ -903,7 +969,7 @@ function createArcEdge(repo, centerX, centerY, z, radius, startAngle, endAngle,
|
|
|
903
969
|
new Direction3("", Math.cos(refAngle), Math.sin(refAngle), 0)
|
|
904
970
|
);
|
|
905
971
|
const placement = repo.add(
|
|
906
|
-
new
|
|
972
|
+
new Axis2Placement3D3("", centerPoint, normalDir, refDir)
|
|
907
973
|
);
|
|
908
974
|
const circle = repo.add(new Circle("", placement, radius));
|
|
909
975
|
return repo.add(new EdgeCurve2("", startVertex, endVertex, circle, false));
|
|
@@ -1289,7 +1355,7 @@ function createCylindricalWall(repo, centerX, centerY, radius, startAngle, endAn
|
|
|
1289
1355
|
new CartesianPoint3("", centerRotated.x, centerRotated.y, zMin)
|
|
1290
1356
|
);
|
|
1291
1357
|
const bottomPlacement = repo.add(
|
|
1292
|
-
new
|
|
1358
|
+
new Axis2Placement3D3(
|
|
1293
1359
|
"",
|
|
1294
1360
|
bottomCenter,
|
|
1295
1361
|
repo.add(new Direction3("", 0, 0, -1)),
|
|
@@ -1303,7 +1369,7 @@ function createCylindricalWall(repo, centerX, centerY, radius, startAngle, endAn
|
|
|
1303
1369
|
const topCenter = repo.add(
|
|
1304
1370
|
new CartesianPoint3("", centerRotated.x, centerRotated.y, zMax)
|
|
1305
1371
|
);
|
|
1306
|
-
const topPlacement = repo.add(new
|
|
1372
|
+
const topPlacement = repo.add(new Axis2Placement3D3("", topCenter, zDir, xDir));
|
|
1307
1373
|
const topCircle = repo.add(new Circle("", topPlacement, radius));
|
|
1308
1374
|
const topArc = repo.add(new EdgeCurve2("", topEnd, topStart, topCircle, false));
|
|
1309
1375
|
const v1 = repo.add(
|
|
@@ -1348,7 +1414,7 @@ function createCylindricalWall(repo, centerX, centerY, radius, startAngle, endAn
|
|
|
1348
1414
|
])
|
|
1349
1415
|
);
|
|
1350
1416
|
const cylinderPlacement = repo.add(
|
|
1351
|
-
new
|
|
1417
|
+
new Axis2Placement3D3("", bottomCenter, zDir, xDir)
|
|
1352
1418
|
);
|
|
1353
1419
|
const cylinderSurface = repo.add(
|
|
1354
1420
|
new CylindricalSurface("", cylinderPlacement, radius)
|
|
@@ -1422,7 +1488,7 @@ function createPlanarWall(repo, startX, startY, endX, endY, rotation, centerX0,
|
|
|
1422
1488
|
);
|
|
1423
1489
|
const planeOrigin = repo.add(new CartesianPoint3("", start.x, start.y, zMin));
|
|
1424
1490
|
const placement = repo.add(
|
|
1425
|
-
new
|
|
1491
|
+
new Axis2Placement3D3("", planeOrigin, normalDir, refDir)
|
|
1426
1492
|
);
|
|
1427
1493
|
const plane = repo.add(new Plane2("", placement));
|
|
1428
1494
|
return repo.add(
|
|
@@ -1631,7 +1697,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1631
1697
|
const xDir = repo.add(new Direction4("", 1, 0, 0));
|
|
1632
1698
|
const zDir = repo.add(new Direction4("", 0, 0, 1));
|
|
1633
1699
|
const bottomFrame = repo.add(
|
|
1634
|
-
new
|
|
1700
|
+
new Axis2Placement3D4(
|
|
1635
1701
|
"",
|
|
1636
1702
|
origin,
|
|
1637
1703
|
repo.add(new Direction4("", 0, 0, -1)),
|
|
@@ -1664,7 +1730,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1664
1730
|
)
|
|
1665
1731
|
);
|
|
1666
1732
|
const holePlacement = repo.add(
|
|
1667
|
-
new
|
|
1733
|
+
new Axis2Placement3D4(
|
|
1668
1734
|
"",
|
|
1669
1735
|
holeCenter,
|
|
1670
1736
|
repo.add(new Direction4("", 0, 0, -1)),
|
|
@@ -1693,7 +1759,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1693
1759
|
)
|
|
1694
1760
|
);
|
|
1695
1761
|
const topOrigin = repo.add(new CartesianPoint4("", 0, 0, halfBoardThickness));
|
|
1696
|
-
const topFrame = repo.add(new
|
|
1762
|
+
const topFrame = repo.add(new Axis2Placement3D4("", topOrigin, zDir, xDir));
|
|
1697
1763
|
const topPlane = repo.add(new Plane3("", topFrame));
|
|
1698
1764
|
const topLoop = repo.add(
|
|
1699
1765
|
new EdgeLoop3(
|
|
@@ -1720,7 +1786,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1720
1786
|
)
|
|
1721
1787
|
);
|
|
1722
1788
|
const holePlacement = repo.add(
|
|
1723
|
-
new
|
|
1789
|
+
new Axis2Placement3D4("", holeCenter, zDir, xDir)
|
|
1724
1790
|
);
|
|
1725
1791
|
const holeCircle = repo.add(new Circle2("", holePlacement, radius));
|
|
1726
1792
|
const holeEdge = repo.add(
|
|
@@ -1758,7 +1824,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1758
1824
|
const normalDir = repo.add(new Direction4("", edgeDir.y, -edgeDir.x, 0));
|
|
1759
1825
|
const refDir = repo.add(new Direction4("", edgeDir.x, edgeDir.y, 0));
|
|
1760
1826
|
const sideFrame = repo.add(
|
|
1761
|
-
new
|
|
1827
|
+
new Axis2Placement3D4("", bottomV1Pnt, normalDir, refDir)
|
|
1762
1828
|
);
|
|
1763
1829
|
const sidePlane = repo.add(new Plane3("", sideFrame));
|
|
1764
1830
|
const sideLoop = repo.add(
|
|
@@ -1798,7 +1864,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1798
1864
|
)
|
|
1799
1865
|
);
|
|
1800
1866
|
const bottomHolePlacement = repo.add(
|
|
1801
|
-
new
|
|
1867
|
+
new Axis2Placement3D4(
|
|
1802
1868
|
"",
|
|
1803
1869
|
bottomHoleCenter,
|
|
1804
1870
|
repo.add(new Direction4("", 0, 0, -1)),
|
|
@@ -1829,7 +1895,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1829
1895
|
)
|
|
1830
1896
|
);
|
|
1831
1897
|
const topHolePlacement = repo.add(
|
|
1832
|
-
new
|
|
1898
|
+
new Axis2Placement3D4("", topHoleCenter, zDir, xDir)
|
|
1833
1899
|
);
|
|
1834
1900
|
const topHoleCircle = repo.add(new Circle2("", topHolePlacement, radius));
|
|
1835
1901
|
const topHoleEdge = repo.add(
|
|
@@ -1842,7 +1908,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1842
1908
|
])
|
|
1843
1909
|
);
|
|
1844
1910
|
const holeCylinderPlacement = repo.add(
|
|
1845
|
-
new
|
|
1911
|
+
new Axis2Placement3D4("", bottomHoleCenter, zDir, xDir)
|
|
1846
1912
|
);
|
|
1847
1913
|
const holeCylinderSurface = repo.add(
|
|
1848
1914
|
new CylindricalSurface2("", holeCylinderPlacement, radius)
|
|
@@ -1976,8 +2042,15 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
1976
2042
|
geomContext
|
|
1977
2043
|
)
|
|
1978
2044
|
);
|
|
2045
|
+
const hasMappedItems = allSolids.some(
|
|
2046
|
+
(itemRef) => itemRef.resolve(repo).type === "MAPPED_ITEM"
|
|
2047
|
+
);
|
|
1979
2048
|
const shapeRep = repo.add(
|
|
1980
|
-
new
|
|
2049
|
+
hasMappedItems ? new ShapeRepresentation(productName, allSolids, geomContext) : new AdvancedBrepShapeRepresentation2(
|
|
2050
|
+
productName,
|
|
2051
|
+
allSolids,
|
|
2052
|
+
geomContext
|
|
2053
|
+
)
|
|
1981
2054
|
);
|
|
1982
2055
|
repo.add(new ShapeDefinitionRepresentation(productDefShape, shapeRep));
|
|
1983
2056
|
const stepText = repo.toPartFile({ name: productName });
|
package/lib/index.ts
CHANGED
|
@@ -37,7 +37,9 @@ import {
|
|
|
37
37
|
StyledItem,
|
|
38
38
|
MechanicalDesignGeometricPresentationRepresentation,
|
|
39
39
|
AdvancedBrepShapeRepresentation,
|
|
40
|
+
ShapeRepresentation,
|
|
40
41
|
ShapeDefinitionRepresentation,
|
|
42
|
+
type Entity,
|
|
41
43
|
type Ref,
|
|
42
44
|
} from "stepts"
|
|
43
45
|
import { generateComponentMeshes } from "./mesh-generation"
|
|
@@ -614,7 +616,7 @@ export async function circuitJsonToStep(
|
|
|
614
616
|
const solid = repo.add(new ManifoldSolidBrep(productName, shell))
|
|
615
617
|
|
|
616
618
|
// Array to hold all solids (board + optional components)
|
|
617
|
-
const allSolids: Ref<
|
|
619
|
+
const allSolids: Ref<Entity>[] = [solid]
|
|
618
620
|
const componentStyledItems: Ref<StyledItem>[] = []
|
|
619
621
|
const solidsWithIntrinsicFaceStyles = new Set<string>()
|
|
620
622
|
|
|
@@ -750,9 +752,17 @@ export async function circuitJsonToStep(
|
|
|
750
752
|
),
|
|
751
753
|
)
|
|
752
754
|
|
|
753
|
-
|
|
755
|
+
const hasMappedItems = allSolids.some(
|
|
756
|
+
(itemRef) => itemRef.resolve(repo).type === "MAPPED_ITEM",
|
|
757
|
+
)
|
|
754
758
|
const shapeRep = repo.add(
|
|
755
|
-
|
|
759
|
+
hasMappedItems
|
|
760
|
+
? new ShapeRepresentation(productName, allSolids, geomContext)
|
|
761
|
+
: new AdvancedBrepShapeRepresentation(
|
|
762
|
+
productName,
|
|
763
|
+
allSolids,
|
|
764
|
+
geomContext,
|
|
765
|
+
),
|
|
756
766
|
)
|
|
757
767
|
repo.add(new ShapeDefinitionRepresentation(productDefShape, shapeRep))
|
|
758
768
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CircuitJson } from "circuit-json"
|
|
2
|
-
import type {
|
|
2
|
+
import type { Entity, Ref, Repository } from "stepts"
|
|
3
3
|
|
|
4
4
|
export type CadComponent = {
|
|
5
5
|
type: "cad_component"
|
|
@@ -28,7 +28,7 @@ export type MergeTransform = {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export type MergeStepModelResult = {
|
|
31
|
-
solids: Ref<
|
|
31
|
+
solids: Ref<Entity>[]
|
|
32
32
|
handledComponentIds: Set<string>
|
|
33
33
|
handledPcbComponentIds: Set<string>
|
|
34
34
|
}
|
package/lib/step-model-merger.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AdvancedBrepShapeRepresentation,
|
|
3
|
+
Axis2Placement3D,
|
|
2
4
|
CartesianPoint,
|
|
3
5
|
Direction,
|
|
4
|
-
|
|
6
|
+
Entity,
|
|
5
7
|
ManifoldSolidBrep,
|
|
6
8
|
Ref,
|
|
7
9
|
Repository,
|
|
8
10
|
Unknown,
|
|
9
11
|
parseRepository,
|
|
12
|
+
stepStr,
|
|
10
13
|
} from "stepts"
|
|
11
14
|
import { eid } from "stepts"
|
|
12
15
|
import { EXCLUDED_ENTITY_TYPES } from "./step-model-merger/excluded-entity-types"
|
|
@@ -47,18 +50,24 @@ export async function mergeExternalStepModels(
|
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
const solids: Ref<
|
|
53
|
+
const solids: Ref<Entity>[] = []
|
|
51
54
|
const handledComponentIds = new Set<string>()
|
|
52
55
|
const handledPcbComponentIds = new Set<string>()
|
|
56
|
+
const importedModels = new Map<string, ImportedStepModel>()
|
|
53
57
|
|
|
54
58
|
for (const component of cadComponents) {
|
|
55
59
|
const componentId = component.cad_component_id ?? ""
|
|
56
60
|
const stepUrl = component.model_step_url!
|
|
57
61
|
|
|
58
62
|
try {
|
|
59
|
-
|
|
60
|
-
if (!
|
|
61
|
-
|
|
63
|
+
let importedModel = importedModels.get(stepUrl)
|
|
64
|
+
if (!importedModel) {
|
|
65
|
+
const stepText = fsMap?.[stepUrl] ?? (await readStepFile(stepUrl))
|
|
66
|
+
if (!stepText.trim()) {
|
|
67
|
+
throw new Error("STEP file is empty")
|
|
68
|
+
}
|
|
69
|
+
importedModel = importStepModelOnce(repo, stepText, stepUrl)
|
|
70
|
+
importedModels.set(stepUrl, importedModel)
|
|
62
71
|
}
|
|
63
72
|
|
|
64
73
|
const pcbComponent = component.pcb_component_id
|
|
@@ -71,9 +80,14 @@ export async function mergeExternalStepModels(
|
|
|
71
80
|
rotation: asVector3(component.rotation),
|
|
72
81
|
}
|
|
73
82
|
|
|
74
|
-
const componentSolids =
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
const componentSolids = createMappedStepModelInstance({
|
|
84
|
+
repo,
|
|
85
|
+
importedModel,
|
|
86
|
+
transform,
|
|
87
|
+
placement: {
|
|
88
|
+
layer,
|
|
89
|
+
boardThickness,
|
|
90
|
+
},
|
|
77
91
|
})
|
|
78
92
|
if (componentSolids.length > 0) {
|
|
79
93
|
if (componentId) {
|
|
@@ -98,12 +112,47 @@ type PlacementOptions = {
|
|
|
98
112
|
boardThickness?: number
|
|
99
113
|
}
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
type ImportedStepModel = {
|
|
116
|
+
entries: RepositoryEntry[]
|
|
117
|
+
representationMap: Ref<RepresentationMap>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
class RepresentationMap extends Entity {
|
|
121
|
+
readonly type = "REPRESENTATION_MAP"
|
|
122
|
+
|
|
123
|
+
constructor(
|
|
124
|
+
public mappingOrigin: Ref<Entity>,
|
|
125
|
+
public mappedRepresentation: Ref<Entity>,
|
|
126
|
+
) {
|
|
127
|
+
super()
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
toStep(): string {
|
|
131
|
+
return `REPRESENTATION_MAP(${this.mappingOrigin},${this.mappedRepresentation})`
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
class MappedItem extends Entity {
|
|
136
|
+
readonly type = "MAPPED_ITEM"
|
|
137
|
+
|
|
138
|
+
constructor(
|
|
139
|
+
public name: string,
|
|
140
|
+
public mappingSource: Ref<RepresentationMap>,
|
|
141
|
+
public mappingTarget: Ref<Entity>,
|
|
142
|
+
) {
|
|
143
|
+
super()
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
toStep(): string {
|
|
147
|
+
return `MAPPED_ITEM(${stepStr(this.name)},${this.mappingSource},${this.mappingTarget})`
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function importStepModelOnce(
|
|
102
152
|
targetRepo: Repository,
|
|
103
153
|
stepText: string,
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
): Ref<ManifoldSolidBrep>[] {
|
|
154
|
+
modelName: string,
|
|
155
|
+
): ImportedStepModel {
|
|
107
156
|
const sourceRepo = parseRepository(stepText)
|
|
108
157
|
let entries: RepositoryEntry[] = sourceRepo
|
|
109
158
|
.entries()
|
|
@@ -112,9 +161,6 @@ function mergeSingleStepModel(
|
|
|
112
161
|
|
|
113
162
|
entries = pruneInvalidEntries(entries)
|
|
114
163
|
|
|
115
|
-
adjustTransformForPlacement(entries, transform, placement)
|
|
116
|
-
applyTransform(entries, transform)
|
|
117
|
-
|
|
118
164
|
const idMapping = allocateIds(targetRepo, entries)
|
|
119
165
|
remapReferences(entries, idMapping)
|
|
120
166
|
|
|
@@ -134,7 +180,84 @@ function mergeSingleStepModel(
|
|
|
134
180
|
}
|
|
135
181
|
}
|
|
136
182
|
|
|
137
|
-
|
|
183
|
+
const originPoint = targetRepo.add(new CartesianPoint("", 0, 0, 0))
|
|
184
|
+
const zDirection = targetRepo.add(new Direction("", 0, 0, 1))
|
|
185
|
+
const xDirection = targetRepo.add(new Direction("", 1, 0, 0))
|
|
186
|
+
const mappingOrigin = targetRepo.add(
|
|
187
|
+
new Axis2Placement3D("", originPoint, zDirection, xDirection),
|
|
188
|
+
)
|
|
189
|
+
const representation = targetRepo.add(
|
|
190
|
+
new AdvancedBrepShapeRepresentation(
|
|
191
|
+
modelName,
|
|
192
|
+
solids,
|
|
193
|
+
getGeomContext(targetRepo),
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
const representationMap = targetRepo.add(
|
|
197
|
+
new RepresentationMap(mappingOrigin, representation),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return { entries, representationMap }
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function createMappedStepModelInstance({
|
|
204
|
+
repo,
|
|
205
|
+
importedModel,
|
|
206
|
+
transform,
|
|
207
|
+
placement,
|
|
208
|
+
}: {
|
|
209
|
+
repo: Repository
|
|
210
|
+
importedModel: ImportedStepModel
|
|
211
|
+
transform: MergeTransform
|
|
212
|
+
placement?: PlacementOptions
|
|
213
|
+
}): Ref<Entity>[] {
|
|
214
|
+
adjustTransformForPlacement(importedModel.entries, transform, placement)
|
|
215
|
+
|
|
216
|
+
const placementTarget = createPlacementTarget(repo, transform)
|
|
217
|
+
const mappedItem = repo.add(
|
|
218
|
+
new MappedItem("", importedModel.representationMap, placementTarget),
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
return [mappedItem]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createPlacementTarget(
|
|
225
|
+
repo: Repository,
|
|
226
|
+
transform: MergeTransform,
|
|
227
|
+
): Ref<Axis2Placement3D> {
|
|
228
|
+
const rotation = toRadians(transform.rotation)
|
|
229
|
+
const [refX, refY, refZ] = transformDirection([1, 0, 0], rotation)
|
|
230
|
+
const [axisX, axisY, axisZ] = transformDirection([0, 0, 1], rotation)
|
|
231
|
+
|
|
232
|
+
const axis = repo.add(new Direction("", axisX, axisY, axisZ))
|
|
233
|
+
const refDirection = repo.add(new Direction("", refX, refY, refZ))
|
|
234
|
+
const origin = repo.add(
|
|
235
|
+
new CartesianPoint(
|
|
236
|
+
"",
|
|
237
|
+
transform.translation.x,
|
|
238
|
+
transform.translation.y,
|
|
239
|
+
transform.translation.z,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return repo.add(new Axis2Placement3D("", origin, axis, refDirection))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getGeomContext(repo: Repository): Ref<Entity> {
|
|
247
|
+
for (const [id, entity] of repo.entries()) {
|
|
248
|
+
if (entity.type === "GEOMETRIC_REPRESENTATION_CONTEXT") {
|
|
249
|
+
return new Ref<Entity>(id)
|
|
250
|
+
}
|
|
251
|
+
if (
|
|
252
|
+
entity instanceof Unknown &&
|
|
253
|
+
entity.args.some((arg) =>
|
|
254
|
+
arg.includes("GEOMETRIC_REPRESENTATION_CONTEXT"),
|
|
255
|
+
)
|
|
256
|
+
) {
|
|
257
|
+
return new Ref<Entity>(id)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
throw new Error("GEOMETRIC_REPRESENTATION_CONTEXT is missing")
|
|
138
261
|
}
|
|
139
262
|
|
|
140
263
|
type RepositoryEntry = readonly [number, Entity]
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-json-to-step",
|
|
3
3
|
"main": "dist/index.js",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.30",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"pull-reference": "git clone https://github.com/tscircuit/circuit-json.git && find circuit-json/tests -name '*.test.ts' -exec bash -c 'mv \"$0\" \"${0%.test.ts}.ts\"' {} \\; && git clone https://github.com/tscircuit/stepts.git && find stepts/tests -name '*.test.ts' -exec bash -c 'mv \"$0\" \"${0%.test.ts}.ts\"' {} \\;",
|
|
Binary file
|
|
Binary file
|
|
@@ -20,6 +20,11 @@ const EXPECTED_COMPONENT_CENTERS = (circuitJson as CadComponentJson[])
|
|
|
20
20
|
x: item.position?.x ?? 0,
|
|
21
21
|
y: item.position?.y ?? 0,
|
|
22
22
|
}))
|
|
23
|
+
const EXPECTED_UNIQUE_STEP_MODEL_COUNT = new Set(
|
|
24
|
+
(circuitJson as CadComponentJson[])
|
|
25
|
+
.filter((item) => item.type === "cad_component" && item.model_step_url)
|
|
26
|
+
.map((item) => item.model_step_url),
|
|
27
|
+
).size
|
|
23
28
|
|
|
24
29
|
const fixturesDir = fileURLToPath(
|
|
25
30
|
new URL("../../fixtures/kicad-models/", import.meta.url),
|
|
@@ -99,6 +104,9 @@ test("kicad-step: merges KiCad STEP models referenced via model_step_url", async
|
|
|
99
104
|
fsMap,
|
|
100
105
|
})
|
|
101
106
|
|
|
107
|
+
const outputPath = "debug-output/kicad-step.step"
|
|
108
|
+
await Bun.write(outputPath, stepText)
|
|
109
|
+
|
|
102
110
|
expect(stepText).toContain("KiCadStepMerge")
|
|
103
111
|
const solidCount = (stepText.match(/MANIFOLD_SOLID_BREP/g) || []).length
|
|
104
112
|
expect(solidCount).toBeGreaterThanOrEqual(3)
|
|
@@ -114,10 +122,16 @@ test("kicad-step: merges KiCad STEP models referenced via model_step_url", async
|
|
|
114
122
|
(entity) => entity.name === "KiCadStepMerge",
|
|
115
123
|
)
|
|
116
124
|
expect(boardSolids.length).toBe(1)
|
|
117
|
-
const
|
|
118
|
-
expect(
|
|
125
|
+
const uniqueComponentSolids = solids.length - boardSolids.length
|
|
126
|
+
expect(uniqueComponentSolids).toBeGreaterThanOrEqual(
|
|
127
|
+
EXPECTED_UNIQUE_STEP_MODEL_COUNT,
|
|
128
|
+
)
|
|
129
|
+
expect(stepText.match(/MAPPED_ITEM/g) ?? []).toHaveLength(
|
|
119
130
|
EXPECTED_COMPONENT_CENTERS.length,
|
|
120
131
|
)
|
|
132
|
+
expect(stepText.match(/REPRESENTATION_MAP/g) ?? []).toHaveLength(
|
|
133
|
+
EXPECTED_UNIQUE_STEP_MODEL_COUNT,
|
|
134
|
+
)
|
|
121
135
|
|
|
122
136
|
try {
|
|
123
137
|
const occtBoard = await importStepWithOcct(stepText)
|