Skip to content
Draft
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
13 changes: 8 additions & 5 deletions src/components/CanvasElementsRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { EditTraceHintOverlay } from "./EditTraceHintOverlay"
import { ErrorOverlay } from "./ErrorOverlay"
import { MouseElementTracker } from "./MouseElementTracker"
import { RatsNestOverlay } from "./RatsNestOverlay"
import { PcbGroupOverlay } from "./PcbGroupOverlay"
import { ToolbarOverlay } from "./ToolbarOverlay"
import type { ManualEditEvent } from "@tscircuit/props"

Expand Down Expand Up @@ -118,10 +119,11 @@ export const CanvasElementsRenderer = (props: CanvasElementsRendererProps) => {
<ToolbarOverlay elements={elements}>
<ErrorOverlay transform={transform} elements={elements}>
<RatsNestOverlay transform={transform} soup={elements}>
<DebugGraphicsOverlay
transform={transform}
debugGraphics={props.debugGraphics}
>
<PcbGroupOverlay transform={transform} soup={elements}>
<DebugGraphicsOverlay
transform={transform}
debugGraphics={props.debugGraphics}
>
<WarningGraphicsOverlay
transform={transform}
elements={elements}
Expand All @@ -134,7 +136,8 @@ export const CanvasElementsRenderer = (props: CanvasElementsRendererProps) => {
grid={props.grid}
/>
</WarningGraphicsOverlay>
</DebugGraphicsOverlay>
</DebugGraphicsOverlay>
</PcbGroupOverlay>
</RatsNestOverlay>
</ErrorOverlay>
</ToolbarOverlay>
Expand Down
121 changes: 121 additions & 0 deletions src/components/PcbGroupOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { type Matrix, applyToPoint, identity } from "transformation-matrix"
import type { AnyCircuitElement } from "circuit-json"
import { su } from "@tscircuit/soup-util"
import { useGlobalStore } from "../global-store"
import { zIndexMap } from "lib/util/z-index-map"
import { useMemo } from "react"
import { findBoundsAndCenter } from "@tscircuit/soup-util"

interface Props {
transform?: Matrix
soup?: AnyCircuitElement[]
children: any
}

const colors = [
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a",
"#ffff99",
"#b15928",
]

export const PcbGroupOverlay = ({ transform, soup, children }: Props) => {
const isShowingPcbGroups = useGlobalStore((s) => s.is_showing_pcb_groups)

const pcbGroups = useMemo(() => {
if (!soup || !isShowingPcbGroups) return []
// TODO assume pcb_group type
return (soup as any).filter((e: any) => e.type === "pcb_group")
}, [soup, isShowingPcbGroups])

const groupBounds = useMemo(() => {
if (pcbGroups.length === 0) return []

return pcbGroups.map((group) => {
const componentsInGroup = group.pcb_component_ids.map((id) =>
su(soup).pcb_component.get(id),
)
const smtPadsInGroup = componentsInGroup.flatMap((c) =>
su(soup).pcb_smtpad.list({ pcb_component_id: c.pcb_component_id }),
)
const holeInGroup = componentsInGroup.flatMap((c) =>
su(soup).pcb_hole.list({ pcb_component_id: c.pcb_component_id }),
)

const allElms = [...smtPadsInGroup, ...holeInGroup]

if (allElms.length === 0) return null

const bounds = findBoundsAndCenter(allElms as any)
return { ...bounds, name: group.name }
})
}, [pcbGroups, soup])

if (!soup || !isShowingPcbGroups || pcbGroups.length === 0) return children
if (!transform) transform = identity()

return (
<div style={{ position: "relative" }}>
{children}
<svg
style={{
position: "absolute",
left: 0,
top: 0,
width: "100%",
height: "100%",
pointerEvents: "none",
zIndex: zIndexMap.pcbGroupOverlay,
}}
>
{groupBounds.map((bounds, i) => {
if (!bounds) return null
const color = colors[i % colors.length]

const { x, y } = applyToPoint(transform, {
x: bounds.center.x - bounds.width / 2,
y: bounds.center.y + bounds.height / 2,
})
const { x: x2, y: y2 } = applyToPoint(transform, {
x: bounds.center.x + bounds.width / 2,
y: bounds.center.y - bounds.height / 2,
})

return (
<g key={i}>
<rect
x={x}
y={y}
width={x2 - x}
height={y2 - y}
fill="none"
stroke={color}
strokeWidth="2"
strokeDasharray="4,4"
/>
<text
x={x + 4}
y={y + 12}
fill={color}
fontSize="12px"
style={{
transform: `scale(1, -1) translate(0, -${y * 2 + 12})`,
}}
>
{bounds.name}
</text>
</g>
)
})}
</svg>
</div>
)
}
12 changes: 12 additions & 0 deletions src/components/ToolbarOverlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,13 +124,15 @@ export const ToolbarOverlay = ({ children, elements }: Props) => {
is_showing_multiple_traces_length,
is_showing_autorouting,
is_showing_drc_errors,
is_showing_pcb_groups,
] = useGlobalStore((s) => [
s.in_move_footprint_mode,
s.in_draw_trace_mode,
s.is_showing_rats_nest,
s.is_showing_multiple_traces_length,
s.is_showing_autorouting,
s.is_showing_drc_errors,
s.is_showing_pcb_groups,
])
const setEditMode = useGlobalStore((s) => s.setEditMode)
const setIsShowingRatsNest = useGlobalStore((s) => s.setIsShowingRatsNest)
Expand All @@ -141,6 +143,9 @@ export const ToolbarOverlay = ({ children, elements }: Props) => {
(s) => s.setIsShowingAutorouting,
)
const setIsShowingDrcErrors = useGlobalStore((s) => s.setIsShowingDrcErrors)
const setIsShowingPcbGroups = useGlobalStore(
(s) => s.setIsShowingPcbGroups,
)

useEffect(() => {
const arm = () => setMeasureToolArmed(true)
Expand Down Expand Up @@ -363,6 +368,13 @@ export const ToolbarOverlay = ({ children, elements }: Props) => {
setIsShowingDrcErrors(!is_showing_drc_errors)
}}
/>
<CheckboxMenuItem
label="View PCB Groups"
checked={is_showing_pcb_groups}
onClick={() => {
setIsShowingPcbGroups(!is_showing_pcb_groups)
}}
/>
</div>
)}
</div>
Expand Down
116 changes: 116 additions & 0 deletions src/examples/pcb-groups.fixture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { PCBViewer } from "../PCBViewer"

const circuitJson = [
{
type: "source_component",
source_component_id: "source_comp_1",
name: "R1",
},
{
type: "source_component",
source_component_id: "source_comp_2",
name: "R2",
},
{
type: "source_component",
source_component_id: "source_comp_3",
name: "U1",
},
{
type: "source_component",
source_component_id: "source_comp_4",
name: "U2",
},
{
type: "pcb_component",
source_component_id: "source_comp_1",
pcb_component_id: "pcb_comp_1",
layer: "top",
},
{
type: "pcb_component",
source_component_id: "source_comp_2",
pcb_component_id: "pcb_comp_2",
layer: "top",
},
{
type: "pcb_component",
source_component_id: "source_comp_3",
pcb_component_id: "pcb_comp_3",
layer: "top",
},
{
type: "pcb_component",
source_component_id: "source_comp_4",
pcb_component_id: "pcb_comp_4",
layer: "top",
},
{
type: "pcb_smtpad",
pcb_component_id: "pcb_comp_1",
port_hints: ["1"],
shape: "rect",
x: 0,
y: 0,
width: 1,
height: 1,
layer: "top",
},
{
type: "pcb_smtpad",
pcb_component_id: "pcb_comp_2",
port_hints: ["1"],
shape: "rect",
x: 5,
y: 5,
width: 1,
height: 1,
layer: "top",
},
{
type: "pcb_smtpad",
pcb_component_id: "pcb_comp_3",
port_hints: ["1"],
shape: "rect",
x: 10,
y: 0,
width: 1,
height: 1,
layer: "top",
},
{
type: "pcb_smtpad",
pcb_component_id: "pcb_comp_4",
port_hints: ["1"],
shape: "rect",
x: 15,
y: 5,
width: 1,
height: 1,
layer: "top",
},
{
type: "pcb_group",
pcb_group_id: "group1",
name: "Group 1",
pcb_component_ids: ["pcb_comp_1", "pcb_comp_2"],
},
{
type: "pcb_group",
pcb_group_id: "group2",
name: "Group 2",
pcb_component_ids: ["pcb_comp_3", "pcb_comp_4"],
},
]

export const PcbGroups = () => {
return (
<div style={{ backgroundColor: "black" }}>
<PCBViewer circuitJson={circuitJson as any} />
</div>
)
}

export default {
PcbGroups,
}
5 changes: 5 additions & 0 deletions src/global-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface State {

is_showing_multiple_traces_length: boolean
is_showing_rats_nest: boolean
is_showing_pcb_groups: boolean

selectLayer: (layer: LayerRef) => void
setEditMode: (mode: "off" | "move_footprint" | "draw_trace") => void
Expand All @@ -33,6 +34,7 @@ export interface State {
setIsShowingAutorouting: (is_showing: boolean) => void
setIsShowingMultipleTracesLength: (is_showing: boolean) => void
setIsShowingDrcErrors: (is_showing: boolean) => void
setIsShowingPcbGroups: (is_showing: boolean) => void
}

export type StateProps = {
Expand All @@ -57,6 +59,7 @@ export const createStore = (initialState: Partial<StateProps> = {}) =>

is_showing_multiple_traces_length: false,
is_showing_rats_nest: false,
is_showing_pcb_groups: true,
is_showing_autorouting: true,
is_showing_drc_errors: true,
...initialState,
Expand Down Expand Up @@ -84,6 +87,8 @@ export const createStore = (initialState: Partial<StateProps> = {}) =>
set({ is_showing_autorouting: is_showing }),
setIsShowingDrcErrors: (is_showing) =>
set({ is_showing_drc_errors: is_showing }),
setIsShowingPcbGroups: (is_showing) =>
set({ is_showing_pcb_groups: is_showing }),
}) as const,
)

Expand Down
1 change: 1 addition & 0 deletions src/lib/util/z-index-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const zIndexMap = {
editTraceHintOverlay: 30,
errorOverlay: 30,
ratsNestOverlay: 20,
pcbGroupOverlay: 15,
toolbarOverlay: 60,
warnings: 20,
topLayer: 10, // each layer after this is 1 less than the previous
Expand Down
Loading