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
39 changes: 28 additions & 11 deletions lib/sexpr/classes/DsnPin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,34 @@ export class DsnPin extends SxClass {
primitiveSexprs: PrimitiveSExpr[],
): DsnPin {
const pin = new DsnPin()
const strings = primitiveSexprs.filter(
(p) => typeof p === "string",
) as string[]
const numbers = primitiveSexprs.filter(
(p) => typeof p === "number",
) as number[]
if (strings[0]) pin._padstackId = strings[0]
if (strings[1]) pin._pinId = strings[1]
if (numbers[0] !== undefined) pin._x = numbers[0]
if (numbers[1] !== undefined) pin._y = numbers[1]
if (numbers[2] !== undefined) pin._rotation = numbers[2]
// DSN format: (pin <padstack_id> [(rotate <angle>)] <pin_id> <x> <y> [rotation])
// Parse positionally - pin_id can be a number or string
// Filter to only strings and numbers (skip nested arrays like (rotate ...))
const primitives = primitiveSexprs.filter(
(p) => typeof p === "string" || typeof p === "number",
) as (string | number)[]

// Check for inline (rotate ...) expression and extract rotation from it
const rotateExpr = primitiveSexprs.find(
(p) => Array.isArray(p) && p[0] === "rotate",
) as PrimitiveSExpr[] | undefined
if (rotateExpr && typeof rotateExpr[1] === "number") {
pin._rotation = rotateExpr[1]
}

if (primitives[0] !== undefined) pin._padstackId = String(primitives[0])
if (primitives[1] !== undefined) pin._pinId = String(primitives[1])
if (primitives[2] !== undefined && typeof primitives[2] === "number")
pin._x = primitives[2]
if (primitives[3] !== undefined && typeof primitives[3] === "number")
pin._y = primitives[3]
// Only set rotation from primitives if not already set from (rotate ...) expression
if (
pin._rotation === undefined &&
primitives[4] !== undefined &&
typeof primitives[4] === "number"
)
pin._rotation = primitives[4]
return pin
}

Expand Down
52 changes: 52 additions & 0 deletions tests/sexpr/classes/DsnPin.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DsnPin } from "lib/sexpr"
import { SxClass } from "lib/sexpr/base-classes/SxClass"
import { test, expect } from "bun:test"

test("DsnPin without quotes on padstack ID", () => {
Expand Down Expand Up @@ -44,3 +45,54 @@ test("DsnPin with rotation", () => {
"(pin RoundRect[T]Pad_540x640_135.514_um_0.000000_0_source_component_0 1 -500 0 90)",
)
})

test("DsnPin parses numeric pin_id correctly from DSN format", () => {
// Format: (pin <padstack_id> <pin_id> <x> <y>)
// Where pin_id is numeric (12), x is -2525, y is -6350
const pin = DsnPin.fromSexprPrimitives([
"Rect[T]Pad_3150.000000x1000.000000_um",
12,
-2525,
-6350,
])

expect(pin.padstackId).toBe("Rect[T]Pad_3150.000000x1000.000000_um")
expect(pin.pinId).toBe("12")
expect(pin.x).toBe(-2525)
expect(pin.y).toBe(-6350)
expect(pin.rotation).toBeUndefined()
})

test("DsnPin parses inline (rotate ...) expression correctly", () => {
// Format: (pin <padstack_id> (rotate <angle>) <pin_id> <x> <y>)
const pin = DsnPin.fromSexprPrimitives([
"Round[T]Pad_1730.000000_um",
["rotate", 90],
1,
0,
0,
])

expect(pin.padstackId).toBe("Round[T]Pad_1730.000000_um")
expect(pin.pinId).toBe("1")
expect(pin.x).toBe(0)
expect(pin.y).toBe(0)
expect(pin.rotation).toBe(90)
})

test("DsnPin parses rotation at end of expression", () => {
// Format: (pin <padstack_id> <pin_id> <x> <y> <rotation>)
const pin = DsnPin.fromSexprPrimitives([
"Rect[T]Pad_1000x1000_um",
"A1",
100,
200,
45,
])

expect(pin.padstackId).toBe("Rect[T]Pad_1000x1000_um")
expect(pin.pinId).toBe("A1")
expect(pin.x).toBe(100)
expect(pin.y).toBe(200)
expect(pin.rotation).toBe(45)
})