pcb-scene3d-viewer 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +7 -0
  2. package/docs/api.md +18 -0
  3. package/docs/circuitjson.md +6 -4
  4. package/docs/release-notes-v1.3.0.md +38 -0
  5. package/package.json +3 -2
  6. package/src/CircuitJsonCadModelAssetResolver.mjs +24 -10
  7. package/src/PcbAssemblyGltfModelMeshParser.mjs +3 -1
  8. package/src/PcbAssemblyModelMeshLoader.mjs +1 -1
  9. package/src/PcbAssemblyTextModelMeshParser.mjs +3 -1
  10. package/src/PcbScene3dBoardAssemblyPresentation.mjs +1 -11
  11. package/src/PcbScene3dBoardMaterialPalette.mjs +12 -0
  12. package/src/PcbScene3dCircuitJsonAdapter.mjs +33 -85
  13. package/src/PcbScene3dCircuitJsonCopperTextBuilder.mjs +385 -0
  14. package/src/PcbScene3dCircuitJsonDocumentationArtworkBuilder.mjs +123 -22
  15. package/src/PcbScene3dCircuitJsonGeometry.mjs +12 -0
  16. package/src/PcbScene3dCircuitJsonPadCorner.mjs +72 -0
  17. package/src/PcbScene3dCircuitJsonSilkscreenBuilder.mjs +140 -25
  18. package/src/PcbScene3dCircuitJsonSilkscreenDetailBuilder.mjs +21 -0
  19. package/src/PcbScene3dCircuitJsonSourceLayer.mjs +124 -0
  20. package/src/PcbScene3dCircuitJsonTraceRouteBuilder.mjs +6 -7
  21. package/src/PcbScene3dCopperDetailFilter.mjs +5 -1
  22. package/src/PcbScene3dCopperFactory.mjs +23 -4
  23. package/src/PcbScene3dCopperTextFactory.mjs +105 -20
  24. package/src/PcbScene3dExternalModels.mjs +26 -0
  25. package/src/PcbScene3dMaskCoveredCopperSideGroupBuilder.mjs +16 -1
  26. package/src/PcbScene3dModelContent.mjs +32 -1
  27. package/src/PcbScene3dRuntimeBoardMeshes.mjs +2 -4
  28. package/src/PcbScene3dSilkscreenCopperCutoutBuilder.mjs +588 -0
  29. package/src/PcbScene3dStepLoader.mjs +2 -1
  30. package/src/PcbScene3dStrokeCutoutBuilder.mjs +98 -0
  31. package/src/PcbScene3dViaFactory.mjs +51 -5
  32. package/src/PcbScene3dViaLayerSpan.mjs +126 -0
package/README.md CHANGED
@@ -22,6 +22,12 @@ factories, model loading, component picking, view presets, archive export,
22
22
  GLTF/GLB assembly writing, and optional DOM shell can be reused by other
23
23
  browser-based ECAD tools.
24
24
 
25
+ Version 1.3.0 renders the CircuitJSON 1.2 fidelity contract directly: exact
26
+ oval and rounded pads, filled and mirrored silkscreen, copper text and cutouts,
27
+ surface-aware blind/buried vias, collision-safe model assets, and a consistent
28
+ light FR-4 substrate edge. See the
29
+ [1.3.0 release notes](docs/release-notes-v1.3.0.md).
30
+
25
31
  ## CircuitJSON 1.1 convergence
26
32
 
27
33
  Version 1.2.2 accepts the common document and prepared-context shapes returned
@@ -131,6 +137,7 @@ const controller = new PcbScene3dController(viewportNode, document)
131
137
  - [API](docs/api.md)
132
138
  - [CircuitJSON usage](docs/circuitjson.md)
133
139
  - [1.2.2 release notes](docs/release-notes-v1.2.2.md)
140
+ - [1.3.0 release notes](docs/release-notes-v1.3.0.md)
134
141
  - [1.2.1 release notes](docs/release-notes-v1.2.1.md)
135
142
  - [1.2.0 release notes](docs/release-notes-v1.2.0.md)
