@tscircuit/core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +35 -0
  2. package/dist/index.cjs +5353 -0
  3. package/index.ts +1 -0
  4. package/lib/Project.ts +84 -0
  5. package/lib/components/base-components/NormalComponent.ts +330 -0
  6. package/lib/components/base-components/PrimitiveComponent.ts +299 -0
  7. package/lib/components/base-components/Renderable.ts +113 -0
  8. package/lib/components/index.ts +14 -0
  9. package/lib/components/normal-components/Board.ts +38 -0
  10. package/lib/components/normal-components/Capacitor.ts +23 -0
  11. package/lib/components/normal-components/Diode.ts +27 -0
  12. package/lib/components/normal-components/Led.ts +27 -0
  13. package/lib/components/normal-components/Resistor.ts +19 -0
  14. package/lib/components/primitive-components/Footprint.ts +4 -0
  15. package/lib/components/primitive-components/Group.ts +10 -0
  16. package/lib/components/primitive-components/Net.ts +12 -0
  17. package/lib/components/primitive-components/Port.ts +187 -0
  18. package/lib/components/primitive-components/SmtPad.ts +80 -0
  19. package/lib/components/primitive-components/Trace.ts +219 -0
  20. package/lib/components/primitive-components/TraceHint.ts +4 -0
  21. package/lib/fiber/catalogue.ts +22 -0
  22. package/lib/fiber/create-instance-from-react-element.ts +168 -0
  23. package/lib/fiber/intrinsic-jsx.ts +43 -0
  24. package/lib/index.ts +5 -0
  25. package/lib/register-catalogue.ts +3 -0
  26. package/lib/utils/autorouting/SimpleRouteJson.ts +37 -0
  27. package/lib/utils/autorouting/computeObstacleBounds.ts +10 -0
  28. package/lib/utils/constants.ts +35 -0
  29. package/lib/utils/createComponentsFromSoup.ts +86 -0
  30. package/lib/utils/get-relative-direction.ts +14 -0
  31. package/lib/utils/getPortFromHints.ts +10 -0
  32. package/lib/utils/projectPointInDirection.ts +18 -0
  33. package/lib/utils/selector-matching/index.ts +62 -0
  34. package/package.json +40 -0
