@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.
- package/README.md +35 -0
- package/dist/index.cjs +5353 -0
- package/index.ts +1 -0
- package/lib/Project.ts +84 -0
- package/lib/components/base-components/NormalComponent.ts +330 -0
- package/lib/components/base-components/PrimitiveComponent.ts +299 -0
- package/lib/components/base-components/Renderable.ts +113 -0
- package/lib/components/index.ts +14 -0
- package/lib/components/normal-components/Board.ts +38 -0
- package/lib/components/normal-components/Capacitor.ts +23 -0
- package/lib/components/normal-components/Diode.ts +27 -0
- package/lib/components/normal-components/Led.ts +27 -0
- package/lib/components/normal-components/Resistor.ts +19 -0
- package/lib/components/primitive-components/Footprint.ts +4 -0
- package/lib/components/primitive-components/Group.ts +10 -0
- package/lib/components/primitive-components/Net.ts +12 -0
- package/lib/components/primitive-components/Port.ts +187 -0
- package/lib/components/primitive-components/SmtPad.ts +80 -0
- package/lib/components/primitive-components/Trace.ts +219 -0
- package/lib/components/primitive-components/TraceHint.ts +4 -0
- package/lib/fiber/catalogue.ts +22 -0
- package/lib/fiber/create-instance-from-react-element.ts +168 -0
- package/lib/fiber/intrinsic-jsx.ts +43 -0
- package/lib/index.ts +5 -0
- package/lib/register-catalogue.ts +3 -0
- package/lib/utils/autorouting/SimpleRouteJson.ts +37 -0
- package/lib/utils/autorouting/computeObstacleBounds.ts +10 -0
- package/lib/utils/constants.ts +35 -0
- package/lib/utils/createComponentsFromSoup.ts +86 -0
- package/lib/utils/get-relative-direction.ts +14 -0
- package/lib/utils/getPortFromHints.ts +10 -0
- package/lib/utils/projectPointInDirection.ts +18 -0
- package/lib/utils/selector-matching/index.ts +62 -0
- 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,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,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
|
+
}
|