136
143
  - [Model format](docs/model-format.md)
package/docs/api.md CHANGED
@@ -68,6 +68,24 @@ controller only and are forwarded to the runtime through
68
68
 
69
69
  ## CircuitJSON Input
70
70
 
71
+ ### `PcbScene3dBoardMaterialPalette`
72
+
73
+ The `pcb-scene3d-viewer/scene3d` subpath exports the shared board-material
74
+ resolver used by both runtime meshes and assembly export.
75
+
76
+ - `resolveSurfaceColor(board, options?)` returns the authored or fallback
77
+ solder-mask face color.
78
+ - `resolveBoardSurfaceColor(board, options?)` returns the display-darkened
79
+ solder-mask face color.
80
+ - `resolveEdgeColor(board)` returns an authored `edgeColor` or the light FR-4
81
+ substrate fallback `0xc9ca78`.
82
+ - `isGeneratedSurfaceVisible(options?)` and
83
+ `isGeneratedBodyVisible(options?)` expose the generated-board visibility
84
+ decision.
85
+
86
+ Runtime and exported board edges call the same resolver, so a missing edge
87
+ color cannot produce different substrate materials in the two paths.
88
+
71
89
  ### `PcbScene3dCircuitJsonAdapter`
72
90
 
73
91
  Converts a common CircuitJSON `DocumentResult`, prepared
@@ -430,10 +430,12 @@ explicit false value exposes the copper:
430
430
  }