@@ -0,0 +1,219 @@
1
+ import { traceProps } from "@tscircuit/props"
2
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
+ import type { Port } from "./Port"
4
+ import { IJumpAutorouter, autoroute } from "@tscircuit/infgrid-ijump-astar"
5
+ import type { AnySoupElement, SchematicTrace } from "@tscircuit/soup"
6
+ import type {
7
+ Obstacle,
8
+ SimpleRouteConnection,
9
+ SimpleRouteJson,
10
+ } from "lib/utils/autorouting/SimpleRouteJson"
11
+ import { computeObstacleBounds } from "lib/utils/autorouting/computeObstacleBounds"
12
+ import { projectPointInDirection } from "lib/utils/projectPointInDirection"
13
+
14
+ export class Trace extends PrimitiveComponent<typeof traceProps> {
15
+ source_trace_id: string | null = null
16
+ pcb_trace_id: string | null = null
17
+ schematic_trace_id: string | null = null
18
+
19
+ get config() {
20
+ return {
21
+ zodProps: traceProps,
22
+ }
23
+ }
24
+
25
+ getTracePortPathSelectors(): string[] {
26
+ if ("from" in this.props && "to" in this.props) {
27
+ return [
28
+ typeof this.props.from === "string"
29
+ ? this.props.from
30
+ : this.props.from.getPortSelector(),
31
+ typeof this.props.to === "string"
32
+ ? this.props.to
33
+ : this.props.to.getPortSelector(),
34
+ ]
35
+ }
36
+ if ("path" in this.props) {
37
+ return this.props.path.map((p) =>
38
+ typeof p === "string" ? p : p.getPortSelector(),
39
+ )
40
+ }
41
+ return []
42
+ }
43
+
44
+ _findConnectedPorts():
45
+ | { allPortsFound: true; ports: Array<{ selector: string; port: Port }> }
46
+ | { allPortsFound: false; ports?: undefined } {
47
+ const { db } = this.project!
48
+ const { _parsedProps: props, parent } = this
49
+
50
+ if (!parent) throw new Error("Trace has no parent")
51
+
52
+ const portSelectors = this.getTracePortPathSelectors()
53
+
54
+ const ports = portSelectors.map((selector) => ({
55
+ selector,
56
+ port: parent.selectOne(selector, { type: "port" }) as Port,
57
+ }))
58
+
59
+ for (const { selector, port } of ports) {
60
+ if (!port) {
61
+ const parentSelector = selector.replace(/\>.*$/, "")
62
+ const targetComponent = parent.selectOne(parentSelector)
63
+ if (!targetComponent) {
64
+ this.renderError(`Could not find port for selector "${selector}"`)
65
+ } else {
66
+ this.renderError(
67
+ `Could not find port for selector "${selector}"\nsearched component ${targetComponent.getString()}, which has ports:${targetComponent.children
68
+ .filter((c) => c.componentName === "Port")
69
+ .map((c) => ` ${c.getString()}`)
70
+ .join("\n")}`,
71
+ )
72
+ }
73
+ }
74
+ }
75
+
76
+ if (ports.some((p) => !p.port)) {
77
+ return { allPortsFound: false }
78
+ }
79
+
80
+ return { allPortsFound: true, ports }
81
+ }
82
+
83
+ doInitialSourceTraceRender(): void {
84
+ const { db } = this.project!
85
+ const { _parsedProps: props, parent } = this
86
+
87
+ if (!parent) {
88
+ this.renderError("Trace has no parent")
89
+ return
90
+ }
91
+
92
+ const { allPortsFound, ports } = this._findConnectedPorts()
93
+ if (!allPortsFound) return
94
+
95
+ const trace = db.source_trace.insert({
96
+ connected_source_port_ids: ports.map((p) => p.port.source_port_id!),
97
+ connected_source_net_ids: [],
98
+ })
99
+
100
+ this.source_trace_id = trace.source_trace_id
101
+ }
102
+
103
+ doInitialPcbTraceRender(): void {
104
+ const { db } = this.project!
105
+ const { _parsedProps: props, parent } = this
106
+
107
+ if (!parent) throw new Error("Trace has no parent")
108
+
109
+ const { allPortsFound, ports } = this._findConnectedPorts()
110
+
111
+ if (!allPortsFound) return
112
+
113
+ const pcbElements: AnySoupElement[] = db
114
+ .toArray()
115
+ .filter(
116
+ (elm) =>
117
+ elm.type === "pcb_smtpad" ||
118
+ elm.type === "pcb_trace" ||
119
+ elm.type === "pcb_plated_hole" ||
120
+ elm.type === "pcb_hole" ||
121
+ elm.type === "source_port" ||
122
+ elm.type === "pcb_port",
123
+ )
124
+
125
+ const source_trace = db.source_trace.get(this.source_trace_id!)!
126
+
127
+ const { solution } = autoroute(pcbElements.concat([source_trace]))
128
+
129
+ // TODO for some reason, the solution gets duplicated. Seems to be an issue
130
+ // with the ijump-astar function
131
+ const pcb_trace = solution[0]
132
+
133
+ db.pcb_trace.insert(pcb_trace)
134
+
135
+ this.pcb_trace_id = pcb_trace.pcb_trace_id
136
+ }
137
+
138
+ doInitialSchematicTraceRender(): void {
139
+ const { db } = this.project!
140
+ const { _parsedProps: props, parent } = this
141
+
142
+ if (!parent) throw new Error("Trace has no parent")
143
+
144
+ const { allPortsFound, ports } = this._findConnectedPorts()
145
+
146
+ if (!allPortsFound) return
147
+
148
+ const obstacles: Obstacle[] = []
149
+ const connection: SimpleRouteConnection = {
150
+ name: this.source_trace_id!,
151
+ pointsToConnect: [],
152
+ }
153
+
154
+ for (const elm of db.toArray()) {
155
+ if (elm.type === "schematic_component") {
156
+ obstacles.push({
157
+ type: "rect",
158
+ center: elm.center,
159
+ width: elm.size.width,
160
+ height: elm.size.height,
161
+ connectedTo: [],
162
+ })
163
+ }
164
+ }
165
+
166
+ for (const { port } of ports) {
167
+ connection.pointsToConnect.push(
168
+ projectPointInDirection(
169
+ port.getGlobalSchematicPosition(),
170
+ port.facingDirection!,
171
+ 0.1501,
172
+ ),
173
+ )
174
+ }
175
+
176
+ const bounds = computeObstacleBounds(obstacles)
177
+
178
+ const simpleRouteJsonInput: SimpleRouteJson = {
179
+ obstacles,
180
+ connections: [connection],
181
+ bounds,
182
+ layerCount: 1,
183
+ }
184
+
185
+ const autorouter = new IJumpAutorouter({
186
+ input: simpleRouteJsonInput,
187
+ })
188
+ const results = autorouter.solve()
189
+
190
+ if (results.length === 0) return
191
+
192
+ const [result] = results
193
+
194
+ if (!result.solved) return
195
+
196
+ const { route } = result
197
+
198
+ const edges: SchematicTrace["edges"] = []
199
+
200
+ for (let i = 0; i < route.length - 1; i++) {
201
+ const from = route[i]
202
+ const to = route[i + 1]
203
+
204
+ edges.push({
205
+ from,
206
+ to,
207
+ // TODO to_schematic_port_id and from_schematic_port_id
208
+ })
209
+ }
210
+
211
+ const trace = db.schematic_trace.insert({
212
+ source_trace_id: this.source_trace_id!,
213
+
214
+ edges,
215
+ })
216
+
217
+ this.schematic_trace_id = trace.schematic_trace_id
218
+ }
219
+ }
@@ -0,0 +1,4 @@
1
+ import { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent"
2
+ import { traceProps } from "@tscircuit/props"
3
+
4
+ export class TraceHint extends PrimitiveComponent<typeof traceProps> {}
@@ -0,0 +1,22 @@
1
+ // See CATALOGUE.md for information about the catalogue pattern
2
+ // The catalogue is a registry of all the component constructors, it has a
3
+ // bunch of purposes but importantly it reduces circular dependencies.
4
+
5
+ export type Instance = {
6
+ // Add any universal methods for classes, e.g. ".add"
7
+ } & { [key: string]: any }
8
+
9
+ export interface Catalogue {
10
+ [name: string]: {
11
+ new (...args: any): Instance
12
+ }
13
+ }
14
+
15
+ export const catalogue: Catalogue = {}
16
+ export const extendCatalogue = (objects: object): void => {
17
+ const altKeys = Object.fromEntries(
18
+ Object.entries(objects).map(([key, v]) => [key.toLowerCase(), v]),
19
+ )
20
+ Object.assign(catalogue, objects)
21
+ Object.assign(catalogue, altKeys)
22
+ }
@@ -0,0 +1,168 @@
1
+ import type React from "react"
2
+ import ReactReconciler, { type HostConfig } from "react-reconciler"
3
+ import { type Renderable } from "lib/components/base-components/Renderable"
4
+ import { type NormalComponent } from "lib/components/base-components/NormalComponent"
5
+ import type { ReactElement, ReactNode } from "react"
6
+ import { catalogue, type Instance } from "./catalogue"
7
+
8
+ export type ReactSubtree = {
9
+ element: ReactElement // TODO rename to "reactElement"
10
+ component: NormalComponent
11
+ }
12
+
13
+ // biome-ignore lint/suspicious/noEmptyInterface: TODO when we have local state
14
+ interface LocalState {}
15
+
16
+ export function prepare<T extends Renderable>(
17
+ object: T,
18
+ state?: Partial<LocalState>,
19
+ ): Instance {
20
+ const instance = object as unknown as Instance
21
+ instance.__tsci = {
22
+ ...state,
23
+ }
24
+
25
+ return object
26
+ }
27
+
28
+ // Define the host config
29
+ const hostConfig: HostConfig<
30
+ string | NormalComponent,
31
+ any,
32
+ any,
33
+ any,
34
+ any,
35
+ any,
36
+ any,
37
+ any,
38
+ any,
39
+ any,
40
+ any,
41
+ any,
42
+ any
43
+ > = {
44
+ supportsMutation: true,
45
+ createInstance(type: string, props: any) {
46
+ const target = catalogue[type]
47
+
48
+ if (!target) {
49
+ if (Object.keys(catalogue).length === 0) {
50
+ throw new Error(
51
+ "No components registered in catalogue, did you forget to import lib/register-catalogue in your test file?",
52
+ )
53
+ }
54
+ throw new Error(
55
+ `Unsupported component type (not registered in @tscircuit/core catalogue): ${type}`,
56
+ )
57
+ }
58
+
59
+ const instance = prepare(new target(props) as any, {})
60
+
61
+ return instance
62
+ },
63
+ createTextInstance() {
64
+ // We don't need to handle text nodes for this use case
65
+ return {}
66
+ },
67
+ appendInitialChild(parentInstance: any, child: any) {
68
+ parentInstance.add(child)
69
+ },
70
+ appendChild(parentInstance: any, child: any) {
71
+ parentInstance.add(child)
72
+ },
73
+ appendChildToContainer(container: any, child: any) {
74
+ container.add(child)
75
+ },
76
+ finalizeInitialChildren() {
77
+ return false
78
+ },
79
+ prepareUpdate() {
80
+ return null
81
+ },
82
+ shouldSetTextContent() {
83
+ return false
84
+ },
85
+ getRootHostContext() {
86
+ return {}
87
+ },
88
+ getChildHostContext() {
89
+ return {}
90
+ },
91
+ prepareForCommit() {
92
+ return null
93
+ },
94
+ resetAfterCommit() {},
95
+ commitMount() {},
96
+ commitUpdate() {},
97
+ removeChild() {},
98
+ clearContainer() {},
99
+ supportsPersistence: false,
100
+ getPublicInstance(instance: any) {
101
+ return instance
102
+ },
103
+ preparePortalMount(containerInfo: any): void {
104
+ throw new Error("Function not implemented.")
105
+ },
106
+ scheduleTimeout(fn: (...args: unknown[]) => unknown, delay?: number) {
107
+ throw new Error("Function not implemented.")
108
+ },
109
+ cancelTimeout(id: any): void {
110
+ throw new Error("Function not implemented.")
111
+ },
112
+ noTimeout: undefined,
113
+ isPrimaryRenderer: false,
114
+ getCurrentEventPriority(): ReactReconciler.Lane {
115
+ throw new Error("Function not implemented.")
116
+ },
117
+ getInstanceFromNode(node: any): ReactReconciler.Fiber | null | undefined {
118
+ throw new Error("Function not implemented.")
119
+ },
120
+ beforeActiveInstanceBlur(): void {
121
+ throw new Error("Function not implemented.")
122
+ },
123
+ afterActiveInstanceBlur(): void {
124
+ throw new Error("Function not implemented.")
125
+ },
126
+ prepareScopeUpdate: (scopeInstance: any, instance: any): void => {
127
+ throw new Error("Function not implemented.")
128
+ },
129
+ getInstanceFromScope: (scopeInstance: any) => {
130
+ throw new Error("Function not implemented.")
131
+ },
132
+ detachDeletedInstance: (node: any): void => {
133
+ throw new Error("Function not implemented.")
134
+ },
135
+ supportsHydration: false,
136
+ }
137
+
138
+ const reconciler = ReactReconciler(hostConfig as any)
139
+
140
+ export const createInstanceFromReactElement = (
141
+ reactElm: React.ReactElement,
142
+ ): NormalComponent => {
143
+ const container = reconciler.createContainer(
144
+ // TODO Replace with store like react-three-fiber
145
+ // https://github.com/pmndrs/react-three-fiber/blob/a457290856f57741bf8beef4f6ff9dbf4879c0a5/packages/fiber/src/core/index.tsx#L172
146
+ // https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/store.ts#L168
147
+ {
148
+ props: {
149
+ name: "$root",
150
+ },
151
+ add(instance: any) {
152
+ instance.parent = this
153
+ },
154
+ },
155
+ 0,
156
+ null,
157
+ false,
158
+ null,
159
+ "tsci",
160
+ (error: Error) => {
161
+ console.log("Error in createContainer")
162
+ console.error(error)
163
+ },
164
+ null,
165
+ )
166
+ reconciler.updateContainer(reactElm, container, null, () => {})
167
+ return reconciler.getPublicRootInstance(container) as NormalComponent
168
+ }
@@ -0,0 +1,43 @@
1
+ import type * as Props from "@tscircuit/props"
2
+
3
+ declare global {
4
+ namespace JSX {
5
+ interface IntrinsicElements {
6
+ resistor: Props.ResistorProps
7
+ capacitor: Props.CapacitorProps
8
+ inductor: Props.InductorProps
9
+ diode: Props.DiodeProps
10
+ led: Props.LedProps
11
+ board: Props.BoardProps
12
+ bug: Props.ChipProps
13
+ // TODO use ChipProps once it gets merged in @tscircuit/props
14
+ chip: Props.ChipProps
15
+ powersource: Props.PowerSourceProps
16
+ via: Props.ViaProps
17
+ schematicbox: Props.SchematicBoxProps
18
+ schematicline: Props.SchematicLineProps
19
+ schematicpath: Props.SchematicPathProps
20
+ schematictext: Props.SchematicTextProps
21
+ smtpad: Props.SmtPadProps
22
+ platedhole: Props.PlatedHoleProps
23
+ hole: Props.HoleProps
24
+ port: Props.PortProps
25
+ group: Props.GroupProps
26
+ netalias: Props.NetAliasProps
27
+ trace: Props.TraceProps
28
+ custom: any
29
+ component: Props.ComponentProps
30
+ footprint: any
31
+ silkscreentext: Props.SilkscreenTextProps
32
+ silkscreenpath: Props.SilkscreenPathProps
33
+ silkscreenline: Props.SilkscreenLineProps
34
+ silkscreenrect: Props.SilkscreenRectProps
35
+ silkscreencircle: Props.SilkscreenCircleProps
36
+ tracehint: Props.TraceHintProps
37
+ pcbtrace: Props.PcbTraceProps
38
+ fabricationnotetext: Props.FabricationNoteTextProps
39
+ fabricationnotepath: Props.FabricationNotePathProps
40
+ jscad: any
41
+ }
42
+ }
43
+ }
package/lib/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from "./components"
2
+ export * from "./Project"
3
+
4
+ import "./register-catalogue"
5
+ import "./fiber/intrinsic-jsx"
@@ -0,0 +1,3 @@
1
+ import * as Components from "./components"
2
+ import { extendCatalogue } from "./fiber/catalogue"
3
+ extendCatalogue(Components)
@@ -0,0 +1,37 @@
1
+ export type SimplifiedPcbTrace = {
2
+ type: "pcb_trace"
3
+ pcb_trace_id: string
4
+ route: Array<{
5
+ route_type: "wire" | "via"
6
+ x: number
7
+ y: number
8
+ width: number
9
+ layer: string
10
+ }>
11
+ }
12
+ export type Obstacle = {
13
+ // TODO include ovals
14
+ type: "rect" // NOTE: most datasets do not contain ovals
15
+ center: { x: number; y: number }
16
+ width: number
17
+ height: number
18
+ connectedTo: string[]
19
+ }
20
+
21
+ export interface SimpleRouteConnection {
22
+ name: string
23
+ pointsToConnect: Array<{ x: number; y: number }>
24
+ }
25
+
26
+ export interface SimpleRouteJson {
27
+ layerCount: number
28
+ obstacles: Obstacle[]
29
+ connections: Array<SimpleRouteConnection>
30
+ bounds: { minX: number; maxX: number; minY: number; maxY: number }
31
+ }
32
+
33
+ // declare module "autorouting-dataset" {
34
+ // export type Obstacle = SimpleRouteJson["obstacles"][number]
35
+ // export type SimpleRouteConnection = SimpleRouteJson["connections"][number]
36
+ // export type SimplifiedPcbTrace = SimpleRouteJson["connections"][number]
37
+ // }
@@ -0,0 +1,10 @@
1
+ import type { Obstacle } from "./SimpleRouteJson"
2
+
3
+ export const computeObstacleBounds = (obstacles: Array<Obstacle>) => {
4
+ const minX = Math.min(...obstacles.map((o) => o.center.x))
5
+ const maxX = Math.max(...obstacles.map((o) => o.center.x))
6
+ const minY = Math.min(...obstacles.map((o) => o.center.y))
7
+ const maxY = Math.max(...obstacles.map((o) => o.center.y))
8
+
9
+ return { minX, maxX, minY, maxY }
10
+ }
@@ -0,0 +1,35 @@
1
+ import type { AnySourceComponent } from "@tscircuit/soup"
2
+ import type { BaseSymbolName } from "schematic-symbols"
3
+
4
+ /**
5
+ * This is just a proxy to make autocomplete easier, it just returns whatever
6
+ * key you pass in. It's helpful when you want to make it a bit more obvious
7
+ * how to select the key you want to use without obscuring the actual key.
8
+ */
9
+ const stringProxy = new Proxy(
10
+ {},
11
+ {
12
+ get: (target, prop) => prop,
13
+ },
14
+ ) as any
15
+
16
+ export type Ftype = AnySourceComponent["ftype"]
17
+
18
+ export const FTYPE: {
19
+ [T in AnySourceComponent["ftype"]]: T
20
+ } = stringProxy
21
+
22
+ export const SYMBOL: {
23
+ [T in BaseSymbolName]: T
24
+ } = stringProxy
25
+
26
+ export type TwoPinPorts = "pin1" | "pin2"
27
+ export type PassivePorts = TwoPinPorts
28
+ export type PolarizedPassivePorts =
29
+ | PassivePorts
30
+ | "anode"
31
+ | "cathode"
32
+ | "pos"
33
+ | "neg"
34
+
35
+ export type { BaseSymbolName }
@@ -0,0 +1,86 @@
1
+ import type { AnySoupElement } from "@tscircuit/soup"
2
+ import type { PrimitiveComponent } from "../components/base-components/PrimitiveComponent"
3
+ import { SmtPad } from "lib/components/primitive-components/SmtPad"
4
+
5
+ export const createComponentsFromSoup = (
6
+ soup: AnySoupElement[],
7
+ ): PrimitiveComponent[] => {
8
+ const components: PrimitiveComponent[] = []
9
+ for (const elm of soup) {
10
+ if (elm.type === "pcb_smtpad" && elm.shape === "rect") {
11
+ components.push(
12
+ new SmtPad({
13
+ pcbX: elm.x,
14
+ pcbY: elm.y,
15
+ layer: elm.layer,
16
+ shape: "rect",
17
+ height: elm.height,
18
+ width: elm.width,
19
+ portHints: elm.port_hints,
20
+ }),
21
+ )
22
+ } else if (elm.type === "pcb_smtpad" && elm.shape === "circle") {
23
+ components.push(
24
+ new SmtPad({
25
+ pcbX: elm.x,
26
+ pcbY: elm.y,
27
+ layer: elm.layer,
28
+ shape: "circle",
29
+ radius: elm.radius,
30
+ portHints: elm.port_hints,
31
+ }),
32
+ )
33
+ }
34
+ }
35
+ return components
36
+ // if (elm.type === "pcb_smtpad") {
37
+ // this.add("smtpad", (pb) => pb.setProps(elm))
38
+ // } else if (elm.type === "pcb_plated_hole") {
39
+ // this.add("platedhole", (pb) => pb.setProps(elm))
40
+ // } else if (elm.type === "pcb_hole") {
41
+ // this.add("hole", (pb) => pb.setProps(elm))
42
+ // } else if (elm.type === "pcb_silkscreen_circle") {
43
+ // this.add("silkscreencircle", (pb) =>
44
+ // pb.setProps({
45
+ // ...elm,
46
+ // pcbX: elm.center.x,
47
+ // pcbY: elm.center.y,
48
+ // })
49
+ // )
50
+ // } else if (elm.type === "pcb_silkscreen_line") {
51
+ // this.add("silkscreenline", (pb) =>
52
+ // pb.setProps({
53
+ // ...elm,
54
+ // strokeWidth: elm.stroke_width,
55
+ // })
56
+ // )
57
+ // } else if (elm.type === "pcb_silkscreen_path") {
58
+ // this.add("silkscreenpath", (pb) =>
59
+ // pb.setProps({
60
+ // ...elm,
61
+ // strokeWidth: elm.stroke_width,
62
+ // })
63
+ // )
64
+ // } else if (elm.type === "pcb_silkscreen_rect") {
65
+ // this.add("silkscreenrect", (pb) =>
66
+ // pb.setProps({
67
+ // ...elm,
68
+ // pcbX: elm.center.x,
69
+ // pcbY: elm.center.y,
70
+ // // TODO silkscreen rect isFilled, isOutline etc.
71
+ // })
72
+ // )
73
+ // } else if (elm.type === "pcb_fabrication_note_path") {
74
+ // this.add("fabricationnotepath", (pb) => pb.setProps(elm))
75
+ // } else if (elm.type === "pcb_fabrication_note_text") {
76
+ // this.add("fabricationnotetext", (pb) =>
77
+ // pb.setProps({
78
+ // ...elm,
79
+ // pcbX: elm.anchor_position.x,
80
+ // pcbY: elm.anchor_position.y,
81
+ // anchorAlignment: elm.anchor_alignment,
82
+ // fontSize: elm.font_size,
83
+ // })
84
+ // )
85
+ // }
86
+ }
@@ -0,0 +1,14 @@
1
+ // Return "up", "down", "left", or "right" based on the angle between two points
2
+ // A & B. The direction is relative to A. So if B is to the right of A, the
3
+ // direction is "right". The largest distance wins
4
+ export function getRelativeDirection(
5
+ pointA: { x: number; y: number },
6
+ pointB: { x: number; y: number },
7
+ ): "up" | "down" | "left" | "right" {
8
+ const dx = pointB.x - pointA.x
9
+ const dy = pointB.y - pointA.y
10
+ if (Math.abs(dx) > Math.abs(dy)) {
11
+ return dx > 0 ? "right" : "left"
12
+ }
13
+ return dy > 0 ? "down" : "up"
14
+ }
@@ -0,0 +1,10 @@
1
+ import { Port } from "lib/components/primitive-components/Port"
2
+
3
+ export function getPortFromHints(hints: string[]): Port | null {
4
+ const pinNumber = hints.find((p) => /^(pin)?\d+$/.test(p))
5
+ if (!pinNumber) return null
6
+ return new Port({
7
+ pinNumber: Number.parseInt(pinNumber.replace(/^pin/, "")),
8
+ aliases: hints.filter((p) => p !== pinNumber),
9
+ })
10
+ }
@@ -0,0 +1,18 @@
1
+ export const projectPointInDirection = (
2
+ point: { x: number; y: number },
3
+ direction: "up" | "down" | "left" | "right",
4
+ distance: number,
5
+ ) => {
6
+ switch (direction) {
7
+ case "up":
8
+ return { x: point.x, y: point.y - distance }
9
+ case "down":
10
+ return { x: point.x, y: point.y + distance }
11
+ case "left":
12
+ return { x: point.x - distance, y: point.y }
13
+ case "right":
14
+ return { x: point.x + distance, y: point.y }
15
+ default:
16
+ throw new Error(`Unknown direction "${direction}"`)
17
+ }
18
+ }