@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,113 @@
1
+ import type { PCBPlacementError, PCBTraceError } from "@tscircuit/soup"
2
+ import { Component, createElement, type ReactElement } from "react"
3
+
4
+ export const orderedRenderPhases = [
5
+ "ReactSubtreesRender", // probably going to be removed b/c subtrees should render instantly
6
+ "SourceRender",
7
+ "SourceParentAttachment",
8
+ "PortDiscovery", // probably going to be removed b/c port discovery can always be done on prop change
9
+ "PortMatching",
10
+ "SourceTraceRender",
11
+ "SchematicComponentRender",
12
+ "SchematicLayout",
13
+ "SchematicPortRender",
14
+ "SchematicTraceRender",
15
+ "PcbComponentRender",
16
+ "PcbPortRender",
17
+ "PcbPrimitiveRender",
18
+ "PcbParentAttachment",
19
+ "PcbLayout",
20
+ "PcbTraceRender",
21
+ "CadModelRender",
22
+ "PcbAnalysis",
23
+ ] as const
24
+
25
+ export type RenderPhase = (typeof orderedRenderPhases)[number]
26
+
27
+ export type RenderPhaseFn<K extends RenderPhase = RenderPhase> =
28
+ | `doInitial${K}`
29
+ | `update${K}`
30
+ | `remove${K}`
31
+
32
+ export type RenderPhaseStates = Record<RenderPhase, { initialized: boolean }>
33
+
34
+ export type RenderPhaseFunctions = {
35
+ [T in RenderPhaseFn]?: () => void
36
+ }
37
+
38
+ export type IRenderable = RenderPhaseFunctions & {
39
+ renderPhaseStates: RenderPhaseStates
40
+ runRenderPhase(phase: RenderPhase): void
41
+ runRenderPhaseForChildren(phase: RenderPhase): void
42
+ shouldBeRemoved: boolean
43
+ children: IRenderable[]
44
+ runRenderCycle(): void
45
+ }
46
+
47
+ let globalRenderCounter = 0
48
+ export abstract class Renderable implements IRenderable {
49
+ renderPhaseStates: RenderPhaseStates
50
+ shouldBeRemoved = false
51
+ children: IRenderable[]
52
+
53
+ /** PCB-only SMTPads, PlatedHoles, Holes, Silkscreen elements etc. */
54
+ isPcbPrimitive = false
55
+ /** Schematic-only, lines, boxes, indicators etc. */
56
+ isSchematicPrimitive = false
57
+
58
+ _renderId: string
59
+
60
+ constructor(props: any) {
61
+ this._renderId = `${globalRenderCounter++}`
62
+ this.children = []
63
+ this.renderPhaseStates = {} as RenderPhaseStates
64
+ for (const phase of orderedRenderPhases) {
65
+ this.renderPhaseStates[phase] = { initialized: false }
66
+ }
67
+ }
68
+
69
+ runRenderCycle() {
70
+ for (const renderPhase of orderedRenderPhases) {
71
+ this.runRenderPhaseForChildren(renderPhase)
72
+ this.runRenderPhase(renderPhase)
73
+ }
74
+ }
75
+
76
+ /**
77
+ * This runs all the render methods for a given phase, calling one of:
78
+ * - doInitial*
79
+ * - update*
80
+ * -remove*
81
+ * ...depending on the current state of the component.
82
+ */
83
+ runRenderPhase(phase: RenderPhase) {
84
+ const isInitialized = this.renderPhaseStates[phase].initialized
85
+ if (!isInitialized && this.shouldBeRemoved) return
86
+ if (this.shouldBeRemoved && isInitialized) {
87
+ ;(this as any)?.[`remove${phase}`]?.()
88
+ this.renderPhaseStates[phase].initialized = false
89
+ return
90
+ }
91
+ if (isInitialized) {
92
+ ;(this as any)?.[`update${phase}`]?.()
93
+ return
94
+ }
95
+ ;(this as any)?.[`doInitial${phase}`]?.()
96
+ this.renderPhaseStates[phase].initialized = true
97
+ }
98
+
99
+ runRenderPhaseForChildren(phase: RenderPhase): void {
100
+ for (const child of this.children) {
101
+ child.runRenderPhaseForChildren(phase)
102
+ child.runRenderPhase(phase)
103
+ }
104
+ }
105
+
106
+ renderError(message: string | PCBTraceError | PCBPlacementError) {
107
+ // TODO add to render phase error list and try to add position or
108
+ // relationships etc.
109
+ if (typeof message === "string") {
110
+ throw new Error(message)
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,14 @@
1
+ export { NormalComponent } from "./base-components/NormalComponent"
2
+ export { PrimitiveComponent } from "./base-components/PrimitiveComponent"
3
+ export { Renderable, type IRenderable } from "./base-components/Renderable"
4
+ export { Board } from "./normal-components/Board"
5
+ export { Footprint } from "./primitive-components/Footprint"
6
+ export { SmtPad } from "./primitive-components/SmtPad"
7
+ export { Port } from "./primitive-components/Port"
8
+ export { Resistor } from "./normal-components/Resistor"
9
+ export { Led } from "./normal-components/Led"
10
+ export { Capacitor } from "./normal-components/Capacitor"
11
+ export { Net } from "./primitive-components/Net"
12
+ export { Trace } from "./primitive-components/Trace"
13
+ export { TraceHint } from "./primitive-components/TraceHint"
14
+ export { Group } from "./primitive-components/Group"
@@ -0,0 +1,38 @@
1
+ import { boardProps } from "@tscircuit/props"
2
+ import type { z } from "zod"
3
+ import { NormalComponent } from "../base-components/NormalComponent"
4
+ import { identity, type Matrix } from "transformation-matrix"
5
+
6
+ export class Board extends NormalComponent<typeof boardProps> {
7
+ pcb_board_id: string | null = null
8
+
9
+ get config() {
10
+ return {
11
+ zodProps: boardProps,
12
+ }
13
+ }
14
+
15
+ doInitialPcbComponentRender(): void {
16
+ const { db } = this.project!
17
+ const { _parsedProps: props } = this
18
+
19
+ const pcb_board = db.pcb_board.insert({
20
+ center: { x: props.pcbX, y: props.pcbY },
21
+ width: props.width,
22
+ height: props.height,
23
+ })
24
+
25
+ this.pcb_board_id = pcb_board.pcb_board_id
26
+ }
27
+
28
+ removePcbComponentRender(): void {
29
+ const { db } = this.project!
30
+ if (!this.pcb_board_id) return
31
+ db.pcb_board.delete(this.pcb_board_id!)
32
+ this.pcb_board_id = null
33
+ }
34
+
35
+ computePcbGlobalTransform(): Matrix {
36
+ return identity()
37
+ }
38
+ }
@@ -0,0 +1,23 @@
1
+ import { ledProps } from "@tscircuit/props"
2
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
+ import { FTYPE, SYMBOL } from "lib/utils/constants"
4
+
5
+ type PortNames =
6
+ | "1"
7
+ | "2"
8
+ | "pin1"
9
+ | "pin2"
10
+ | "left"
11
+ | "right"
12
+ | "anode"
13
+ | "cathode"
14
+
15
+ export class Capacitor extends PrimitiveComponent<typeof ledProps, PortNames> {
16
+ get config() {
17
+ return {
18
+ // schematicSymbolName: BASE_SYMBOLS.capacitor,
19
+ zodProps: ledProps,
20
+ sourceFtype: FTYPE.simple_capacitor,
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,27 @@
1
+ import { diodeProps } from "@tscircuit/props"
2
+ import {
3
+ FTYPE,
4
+ SYMBOL,
5
+ type Ftype,
6
+ type TwoPinPorts,
7
+ } from "lib/utils/constants"
8
+ import { Port } from "../primitive-components/Port"
9
+ import { NormalComponent } from "../base-components/NormalComponent"
10
+
11
+ export class Diode extends NormalComponent<typeof diodeProps, TwoPinPorts> {
12
+ pin1 = this.portMap.pin1
13
+ pin2 = this.portMap.pin2
14
+
15
+ get config() {
16
+ return {
17
+ // schematicSymbolName: "diode" as BaseSymbolName,
18
+ zodProps: diodeProps,
19
+ sourceFtype: "simple_diode" as Ftype,
20
+ }
21
+ }
22
+
23
+ initPorts() {
24
+ this.add(new Port({ name: "pin1", aliases: ["1", "pin1"] }))
25
+ this.add(new Port({ name: "pin2", aliases: ["2", "pin2"] }))
26
+ }
27
+ }
@@ -0,0 +1,27 @@
1
+ import { ledProps } from "@tscircuit/props"
2
+ import type {
3
+ BaseSymbolName,
4
+ Ftype,
5
+ PolarizedPassivePorts,
6
+ } from "lib/utils/constants"
7
+ import { NormalComponent } from "../base-components/NormalComponent"
8
+
9
+ export class Led extends NormalComponent<
10
+ typeof ledProps,
11
+ PolarizedPassivePorts
12
+ > {
13
+ get config() {
14
+ return {
15
+ schematicSymbolName: "led" as BaseSymbolName,
16
+ zodProps: ledProps,
17
+ sourceFtype: "simple_diode" as Ftype,
18
+ }
19
+ }
20
+
21
+ pos = this.portMap.pin1
22
+ pin1 = this.portMap.pin1
23
+ anode = this.portMap.pin1
24
+ neg = this.portMap.pin2
25
+ pin2 = this.portMap.pin2
26
+ cathode = this.portMap.pin2
27
+ }
@@ -0,0 +1,19 @@
1
+ import { resistorProps } from "@tscircuit/props"
2
+ import type { PassivePorts, Ftype, BaseSymbolName } from "lib/utils/constants"
3
+ import { NormalComponent } from "../base-components/NormalComponent"
4
+
5
+ export class Resistor extends NormalComponent<
6
+ typeof resistorProps,
7
+ PassivePorts
8
+ > {
9
+ get config() {
10
+ return {
11
+ schematicSymbolName: "boxresistor" as BaseSymbolName,
12
+ zodProps: resistorProps,
13
+ sourceFtype: "simple_resistor" as Ftype,
14
+ }
15
+ }
16
+
17
+ pin1 = this.portMap.pin1
18
+ pin2 = this.portMap.pin2
19
+ }
@@ -0,0 +1,4 @@
1
+ import type { footprintProps } from "@tscircuit/props"
2
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
+
4
+ export class Footprint extends PrimitiveComponent<typeof footprintProps> {}
@@ -0,0 +1,10 @@
1
+ import { groupProps } from "@tscircuit/props"
2
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
+
4
+ export class Group extends PrimitiveComponent<typeof groupProps> {
5
+ get config() {
6
+ return {
7
+ zodProps: groupProps,
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,12 @@
1
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
2
+ import { z } from "zod"
3
+
4
+ export const netProps = z.object({
5
+ name: z.string(),
6
+ })
7
+
8
+ export class Net extends PrimitiveComponent<typeof netProps> {
9
+ getPortSelector() {
10
+ return `net.${this.props.name}`
11
+ }
12
+ }
@@ -0,0 +1,187 @@
1
+ import type { PCBSMTPad } from "@tscircuit/soup"
2
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
+ import { z } from "zod"
4
+ import { getRelativeDirection } from "lib/utils/get-relative-direction"
5
+ import { symbols, type SchSymbol } from "schematic-symbols"
6
+ import { applyToPoint, compose, translate } from "transformation-matrix"
7
+
8
+ export const portProps = z.object({
9
+ name: z.string().optional(),
10
+ pinNumber: z.number().optional(),
11
+ aliases: z.array(z.string()).optional(),
12
+ })
13
+
14
+ export type PortProps = z.infer<typeof portProps>
15
+
16
+ export class Port extends PrimitiveComponent<typeof portProps> {
17
+ source_port_id: string | null = null
18
+ pcb_port_id: string | null = null
19
+ schematic_port_id: string | null = null
20
+
21
+ schematicSymbolPortDef: SchSymbol["ports"][number] | null = null
22
+ matchedComponents: PrimitiveComponent[]
23
+ facingDirection: "up" | "down" | "left" | "right" | null = null
24
+
25
+ constructor(props: z.input<typeof portProps>) {
26
+ if (!props.name && props.pinNumber) props.name = `pin${props.pinNumber}`
27
+ if (!props.name) {
28
+ throw new Error("Port must have a name or a pinNumber")
29
+ }
30
+ super(props)
31
+ this.matchedComponents = []
32
+ }
33
+
34
+ getGlobalPcbPosition(): { x: number; y: number } {
35
+ const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive)
36
+
37
+ if (!matchedPcbElm) {
38
+ throw new Error(
39
+ `Port ${this} has no matched pcb component, can't get global schematic position`,
40
+ )
41
+ }
42
+
43
+ return matchedPcbElm?.getGlobalPcbPosition() ?? { x: 0, y: 0 }
44
+ }
45
+
46
+ getGlobalSchematicPosition(): { x: number; y: number } {
47
+ if (!this.schematicSymbolPortDef) {
48
+ throw new Error(
49
+ `Could not find schematic symbol port for port ${this} so couldn't determine port position`,
50
+ )
51
+ }
52
+
53
+ const symbol = this.parent?.getSchematicSymbol()
54
+ if (!symbol) throw new Error(`Could not find parent symbol for ${this}`)
55
+
56
+ const transform = compose(
57
+ this.parent!.computeSchematicGlobalTransform(),
58
+ translate(-symbol.center.x, -symbol.center.y),
59
+ )
60
+
61
+ return applyToPoint(transform, this.schematicSymbolPortDef)
62
+ }
63
+
64
+ /**
65
+ * Smtpads and platedholes call this method to register themselves as a match
66
+ * for this port. All the matching is done by primitives other than the Port,
67
+ * but everyone registers themselves as a match with their Port.
68
+ */
69
+ registerMatch(component: PrimitiveComponent) {
70
+ this.matchedComponents.push(component)
71
+ }
72
+ getNameAndAliases() {
73
+ const { _parsedProps: props } = this
74
+ return Array.from(
75
+ new Set([
76
+ ...(props.aliases ?? []),
77
+ props.name,
78
+ ...(typeof props.pinNumber === "number"
79
+ ? [`pin${props.pinNumber}`, props.pinNumber.toString()]
80
+ : []),
81
+ ]),
82
+ ) as string[]
83
+ }
84
+ isMatchingPort(port: Port) {
85
+ return this.isMatchingAnyOf(port.getNameAndAliases())
86
+ }
87
+ getPortSelector() {
88
+ return `.${this.parent?.props.name} > port.${this.props.name}`
89
+ // return `#${this.props.id}`
90
+ }
91
+
92
+ doInitialSourceRender(): void {
93
+ const { db } = this.project!
94
+ const { _parsedProps: props } = this
95
+
96
+ const port_hints = this.getNameAndAliases()
97
+
98
+ const source_port = db.source_port.insert({
99
+ name: props.name!,
100
+ pin_number: props.pinNumber,
101
+ port_hints,
102
+ source_component_id: this.parent?.source_component_id!,
103
+ })
104
+
105
+ this.source_port_id = source_port.source_port_id
106
+ }
107
+
108
+ doInitialSourceParentAttachment(): void {
109
+ const { db } = this.project!
110
+ if (!this.parent?.source_component_id) {
111
+ throw new Error(
112
+ `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`,
113
+ )
114
+ }
115
+
116
+ db.source_port.update(this.source_port_id!, {
117
+ source_component_id: this.parent?.source_component_id!,
118
+ })
119
+
120
+ this.source_component_id = this.parent?.source_component_id
121
+ }
122
+
123
+ /**
124
+ * For PcbPorts, we use the parent attachment phase to determine where to place
125
+ * the pcb_port (prior to this phase, the smtpad/platedhole isn't guaranteed
126
+ * to exist)
127
+ */
128
+ doInitialPcbPortRender(): void {
129
+ const { db } = this.project!
130
+ const { matchedComponents } = this
131
+
132
+ if (!this.parent?.pcb_component_id) {
133
+ throw new Error(
134
+ `${this.getString()} has no parent pcb component, cannot render pcb_port (parent: ${this.parent?.getString()})`,
135
+ )
136
+ }
137
+
138
+ const pcbMatches = matchedComponents.filter((c) => c.isPcbPrimitive)
139
+
140
+ if (pcbMatches.length === 0) return
141
+
142
+ if (pcbMatches.length > 1) {
143
+ throw new Error(
144
+ `${this.getString()} has multiple pcb matches, unclear how to place pcb_port: ${pcbMatches.map((c) => c.getString()).join(", ")}`,
145
+ )
146
+ }
147
+
148
+ const pcbMatch: any = pcbMatches[0]
149
+
150
+ if ("getGlobalPcbPosition" in pcbMatch) {
151
+ const pcb_port = db.pcb_port.insert({
152
+ pcb_component_id: this.parent?.pcb_component_id!,
153
+ layers: ["top"],
154
+
155
+ ...pcbMatch.getGlobalPcbPosition(),
156
+
157
+ source_port_id: this.source_port_id!,
158
+ })
159
+ this.pcb_port_id = pcb_port.pcb_port_id
160
+ } else {
161
+ throw new Error(
162
+ `${pcbMatch.getString()} does not have a getGlobalPcbPosition method (needed for pcb_port placement)`,
163
+ )
164
+ }
165
+ }
166
+
167
+ doInitialSchematicPortRender(): void {
168
+ const { db } = this.project!
169
+ const { _parsedProps: props } = this
170
+
171
+ if (!this.parent) return
172
+
173
+ const center = this.getGlobalSchematicPosition()
174
+ const parentCenter = this.parent?.getGlobalSchematicPosition()
175
+
176
+ this.facingDirection = getRelativeDirection(parentCenter, center)
177
+
178
+ const schematic_port = db.schematic_port.insert({
179
+ schematic_component_id: this.parent?.schematic_component_id!,
180
+ center,
181
+ source_port_id: this.source_port_id!,
182
+ facing_direction: this.facingDirection,
183
+ })
184
+
185
+ this.schematic_port_id = schematic_port.schematic_port_id
186
+ }
187
+ }
@@ -0,0 +1,80 @@
1
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
2
+ import { smtPadProps } from "@tscircuit/props"
3
+ import type { Port } from "./Port"
4
+ import type { RenderPhaseFn } from "../base-components/Renderable"
5
+ import type { PCBSMTPad } from "@tscircuit/soup"
6
+
7
+ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
8
+ pcb_smtpad_id: string | null = null
9
+
10
+ matchedPort: Port | null = null
11
+
12
+ isPcbPrimitive = true
13
+
14
+ get config() {
15
+ return {
16
+ zodProps: smtPadProps,
17
+ }
18
+ }
19
+
20
+ doInitialPortMatching(): void {
21
+ const parentPorts = (this.parent?.children ?? []).filter(
22
+ (c) => c.componentName === "Port",
23
+ ) as Port[]
24
+
25
+ if (!this.props.portHints) {
26
+ return
27
+ }
28
+
29
+ for (const port of parentPorts) {
30
+ if (port.isMatchingAnyOf(this.props.portHints)) {
31
+ this.matchedPort = port
32
+ port.registerMatch(this)
33
+ return
34
+ }
35
+ }
36
+ }
37
+
38
+ doInitialPcbPrimitiveRender(): void {
39
+ const { db } = this.project!
40
+ const { _parsedProps: props } = this
41
+ if (!props.portHints) return
42
+ const position = this.getGlobalPcbPosition()
43
+ let pcb_smtpad: PCBSMTPad | null = null
44
+ if (props.shape === "circle") {
45
+ pcb_smtpad = db.pcb_smtpad.insert({
46
+ pcb_component_id: this.parent?.pcb_component_id!,
47
+ pcb_port_id: this.matchedPort?.pcb_port_id!,
48
+ layer: props.layer ?? "top",
49
+ shape: "circle",
50
+
51
+ // @ts-ignore: no idea why this is triggering
52
+ radius: props.radius!,
53
+
54
+ port_hints: props.portHints.map((ph) => ph.toString()),
55
+
56
+ x: position.x,
57
+ y: position.y,
58
+ })
59
+ } else if (props.shape === "rect") {
60
+ pcb_smtpad = db.pcb_smtpad.insert({
61
+ pcb_component_id: this.parent?.pcb_component_id!,
62
+ pcb_port_id: this.matchedPort?.pcb_port_id!,
63
+ layer: props.layer ?? "top",
64
+ shape: "rect",
65
+
66
+ // @ts-ignore: no idea why this is triggering
67
+ width: props.width,
68
+ height: props.height,
69
+
70
+ port_hints: props.portHints.map((ph) => ph.toString()),
71
+
72
+ x: position.x,
73
+ y: position.y,
74
+ })
75
+ }
76
+ if (pcb_smtpad) {
77
+ this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id
78
+ }
79
+ }
80
+ }