431
431
  ```
432
432
 
433
- Silkscreen text uses `anchor_position` when available, falling back to `x` and
434
- `y`. Common `anchor_alignment` values such as `center`, `bottom_left`, or
435
- `top_right` are normalized into horizontal and vertical alignment metadata for
436
- stroke text rendering.
433
+ Silkscreen and copper text use `anchor_position` when available, falling back
434
+ to `x` and `y`. Independent `font_width` and `font_height`, `stroke_width`,
435
+ `source_anchor_alignment`, source layer/type provenance, mirroring, and hidden
436
+ state are honored when present. Filled silkscreen shapes retain their fill and
437
+ drill/copper cutouts, and bottom-side artwork is mirrored around its authored
438
+ anchor instead of being repositioned as top-side text.
437
439
 
438
440
  Component model metadata can be supplied on `cad_component` elements with
439
441
  `model_3mf_url`, `model_step_url`, `model_wrl_url`, `model_glb_url`,
@@ -0,0 +1,38 @@
1
+ # pcb-scene3d-viewer 1.3.0
2
+
3
+ This minor release consumes CircuitJSON Toolkit 1.2 directly and restores exact
4
+ PCB surface fidelity without source-format or host-app adapters.
5
+
6
+ ## Rendering and API changes
7
+
8
+ - Oval, rounded-rectangle, pill, polygon, and independently rotated drilled
9
+ pads keep their authored copper and drill geometry.
10
+ - Silkscreen strokes, fills, text dimensions, anchors, mirroring, source-layer
11
+ mapping, and surface cutouts render from canonical CircuitJSON fields.
12
+ - Copper text participates in the same solder-mask and cutout pipeline as other
13
+ copper detail.
14
+ - Blind and buried vias render only on the board surfaces reached by their
15
+ explicit layer span.
16
+ - `PcbScene3dBoardMaterialPalette.resolveEdgeColor(board)` is now public on the
17
+ `scene3d` subpath. Runtime and exported board edges share its authored-color
18
+ lookup and light FR-4 fallback (`0xc9ca78`).
19
+
20
+ ## Models and diagnostics
21
+
22
+ - Canonical and session CAD assets preserve exact case-sensitive project-path
23
+ aliases. Case-insensitive fallback is accepted only when unique, preventing
24
+ same-name and same-stem collisions across folders.
25
+ - Missing-model diagnostics are deferred until resolution has actually failed;
26
+ retryable or externally supplied assets are not reported as unavailable
27
+ prematurely.
28
+ - STEP, WRL, GLTF, and text/binary model payload handling avoids redundant
29
+ copies and retains the existing opt-in network policy.
30
+
31
+ ## Compatibility and performance
32
+
33
+ - The runtime baseline is `circuitjson-toolkit@^1.2.0`; the existing
34
+ `@sunbox/occt-import-js@^0.0.28` WASM package remains compatible and does not
35
+ require a coordinated rebuild.
36
+ - Existing scene descriptions, document envelopes, controller/runtime
37
+ parameters, and return shapes remain accepted. The palette method and
38
+ CircuitJSON fields above are additive.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pcb-scene3d-viewer",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Reusable Three.js PCB 3D scene viewer for normalized ECAD and CircuitJSON scene descriptions",
5
5
  "keywords": [
6
6
  "pcb",
@@ -36,6 +36,7 @@
36
36
  "docs/release-notes-v1.2.0.md",
37
37
  "docs/release-notes-v1.2.1.md",
38
38
  "docs/release-notes-v1.2.2.md",
39
+ "docs/release-notes-v1.3.0.md",
39
40
  "docs/model-format.md",
40
41
  "docs/testing.md",
41
42
  "spec",
@@ -57,7 +58,7 @@
57
58
  },
58
59
  "dependencies": {
59
60
  "@sunbox/occt-import-js": "^0.0.28",
60
- "circuitjson-toolkit": "^1.1.2",
61
+ "circuitjson-toolkit": "^1.2.0",
61
62
  "earcut": "3.0.2",
62
63
  "fflate": "^0.8.2",
63
64
  "polygon-clipping": "^0.15.7",
@@ -352,7 +352,7 @@ export class CircuitJsonCadModelAssetResolver {
352
352
  * Finds the earliest indexed asset matching one CAD model URL.
353
353
  * @param {string} url Model URL.
354
354
  * @param {object} context Resolver context.
355
- * @param {Map<string, object>} assetIndex Asset alias index.
355
+ * @param {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }} assetIndex Asset alias index.
356
356
  * @returns {object | null}
357
357
  */
358
358
  static #matchingIndexedEntry(url, context, assetIndex) {
@@ -363,7 +363,11 @@ export class CircuitJsonCadModelAssetResolver {
363
363
  let match = null
364
364
  for (const candidate of candidates) {
365
365
  const indexed = assetIndex.exact.get(candidate)
366
- if (indexed && (!match || indexed.index < match.index)) {
366
+ if (
367
+ indexed &&
368
+ indexed !== AMBIGUOUS_ASSET &&
369
+ (!match || indexed.index < match.index)
370
+ ) {
367
371
  match = indexed
368
372
  }
369
373
  }
@@ -554,12 +558,13 @@ export class CircuitJsonCadModelAssetResolver {
554
558
 
555
559
  /**
556
560
  * Resolves an exact alias or one unambiguous case-insensitive fallback.
557
- * @param {{ exact: Map<string, object>, folded: Map<string, object | symbol> }} index Asset index.
561
+ * @param {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }} index Asset index.
558
562
  * @param {string} key Exact normalized key.
559
563
  * @returns {object | null} Indexed asset entry.
560
564
  */
561
565
  static #indexedAsset(index, key) {
562
566
  const exact = index.exact.get(key)
567
+ if (exact === AMBIGUOUS_ASSET) return null
563
568
  if (exact) return exact
564
569
  const folded = index.folded.get(key.toLowerCase())
565
570
  return folded && folded !== AMBIGUOUS_ASSET ? folded : null
@@ -606,10 +611,10 @@ export class CircuitJsonCadModelAssetResolver {
606
611
  }
607
612
 
608
613
  /**
609
- * Builds one descriptor-safe, first-match-preserving asset alias index.
614
+ * Builds one descriptor-safe asset alias index with collision rejection.
610
615
  * @param {object[]} assets Asset rows.
611
616
  * @param {boolean} [canonical] Whether rows use the canonical toolkit asset contract.
612
- * @returns {{ exact: Map<string, object>, folded: Map<string, object | symbol> }}
617
+ * @returns {{ exact: Map<string, object | symbol>, folded: Map<string, object | symbol> }}
613
618
  */
614
619
  static #assetIndex(assets, canonical = false) {
615
620
  const exact = new Map()
@@ -625,7 +630,12 @@ export class CircuitJsonCadModelAssetResolver {
625
630
  for (const alias of CircuitJsonCadModelAssetResolver.#assetAliases(
626
631
  asset
627
632
  )) {
628
- if (!exact.has(alias)) exact.set(alias, entry)
633
+ const exactPrevious = exact.get(alias)
634
+ if (!exactPrevious) {
635
+ exact.set(alias, entry)
636
+ } else if (exactPrevious !== entry) {
637
+ exact.set(alias, AMBIGUOUS_ASSET)
638
+ }
629
639
  const foldedAlias = alias.toLowerCase()
630
640
  const previous = folded.get(foldedAlias)
631
641
  if (!previous) {
@@ -719,6 +729,9 @@ export class CircuitJsonCadModelAssetResolver {
719
729
  asset,
720
730
  'source'
721
731
  )
732
+ const explicitAliases = CircuitJsonCadModelAssetResolver.#denseArray(
733
+ CircuitJsonCadModelAssetResolver.#ownData(asset, 'aliases')
734
+ ).filter((value) => typeof value === 'string')
722
735
  return new Set(
723
736
  [
724
737
  CircuitJsonCadModelAssetResolver.#ownData(
@@ -742,7 +755,8 @@ export class CircuitJsonCadModelAssetResolver {
742
755
  'relativePath'
743
756
  ),
744
757
  CircuitJsonCadModelAssetResolver.#ownData(source, 'url'),
745
- CircuitJsonCadModelAssetResolver.#ownData(source, 'uri')
758
+ CircuitJsonCadModelAssetResolver.#ownData(source, 'uri'),
759
+ ...explicitAliases
746
760
  ]
747
761
  .map((value) => CircuitJsonCadModelAssetResolver.#key(value))
748
762
  .filter(Boolean)
@@ -866,10 +880,10 @@ export class CircuitJsonCadModelAssetResolver {
866
880
  */
867
881
  static #key(value) {
868
882
  if (typeof value !== 'string' && typeof value !== 'number') return ''
869
- return String(value || '')
883
+ const key = String(value || '')
870
884
  .trim()
871
885
  .split(/[?#]/u)[0]
872
- .replace(/^https?:\/\/[^/]+\//iu, '')
873
- .replace(/^\/+/u, '')
886
+ .replaceAll('\\', '/')
887
+ return /^https?:\/\//iu.test(key) ? key : key.replace(/^\/+/u, '')
874
888
  }
875
889
  }
@@ -1,3 +1,5 @@
1
+ import { PcbScene3dModelContent } from './PcbScene3dModelContent.mjs'
2
+
1
3
  const MM_TO_MIL = 1000 / 25.4
2
4
  const GLB_MAGIC = 0x46546c67
3
5
  const GLB_VERSION = 2
@@ -887,7 +889,7 @@ export class PcbAssemblyGltfModelMeshParser {
887
889
  return bytes
888
890
  }
889
891
 
890
- throw new Error(label + ' model content is not available.')
892
+ throw PcbScene3dModelContent.unavailableError(label)
891
893
  }
892
894
 
893
895
  /**
@@ -334,7 +334,7 @@ export class PcbAssemblyModelMeshLoader {
334
334
  if (bytes) return new TextDecoder().decode(bytes)
335
335
  }
336
336
 
337
- throw new Error('WRL model content is not available.')
337
+ throw PcbScene3dModelContent.unavailableError('WRL')
338
338
  }
339
339
 
340
340
  /**
@@ -1,3 +1,5 @@
1
+ import { PcbScene3dModelContent } from './PcbScene3dModelContent.mjs'
2
+
1
3
  const MM_TO_MIL = 1000 / 25.4
2
4
 
3
5
  /**
@@ -548,7 +550,7 @@ export class PcbAssemblyTextModelMeshParser {
548
550
  return bytes
549
551
  }
550
552
 
551
- throw new Error(label + ' model content is not available.')
553
+ throw PcbScene3dModelContent.unavailableError(label)
552
554
  }
553
555
 
554
556
  /**
@@ -35,8 +35,7 @@ export class PcbScene3dBoardAssemblyPresentation {
35
35
  board,
36
36
  options
37
37
  )
38
- const edgeColor =
39
- PcbScene3dBoardAssemblyPresentation.#resolveEdgeColor(board)
38
+ const edgeColor = PcbScene3dBoardMaterialPalette.resolveEdgeColor(board)
40
39
  const importedSurfaceColor =
41
40
  PcbScene3dBoardAssemblyPresentation.#resolveImportedSurfaceColor(
42
41
  meshRecords
@@ -426,15 +425,6 @@ export class PcbScene3dBoardAssemblyPresentation {
426
425
  })
427
426
  }
428
427
 
429
- /**
430
- * Resolves the app-level substrate edge color for board assembly meshes.
431
- * @param {{ edgeColor?: number } | null | undefined} board Board dimensions.
432
- * @returns {number}
433
- */
434
- static #resolveEdgeColor(board) {
435
- return Number.isInteger(board?.edgeColor) ? board.edgeColor : 0xc9ca78
436
- }
437
-
438
428
  /**
439
429
  * Applies display treatment to board substrate material.
440
430
  * @param {any | any[]} material Material or material list.
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export class PcbScene3dBoardMaterialPalette {
5
5
  static #DEFAULT_SURFACE_COLOR = 0x2a5f27
6
+ static #DEFAULT_EDGE_COLOR = 0xc9ca78
6
7
  static #BOARD_SURFACE_DARKEN_RATIO = 0.88
7
8
 
8
9
  /**
@@ -39,6 +40,17 @@ export class PcbScene3dBoardMaterialPalette {
39
40
  )
40
41
  }
41
42
 
43
+ /**
44
+ * Resolves the visible substrate-core color for board edge faces.
45
+ * @param {{ edgeColor?: number } | null | undefined} board Board metadata.
46
+ * @returns {number}
47
+ */
48
+ static resolveEdgeColor(board) {
49
+ return Number.isInteger(board?.edgeColor)
50
+ ? board.edgeColor
51
+ : PcbScene3dBoardMaterialPalette.#DEFAULT_EDGE_COLOR
52
+ }
53
+
42
54
  /**
43
55
  * Returns true when the source format supplies display-stable board colors.
44
56
  * @param {{ sourceFormat?: string }} options Scene options.
@@ -7,7 +7,8 @@ import { PcbScene3dCircuitJsonGeometry } from './PcbScene3dCircuitJsonGeometry.m
7
7
  import { PcbScene3dCircuitJsonDrillDetail } from './PcbScene3dCircuitJsonDrillDetail.mjs'
8
8
  import { PcbScene3dCircuitJsonModelTransform } from './PcbScene3dCircuitJsonModelTransform.mjs'
9
9
  import { PcbScene3dCircuitJsonCopperPourBuilder } from './PcbScene3dCircuitJsonCopperPourBuilder.mjs'
10
- import { PcbScene3dCircuitJsonSilkscreenBuilder } from './PcbScene3dCircuitJsonSilkscreenBuilder.mjs'
10
+ import { PcbScene3dCircuitJsonCopperTextBuilder } from './PcbScene3dCircuitJsonCopperTextBuilder.mjs'
11
+ import { PcbScene3dCircuitJsonSilkscreenDetailBuilder } from './PcbScene3dCircuitJsonSilkscreenDetailBuilder.mjs'
11
12
  import { PcbScene3dFootprintBodyBuilder } from './PcbScene3dFootprintBodyBuilder.mjs'
12
13
  import { PcbScene3dCircuitJsonLayer } from './PcbScene3dCircuitJsonLayer.mjs'
13
14
  import { PcbScene3dCircuitJsonInput } from './PcbScene3dCircuitJsonInput.mjs'
@@ -18,6 +19,7 @@ import { PcbScene3dCircuitJsonSolderPasteBuilder } from './PcbScene3dCircuitJson
18
19
  import { CircuitJsonCadModelAssetResolver } from './CircuitJsonCadModelAssetResolver.mjs'
19
20
  import { PcbScene3dDescriptorSafeRecord } from './PcbScene3dDescriptorSafeRecord.mjs'
20
21
  import { PcbScene3dCircuitJsonModelAsset } from './PcbScene3dCircuitJsonModelAsset.mjs'
22
+ import { PcbScene3dCircuitJsonPadCorner } from './PcbScene3dCircuitJsonPadCorner.mjs'
21
23
 
22
24
  const DEFAULT_COMPONENT_HEIGHT_MIL = 60
23
25
  const RECTANGULAR_PAD_SHAPE = 2
@@ -682,27 +684,34 @@ export class PcbScene3dCircuitJsonAdapter {
682
684
  index,
683
685
  options
684
686
  )
687
+ const pads = [
688
+ ...PcbScene3dCircuitJsonAdapter.#buildSmtPads(index),
689
+ ...PcbScene3dCircuitJsonAdapter.#buildPlatedHoles(index),
690
+ ...PcbScene3dCircuitJsonAdapter.#buildNonPlatedHoles(index)
691
+ ]
692
+ const vias = PcbScene3dCircuitJsonTraceRouteBuilder.buildVias(index)
693
+ const tracks = [
694
+ ...PcbScene3dCircuitJsonTraceRouteBuilder.buildTracks(index),
695
+ ...PcbScene3dCircuitJsonThermalSpokeBuilder.build(index)
696
+ ]
697
+ const polygons = PcbScene3dCircuitJsonCopperPourBuilder.build(index)
698
+ const copperTexts = PcbScene3dCircuitJsonCopperTextBuilder.build(index)
699
+ const silkscreen = PcbScene3dCircuitJsonSilkscreenDetailBuilder.build(
700
+ index,
701
+ { pads, vias, tracks, polygons, copperTexts },
702
+ options
703
+ )
685
704
 
686
705
  return {
687
- pads: [
688
- ...PcbScene3dCircuitJsonAdapter.#buildSmtPads(index),
689
- ...PcbScene3dCircuitJsonAdapter.#buildPlatedHoles(index),
690
- ...PcbScene3dCircuitJsonAdapter.#buildNonPlatedHoles(index)
691
- ],
692
- tracks: [
693
- ...PcbScene3dCircuitJsonTraceRouteBuilder.buildTracks(index),
694
- ...PcbScene3dCircuitJsonThermalSpokeBuilder.build(index)
695
- ],
706
+ pads,
707
+ tracks,
696
708
  arcs: [],
697
709
  fills: [],
698
- vias: PcbScene3dCircuitJsonTraceRouteBuilder.buildVias(index),
699
- polygons: PcbScene3dCircuitJsonCopperPourBuilder.build(index),
700
- copperTexts: [],
710
+ vias,
711
+ polygons,
712
+ copperTexts,
701
713
  embeddedFonts: [],
702
- silkscreen: PcbScene3dCircuitJsonSilkscreenBuilder.build(
703
- index,
704
- options
705
- ),
714
+ silkscreen,
706
715
  ...(paste ? { paste } : {}),
707
716
  drillQuality: PcbScene3dCircuitJsonGeometry.normalizeDrillQuality(
708
717
  options?.boardDrillQuality
@@ -745,11 +754,7 @@ export class PcbScene3dCircuitJsonAdapter {
745
754
  holeDiameter: 0,
746
755
  hasTopSolderMaskOpening: !isBottom && exposesCopper,
747
756
  hasBottomSolderMaskOpening: isBottom && exposesCopper,
748
- ...PcbScene3dCircuitJsonAdapter.#roundedPadMetadata(
749
- pad,
750
- size,
751
- isBottom
752
- )
757
+ ...PcbScene3dCircuitJsonPadCorner.metadata(pad, size, isBottom)
753
758
  }
754
759
  return padDetail
755
760
  })
@@ -768,7 +773,7 @@ export class PcbScene3dCircuitJsonAdapter {
768
773
  y: Number(hole?.y || 0)
769
774
  })
770
775
  const size =
771
- PcbScene3dCircuitJsonAdapter.#platedHoleOuterSize(geometry)
776
+ PcbScene3dCircuitJsonGeometry.platedHoleOuterSize(geometry)
772
777
  const center = CircuitJsonUnits.pointMmToMil({
773
778
  x: hole?.x,
774
779
  y: hole?.y
@@ -796,6 +801,11 @@ export class PcbScene3dCircuitJsonAdapter {
796
801
  sizeMidY: size.height,
797
802
  sizeBottomX: size.width,
798
803
  sizeBottomY: size.height,
804
+ ...PcbScene3dCircuitJsonPadCorner.metadata(
805
+ geometry,
806
+ size,
807
+ null
808
+ ),
799
809
  ...PcbScene3dCircuitJsonDrillDetail.fields(drill),
800
810
  holeOffsetX: drill.center.x - center.x,
801
811
  holeOffsetY: drill.center.y - center.y,
@@ -852,68 +862,6 @@ export class PcbScene3dCircuitJsonAdapter {
852
862
  height: CircuitJsonUnits.mmToMil(pad?.height, 1)
853
863
  }
854
864
  }
855
-
856
- /**
857
- * Builds rounded-rectangle metadata for pill-shaped SMT pads.
858
- * @param {object} pad Pad element.
859
- * @param {{ width: number, height: number }} size Pad copper size.
860
- * @param {boolean} isBottom True when the pad is on the bottom side.
861
- * @returns {object}
862
- */
863
- static #roundedPadMetadata(pad, size, isBottom) {
864
- if (!String(pad?.shape || '').endsWith('pill')) {
865
- return {}
866
- }
867
-
868
- const cornerRadius =
869
- PcbScene3dCircuitJsonAdapter.#roundedPadCornerRadiusPercent(
870
- pad,
871
- size
872
- )
873
- return {
874
- hasRoundedRect: true,
875
- roundedRectShapeTop: isBottom ? null : RECTANGULAR_PAD_SHAPE,
876
- roundedRectShapeBottom: isBottom ? RECTANGULAR_PAD_SHAPE : null,
877
- cornerRadiusTop: isBottom ? null : cornerRadius,
878
- cornerRadiusBottom: isBottom ? cornerRadius : null
879
- }
880
- }
881
-
882
- /**
883
- * Resolves a pill-pad corner radius as a percent of the shortest side.
884
- * @param {object} pad Pad element.
885
- * @param {{ width: number, height: number }} size Pad copper size.
886
- * @returns {number}
887
- */
888
- static #roundedPadCornerRadiusPercent(pad, size) {
889
- const width = CircuitJsonUnits.optionalLength(pad?.width)
890
- const height = CircuitJsonUnits.optionalLength(pad?.height)
891
- const radius = CircuitJsonUnits.optionalLength(pad?.radius)
892
- if (width > 0 && height > 0 && radius > 0) {
893
- return Math.min((radius / Math.min(width, height)) * 100, 50)
894
- }
895
-
896
- const shortestSide = Math.min(Number(size.width), Number(size.height))
897
- const radiusMil = CircuitJsonUnits.mmToMil(pad?.radius, 0)
898
- if (shortestSide > 0 && radiusMil > 0) {
899
- return Math.min((radiusMil / shortestSide) * 100, 50)
900
- }
901
-
902
- return 50
903
- }
904
-
905
- /**
906
- * Resolves plated-hole copper size from CircuitJSON fields.
907
- * @param {object} geometry Shared drilled-primitive geometry.
908
- * @returns {{ width: number, height: number }}
909
- */
910
- static #platedHoleOuterSize(geometry) {
911
- return {
912
- width: CircuitJsonUnits.mmToMil(geometry?.width, 1),
913
- height: CircuitJsonUnits.mmToMil(geometry?.height, 1)
914
- }
915
- }
916
-
917
865
  /**
918
866
  * Resolves the viewer pad shape code.
919
867
  * @param {object} pad CircuitJSON pad element.