From 7ae58694906c99f1409d484f227786d9e0002d09 Mon Sep 17 00:00:00 2001 From: imrishabh18 Date: Thu, 30 Oct 2025 01:02:55 +0530 Subject: [PATCH] feat: add polyline class and extend that to be used as SymbolPolyline --- lib/sexpr/classes/KicadSch.ts | 18 +++ lib/sexpr/classes/Polyline.ts | 162 +++++++++++++++++++++++++++ lib/sexpr/classes/Symbol.ts | 61 +--------- lib/sexpr/index.ts | 1 + tests/sexpr/classes/Polyline.test.ts | 44 ++++++++ 5 files changed, 226 insertions(+), 60 deletions(-) create mode 100644 lib/sexpr/classes/Polyline.ts create mode 100644 tests/sexpr/classes/Polyline.test.ts diff --git a/lib/sexpr/classes/KicadSch.ts b/lib/sexpr/classes/KicadSch.ts index fdf5f99..c5b56dc 100644 --- a/lib/sexpr/classes/KicadSch.ts +++ b/lib/sexpr/classes/KicadSch.ts @@ -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", @@ -43,6 +44,7 @@ const MULTI_CHILD_TOKENS = new Set([ "wire", "no_connect", "sheet_instances", + "polyline", ]) const SUPPORTED_CHILD_TOKENS = new Set([ @@ -70,6 +72,7 @@ export interface KicadSchConstructorParams { wires?: Wire[] junctions?: Junction[] noConnects?: NoConnect[] + polylines?: Polyline[] } export class KicadSch extends SxClass { @@ -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() @@ -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( @@ -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[]) ?? [], }) } @@ -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) @@ -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 } } diff --git a/lib/sexpr/classes/Polyline.ts b/lib/sexpr/classes/Polyline.ts new file mode 100644 index 0000000..8de00e7 --- /dev/null +++ b/lib/sexpr/classes/Polyline.ts @@ -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) diff --git a/lib/sexpr/classes/Symbol.ts b/lib/sexpr/classes/Symbol.ts index 84f045a..cb0a0d1 100644 --- a/lib/sexpr/classes/Symbol.ts +++ b/lib/sexpr/classes/Symbol.ts @@ -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" @@ -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" diff --git a/lib/sexpr/index.ts b/lib/sexpr/index.ts index 1e4fd6a..06faa41 100644 --- a/lib/sexpr/index.ts +++ b/lib/sexpr/index.ts @@ -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" diff --git a/tests/sexpr/classes/Polyline.test.ts b/tests/sexpr/classes/Polyline.test.ts new file mode 100644 index 0000000..fa17590 --- /dev/null +++ b/tests/sexpr/classes/Polyline.test.ts @@ -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) + )" + `) +})