altium-toolkit 1.4.4 → 1.4.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "altium-toolkit",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "description": "Altium document parsing and non-interactive rendering utilities",
5
5
  "keywords": [
6
6
  "altium",
@@ -123,10 +123,14 @@ export class PcbSideResolvedRenderModel {
123
123
  const layerId = PcbSideResolvedRenderModel.#effectivePadLayerId(pad)
124
124
  const apertureSide =
125
125
  layerId === 1 ? 'front' : layerId === 32 ? 'back' : side
126
- if (apertureSide !== 'back') return { ...pad }
126
+ const projectedPad = {
127
+ ...pad,
128
+ copperRenderGroup: apertureSide === side ? 'surface' : 'subsurface'
129
+ }
130
+ if (apertureSide !== 'back') return projectedPad
127
131
 
128
132
  return {
129
- ...pad,
133
+ ...projectedPad,
130
134
  sizeTopX: PcbSideResolvedRenderModel.#firstFiniteValue(
131
135
  pad.sizeBottomX,
132
136
  pad.sizeMidX,
@@ -0,0 +1,130 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ import { PcbSvgRenderer as LegacyPcbSvgRenderer } from '../ui/PcbSvgRenderer.mjs'
5
+
6
+ /**
7
+ * Renders native Altium PCB models through the preserved historical renderer
8
+ * while applying convergence-owned copper grouping semantics.
9
+ */
10
+ export class PcbSvgRenderer {
11
+ static #SUBSURFACE_GROUP = '<g class="pcb-copper pcb-copper--subsurface">'
12
+ static #SURFACE_GROUP = '<g class="pcb-copper pcb-copper--surface">'
13
+
14
+ /**
15
+ * Renders one native Altium PCB document as SVG markup.
16
+ * @param {Record<string, any>} documentModel Native renderer document.
17
+ * @param {Record<string, any>} [options] Historical renderer options.
18
+ * @returns {string} Rendered SVG panel markup.
19
+ */
20
+ static render(documentModel, options = {}) {
21
+ const markup = LegacyPcbSvgRenderer.render(documentModel, options)
22
+ const subsurfacePadIndexes = PcbSvgRenderer.#subsurfacePadIndexes(
23
+ documentModel,
24
+ options
25
+ )
26
+ if (subsurfacePadIndexes.length === 0) return markup
27
+
28
+ return PcbSvgRenderer.#movePadsToSubsurfaceGroup(
29
+ markup,
30
+ subsurfacePadIndexes
31
+ )
32
+ }
33
+
34
+ /**
35
+ * Renders one deterministic SVG entry per physical or primitive layer.
36
+ * Layer exports intentionally preserve the historical layer-only output.
37
+ * @param {Record<string, any>} documentModel Native renderer document.
38
+ * @returns {{ layerId?: number, layerKey: string, displayName: string, role: string, svg: string }[]}
39
+ */
40
+ static renderLayerSvgs(documentModel) {
41
+ return LegacyPcbSvgRenderer.renderLayerSvgs(documentModel)
42
+ }
43
+
44
+ /**
45
+ * Finds pad indexes whose copper must share the contextual copper group.
46
+ * Layer-only exports do not use composite surface/subsurface grouping.
47
+ * @param {Record<string, any>} documentModel Native renderer document.
48
+ * @param {Record<string, any>} options Historical renderer options.
49
+ * @returns {number[]} Stable pad indexes.
50
+ */
51
+ static #subsurfacePadIndexes(documentModel, options) {
52
+ if (options?.layerView) return []
53
+
54
+ return (documentModel?.pcb?.pads || [])
55
+ .map((pad, index) =>
56
+ pad?.copperRenderGroup === 'subsurface' ? index : -1
57
+ )
58
+ .filter((index) => index >= 0)
59
+ }
60
+
61
+ /**
62
+ * Moves contextual pad groups into the same SVG group as contextual traces
63
+ * so the browser composites both primitives with one shared opacity.
64
+ * @param {string} markup Historical renderer markup.
65
+ * @param {number[]} padIndexes Stable pad indexes to relocate.
66
+ * @returns {string} Copper-group-aware markup.
67
+ */
68
+ static #movePadsToSubsurfaceGroup(markup, padIndexes) {
69
+ const subsurfaceStart = markup.indexOf(PcbSvgRenderer.#SUBSURFACE_GROUP)
70
+ const surfaceStart = markup.indexOf(PcbSvgRenderer.#SURFACE_GROUP)
71
+ if (subsurfaceStart < 0 || surfaceStart <= subsurfaceStart) {
72
+ return markup
73
+ }
74
+
75
+ const subsurfaceClose = markup.lastIndexOf('</g>', surfaceStart)
76
+ if (subsurfaceClose < subsurfaceStart) return markup
77
+
78
+ let remainingMarkup = markup
79
+ const relocatedPads = []
80
+ for (const padIndex of padIndexes) {
81
+ const result = PcbSvgRenderer.#extractPadGroup(
82
+ remainingMarkup,
83
+ padIndex,
84
+ surfaceStart
85
+ )
86
+ if (!result) continue
87
+ remainingMarkup = result.markup
88
+ relocatedPads.push(result.padMarkup)
89
+ }
90
+ if (relocatedPads.length === 0) return markup
91
+
92
+ const updatedSurfaceStart = remainingMarkup.indexOf(
93
+ PcbSvgRenderer.#SURFACE_GROUP
94
+ )
95
+ const updatedSubsurfaceClose = remainingMarkup.lastIndexOf(
96
+ '</g>',
97
+ updatedSurfaceStart
98
+ )
99
+ return (
100
+ remainingMarkup.slice(0, updatedSubsurfaceClose) +
101
+ relocatedPads.join('') +
102
+ remainingMarkup.slice(updatedSubsurfaceClose)
103
+ )
104
+ }
105
+
106
+ /**
107
+ * Extracts one top-level pad group from the historical surface group.
108
+ * Pad groups contain only leaf SVG shapes, so their first closing group is
109
+ * also the matching closing tag.
110
+ * @param {string} markup Current renderer markup.
111
+ * @param {number} padIndex Stable pad index.
112
+ * @param {number} minimumStart Earliest valid surface-group position.
113
+ * @returns {{ markup: string, padMarkup: string } | null} Extraction result.
114
+ */
115
+ static #extractPadGroup(markup, padIndex, minimumStart) {
116
+ const elementKey = 'data-element-key="pcb-pad-' + padIndex + '"'
117
+ const keyStart = markup.indexOf(elementKey, minimumStart)
118
+ if (keyStart < 0) return null
119
+
120
+ const groupStart = markup.lastIndexOf('<g', keyStart)
121
+ const groupEndStart = markup.indexOf('</g>', keyStart)
122
+ if (groupStart < minimumStart || groupEndStart < 0) return null
123
+
124
+ const groupEnd = groupEndStart + '</g>'.length
125
+ return {
126
+ markup: markup.slice(0, groupStart) + markup.slice(groupEnd),
127
+ padMarkup: markup.slice(groupStart, groupEnd)
128
+ }
129
+ }
130
+ }
@@ -4,6 +4,7 @@
4
4
  export * from 'circuitjson-toolkit/extensions'
5
5
 
6
6
  export { AltiumExtensionResolver } from './convergence/AltiumExtensionResolver.mjs'
7
+ export { PcbSvgRenderer } from './convergence/PcbSvgRenderer.mjs'
7
8
  export { SchematicSvgRenderer } from './convergence/SchematicSvgRenderer.mjs'
8
9
  export * from './legacy-parser.mjs'
9
10
  export * from './legacy-netlist-query.mjs'
@@ -270,6 +270,10 @@
270
270
  fill: var(--pcb-copper-solid-fill);
271
271
  }
272
272
 
273
+ .pcb-copper--subsurface .pcb-pad--smd .pcb-pad__ring {
274
+ fill: var(--pcb-subsurface-track-color);
275
+ }
276
+
273
277
  .pcb-footprint-fill {
274
278
  fill: var(--pcb-footprint-fill);
275
279
  }