-
Notifications
You must be signed in to change notification settings - Fork 43
feat: add pcb_placement_hint element and extend pcb_placement_error #526
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { z } from "zod" | ||
| import { distance } from "src/units" | ||
| import { getZodPrefixedIdWithDefault } from "src/common" | ||
| import { expectTypesMatch } from "src/utils/expect-types-match" | ||
|
|
||
| /** | ||
| * A placement hint that tells the autoplacer to position a component near | ||
| * a specific target pin, optionally with a facing pad constraint. | ||
| */ | ||
| export interface PcbPlacementHint { | ||
| type: "pcb_placement_hint" | ||
| pcb_placement_hint_id: string | ||
| pcb_component_id: string | ||
|
|
||
| /** The target port this component should be placed near */ | ||
| target_pcb_port_id: string | ||
|
|
||
| /** The port of this component that should face the target */ | ||
| facing_pcb_port_id?: string | ||
|
|
||
| /** Max center-to-center distance in mm. Default: 5mm */ | ||
| max_distance?: number | ||
|
|
||
| /** Whether the placer has satisfied this hint */ | ||
| is_satisfied?: boolean | ||
|
|
||
| subcircuit_id?: string | ||
| } | ||
|
|
||
| export const pcb_placement_hint = z | ||
| .object({ | ||
| type: z.literal("pcb_placement_hint"), | ||
| pcb_placement_hint_id: getZodPrefixedIdWithDefault("pcb_placement_hint"), | ||
| pcb_component_id: z.string(), | ||
| target_pcb_port_id: z.string(), | ||
| facing_pcb_port_id: z.string().optional(), | ||
| max_distance: distance.optional(), | ||
| is_satisfied: z.boolean().optional(), | ||
| subcircuit_id: z.string().optional(), | ||
| }) | ||
| .describe( | ||
| "A placement hint that tells the autoplacer to position a component near a specific target pin", | ||
| ) | ||
|
|
||
| export type PcbPlacementHintInput = z.input<typeof pcb_placement_hint> | ||
| type InferredPcbPlacementHint = z.infer<typeof pcb_placement_hint> | ||
|
|
||
| expectTypesMatch<PcbPlacementHint, InferredPcbPlacementHint>(true) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_error } from "src/pcb/pcb_placement_error" | ||
|
|
||
| test("pcb_placement_error parses distance_exceeded error", () => { | ||
| const data = { | ||
| type: "pcb_placement_error", | ||
| pcb_placement_error_id: "pcb_placement_error_1", | ||
| error_type: "distance_exceeded", | ||
| message: "C1 is 8mm from U1.VCC, max allowed is 5mm", | ||
| pcb_component_id: "pcb_component_1", | ||
| pcb_placement_hint_id: "pcb_placement_hint_1", | ||
| actual_distance: 0.008, | ||
| max_distance: 0.005, | ||
| } | ||
|
|
||
| const parsed = pcb_placement_error.parse(data) | ||
|
|
||
| expect(parsed.error_type).toBe("distance_exceeded") | ||
| expect(parsed.pcb_component_id).toBe("pcb_component_1") | ||
| expect(parsed.pcb_placement_hint_id).toBe("pcb_placement_hint_1") | ||
| expect(parsed.actual_distance).toBeCloseTo(0.008) | ||
| expect(parsed.max_distance).toBeCloseTo(0.005) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_error } from "src/pcb/pcb_placement_error" | ||
|
|
||
| test("pcb_placement_error parses wrong_pad_orientation error", () => { | ||
| const data = { | ||
| type: "pcb_placement_error", | ||
| pcb_placement_error_id: "pcb_placement_error_2", | ||
| error_type: "wrong_pad_orientation", | ||
| message: "C1 facing pad is the farthest from target (270° rule violated)", | ||
| pcb_component_id: "pcb_component_1", | ||
| pcb_placement_hint_id: "pcb_placement_hint_1", | ||
| } | ||
|
|
||
| const parsed = pcb_placement_error.parse(data) | ||
|
|
||
| expect(parsed.error_type).toBe("wrong_pad_orientation") | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_error } from "src/pcb/pcb_placement_error" | ||
|
|
||
| test("pcb_placement_error parses decoupling_trace_too_long error", () => { | ||
| const data = { | ||
| type: "pcb_placement_error", | ||
| pcb_placement_error_id: "pcb_placement_error_3", | ||
| error_type: "decoupling_trace_too_long", | ||
| message: "Decoupling trace for C1 is 12mm, max allowed is 5mm", | ||
| pcb_component_id: "pcb_component_1", | ||
| } | ||
|
|
||
| const parsed = pcb_placement_error.parse(data) | ||
|
|
||
| expect(parsed.error_type).toBe("decoupling_trace_too_long") | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_error } from "src/pcb/pcb_placement_error" | ||
|
|
||
| test("pcb_placement_error defaults to pcb_placement_error type", () => { | ||
| const data = { | ||
| type: "pcb_placement_error", | ||
| pcb_placement_error_id: "pcb_placement_error_4", | ||
| message: "Generic placement error", | ||
| } | ||
|
|
||
| const parsed = pcb_placement_error.parse(data) | ||
|
|
||
| expect(parsed.error_type).toBe("pcb_placement_error") | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_hint } from "src/pcb/pcb_placement_hint" | ||
|
|
||
| test("pcb_placement_hint parses with all fields", () => { | ||
| const data = { | ||
| type: "pcb_placement_hint", | ||
| pcb_placement_hint_id: "pcb_placement_hint_1", | ||
| pcb_component_id: "pcb_component_1", | ||
| target_pcb_port_id: "pcb_port_5", | ||
| facing_pcb_port_id: "pcb_port_10", | ||
| max_distance: "2mm", | ||
| is_satisfied: false, | ||
| subcircuit_id: "subcircuit_1", | ||
| } | ||
|
|
||
| const parsed = pcb_placement_hint.parse(data) | ||
|
|
||
| expect(parsed.type).toBe("pcb_placement_hint") | ||
| expect(parsed.pcb_component_id).toBe("pcb_component_1") | ||
| expect(parsed.target_pcb_port_id).toBe("pcb_port_5") | ||
| expect(parsed.facing_pcb_port_id).toBe("pcb_port_10") | ||
| expect(parsed.max_distance).toBeCloseTo(2) | ||
| expect(parsed.is_satisfied).toBe(false) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { pcb_placement_hint } from "src/pcb/pcb_placement_hint" | ||
|
|
||
| test("pcb_placement_hint parses without optional fields", () => { | ||
| const data = { | ||
| type: "pcb_placement_hint", | ||
| pcb_component_id: "pcb_component_1", | ||
| target_pcb_port_id: "pcb_port_5", | ||
| } | ||
|
|
||
| const parsed = pcb_placement_hint.parse(data) | ||
|
|
||
| expect(parsed.type).toBe("pcb_placement_hint") | ||
| expect(parsed.pcb_component_id).toBe("pcb_component_1") | ||
| expect(parsed.target_pcb_port_id).toBe("pcb_port_5") | ||
| expect(parsed.facing_pcb_port_id).toBeUndefined() | ||
| expect(parsed.max_distance).toBeUndefined() | ||
| expect(parsed.is_satisfied).toBeUndefined() | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { any_circuit_element } from "src/any_circuit_element" | ||
|
|
||
| test("any_circuit_element includes pcb_placement_hint", () => { | ||
| const data = { | ||
| type: "pcb_placement_hint", | ||
| pcb_placement_hint_id: "pcb_placement_hint_1", | ||
| pcb_component_id: "pcb_component_1", | ||
| target_pcb_port_id: "pcb_port_5", | ||
| } | ||
|
|
||
| expect(() => any_circuit_element.parse(data)).not.toThrow() | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { source_component_base } from "src/source/base/source_component_base" | ||
|
|
||
| test("source_component_base accepts placement fields", () => { | ||
| const data = { | ||
| type: "source_component", | ||
| source_component_id: "source_component_1", | ||
| name: "C1", | ||
| place_near_selector: "U1.VCC", | ||
| place_near_port_id: "source_port_5", | ||
| facing_pad_port_id: "source_port_10", | ||
| place_near_max_distance: 0.005, | ||
| } | ||
|
|
||
| const parsed = source_component_base.parse(data) | ||
|
|
||
| expect(parsed.place_near_selector).toBe("U1.VCC") | ||
| expect(parsed.place_near_port_id).toBe("source_port_5") | ||
| expect(parsed.facing_pad_port_id).toBe("source_port_10") | ||
| expect(parsed.place_near_max_distance).toBeCloseTo(0.005) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { source_simple_capacitor } from "src/source/source_simple_capacitor" | ||
|
|
||
| test("source_simple_capacitor inherits placement fields", () => { | ||
| const data = { | ||
| type: "source_component", | ||
| ftype: "simple_capacitor", | ||
| source_component_id: "source_component_1", | ||
| name: "C1", | ||
| capacitance: "100nF", | ||
| place_near_selector: "U1.VCC", | ||
| facing_pad_port_id: "source_port_10", | ||
| place_near_max_distance: 0.002, | ||
| max_decoupling_trace_length: "5mm", | ||
| } | ||
|
|
||
| const parsed = source_simple_capacitor.parse(data) | ||
|
|
||
| expect(parsed.place_near_selector).toBe("U1.VCC") | ||
| expect(parsed.facing_pad_port_id).toBe("source_port_10") | ||
| expect(parsed.place_near_max_distance).toBeCloseTo(0.002) | ||
| expect(parsed.max_decoupling_trace_length).toBeCloseTo(5) | ||
| }) | ||
|
Comment on lines
+1
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test file violates the rule that numbered test files should only be used when splitting files with multiple tests. The file 'source_component_placement_fields2.test.ts' contains only one test() function but uses numbered naming (2). Since it has AT MOST one test, it should be named without the number suffix, like 'source_component_placement_fields_inheritance.test.ts'. Spotted by Graphite (based on custom rule: Custom rule)
Comment on lines
+4
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test file name is inconsistent with the project's naming pattern. The file name 'source_component_placement_fields_inheritance.test.ts' is very long and uses underscores throughout, while other test files in the same directory use shorter, more concise names. The rule states that file names should be consistent with the project and generally use kebab-case. This file should be renamed to follow a more consistent pattern, such as 'source-component-placement-inheritance.test.ts' or split into a more appropriately named file. Spotted by Graphite (based on custom rule: Custom rule)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All existing test files in this repo use snake_case (e.g. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { test, expect } from "bun:test" | ||
| import { source_component_base } from "src/source/base/source_component_base" | ||
|
|
||
| test("placement fields are optional on source_component_base", () => { | ||
| const data = { | ||
| type: "source_component", | ||
| source_component_id: "source_component_1", | ||
| name: "R1", | ||
| } | ||
|
|
||
| const parsed = source_component_base.parse(data) | ||
|
|
||
| expect(parsed.place_near_selector).toBeUndefined() | ||
| expect(parsed.place_near_port_id).toBeUndefined() | ||
| expect(parsed.facing_pad_port_id).toBeUndefined() | ||
| expect(parsed.place_near_max_distance).toBeUndefined() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test file violates the rule that numbered test files should only be used when splitting files with multiple tests. The file 'source_component_placement_fields1.test.ts' contains only one test() function but uses numbered naming (1). Since it has AT MOST one test, it should be named without the number suffix, like 'source_component_placement_fields.test.ts'.
Spotted by Graphite (based on custom rule: Custom rule)

Is this helpful? React 👍 or 👎 to let us know.