Skip to content

Commit

Permalink
Merge pull request #68 from tscircuit/fix/port-circle
Browse files Browse the repository at this point in the history
fix: port design according to kicad
  • Loading branch information
imrishabh18 authored Oct 27, 2024
2 parents 0167017 + 417dba1 commit 8ac89bb
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 97 deletions.
11 changes: 6 additions & 5 deletions src/Schematic.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import { useRenderedCircuit } from "@tscircuit/core"
import { findBoundsAndCenter } from "@tscircuit/soup-util"
import type { AnyCircuitElement } from "circuit-json"
import { useGlobalStore } from "lib/render-context"
import React, { useCallback, useEffect, useRef, useState } from "react"
import type React from "react"
import { useCallback, useEffect, useRef, useState } from "react"
import { ErrorBoundary as TypedErrorBoundary } from "react-error-boundary"
import { SuperGrid, toMMSI } from "react-supergrid"
import useMeasure from "react-use-measure"
import { ContextProviders } from "schematic-components"
import { SchematicElement } from "schematic-components/SchematicElement"
import {
type Matrix,
applyToPoint,
compose,
inverse,
Matrix,
scale,
translate,
} from "transformation-matrix"
Expand Down Expand Up @@ -122,7 +123,7 @@ export const SchematicWithoutContext = ({

updateTransform(newTransform)
}
}, [elements, bounds.width, bounds.height, updateTransform])
}, [elements, updateTransform])

