altium-toolkit 1.4.6 → 1.4.7
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
|
@@ -10,6 +10,7 @@ import { PcbSvgRenderer as LegacyPcbSvgRenderer } from '../ui/PcbSvgRenderer.mjs
|
|
|
10
10
|
export class PcbSvgRenderer {
|
|
11
11
|
static #SUBSURFACE_GROUP = '<g class="pcb-copper pcb-copper--subsurface">'
|
|
12
12
|
static #SURFACE_GROUP = '<g class="pcb-copper pcb-copper--surface">'
|
|
13
|
+
static #FOOTPRINT_GROUP = '<g class="pcb-footprints">'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Renders one native Altium PCB document as SVG markup.
|
|
@@ -23,7 +24,7 @@ export class PcbSvgRenderer {
|
|
|
23
24
|
documentModel,
|
|
24
25
|
options
|
|
25
26
|
)
|
|
26
|
-
if (subsurfacePadIndexes.
|
|
27
|
+
if (subsurfacePadIndexes.size === 0) return markup
|
|
27
28
|
|
|
28
29
|
return PcbSvgRenderer.#movePadsToSubsurfaceGroup(
|
|
29
30
|
markup,
|
|
@@ -46,85 +47,125 @@ export class PcbSvgRenderer {
|
|
|
46
47
|
* Layer-only exports do not use composite surface/subsurface grouping.
|
|
47
48
|
* @param {Record<string, any>} documentModel Native renderer document.
|
|
48
49
|
* @param {Record<string, any>} options Historical renderer options.
|
|
49
|
-
* @returns {number
|
|
50
|
+
* @returns {Set<number>} Stable pad indexes.
|
|
50
51
|
*/
|
|
51
52
|
static #subsurfacePadIndexes(documentModel, options) {
|
|
52
|
-
if (options?.layerView) return
|
|
53
|
+
if (options?.layerView) return new Set()
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
const indexes = new Set()
|
|
56
|
+
for (const [index, pad] of (documentModel?.pcb?.pads || []).entries()) {
|
|
57
|
+
if (pad?.copperRenderGroup === 'subsurface') {
|
|
58
|
+
indexes.add(index)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return indexes
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
/**
|
|
62
65
|
* Moves contextual pad groups into the same SVG group as contextual traces
|
|
63
|
-
*
|
|
66
|
+
* with one forward scan and one final markup reconstruction.
|
|
64
67
|
* @param {string} markup Historical renderer markup.
|
|
65
|
-
* @param {number
|
|
68
|
+
* @param {Set<number>} padIndexes Stable pad indexes to relocate.
|
|
66
69
|
* @returns {string} Copper-group-aware markup.
|
|
67
70
|
*/
|
|
68
71
|
static #movePadsToSubsurfaceGroup(markup, padIndexes) {
|
|
69
72
|
const subsurfaceStart = markup.indexOf(PcbSvgRenderer.#SUBSURFACE_GROUP)
|
|
70
73
|
const surfaceStart = markup.indexOf(PcbSvgRenderer.#SURFACE_GROUP)
|
|
71
|
-
|
|
74
|
+
const surfaceEnd = markup.indexOf(
|
|
75
|
+
PcbSvgRenderer.#FOOTPRINT_GROUP,
|
|
76
|
+
surfaceStart
|
|
77
|
+
)
|
|
78
|
+
if (
|
|
79
|
+
subsurfaceStart < 0 ||
|
|
80
|
+
surfaceStart <= subsurfaceStart ||
|
|
81
|
+
surfaceEnd <= surfaceStart
|
|
82
|
+
) {
|
|
72
83
|
return markup
|
|
73
84
|
}
|
|
74
85
|
|
|
75
86
|
const subsurfaceClose = markup.lastIndexOf('</g>', surfaceStart)
|
|
76
87
|
if (subsurfaceClose < subsurfaceStart) return markup
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
89
|
+
const partition = PcbSvgRenderer.#partitionSurfacePads(
|
|
90
|
+
markup,
|
|
91
|
+
surfaceStart,
|
|
92
|
+
surfaceEnd,
|
|
93
|
+
padIndexes
|
|
98
94
|
)
|
|
95
|
+
if (!partition.subsurfacePadMarkup) return markup
|
|
96
|
+
|
|
99
97
|
return (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
98
|
+
markup.slice(0, subsurfaceClose) +
|
|
99
|
+
partition.subsurfacePadMarkup +
|
|
100
|
+
markup.slice(subsurfaceClose, surfaceStart) +
|
|
101
|
+
partition.surfaceMarkup +
|
|
102
|
+
markup.slice(surfaceEnd)
|
|
103
103
|
)
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
* @param {
|
|
111
|
-
* @param {number}
|
|
112
|
-
* @
|
|
113
|
-
* @returns {{ markup: string, padMarkup: string } | null} Extraction result.
|
|
107
|
+
* Partitions selected top-level pad groups from one surface-group span.
|
|
108
|
+
* @param {string} markup Complete historical renderer markup.
|
|
109
|
+
* @param {number} start Surface-group start offset.
|
|
110
|
+
* @param {number} end Surface-group end offset.
|
|
111
|
+
* @param {Set<number>} padIndexes Stable pad indexes to relocate.
|
|
112
|
+
* @returns {{ surfaceMarkup: string, subsurfacePadMarkup: string }}
|
|
114
113
|
*/
|
|
115
|
-
static #
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
114
|
+
static #partitionSurfacePads(markup, start, end, padIndexes) {
|
|
115
|
+
const surfaceFragments = []
|
|
116
|
+
const subsurfacePadFragments = []
|
|
117
|
+
const padGroupPattern =
|
|
118
|
+
/<g class="pcb-pad\b[^"]*"[^>]*data-element-key="pcb-pad-(\d+)"[^>]*>/gu
|
|
119
|
+
let cursor = start
|
|
120
|
+
padGroupPattern.lastIndex = start
|
|
119
121
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
122
|
+
for (
|
|
123
|
+
let match = padGroupPattern.exec(markup);
|
|
124
|
+
match && match.index < end;
|
|
125
|
+
match = padGroupPattern.exec(markup)
|
|
126
|
+
) {
|
|
127
|
+
const groupEnd = PcbSvgRenderer.#groupEnd(
|
|
128
|
+
markup,
|
|
129
|
+
padGroupPattern.lastIndex,
|
|
130
|
+
end
|
|
131
|
+
)
|
|
132
|
+
if (groupEnd < 0) break
|
|
133
|
+
|
|
134
|
+
if (padIndexes.has(Number(match[1]))) {
|
|
135
|
+
surfaceFragments.push(markup.slice(cursor, match.index))
|
|
136
|
+
subsurfacePadFragments.push(markup.slice(match.index, groupEnd))
|
|
137
|
+
cursor = groupEnd
|
|
138
|
+
}
|
|
139
|
+
padGroupPattern.lastIndex = groupEnd
|
|
140
|
+
}
|
|
123
141
|
|
|
124
|
-
|
|
142
|
+
surfaceFragments.push(markup.slice(cursor, end))
|
|
125
143
|
return {
|
|
126
|
-
|
|
127
|
-
|
|
144
|
+
surfaceMarkup: surfaceFragments.join(''),
|
|
145
|
+
subsurfacePadMarkup: subsurfacePadFragments.join('')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Finds the matching close for one renderer-owned pad group.
|
|
151
|
+
* @param {string} markup Complete historical renderer markup.
|
|
152
|
+
* @param {number} contentStart Offset immediately after the pad open tag.
|
|
153
|
+
* @param {number} limit Exclusive end of the surface-group span.
|
|
154
|
+
* @returns {number} Offset immediately after the matching close, or -1.
|
|
155
|
+
*/
|
|
156
|
+
static #groupEnd(markup, contentStart, limit) {
|
|
157
|
+
const groupTagPattern = /<g\b[^>]*>|<\/g>/gu
|
|
158
|
+
let depth = 1
|
|
159
|
+
groupTagPattern.lastIndex = contentStart
|
|
160
|
+
|
|
161
|
+
for (
|
|
162
|
+
let match = groupTagPattern.exec(markup);
|
|
163
|
+
match && match.index < limit;
|
|
164
|
+
match = groupTagPattern.exec(markup)
|
|
165
|
+
) {
|
|
166
|
+
depth += match[0] === '</g>' ? -1 : 1
|
|
167
|
+
if (depth === 0) return groupTagPattern.lastIndex
|
|
128
168
|
}
|
|
169
|
+
return -1
|
|
129
170
|
}
|
|
130
171
|
}
|