altium-toolkit 1.1.3 → 1.1.22
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/docs/api.md +37 -0
- package/docs/model-format.md +18 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +2 -2
- package/docs/testing.md +5 -0
- package/package.json +1 -1
- package/spec/library-scope.md +5 -0
- package/src/core/altium/AltiumLibraryBatchExporter.mjs +206 -0
- package/src/core/altium/AltiumLibraryRecordBuilder.mjs +293 -0
- package/src/core/altium/AltiumParser.mjs +5 -2
- package/src/core/altium/AltiumPcbLibExporter.mjs +101 -0
- package/src/core/altium/AltiumSchLibExporter.mjs +57 -0
- package/src/core/altium/AsciiRecordParser.mjs +43 -11
- package/src/core/altium/PcbComponentKindPolicy.mjs +9 -9
- package/src/core/altium/PcbEmbeddedModelExtractor.mjs +22 -3
- package/src/core/altium/PcbOutlineRecovery.mjs +94 -0
- package/src/core/altium/SchematicDirectiveParser.mjs +5 -17
- package/src/core/altium/SchematicNoErcSymbolResolver.mjs +36 -0
- package/src/core/altium/SchematicPinParser.mjs +87 -20
- package/src/core/altium/SchematicPrimitiveParser.mjs +116 -8
- package/src/core/altium/SchematicStreamExtractor.mjs +62 -15
- package/src/core/altium/SourceBundleExporter.mjs +156 -0
- package/src/core/altium/SourceComponentBundleNormalizer.mjs +295 -0
- package/src/core/altium/SourceComponentClient.mjs +239 -0
- package/src/core/ole/OleCompoundDocumentWriter.mjs +449 -0
- package/src/parser.mjs +8 -0
- package/src/styles/altium-renderers.css +6 -6
- package/src/ui/PcbArcUtils.mjs +19 -2
- package/src/ui/PcbScene3dBuilder.mjs +202 -20
- package/src/ui/PcbScene3dModelRegistry.mjs +28 -18
- package/src/ui/PcbScene3dPlacementSideResolver.mjs +48 -6
- package/src/ui/SchematicColorResolver.mjs +185 -0
- package/src/ui/SchematicDirectiveRenderer.mjs +133 -22
- package/src/ui/SchematicLineColorResolver.mjs +88 -0
- package/src/ui/SchematicNoteRenderer.mjs +5 -1
- package/src/ui/SchematicOwnerPinLabelLayout.mjs +269 -8
- package/src/ui/SchematicOwnerPinMarkerLineThemer.mjs +155 -0
- package/src/ui/SchematicPinSvgRenderer.mjs +229 -62
- package/src/ui/SchematicShapeRenderer.mjs +37 -11
- package/src/ui/SchematicSvgRenderer.mjs +944 -51
|
@@ -31,6 +31,7 @@ export class SchematicColorResolver {
|
|
|
31
31
|
* Resolves one SVG color value to a schematic theme variable.
|
|
32
32
|
* @param {string | undefined} color
|
|
33
33
|
* @param {string} fallbackVariable
|
|
34
|
+
* @param {boolean} [preserveUnknown]
|
|
34
35
|
* @returns {string}
|
|
35
36
|
*/
|
|
36
37
|
static resolveColor(color, fallbackVariable, preserveUnknown = false) {
|
|
@@ -88,6 +89,7 @@ export class SchematicColorResolver {
|
|
|
88
89
|
* Resolves one SVG fill value to a schematic theme variable.
|
|
89
90
|
* @param {string | undefined} fill
|
|
90
91
|
* @param {string} fallbackVariable
|
|
92
|
+
* @param {boolean} [preserveUnknown]
|
|
91
93
|
* @returns {string}
|
|
92
94
|
*/
|
|
93
95
|
static resolveFill(fill, fallbackVariable, preserveUnknown = false) {
|
|
@@ -153,6 +155,32 @@ export class SchematicColorResolver {
|
|
|
153
155
|
)
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Resolves an authored source stroke through a muted source palette.
|
|
160
|
+
* @param {string | undefined} color
|
|
161
|
+
* @param {string} fallbackVariable
|
|
162
|
+
* @returns {string}
|
|
163
|
+
*/
|
|
164
|
+
static resolveMutedSourceColor(color, fallbackVariable) {
|
|
165
|
+
return SchematicColorResolver.#resolveMutedSourcePaint(
|
|
166
|
+
color,
|
|
167
|
+
fallbackVariable
|
|
168
|
+
)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Resolves an authored source fill through a muted source palette.
|
|
173
|
+
* @param {string | undefined} fill
|
|
174
|
+
* @param {string} fallbackVariable
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
177
|
+
static resolveMutedSourceFill(fill, fallbackVariable) {
|
|
178
|
+
return SchematicColorResolver.#resolveMutedSourcePaint(
|
|
179
|
+
fill,
|
|
180
|
+
fallbackVariable
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
156
184
|
/**
|
|
157
185
|
* Resolves one literal source paint value while preserving SVG control
|
|
158
186
|
* values and falling back only when no source paint exists.
|
|
@@ -178,6 +206,42 @@ export class SchematicColorResolver {
|
|
|
178
206
|
return normalized
|
|
179
207
|
}
|
|
180
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Resolves one literal source paint while reducing vivid palette colors.
|
|
211
|
+
* @param {string | undefined} paint
|
|
212
|
+
* @param {string} fallbackVariable
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
static #resolveMutedSourcePaint(paint, fallbackVariable) {
|
|
216
|
+
const normalized = SchematicColorResolver.#normalizeColor(paint)
|
|
217
|
+
|
|
218
|
+
if (!normalized) {
|
|
219
|
+
return SchematicColorResolver.#toVariable(fallbackVariable)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
normalized === 'none' ||
|
|
224
|
+
normalized === 'transparent' ||
|
|
225
|
+
normalized.startsWith('var(')
|
|
226
|
+
) {
|
|
227
|
+
return normalized
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const rgb = SchematicColorResolver.#parseHexColor(normalized)
|
|
231
|
+
if (!rgb) {
|
|
232
|
+
return normalized
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const hsl = SchematicColorResolver.#rgbToHsl(rgb)
|
|
236
|
+
const mutedRgb = SchematicColorResolver.#hslToRgb({
|
|
237
|
+
h: hsl.h,
|
|
238
|
+
s: Math.min(hsl.s, 45),
|
|
239
|
+
l: Math.min(Math.max(hsl.l, 30), 68)
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
return SchematicColorResolver.#formatHexColor(mutedRgb)
|
|
243
|
+
}
|
|
244
|
+
|
|
181
245
|
/**
|
|
182
246
|
* Normalizes one raw color string for token lookup.
|
|
183
247
|
* @param {string | undefined} color
|
|
@@ -189,6 +253,127 @@ export class SchematicColorResolver {
|
|
|
189
253
|
.toLowerCase()
|
|
190
254
|
}
|
|
191
255
|
|
|
256
|
+
/**
|
|
257
|
+
* Parses a six-digit hex color.
|
|
258
|
+
* @param {string} color
|
|
259
|
+
* @returns {{ r: number, g: number, b: number } | null}
|
|
260
|
+
*/
|
|
261
|
+
static #parseHexColor(color) {
|
|
262
|
+
const match = /^#([0-9a-f]{6})$/u.exec(color)
|
|
263
|
+
if (!match) {
|
|
264
|
+
return null
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const value = match[1]
|
|
268
|
+
return {
|
|
269
|
+
r: Number.parseInt(value.slice(0, 2), 16),
|
|
270
|
+
g: Number.parseInt(value.slice(2, 4), 16),
|
|
271
|
+
b: Number.parseInt(value.slice(4, 6), 16)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Converts RGB channels to HSL.
|
|
277
|
+
* @param {{ r: number, g: number, b: number }} color
|
|
278
|
+
* @returns {{ h: number, s: number, l: number }}
|
|
279
|
+
*/
|
|
280
|
+
static #rgbToHsl(color) {
|
|
281
|
+
const r = color.r / 255
|
|
282
|
+
const g = color.g / 255
|
|
283
|
+
const b = color.b / 255
|
|
284
|
+
const max = Math.max(r, g, b)
|
|
285
|
+
const min = Math.min(r, g, b)
|
|
286
|
+
let h = 0
|
|
287
|
+
let s = 0
|
|
288
|
+
const l = ((max + min) / 2) * 100
|
|
289
|
+
|
|
290
|
+
if (max !== min) {
|
|
291
|
+
const delta = max - min
|
|
292
|
+
s =
|
|
293
|
+
((max + min) / 2 > 0.5
|
|
294
|
+
? delta / (2 - max - min)
|
|
295
|
+
: delta / (max + min)) * 100
|
|
296
|
+
|
|
297
|
+
switch (max) {
|
|
298
|
+
case r:
|
|
299
|
+
h = (g - b) / delta + (g < b ? 6 : 0)
|
|
300
|
+
break
|
|
301
|
+
case g:
|
|
302
|
+
h = (b - r) / delta + 2
|
|
303
|
+
break
|
|
304
|
+
default:
|
|
305
|
+
h = (r - g) / delta + 4
|
|
306
|
+
break
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
h /= 6
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return { h, s, l }
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Converts HSL channels to RGB.
|
|
317
|
+
* @param {{ h: number, s: number, l: number }} color
|
|
318
|
+
* @returns {{ r: number, g: number, b: number }}
|
|
319
|
+
*/
|
|
320
|
+
static #hslToRgb(color) {
|
|
321
|
+
const s = color.s / 100
|
|
322
|
+
const l = color.l / 100
|
|
323
|
+
|
|
324
|
+
if (s === 0) {
|
|
325
|
+
const value = Math.round(l * 255)
|
|
326
|
+
return { r: value, g: value, b: value }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s
|
|
330
|
+
const p = 2 * l - q
|
|
331
|
+
const r = SchematicColorResolver.#hueToRgb(p, q, color.h + 1 / 3)
|
|
332
|
+
const g = SchematicColorResolver.#hueToRgb(p, q, color.h)
|
|
333
|
+
const b = SchematicColorResolver.#hueToRgb(p, q, color.h - 1 / 3)
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
r: Math.round(r * 255),
|
|
337
|
+
g: Math.round(g * 255),
|
|
338
|
+
b: Math.round(b * 255)
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Converts one hue channel to RGB.
|
|
344
|
+
* @param {number} p
|
|
345
|
+
* @param {number} q
|
|
346
|
+
* @param {number} t
|
|
347
|
+
* @returns {number}
|
|
348
|
+
*/
|
|
349
|
+
static #hueToRgb(p, q, t) {
|
|
350
|
+
let value = t
|
|
351
|
+
if (value < 0) value += 1
|
|
352
|
+
if (value > 1) value -= 1
|
|
353
|
+
if (value < 1 / 6) return p + (q - p) * 6 * value
|
|
354
|
+
if (value < 1 / 2) return q
|
|
355
|
+
if (value < 2 / 3) return p + (q - p) * (2 / 3 - value) * 6
|
|
356
|
+
return p
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Formats RGB channels as a hex color.
|
|
361
|
+
* @param {{ r: number, g: number, b: number }} color
|
|
362
|
+
* @returns {string}
|
|
363
|
+
*/
|
|
364
|
+
static #formatHexColor(color) {
|
|
365
|
+
return (
|
|
366
|
+
'#' +
|
|
367
|
+
[color.r, color.g, color.b]
|
|
368
|
+
.map((channel) =>
|
|
369
|
+
Math.max(0, Math.min(255, channel))
|
|
370
|
+
.toString(16)
|
|
371
|
+
.padStart(2, '0')
|
|
372
|
+
)
|
|
373
|
+
.join('')
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
192
377
|
/**
|
|
193
378
|
* Wraps one CSS custom property name in `var(...)` markup.
|
|
194
379
|
* @param {string} variableName
|
|
@@ -50,32 +50,45 @@ export class SchematicDirectiveRenderer {
|
|
|
50
50
|
return SchematicDirectiveRenderer.#buildRouteMarkup(
|
|
51
51
|
directive,
|
|
52
52
|
sheetHeight,
|
|
53
|
-
sheet
|
|
53
|
+
sheet,
|
|
54
|
+
'route'
|
|
54
55
|
)
|
|
55
56
|
default:
|
|
56
|
-
return
|
|
57
|
+
return directive?.name
|
|
58
|
+
? SchematicDirectiveRenderer.#buildRouteMarkup(
|
|
59
|
+
directive,
|
|
60
|
+
sheetHeight,
|
|
61
|
+
sheet,
|
|
62
|
+
'parameter-set'
|
|
63
|
+
)
|
|
64
|
+
: ''
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
/**
|
|
61
|
-
* Builds the labeled
|
|
62
|
-
*
|
|
63
|
-
* @param {{ x: number, y: number, color: string, orientation?: number }} directive
|
|
69
|
+
* Builds the labeled info-callout marker for one parameter-set directive.
|
|
70
|
+
* @param {{ x: number, y: number, color: string, name: string, orientation?: number }} directive
|
|
64
71
|
* @param {number} sheetHeight
|
|
65
72
|
* @param {{ fonts?: Record<string, { size: number, family: string, bold: boolean }> }} sheet
|
|
73
|
+
* @param {string} classModifier
|
|
66
74
|
* @returns {string}
|
|
67
75
|
*/
|
|
68
|
-
static #buildRouteMarkup(directive, sheetHeight, sheet) {
|
|
76
|
+
static #buildRouteMarkup(directive, sheetHeight, sheet, classModifier) {
|
|
69
77
|
const color = SchematicColorResolver.resolveColor(
|
|
70
78
|
directive.color,
|
|
71
79
|
'--schematic-alert-color'
|
|
72
80
|
)
|
|
73
81
|
const projectedY = projectSchematicY(sheetHeight, directive.y)
|
|
74
|
-
const
|
|
75
|
-
|
|
82
|
+
const direction = SchematicDirectiveRenderer.#resolveCalloutDirection(
|
|
83
|
+
directive.orientation
|
|
84
|
+
)
|
|
76
85
|
const circleRadius = 7
|
|
77
|
-
const
|
|
78
|
-
|
|
86
|
+
const calloutDistance =
|
|
87
|
+
SchematicDirectiveRenderer.#resolveCalloutDistance(direction)
|
|
88
|
+
const circleCenterX = directive.x + direction.x * calloutDistance
|
|
89
|
+
const circleCenterY = projectedY + direction.y * calloutDistance
|
|
90
|
+
const leaderEndX = circleCenterX - direction.x * circleRadius
|
|
91
|
+
const leaderEndY = circleCenterY - direction.y * circleRadius
|
|
79
92
|
const labelOptions =
|
|
80
93
|
SchematicTypography.buildViewerSchematicFontOptions(sheet)
|
|
81
94
|
const infoOptions = {
|
|
@@ -83,26 +96,32 @@ export class SchematicDirectiveRenderer {
|
|
|
83
96
|
fontSize: Math.max(Number(labelOptions.fontSize || 9) - 1, 6),
|
|
84
97
|
fontWeight: 700
|
|
85
98
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
const labelPlacement =
|
|
100
|
+
SchematicDirectiveRenderer.#resolveLabelPlacement(
|
|
101
|
+
circleCenterX,
|
|
102
|
+
circleCenterY,
|
|
103
|
+
direction,
|
|
104
|
+
circleRadius,
|
|
105
|
+
Number(labelOptions.fontSize || 9)
|
|
106
|
+
)
|
|
90
107
|
|
|
91
108
|
return (
|
|
92
|
-
'<g class="schematic-directive schematic-directive--
|
|
109
|
+
'<g class="schematic-directive schematic-directive--' +
|
|
110
|
+
escapeHtml(classModifier) +
|
|
111
|
+
'">' +
|
|
93
112
|
'<line x1="' +
|
|
94
113
|
formatNumber(directive.x) +
|
|
95
114
|
'" y1="' +
|
|
96
115
|
formatNumber(projectedY) +
|
|
97
116
|
'" x2="' +
|
|
98
|
-
formatNumber(
|
|
117
|
+
formatNumber(leaderEndX) +
|
|
99
118
|
'" y2="' +
|
|
100
119
|
formatNumber(leaderEndY) +
|
|
101
120
|
'" stroke="' +
|
|
102
121
|
escapeHtml(color) +
|
|
103
122
|
'" stroke-width="1" />' +
|
|
104
123
|
'<circle cx="' +
|
|
105
|
-
formatNumber(
|
|
124
|
+
formatNumber(circleCenterX) +
|
|
106
125
|
'" cy="' +
|
|
107
126
|
formatNumber(circleCenterY) +
|
|
108
127
|
'" r="' +
|
|
@@ -112,17 +131,20 @@ export class SchematicDirectiveRenderer {
|
|
|
112
131
|
'" stroke-width="1" />' +
|
|
113
132
|
createSvgText(
|
|
114
133
|
'schematic-directive-label',
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
labelPlacement.x,
|
|
135
|
+
labelPlacement.y,
|
|
117
136
|
String(directive.name || ''),
|
|
118
137
|
color,
|
|
119
|
-
|
|
138
|
+
labelPlacement.anchor,
|
|
120
139
|
labelOptions
|
|
121
140
|
) +
|
|
122
141
|
createSvgText(
|
|
123
142
|
'schematic-directive-info',
|
|
124
|
-
|
|
125
|
-
circleCenterY +
|
|
143
|
+
circleCenterX,
|
|
144
|
+
circleCenterY +
|
|
145
|
+
SchematicDirectiveRenderer.#baselineOffset(
|
|
146
|
+
Number(infoOptions.fontSize || 8)
|
|
147
|
+
),
|
|
126
148
|
'i',
|
|
127
149
|
color,
|
|
128
150
|
'middle',
|
|
@@ -132,6 +154,95 @@ export class SchematicDirectiveRenderer {
|
|
|
132
154
|
)
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
/**
|
|
158
|
+
* Resolves Altium's four-way callout orientation into an outward vector.
|
|
159
|
+
* @param {number | undefined} orientation
|
|
160
|
+
* @returns {{ x: number, y: number }}
|
|
161
|
+
*/
|
|
162
|
+
static #resolveCalloutDirection(orientation) {
|
|
163
|
+
switch (Number(orientation || 0)) {
|
|
164
|
+
case 1:
|
|
165
|
+
return { x: 0, y: -1 }
|
|
166
|
+
case 2:
|
|
167
|
+
return { x: -1, y: 0 }
|
|
168
|
+
case 3:
|
|
169
|
+
return { x: 0, y: 1 }
|
|
170
|
+
default:
|
|
171
|
+
return { x: 1, y: 0 }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Resolves the distance from a directive anchor to its info marker center.
|
|
177
|
+
* @param {{ x: number, y: number }} direction
|
|
178
|
+
* @returns {number}
|
|
179
|
+
*/
|
|
180
|
+
static #resolveCalloutDistance(direction) {
|
|
181
|
+
return direction.y < 0 ? 12 : 18
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolves label placement outside one directive info marker.
|
|
186
|
+
* @param {number} circleCenterX
|
|
187
|
+
* @param {number} circleCenterY
|
|
188
|
+
* @param {{ x: number, y: number }} direction
|
|
189
|
+
* @param {number} circleRadius
|
|
190
|
+
* @param {number} fontSize
|
|
191
|
+
* @returns {{ x: number, y: number, anchor: 'start' | 'middle' | 'end' }}
|
|
192
|
+
*/
|
|
193
|
+
static #resolveLabelPlacement(
|
|
194
|
+
circleCenterX,
|
|
195
|
+
circleCenterY,
|
|
196
|
+
direction,
|
|
197
|
+
circleRadius,
|
|
198
|
+
fontSize
|
|
199
|
+
) {
|
|
200
|
+
const labelDistance = circleRadius + fontSize
|
|
201
|
+
|
|
202
|
+
if (direction.x < 0) {
|
|
203
|
+
return {
|
|
204
|
+
x: circleCenterX - labelDistance,
|
|
205
|
+
y:
|
|
206
|
+
circleCenterY +
|
|
207
|
+
SchematicDirectiveRenderer.#baselineOffset(fontSize),
|
|
208
|
+
anchor: 'end'
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (direction.x > 0) {
|
|
213
|
+
return {
|
|
214
|
+
x: circleCenterX + labelDistance,
|
|
215
|
+
y:
|
|
216
|
+
circleCenterY +
|
|
217
|
+
SchematicDirectiveRenderer.#baselineOffset(fontSize),
|
|
218
|
+
anchor: 'start'
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (direction.y < 0) {
|
|
223
|
+
return {
|
|
224
|
+
x: circleCenterX,
|
|
225
|
+
y: circleCenterY - circleRadius - 2,
|
|
226
|
+
anchor: 'middle'
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
x: circleCenterX,
|
|
232
|
+
y: circleCenterY + direction.y * labelDistance,
|
|
233
|
+
anchor: 'middle'
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns a baseline offset that visually centers text around a marker.
|
|
239
|
+
* @param {number} fontSize
|
|
240
|
+
* @returns {number}
|
|
241
|
+
*/
|
|
242
|
+
static #baselineOffset(fontSize) {
|
|
243
|
+
return fontSize * 0.34
|
|
244
|
+
}
|
|
245
|
+
|
|
135
246
|
/**
|
|
136
247
|
* Builds the paired-trace differential-pair marker glyph.
|
|
137
248
|
* @param {{ x: number, y: number, color: string }} directive
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { SchematicColorResolver } from './SchematicColorResolver.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves schematic line stroke colors from normalized line metadata.
|
|
9
|
+
*/
|
|
10
|
+
export class SchematicLineColorResolver {
|
|
11
|
+
/**
|
|
12
|
+
* Resolves one schematic line stroke color.
|
|
13
|
+
* @param {{ color?: string, ownerIndex?: string, isBus?: boolean, recordType?: string, x1?: number, x2?: number }} line Line primitive.
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
static resolveColor(line) {
|
|
17
|
+
if (SchematicLineColorResolver.isElectricalLine(line)) {
|
|
18
|
+
return SchematicColorResolver.resolveNonTextColor(
|
|
19
|
+
line?.color,
|
|
20
|
+
'--schematic-default-ink-color'
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (SchematicLineColorResolver.#isOwnerVerticalLine(line)) {
|
|
25
|
+
const resolvedColor = SchematicColorResolver.resolveNonTextColor(
|
|
26
|
+
line?.color,
|
|
27
|
+
'--schematic-default-ink-color',
|
|
28
|
+
true
|
|
29
|
+
)
|
|
30
|
+
return resolvedColor.startsWith('var(')
|
|
31
|
+
? resolvedColor
|
|
32
|
+
: SchematicColorResolver.resolveMutedSourceColor(
|
|
33
|
+
line?.color,
|
|
34
|
+
'--schematic-default-ink-color'
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return SchematicColorResolver.resolveColor(
|
|
39
|
+
line?.color,
|
|
40
|
+
'--schematic-default-ink-color'
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns true when one normalized line can carry schematic net color.
|
|
46
|
+
* @param {{ ownerIndex?: string, isBus?: boolean, recordType?: string } | null | undefined} line Line primitive.
|
|
47
|
+
* @returns {boolean}
|
|
48
|
+
*/
|
|
49
|
+
static isElectricalLine(line) {
|
|
50
|
+
if (line?.ownerIndex || line?.isBus === true) {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(line || {}, 'recordType')) {
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return !['6', '7', '26'].includes(String(line.recordType || ''))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns true when owner geometry encodes a vertical side rail as a line.
|
|
63
|
+
* @param {{ ownerIndex?: string, x1?: number, x2?: number } | null | undefined} line Line primitive.
|
|
64
|
+
* @returns {boolean}
|
|
65
|
+
*/
|
|
66
|
+
static #isOwnerVerticalLine(line) {
|
|
67
|
+
return (
|
|
68
|
+
Boolean(String(line?.ownerIndex || '').trim()) &&
|
|
69
|
+
SchematicLineColorResolver.#isVerticalLine(line)
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns true when one line is vertical within parser coordinate tolerance.
|
|
75
|
+
* @param {{ x1?: number, x2?: number } | null | undefined} line Line primitive.
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
static #isVerticalLine(line) {
|
|
79
|
+
const x1 = Number(line?.x1)
|
|
80
|
+
const x2 = Number(line?.x2)
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
Number.isFinite(x1) &&
|
|
84
|
+
Number.isFinite(x2) &&
|
|
85
|
+
Math.abs(x1 - x2) <= 0.001
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -8,6 +8,7 @@ import { SchematicTypography } from './SchematicTypography.mjs'
|
|
|
8
8
|
|
|
9
9
|
const { escapeHtml, formatNumber, projectSchematicY } = SchematicSvgUtils
|
|
10
10
|
const MINIMUM_NOTE_TEXT_SIZE = 4
|
|
11
|
+
const COMPACT_SINGLE_LINE_HEIGHT_RATIO = 1.6
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Renders boxed schematic notes recovered from Altium note records.
|
|
@@ -328,7 +329,10 @@ export class SchematicNoteRenderer {
|
|
|
328
329
|
String(line || '').trim()
|
|
329
330
|
).length
|
|
330
331
|
|
|
331
|
-
return
|
|
332
|
+
return (
|
|
333
|
+
visibleLineCount === 1 &&
|
|
334
|
+
height <= requestedTextSize * COMPACT_SINGLE_LINE_HEIGHT_RATIO
|
|
335
|
+
)
|
|
332
336
|
}
|
|
333
337
|
|
|
334
338
|
/**
|