const handleMouseDown = useCallback((e: React.MouseEvent) => {
isDraggingRef.current = true
Expand Down Expand Up @@ -156,7 +157,7 @@ export const SchematicWithoutContext = ({
const handleWheel = useCallback(
(e: WheelEvent) => {
e.preventDefault()
const scaleMultiplier = Math.pow(0.999, e.deltaY)
const scaleMultiplier = 0.999 ** e.deltaY

if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect()
Expand Down Expand Up @@ -228,7 +229,7 @@ export const SchematicWithoutContext = ({
transform={transformRef.current}
/>
{elements?.map((elm, i) => (
<ErrorBoundary key={i} fallbackRender={fallbackRender(elm)}>
<ErrorBoundary key={`${elm}`} fallbackRender={fallbackRender(elm)}>
<SchematicElement
element={elm}
allElements={elements}
Expand Down
5 changes: 5 additions & 0 deletions src/pages/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&display=swap");

.schematic-text {
font-family: "IBM Plex Mono", monospace;
}
97 changes: 68 additions & 29 deletions src/schematic-components/SVGPathComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
toSVG,
translate,
} from "transformation-matrix"

import "../pages/style.css"
interface PathProps {
type?: "path"
strokeWidth: number
Expand All @@ -27,7 +27,19 @@ interface CircleProps {
fill?: string
}

export type SVGElement = PathProps | CircleProps
interface TextProps {
type: "text"
cx: number
cy: number
text: string
fontSize?: number
fill: string
anchor?: "start" | "middle" | "end"
rotation?: number
stroke?: string
}

export type SVGElement = PathProps | CircleProps | TextProps

interface Props {
rotation: number
Expand All @@ -52,9 +64,10 @@ export const SVGPathComponent = ({
}: Props) => {
const ct = useGlobalStore((s) => s.camera_transform)
const pathBounds = getSVGPathBounds(
paths.filter((p): p is PathProps => p.type !== "circle").map((p) => p.d),
paths
.filter((p): p is PathProps => p.type !== "circle" && p.type !== "text")
.map((p) => p.d),
)

const padding = { x: 0, y: 0 }
const absoluteCenter = applyToPoint(ct, center)
const innerSize = {
Expand All @@ -65,17 +78,13 @@ export const SVGPathComponent = ({
width: innerSize.width + padding.x * 2,
height: innerSize.height + padding.y * 2,
}

const [hovering, setHovering] = useState(false)

const svgLeft = absoluteCenter.x - fullSize.width / 2
const svgTop = absoluteCenter.y - fullSize.height / 2

const preferredRatio =
pathBounds.width === 0
? innerSize.height / pathBounds.height
: innerSize.width / pathBounds.width

const svgToScreen = compose(
scale(
pathBounds.width === 0
Expand All @@ -88,10 +97,15 @@ export const SVGPathComponent = ({
translate(-pathBounds.minX, -pathBounds.minY),
)

const baseFontSize = 0.15 // Fixed base font size in schematic units

return (
// biome-ignore lint/a11y/noSvgWithoutTitle: <explanation>
<svg
onMouseOver={() => setHovering(Boolean(hoverContent))}
onFocus={() => setHovering(Boolean(hoverContent))}
onMouseOut={() => setHovering(false)}
onBlur={() => setHovering(false)}
style={{
position: "absolute",
cursor: hovering ? "pointer" : undefined,
Expand All @@ -108,35 +122,60 @@ export const SVGPathComponent = ({
width={fullSize.width}
height={fullSize.height}
>
{paths.map((p, i) =>
p.type === "circle" ? (
<circle
key={i}
transform={toSVG(
compose(
scale(1, 1), // Add a smaller scale factor for circles
svgToScreen,
),
)}
cx={p.cx}
cy={p.cy}
r={p.r}
fill={"none"}
strokeWidth={2.25 * (p.strokeWidth || 1)}
stroke={p.stroke || "red"}
/>
) : (
{paths.map((p, i) => {
if (p.type === "circle") {
return (
<circle
key={`${p.type}-${i}`}
transform={toSVG(compose(scale(1, 1), svgToScreen))}
cx={p.cx}
cy={p.cy}
r={p.r}
fill={"none"}
strokeWidth={2.25 * (p.strokeWidth || 1)}
stroke={p.stroke || "red"}
/>
)
}
if (p.type === "text") {
const transformedPos = applyToPoint(svgToScreen, { x: p.cx, y: p.cy })
const scaleFactor = fullSize.width / pathBounds.width || 1

return (
<g key={`${p.type}-${i}`}>
<text
className="schematic-text"
x={transformedPos.x}
y={transformedPos.y}
fill={p.fill}
fontSize={baseFontSize * scaleFactor}
textAnchor={p.anchor || "middle"}
dominantBaseline="middle"
transform={`scale(1,-1) rotate(${p.rotation || 0})`}
style={{
transformBox: "fill-box",
transformOrigin: "center",
}}
stroke={p.stroke}
>
{p.text}
</text>
</g>
)
}
// Handle the "path" type directly
return (
<path
key={i}
key={`${p.type}-${i}`}
transform={toSVG(svgToScreen)}
fill={p.fill ?? "none"}
strokeLinecap="round"
strokeWidth={1.5 * (p.strokeWidth || 1)}
stroke={p.stroke || "red"}
d={p.d || ""}
/>
),
)}
)
})}
</svg>
)
}
Expand Down
90 changes: 35 additions & 55 deletions src/schematic-components/SchematicChip.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {
import type {
AnyCircuitElement,
SchematicPort as OriginalSchematicPort,
SchematicComponent,
} from "circuit-json"
import * as Type from "lib/types"
import type * as Type from "lib/types"
import { colorMap } from "lib/utils/colors"
import React from "react"
import type React from "react"
import SVGPathComponent from "./SVGPathComponent"
import SchematicText from "./SchematicText"

Expand Down Expand Up @@ -37,14 +37,17 @@ export const SchematicChip: React.FC<Props> = ({
const chipHeight = size.height

const paths: Array<{
type?: "path" | "circle"
type: "path" | "circle" | "text"
strokeWidth: number
stroke: string
fill?: string
d?: string
cx?: number
cy?: number
r?: number
text?: string
anchor?: string
rotation?: number
}> = []

// Main chip rectangle
Expand All @@ -62,31 +65,23 @@ export const SchematicChip: React.FC<Props> = ({
item.schematic_component_id === schematic_component_id,
)

const portLength = 0.2
const circleRadius = 0.05
const portLength = 0.5
const circleRadius = 0.04
const labelOffset = 0.1

const pinLabels: Array<{
x: number
y: number
text: string
anchor: string
rotation: number
}> = []

schematicPorts.forEach((port) => {
for (const port of schematicPorts) {
const { side, pinNumber, distanceFromEdge } = port.center
let x = 0,
y = 0,
endX = 0,
endY = 0
let labelX = 0,
labelY = 0
let x = 0
let y = 0
let endX = 0
let endY = 0
let pinX = 0
let pinY = 0
let textAnchor = "middle"
let rotation = 0

if (side === "center") {
return
continue
}

switch (side) {
Expand All @@ -95,35 +90,35 @@ export const SchematicChip: React.FC<Props> = ({
y = -chipHeight / 2 + distanceFromEdge
endX = x - portLength
endY = y
labelX = endX
labelY = y + labelOffset
textAnchor = "end"
pinX = x - portLength / 2
pinY = y + labelOffset
textAnchor = "middle"
break
case "right":
x = chipWidth / 2
y = chipHeight / 2 - distanceFromEdge
endX = x + portLength
endY = y
labelX = endX - labelOffset
labelY = y + labelOffset
pinX = x + portLength / 2 - labelOffset
pinY = y + labelOffset
textAnchor = "start"
break
case "bottom":
x = -chipWidth / 2 + distanceFromEdge
y = -chipHeight / 2
endX = x
endY = y - portLength
labelX = x
labelY = endY + labelOffset
pinX = x - labelOffset
pinY = y - portLength / 2
rotation = -90
break
case "top":
x = chipWidth / 2 - distanceFromEdge
y = chipHeight / 2
endX = x
endY = y + portLength
labelX = x
labelY = endY + labelOffset
pinX = x - labelOffset
pinY = y + portLength / 2
rotation = -90
break
}
Expand All @@ -142,22 +137,25 @@ export const SchematicChip: React.FC<Props> = ({
cx: endX,
cy: endY,
r: circleRadius,
strokeWidth: 0.01,
strokeWidth: 0.005,
stroke: colorMap.schematic.component_outline,
fill: colorMap.schematic.component_outline,
})

// Add pin label
// Add pin number
if (pinNumber !== undefined) {
pinLabels.push({
x: labelX,
y: labelY,
paths.push({
type: "text",
cx: pinX,
cy: pinY,
text: `${pinNumber}`,
anchor: textAnchor,
rotation: rotation,
strokeWidth: 0.005,
stroke: colorMap.schematic.pin_number,
})
}
})
}

return (
<>
Expand All @@ -167,24 +165,6 @@ export const SchematicChip: React.FC<Props> = ({
size={size}
paths={paths as any}
/>
{pinLabels.map((label, index) => (
<SchematicText
key={index}
schematic_text={{
anchor: label.anchor as any,
rotation: 0,
position: {
x: center.x + label.x,
y: center.y + label.y,
},
schematic_component_id: "SYNTHETIC",
schematic_text_id: `PIN_LABEL_${index}`,
text: label.text,
type: "schematic_text",
color: colorMap.schematic.pin_number,
}}
/>
))}
<SchematicText
schematic_text={{
anchor: "right",
Expand Down
2 changes: 1 addition & 1 deletion src/schematic-components/SchematicComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {
import type {
AnyCircuitElement,
SchematicComponent as SchematicComponentType,
} from "circuit-json"
Expand Down
Loading

0 comments on commit 8ac89bb

Please sign in to comment.