Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions lib/sexpr/classes/KicadSch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Uuid } from "./Uuid"
import { Wire } from "./Wire"
import { Junction } from "./Junction"
import { NoConnect } from "./NoConnect"
import { Polyline } from "./Polyline"

const SINGLE_CHILD_TOKENS = new Set([
"version",
Expand All @@ -43,6 +44,7 @@ const MULTI_CHILD_TOKENS = new Set([
"wire",
"no_connect",
"sheet_instances",
"polyline",
])

const SUPPORTED_CHILD_TOKENS = new Set([
Expand Down Expand Up @@ -70,6 +72,7 @@ export interface KicadSchConstructorParams {
wires?: Wire[]
junctions?: Junction[]
noConnects?: NoConnect[]
polylines?: Polyline[]
}

export class KicadSch extends SxClass {
Expand All @@ -95,6 +98,7 @@ export class KicadSch extends SxClass {
private _wires: Wire[] = []
private _junctions: Junction[] = []
private _noConnects: NoConnect[] = []
private _polylines: Polyline[] = []

constructor(params: KicadSchConstructorParams = {}) {
super()
Expand Down Expand Up @@ -180,6 +184,10 @@ export class KicadSch extends SxClass {
if (params.noConnects !== undefined) {
this.noConnects = params.noConnects
}

if (params.polylines !== undefined) {
this.polylines = params.polylines
}
}

static override fromSexprPrimitives(
Expand Down Expand Up @@ -246,6 +254,7 @@ export class KicadSch extends SxClass {
junctions: (arrayPropertyMap.junction as Junction[]) ?? [],
wires: (arrayPropertyMap.wire as Wire[]) ?? [],
noConnects: (arrayPropertyMap.no_connect as NoConnect[]) ?? [],
polylines: (arrayPropertyMap.polyline as Polyline[]) ?? [],
})
}

Expand Down Expand Up @@ -412,6 +421,14 @@ export class KicadSch extends SxClass {
this._noConnects = [...value]
}

get polylines(): Polyline[] {
return [...this._polylines]
}

set polylines(value: Polyline[]) {
this._polylines = [...value]
}

override getChildren(): SxClass[] {
const children: SxClass[] = []
if (this._sxVersion) children.push(this._sxVersion)
Expand All @@ -433,6 +450,7 @@ export class KicadSch extends SxClass {
children.push(...this._junctions)
children.push(...this._wires)
children.push(...this._noConnects)
children.push(...this._polylines)
return children
}
}
Expand Down
162 changes: 162 additions & 0 deletions lib/sexpr/classes/Polyline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { SxClass } from "../base-classes/SxClass"
import type { PrimitiveSExpr } from "../parseToPrimitiveSExpr"
import { Pts } from "./Pts"
import { Stroke } from "./Stroke"
import { Uuid } from "./Uuid"

const SUPPORTED_TOKENS_KICAD_SCH = new Set(["pts", "stroke", "uuid"])
const SUPPORTED_TOKENS_SYMBOL = new Set(["pts", "stroke", "fill"])

// Forward declaration for SymbolPolylineFill to avoid circular dependency
export interface SymbolPolylineFillLike extends SxClass {
type?: string
}

export interface PolylineConstructorParams {
points?: Pts
stroke?: Stroke
uuid?: string | Uuid
fill?: SymbolPolylineFillLike
}

/**
* Base Polyline class that can be used in both kicad_sch and symbol contexts.
* - When parent is "kicad_sch": supports pts, stroke, uuid
* - When parent is "symbol": supports pts, stroke, fill
*/
export class Polyline extends SxClass {
static override token = "polyline"
override token = "polyline"

private _sxPts?: Pts
private _sxStroke?: Stroke
private _sxUuid?: Uuid
private _sxFill?: SymbolPolylineFillLike

constructor(params: PolylineConstructorParams = {}) {
super()

if (params.points !== undefined) {
this.points = params.points
}

if (params.stroke !== undefined) {
this.stroke = params.stroke
}

if (params.uuid !== undefined) {
this.uuid = params.uuid
}

if (params.fill !== undefined) {
this.fill = params.fill
}
}

static override fromSexprPrimitives(
primitiveSexprs: PrimitiveSExpr[],
): Polyline {
const polyline = new Polyline()

const { propertyMap, arrayPropertyMap } =
SxClass.parsePrimitivesToClassProperties(primitiveSexprs, this.token)

// Determine which tokens are supported based on parent context
// Check the static parentToken property of the class being instantiated
const supportedTokens =
this.parentToken === "symbol"
? SUPPORTED_TOKENS_SYMBOL
: SUPPORTED_TOKENS_KICAD_SCH

for (const [token, entries] of Object.entries(arrayPropertyMap)) {
if (!supportedTokens.has(token)) {
throw new Error(
`Unsupported child tokens inside polyline expression: ${token}`,
)
}
if (entries.length > 1) {
throw new Error(
`polyline does not support repeated child tokens: ${token}`,
)
}
}

const unsupportedTokens = Object.keys(propertyMap).filter(
(token) => !supportedTokens.has(token),
)
if (unsupportedTokens.length > 0) {
throw new Error(
`Unsupported child tokens inside polyline expression: ${unsupportedTokens.join(", ")}`,
)
}

polyline._sxPts =
(arrayPropertyMap.pts?.[0] as Pts | undefined) ??
(propertyMap.pts as Pts | undefined)
polyline._sxStroke = propertyMap.stroke as Stroke | undefined
polyline._sxUuid = propertyMap.uuid as Uuid | undefined
polyline._sxFill = propertyMap.fill

return polyline
}

get points(): Pts | undefined {
return this._sxPts
}

set points(value: Pts | undefined) {
this._sxPts = value
}

get stroke(): Stroke | undefined {
return this._sxStroke
}

set stroke(value: Stroke | undefined) {
this._sxStroke = value
}

get uuid(): Uuid | undefined {
return this._sxUuid
}

set uuid(value: Uuid | string | undefined) {
if (value === undefined) {
this._sxUuid = undefined
return
}
this._sxUuid = value instanceof Uuid ? value : new Uuid(value)
}

get fill(): SymbolPolylineFillLike | undefined {
return this._sxFill
}

set fill(value: SymbolPolylineFillLike | undefined) {
this._sxFill = value
}

override getChildren(): SxClass[] {
const children: SxClass[] = []
if (this._sxPts) children.push(this._sxPts)
if (this._sxStroke) children.push(this._sxStroke)
if (this._sxFill) children.push(this._sxFill)
if (this._sxUuid) children.push(this._sxUuid)
return children
}
}

// Register for both kicad_sch and symbol parents
SxClass.register(Polyline)

// Create a class that explicitly sets parentToken for kicad_sch
export class SchematicPolyline extends Polyline {
static override parentToken = "kicad_sch"
}
SxClass.register(SchematicPolyline)

// Create a class that explicitly sets parentToken for symbol
export class SymbolPolyline extends Polyline {
static override parentToken = "symbol"
}
SxClass.register(SymbolPolyline)
61 changes: 1 addition & 60 deletions lib/sexpr/classes/Symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import { OnBoard } from "./OnBoard"
import { FieldsAutoplaced } from "./FieldsAutoplaced"
import { TextEffects } from "./TextEffects"
import { Uuid } from "./Uuid"
import { Pts } from "./Pts"
import { Stroke } from "./Stroke"
import { SymbolPolyline } from "./Polyline"

export class SymbolUnit extends SxPrimitiveNumber {
static override token = "unit"
Expand Down Expand Up @@ -437,65 +437,6 @@ export class SymbolFillType extends SxClass {
}
SxClass.register(SymbolFillType)

export class SymbolPolyline extends SxClass {
static override token = "polyline"
static override parentToken = "symbol"
token = "polyline"

private _sxPts?: Pts
private _sxStroke?: Stroke
private _sxFill?: SymbolPolylineFill

static override fromSexprPrimitives(
primitiveSexprs: PrimitiveSExpr[],
): SymbolPolyline {
const polyline = new SymbolPolyline()
const { propertyMap } = SxClass.parsePrimitivesToClassProperties(
primitiveSexprs,
this.token,
)

polyline._sxPts = propertyMap.pts as Pts
polyline._sxStroke = propertyMap.stroke as Stroke
polyline._sxFill = propertyMap.fill as SymbolPolylineFill

return polyline
}

get points(): Pts | undefined {
return this._sxPts
}

set points(value: Pts | undefined) {
this._sxPts = value
}

get stroke(): Stroke | undefined {
return this._sxStroke
}

set stroke(value: Stroke | undefined) {
this._sxStroke = value
}

get fill(): SymbolPolylineFill | undefined {
return this._sxFill
}

set fill(value: SymbolPolylineFill | undefined) {
this._sxFill = value
}

override getChildren(): SxClass[] {
const children: SxClass[] = []
if (this._sxPts) children.push(this._sxPts)
if (this._sxStroke) children.push(this._sxStroke)
if (this._sxFill) children.push(this._sxFill)
return children
}
}
SxClass.register(SymbolPolyline)

export class SymbolRectangle extends SxClass {
static override token = "rectangle"
static override parentToken = "symbol"
Expand Down
1 change: 1 addition & 0 deletions lib/sexpr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export * from "./classes/Wire"
export * from "./classes/Bus"
export * from "./classes/Junction"
export * from "./classes/NoConnect"
export * from "./classes/Polyline" // Exports Polyline, SchematicPolyline, SymbolPolyline
export * from "./classes/BusEntry"
export * from "./classes/Label"
export * from "./classes/GlobalLabel"
Expand Down
44 changes: 44 additions & 0 deletions tests/sexpr/classes/Polyline.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { SxClass, Polyline, KicadSch, Pts, Stroke, Uuid } from "lib/sexpr"
import { expect, test } from "bun:test"

test("Polyline", () => {
const [parsed] = SxClass.parse(`
(kicad_sch
(polyline
(pts
(xy 172.72 196.85) (xy 148.59 196.85)
)
(stroke
(width 0)
(type dash)
)
(uuid "088f800b-89af-4049-b7bd-bc3ee6bc7fd7")
)
)
`)

expect(parsed).toBeInstanceOf(KicadSch)
const kicadSch = parsed as KicadSch
expect(kicadSch.polylines).toHaveLength(1)

const polyline = kicadSch.polylines[0]
expect(polyline).toBeInstanceOf(Polyline)
const pl = polyline as Polyline
expect(pl.points).toBeInstanceOf(Pts)
expect(pl.stroke).toBeInstanceOf(Stroke)
expect(pl.uuid).toBeInstanceOf(Uuid)

expect(pl.getString()).toMatchInlineSnapshot(`
"(polyline
(pts
(xy 172.72 196.85)
(xy 148.59 196.85)
)
(stroke
(width 0)
(type dash)
)
(uuid 088f800b-89af-4049-b7bd-bc3ee6bc7fd7)
)"
`)
})