circuit-json-to-step 0.0.2 → 0.0.3
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.js +470 -40
- package/lib/index.ts +26 -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 +29 -0
- package/lib/step-model-merger/types.ts +40 -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 +208 -0
- package/test/repros/repro01/__snapshots__/repro01.snap.png +0 -0
- package/test/repros/repro01/repro01.test.ts +3 -2
- 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,408 @@ 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
|
+
import { promises as fs } from "fs";
|
|
413
|
+
import path from "path";
|
|
414
|
+
import { fileURLToPath } from "url";
|
|
415
|
+
async function readStepFile(modelUrl) {
|
|
416
|
+
if (/^https?:\/\//i.test(modelUrl)) {
|
|
417
|
+
const globalFetch = globalThis.fetch;
|
|
418
|
+
if (!globalFetch) {
|
|
419
|
+
throw new Error("fetch is not available in this environment");
|
|
420
|
+
}
|
|
421
|
+
const res = await globalFetch(modelUrl);
|
|
422
|
+
if (!res.ok) {
|
|
423
|
+
throw new Error(`HTTP ${res.status} ${res.statusText}`);
|
|
424
|
+
}
|
|
425
|
+
return await res.text();
|
|
426
|
+
}
|
|
427
|
+
if (modelUrl.startsWith("file://")) {
|
|
428
|
+
const filePath = fileURLToPath(modelUrl);
|
|
429
|
+
return await fs.readFile(filePath, "utf8");
|
|
430
|
+
}
|
|
431
|
+
const resolvedPath = path.isAbsolute(modelUrl) ? modelUrl : path.resolve(process.cwd(), modelUrl);
|
|
432
|
+
return await fs.readFile(resolvedPath, "utf8");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// lib/step-model-merger.ts
|
|
436
|
+
async function mergeExternalStepModels(options) {
|
|
437
|
+
const { repo, circuitJson, boardThickness } = options;
|
|
438
|
+
const cadComponents = circuitJson.filter(
|
|
439
|
+
(item) => item?.type === "cad_component" && typeof item.model_step_url === "string"
|
|
440
|
+
);
|
|
441
|
+
const pcbComponentMap = /* @__PURE__ */ new Map();
|
|
442
|
+
for (const item of circuitJson) {
|
|
443
|
+
if (item?.type === "pcb_component" && item.pcb_component_id) {
|
|
444
|
+
pcbComponentMap.set(item.pcb_component_id, item);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
const solids = [];
|
|
448
|
+
const handledComponentIds = /* @__PURE__ */ new Set();
|
|
449
|
+
const handledPcbComponentIds = /* @__PURE__ */ new Set();
|
|
450
|
+
for (const component of cadComponents) {
|
|
451
|
+
const componentId = component.cad_component_id ?? "";
|
|
452
|
+
const stepUrl = component.model_step_url;
|
|
453
|
+
try {
|
|
454
|
+
const stepText = await readStepFile(stepUrl);
|
|
455
|
+
if (!stepText.trim()) {
|
|
456
|
+
throw new Error("STEP file is empty");
|
|
457
|
+
}
|
|
458
|
+
const pcbComponent = component.pcb_component_id ? pcbComponentMap.get(component.pcb_component_id) : void 0;
|
|
459
|
+
const layer = pcbComponent?.layer?.toLowerCase();
|
|
460
|
+
const transform = {
|
|
461
|
+
translation: asVector3(component.position),
|
|
462
|
+
rotation: asVector3(component.rotation)
|
|
463
|
+
};
|
|
464
|
+
const componentSolids = mergeSingleStepModel(repo, stepText, transform, {
|
|
465
|
+
layer,
|
|
466
|
+
boardThickness
|
|
467
|
+
});
|
|
468
|
+
if (componentSolids.length > 0) {
|
|
469
|
+
if (componentId) {
|
|
470
|
+
handledComponentIds.add(componentId);
|
|
471
|
+
}
|
|
472
|
+
const pcbComponentId = component.pcb_component_id;
|
|
473
|
+
if (pcbComponentId) {
|
|
474
|
+
handledPcbComponentIds.add(pcbComponentId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
solids.push(...componentSolids);
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.warn(`Failed to merge STEP model from ${stepUrl}:`, error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return { solids, handledComponentIds, handledPcbComponentIds };
|
|
483
|
+
}
|
|
484
|
+
function mergeSingleStepModel(targetRepo, stepText, transform, placement) {
|
|
485
|
+
const sourceRepo = parseRepository(stepText);
|
|
486
|
+
let entries = sourceRepo.entries().map(([id, entity]) => [Number(id), entity]).filter(([, entity]) => !EXCLUDED_ENTITY_TYPES.has(entity.type));
|
|
487
|
+
entries = pruneInvalidEntries(entries);
|
|
488
|
+
adjustTransformForPlacement(entries, transform, placement);
|
|
489
|
+
applyTransform(entries, transform);
|
|
490
|
+
const idMapping = allocateIds(targetRepo, entries);
|
|
491
|
+
remapReferences(entries, idMapping);
|
|
492
|
+
for (const [oldId, entity] of entries) {
|
|
493
|
+
const mappedId = idMapping.get(oldId);
|
|
494
|
+
if (mappedId === void 0) continue;
|
|
495
|
+
targetRepo.set(eid(mappedId), entity);
|
|
496
|
+
}
|
|
497
|
+
const solids = [];
|
|
498
|
+
for (const [oldId, entity] of entries) {
|
|
499
|
+
if (entity instanceof ManifoldSolidBrep2) {
|
|
500
|
+
const mappedId = idMapping.get(oldId);
|
|
501
|
+
if (mappedId !== void 0) {
|
|
502
|
+
solids.push(new Ref(eid(mappedId)));
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return solids;
|
|
507
|
+
}
|
|
508
|
+
function adjustTransformForPlacement(entries, transform, placement) {
|
|
509
|
+
if (!placement) return;
|
|
510
|
+
const points = [];
|
|
511
|
+
for (const [, entity] of entries) {
|
|
512
|
+
if (entity instanceof CartesianPoint2) {
|
|
513
|
+
points.push([entity.x, entity.y, entity.z]);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!points.length) return;
|
|
517
|
+
const rotationRadians = toRadians(transform.rotation);
|
|
518
|
+
let minX = Infinity;
|
|
519
|
+
let minY = Infinity;
|
|
520
|
+
let minZ = Infinity;
|
|
521
|
+
let maxX = -Infinity;
|
|
522
|
+
let maxY = -Infinity;
|
|
523
|
+
let maxZ = -Infinity;
|
|
524
|
+
for (const point of points) {
|
|
525
|
+
const [x, y, z] = rotateVector(point, rotationRadians);
|
|
526
|
+
if (x < minX) minX = x;
|
|
527
|
+
if (y < minY) minY = y;
|
|
528
|
+
if (z < minZ) minZ = z;
|
|
529
|
+
if (x > maxX) maxX = x;
|
|
530
|
+
if (y > maxY) maxY = y;
|
|
531
|
+
if (z > maxZ) maxZ = z;
|
|
532
|
+
}
|
|
533
|
+
if (!Number.isFinite(minX)) return;
|
|
534
|
+
const center = {
|
|
535
|
+
x: (minX + maxX) / 2,
|
|
536
|
+
y: (minY + maxY) / 2,
|
|
537
|
+
z: (minZ + maxZ) / 2
|
|
538
|
+
};
|
|
539
|
+
const normalizedLayer = placement.layer?.toLowerCase() === "bottom" ? "bottom" : "top";
|
|
540
|
+
const boardThickness = placement.boardThickness ?? 0;
|
|
541
|
+
const halfThickness = boardThickness / 2;
|
|
542
|
+
const targetX = transform.translation.x;
|
|
543
|
+
const targetY = transform.translation.y;
|
|
544
|
+
const targetZ = transform.translation.z;
|
|
545
|
+
transform.translation.x = targetX - center.x;
|
|
546
|
+
transform.translation.y = targetY - center.y;
|
|
547
|
+
if (boardThickness > 0) {
|
|
548
|
+
const offsetZ = targetZ - halfThickness;
|
|
549
|
+
if (normalizedLayer === "bottom") {
|
|
550
|
+
transform.translation.z = -maxZ + offsetZ;
|
|
551
|
+
transform.rotation.x = normalizeDegrees(transform.rotation.x + 180);
|
|
552
|
+
} else {
|
|
553
|
+
transform.translation.z = boardThickness - minZ + offsetZ;
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
transform.translation.z = targetZ - center.z;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
function normalizeDegrees(value) {
|
|
560
|
+
const wrapped = value % 360;
|
|
561
|
+
return wrapped < 0 ? wrapped + 360 : wrapped;
|
|
562
|
+
}
|
|
563
|
+
function pruneInvalidEntries(entries) {
|
|
564
|
+
let remaining = entries.slice();
|
|
565
|
+
let remainingIds = new Set(remaining.map(([id]) => id));
|
|
566
|
+
let changed = true;
|
|
567
|
+
while (changed) {
|
|
568
|
+
changed = false;
|
|
569
|
+
const toRemove = /* @__PURE__ */ new Set();
|
|
570
|
+
for (const [entityId, entity] of remaining) {
|
|
571
|
+
const refs = collectReferencedIds(entity);
|
|
572
|
+
for (const refId of refs) {
|
|
573
|
+
if (!remainingIds.has(refId)) {
|
|
574
|
+
toRemove.add(entityId);
|
|
575
|
+
break;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (toRemove.size > 0) {
|
|
580
|
+
changed = true;
|
|
581
|
+
remaining = remaining.filter(([id]) => !toRemove.has(id));
|
|
582
|
+
remainingIds = new Set(remaining.map(([id]) => id));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return remaining;
|
|
586
|
+
}
|
|
587
|
+
function collectReferencedIds(entity) {
|
|
588
|
+
const result = /* @__PURE__ */ new Set();
|
|
589
|
+
collectReferencedIdsRecursive(entity, result, /* @__PURE__ */ new Set());
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
function collectReferencedIdsRecursive(value, result, seen) {
|
|
593
|
+
if (!value) return;
|
|
594
|
+
if (value instanceof Ref) {
|
|
595
|
+
result.add(Number(value.id));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
if (value instanceof Unknown) {
|
|
599
|
+
for (const arg of value.args) {
|
|
600
|
+
arg.replace(/#(\d+)/g, (_, num) => {
|
|
601
|
+
result.add(Number(num));
|
|
602
|
+
return _;
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
if (Array.isArray(value)) {
|
|
608
|
+
for (const item of value) {
|
|
609
|
+
collectReferencedIdsRecursive(item, result, seen);
|
|
610
|
+
}
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
if (typeof value === "object") {
|
|
614
|
+
if (seen.has(value)) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
seen.add(value);
|
|
618
|
+
for (const entry of Object.values(value)) {
|
|
619
|
+
collectReferencedIdsRecursive(entry, result, seen);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function applyTransform(entries, transform) {
|
|
624
|
+
const rotation = toRadians(transform.rotation);
|
|
625
|
+
for (const [, entity] of entries) {
|
|
626
|
+
if (entity instanceof CartesianPoint2) {
|
|
627
|
+
const [x, y, z] = transformPoint(
|
|
628
|
+
[entity.x, entity.y, entity.z],
|
|
629
|
+
rotation,
|
|
630
|
+
transform.translation
|
|
631
|
+
);
|
|
632
|
+
entity.x = x;
|
|
633
|
+
entity.y = y;
|
|
634
|
+
entity.z = z;
|
|
635
|
+
} else if (entity instanceof Direction2) {
|
|
636
|
+
const [dx, dy, dz] = transformDirection(
|
|
637
|
+
[entity.dx, entity.dy, entity.dz],
|
|
638
|
+
rotation
|
|
639
|
+
);
|
|
640
|
+
const length = Math.hypot(dx, dy, dz);
|
|
641
|
+
if (length > 0) {
|
|
642
|
+
entity.dx = dx / length;
|
|
643
|
+
entity.dy = dy / length;
|
|
644
|
+
entity.dz = dz / length;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
function allocateIds(targetRepo, entries) {
|
|
650
|
+
let nextId = getNextEntityId(targetRepo);
|
|
651
|
+
const idMapping = /* @__PURE__ */ new Map();
|
|
652
|
+
for (const [oldId] of entries) {
|
|
653
|
+
idMapping.set(oldId, nextId);
|
|
654
|
+
nextId += 1;
|
|
655
|
+
}
|
|
656
|
+
return idMapping;
|
|
657
|
+
}
|
|
658
|
+
function getNextEntityId(repo) {
|
|
659
|
+
let maxId = 0;
|
|
660
|
+
for (const [id] of repo.entries()) {
|
|
661
|
+
const numericId = Number(id);
|
|
662
|
+
if (numericId > maxId) {
|
|
663
|
+
maxId = numericId;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return maxId + 1;
|
|
667
|
+
}
|
|
668
|
+
function remapReferences(entries, idMapping) {
|
|
669
|
+
for (const [, entity] of entries) {
|
|
670
|
+
remapValue(entity, idMapping, /* @__PURE__ */ new Set());
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
function remapValue(value, idMapping, seen) {
|
|
674
|
+
if (!value) return;
|
|
675
|
+
if (value instanceof Ref) {
|
|
676
|
+
const mapped = idMapping.get(Number(value.id));
|
|
677
|
+
if (mapped !== void 0) {
|
|
678
|
+
value.id = eid(mapped);
|
|
679
|
+
}
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
if (value instanceof Unknown) {
|
|
683
|
+
value.args = value.args.map(
|
|
684
|
+
(arg) => arg.replace(/#(\d+)/g, (match, num) => {
|
|
685
|
+
const mapped = idMapping.get(Number(num));
|
|
686
|
+
return mapped !== void 0 ? `#${mapped}` : match;
|
|
687
|
+
})
|
|
688
|
+
);
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
if (Array.isArray(value)) {
|
|
692
|
+
for (const item of value) {
|
|
693
|
+
remapValue(item, idMapping, seen);
|
|
694
|
+
}
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
if (typeof value === "object") {
|
|
698
|
+
if (seen.has(value)) return;
|
|
699
|
+
seen.add(value);
|
|
700
|
+
for (const key of Object.keys(value)) {
|
|
701
|
+
remapValue(value[key], idMapping, seen);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// lib/step-text-utils.ts
|
|
707
|
+
function normalizeStepNumericExponents(stepText) {
|
|
708
|
+
return stepText.replace(
|
|
709
|
+
/(-?(?:\d+\.\d*|\.\d+|\d+))e([+-]?\d+)/g,
|
|
710
|
+
(_match, mantissa, exponent) => `${mantissa}E${exponent}`
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
|
|
301
714
|
// lib/index.ts
|
|
302
715
|
async function circuitJsonToStep(circuitJson, options = {}) {
|
|
303
|
-
const repo = new
|
|
716
|
+
const repo = new Repository2();
|
|
304
717
|
const pcbBoard = circuitJson.find((item) => item.type === "pcb_board");
|
|
305
718
|
const holes = circuitJson.filter(
|
|
306
719
|
(item) => item.type === "pcb_hole" || item.type === "pcb_plated_hole"
|
|
@@ -348,22 +761,22 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
348
761
|
new ProductDefinitionShape("", "", productDef)
|
|
349
762
|
);
|
|
350
763
|
const lengthUnit = repo.add(
|
|
351
|
-
new
|
|
764
|
+
new Unknown2("", [
|
|
352
765
|
"( LENGTH_UNIT() NAMED_UNIT(*) SI_UNIT(.MILLI.,.METRE.) )"
|
|
353
766
|
])
|
|
354
767
|
);
|
|
355
768
|
const angleUnit = repo.add(
|
|
356
|
-
new
|
|
769
|
+
new Unknown2("", [
|
|
357
770
|
"( NAMED_UNIT(*) PLANE_ANGLE_UNIT() SI_UNIT($,.RADIAN.) )"
|
|
358
771
|
])
|
|
359
772
|
);
|
|
360
773
|
const solidAngleUnit = repo.add(
|
|
361
|
-
new
|
|
774
|
+
new Unknown2("", [
|
|
362
775
|
"( NAMED_UNIT(*) SI_UNIT($,.STERADIAN.) SOLID_ANGLE_UNIT() )"
|
|
363
776
|
])
|
|
364
777
|
);
|
|
365
778
|
const uncertainty = repo.add(
|
|
366
|
-
new
|
|
779
|
+
new Unknown2("UNCERTAINTY_MEASURE_WITH_UNIT", [
|
|
367
780
|
`LENGTH_MEASURE(1.E-07)`,
|
|
368
781
|
`${lengthUnit}`,
|
|
369
782
|
`'distance_accuracy_value'`,
|
|
@@ -371,7 +784,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
371
784
|
])
|
|
372
785
|
);
|
|
373
786
|
const geomContext = repo.add(
|
|
374
|
-
new
|
|
787
|
+
new Unknown2("", [
|
|
375
788
|
`( GEOMETRIC_REPRESENTATION_CONTEXT(3) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((${uncertainty})) GLOBAL_UNIT_ASSIGNED_CONTEXT((${lengthUnit},${angleUnit},${solidAngleUnit})) REPRESENTATION_CONTEXT('${productName}','3D') )`
|
|
376
789
|
])
|
|
377
790
|
);
|
|
@@ -383,7 +796,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
383
796
|
(point) => repo.add(
|
|
384
797
|
new VertexPoint2(
|
|
385
798
|
"",
|
|
386
|
-
repo.add(new
|
|
799
|
+
repo.add(new CartesianPoint3("", point.x, point.y, 0))
|
|
387
800
|
)
|
|
388
801
|
)
|
|
389
802
|
);
|
|
@@ -391,7 +804,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
391
804
|
(point) => repo.add(
|
|
392
805
|
new VertexPoint2(
|
|
393
806
|
"",
|
|
394
|
-
repo.add(new
|
|
807
|
+
repo.add(new CartesianPoint3("", point.x, point.y, boardThickness))
|
|
395
808
|
)
|
|
396
809
|
)
|
|
397
810
|
);
|
|
@@ -410,7 +823,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
410
823
|
];
|
|
411
824
|
const vertices = corners.map(
|
|
412
825
|
([x, y, z]) => repo.add(
|
|
413
|
-
new VertexPoint2("", repo.add(new
|
|
826
|
+
new VertexPoint2("", repo.add(new CartesianPoint3("", x, y, z)))
|
|
414
827
|
)
|
|
415
828
|
);
|
|
416
829
|
bottomVertices = [vertices[0], vertices[1], vertices[2], vertices[3]];
|
|
@@ -420,7 +833,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
420
833
|
const p1 = v1.resolve(repo).pnt.resolve(repo);
|
|
421
834
|
const p2 = v2.resolve(repo).pnt.resolve(repo);
|
|
422
835
|
const dir = repo.add(
|
|
423
|
-
new
|
|
836
|
+
new Direction3("", p2.x - p1.x, p2.y - p1.y, p2.z - p1.z)
|
|
424
837
|
);
|
|
425
838
|
const vec = repo.add(new Vector2("", dir, 1));
|
|
426
839
|
const line = repo.add(new Line2("", v1.resolve(repo).pnt, vec));
|
|
@@ -442,14 +855,14 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
442
855
|
for (let i = 0; i < bottomVertices.length; i++) {
|
|
443
856
|
verticalEdges.push(createEdge(bottomVertices[i], topVertices[i]));
|
|
444
857
|
}
|
|
445
|
-
const origin = repo.add(new
|
|
446
|
-
const xDir = repo.add(new
|
|
447
|
-
const zDir = repo.add(new
|
|
858
|
+
const origin = repo.add(new CartesianPoint3("", 0, 0, 0));
|
|
859
|
+
const xDir = repo.add(new Direction3("", 1, 0, 0));
|
|
860
|
+
const zDir = repo.add(new Direction3("", 0, 0, 1));
|
|
448
861
|
const bottomFrame = repo.add(
|
|
449
862
|
new Axis2Placement3D2(
|
|
450
863
|
"",
|
|
451
864
|
origin,
|
|
452
|
-
repo.add(new
|
|
865
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
453
866
|
xDir
|
|
454
867
|
)
|
|
455
868
|
);
|
|
@@ -467,18 +880,18 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
467
880
|
const holeX = typeof hole.x === "number" ? hole.x : hole.x.value;
|
|
468
881
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
469
882
|
const radius = hole.hole_diameter / 2;
|
|
470
|
-
const holeCenter = repo.add(new
|
|
883
|
+
const holeCenter = repo.add(new CartesianPoint3("", holeX, holeY, 0));
|
|
471
884
|
const holeVertex = repo.add(
|
|
472
885
|
new VertexPoint2(
|
|
473
886
|
"",
|
|
474
|
-
repo.add(new
|
|
887
|
+
repo.add(new CartesianPoint3("", holeX + radius, holeY, 0))
|
|
475
888
|
)
|
|
476
889
|
);
|
|
477
890
|
const holePlacement = repo.add(
|
|
478
891
|
new Axis2Placement3D2(
|
|
479
892
|
"",
|
|
480
893
|
holeCenter,
|
|
481
|
-
repo.add(new
|
|
894
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
482
895
|
xDir
|
|
483
896
|
)
|
|
484
897
|
);
|
|
@@ -503,7 +916,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
503
916
|
true
|
|
504
917
|
)
|
|
505
918
|
);
|
|
506
|
-
const topOrigin = repo.add(new
|
|
919
|
+
const topOrigin = repo.add(new CartesianPoint3("", 0, 0, boardThickness));
|
|
507
920
|
const topFrame = repo.add(new Axis2Placement3D2("", topOrigin, zDir, xDir));
|
|
508
921
|
const topPlane = repo.add(new Plane2("", topFrame));
|
|
509
922
|
const topLoop = repo.add(
|
|
@@ -520,13 +933,13 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
520
933
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
521
934
|
const radius = hole.hole_diameter / 2;
|
|
522
935
|
const holeCenter = repo.add(
|
|
523
|
-
new
|
|
936
|
+
new CartesianPoint3("", holeX, holeY, boardThickness)
|
|
524
937
|
);
|
|
525
938
|
const holeVertex = repo.add(
|
|
526
939
|
new VertexPoint2(
|
|
527
940
|
"",
|
|
528
941
|
repo.add(
|
|
529
|
-
new
|
|
942
|
+
new CartesianPoint3("", holeX + radius, holeY, boardThickness)
|
|
530
943
|
)
|
|
531
944
|
)
|
|
532
945
|
);
|
|
@@ -563,8 +976,8 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
563
976
|
y: bottomV2.y - bottomV1.y,
|
|
564
977
|
z: 0
|
|
565
978
|
};
|
|
566
|
-
const normalDir = repo.add(new
|
|
567
|
-
const refDir = repo.add(new
|
|
979
|
+
const normalDir = repo.add(new Direction3("", edgeDir.y, -edgeDir.x, 0));
|
|
980
|
+
const refDir = repo.add(new Direction3("", edgeDir.x, edgeDir.y, 0));
|
|
568
981
|
const sideFrame = repo.add(
|
|
569
982
|
new Axis2Placement3D2("", bottomV1Pnt, normalDir, refDir)
|
|
570
983
|
);
|
|
@@ -594,18 +1007,18 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
594
1007
|
const holeX = typeof hole.x === "number" ? hole.x : hole.x.value;
|
|
595
1008
|
const holeY = typeof hole.y === "number" ? hole.y : hole.y.value;
|
|
596
1009
|
const radius = hole.hole_diameter / 2;
|
|
597
|
-
const bottomHoleCenter = repo.add(new
|
|
1010
|
+
const bottomHoleCenter = repo.add(new CartesianPoint3("", holeX, holeY, 0));
|
|
598
1011
|
const bottomHoleVertex = repo.add(
|
|
599
1012
|
new VertexPoint2(
|
|
600
1013
|
"",
|
|
601
|
-
repo.add(new
|
|
1014
|
+
repo.add(new CartesianPoint3("", holeX + radius, holeY, 0))
|
|
602
1015
|
)
|
|
603
1016
|
);
|
|
604
1017
|
const bottomHolePlacement = repo.add(
|
|
605
1018
|
new Axis2Placement3D2(
|
|
606
1019
|
"",
|
|
607
1020
|
bottomHoleCenter,
|
|
608
|
-
repo.add(new
|
|
1021
|
+
repo.add(new Direction3("", 0, 0, -1)),
|
|
609
1022
|
xDir
|
|
610
1023
|
)
|
|
611
1024
|
);
|
|
@@ -622,13 +1035,13 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
622
1035
|
)
|
|
623
1036
|
);
|
|
624
1037
|
const topHoleCenter = repo.add(
|
|
625
|
-
new
|
|
1038
|
+
new CartesianPoint3("", holeX, holeY, boardThickness)
|
|
626
1039
|
);
|
|
627
1040
|
const topHoleVertex = repo.add(
|
|
628
1041
|
new VertexPoint2(
|
|
629
1042
|
"",
|
|
630
1043
|
repo.add(
|
|
631
|
-
new
|
|
1044
|
+
new CartesianPoint3("", holeX + radius, holeY, boardThickness)
|
|
632
1045
|
)
|
|
633
1046
|
)
|
|
634
1047
|
);
|
|
@@ -664,20 +1077,36 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
664
1077
|
}
|
|
665
1078
|
const allFaces = [bottomFace, topFace, ...sideFaces, ...holeCylindricalFaces];
|
|
666
1079
|
const shell = repo.add(new ClosedShell2("", allFaces));
|
|
667
|
-
const solid = repo.add(new
|
|
1080
|
+
const solid = repo.add(new ManifoldSolidBrep3(productName, shell));
|
|
668
1081
|
const allSolids = [solid];
|
|
1082
|
+
let handledComponentIds = /* @__PURE__ */ new Set();
|
|
1083
|
+
let handledPcbComponentIds = /* @__PURE__ */ new Set();
|
|
1084
|
+
if (options.includeComponents && options.includeExternalMeshes) {
|
|
1085
|
+
const mergeResult = await mergeExternalStepModels({
|
|
1086
|
+
repo,
|
|
1087
|
+
circuitJson,
|
|
1088
|
+
boardThickness
|
|
1089
|
+
});
|
|
1090
|
+
handledComponentIds = mergeResult.handledComponentIds;
|
|
1091
|
+
handledPcbComponentIds = mergeResult.handledPcbComponentIds;
|
|
1092
|
+
allSolids.push(...mergeResult.solids);
|
|
1093
|
+
}
|
|
669
1094
|
if (options.includeComponents) {
|
|
670
1095
|
const componentSolids = await generateComponentMeshes({
|
|
671
1096
|
repo,
|
|
672
1097
|
circuitJson,
|
|
673
1098
|
boardThickness,
|
|
674
|
-
includeExternalMeshes: options.includeExternalMeshes
|
|
1099
|
+
includeExternalMeshes: options.includeExternalMeshes,
|
|
1100
|
+
excludeCadComponentIds: handledComponentIds,
|
|
1101
|
+
excludePcbComponentIds: handledPcbComponentIds
|
|
675
1102
|
});
|
|
676
1103
|
allSolids.push(...componentSolids);
|
|
677
1104
|
}
|
|
678
1105
|
const styledItems = [];
|
|
679
|
-
|
|
680
|
-
const
|
|
1106
|
+
allSolids.forEach((solidRef, index) => {
|
|
1107
|
+
const isBoard = index === 0;
|
|
1108
|
+
const [r, g, b] = isBoard ? [0.2, 0.6, 0.2] : [0.75, 0.75, 0.75];
|
|
1109
|
+
const color = repo.add(new ColourRgb("", r, g, b));
|
|
681
1110
|
const fillColor = repo.add(new FillAreaStyleColour("", color));
|
|
682
1111
|
const fillStyle = repo.add(new FillAreaStyle("", [fillColor]));
|
|
683
1112
|
const surfaceFill = repo.add(new SurfaceStyleFillArea(fillStyle));
|
|
@@ -686,7 +1115,7 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
686
1115
|
const presStyle = repo.add(new PresentationStyleAssignment([surfaceUsage]));
|
|
687
1116
|
const styledItem = repo.add(new StyledItem("", [presStyle], solidRef));
|
|
688
1117
|
styledItems.push(styledItem);
|
|
689
|
-
}
|
|
1118
|
+
});
|
|
690
1119
|
repo.add(
|
|
691
1120
|
new MechanicalDesignGeometricPresentationRepresentation(
|
|
692
1121
|
"",
|
|
@@ -698,7 +1127,8 @@ async function circuitJsonToStep(circuitJson, options = {}) {
|
|
|
698
1127
|
new AdvancedBrepShapeRepresentation(productName, allSolids, geomContext)
|
|
699
1128
|
);
|
|
700
1129
|
repo.add(new ShapeDefinitionRepresentation(productDefShape, shapeRep));
|
|
701
|
-
|
|
1130
|
+
const stepText = repo.toPartFile({ name: productName });
|
|
1131
|
+
return normalizeStepNumericExponents(stepText);
|
|
702
1132
|
}
|
|
703
1133
|
export {
|
|
704
1134
|
circuitJsonToStep
|