1
0
forked from HEL/circuiteria

initial rewrite of drawing pipeline + positioning

This commit is contained in:
2025-04-18 17:25:38 +02:00
parent 2bb7e3b5a9
commit 358de4825d
6 changed files with 536 additions and 124 deletions

View File

@ -9,6 +9,38 @@
/// - length (length, ratio): Optional base unit
/// -> none
#let circuit(body, length: 2em) = {
set text(font: "Source Sans 3")
canvas(length: length, body)
let next-id = 0
let elements = (:)
for element in body {
let internal = type(element) == dictionary and "id" in element
let eid = if internal {element.id} else {none}
if eid == none {
while str(next-id) in elements {
next-id += 1
}
eid = str(next-id)
if internal {
element.id = eid
}
next-id += 1
}
elements.insert(eid, element)
}
for element in elements.values() {
if type(element) == dictionary and "pre-process" in element {
elements = (element.pre-process)(elements, element)
}
}
canvas(length: length, {
for element in elements.values() {
if type(element) == dictionary and "draw" in element {
(element.draw)(element)
} else {
(element,)
}
}
})
}

View File

@ -1,16 +1,15 @@
#import "@preview/cetz:0.3.2": draw
#import "element.typ"
#let draw-shape(id, tl, tr, br, bl, fill, stroke) = {
#let draw-shape(elmt, bounds) = {
let f = draw.rect(
radius: 0.5em,
inset: 0.5em,
fill: fill,
stroke: stroke,
name: id,
bl, tr
fill: elmt.fill,
stroke: elmt.stroke,
bounds.bl, bounds.tr
)
return (f, tl, tr, br, bl)
return (f, bounds)
}
/// Draws a block element
@ -18,32 +17,8 @@
/// #examples.block
/// For parameters description, see #doc-ref("element.elmt")
#let block(
x: none,
y: none,
w: none,
h: none,
name: none,
name-anchor: "center",
ports: (),
ports-margins: (),
fill: none,
stroke: black + 1pt,
id: "",
debug: (
ports: false
)
..args
) = element.elmt(
draw-shape: draw-shape,
x: x,
y: y,
w: w,
h: h,
name: name,
name-anchor: name-anchor,
ports: ports,
ports-margins: ports-margins,
fill: fill,
stroke: stroke,
id: id,
debug: debug
..args
)

View File

@ -1,4 +1,4 @@
#import "@preview/cetz:0.3.2": draw, coordinate
#import "@preview/cetz:0.3.2": draw, coordinate, matrix, vector
#import "ports.typ": add-ports, add-port
#import "../util.typ"
@ -13,10 +13,163 @@
panic("Could not find port with id " + str(id))
}
#let default-draw-shape(id, tl, tr, br, bl, fill, stroke) = {
return ({}, tl, tr, br, bl)
#let local-to-global(origin, u, v, points) = {
return points-real = points.map(p => {
let (pu, pv) = p
return vector.add(
origin,
vector.add(
vector.scale(u, pu),
vector.scale(v, pv)
)
)
})
}
#let default-draw-shape(elmt, bounds) = {
return ({}, bounds)
}
#let default-pre-process(elements, element) = {
return elements
}
#let resolve-offset(ctx, offset, from, axis) = {
let (ctx, pos) = coordinate.resolve(
ctx,
(rel: offset, to: from)
)
return pos.at(axis)
}
#let resolve-align(ctx, elmt, align, with, axis) = {
let (align-side, i) = find-port(elmt.ports, align)
let margins = (0%, 0%)
if align-side in elmt.ports-margins {
margins = elmt.ports-margins.at(align-side)
}
let parallel-sides = (
("north", "south"),
("west", "east")
).at(axis)
let ortho-sides = (
("west", "east"),
("north", "south")
).at(axis)
let dl
let start-margin
let len = elmt.size.at(axis)
if align-side in parallel-sides {
let used-pct = 100% - margins.at(0) - margins.at(1)
let used-len = len * used-pct / 100%
start-margin = len * margins.at(0) / 100%
dl = used-len * (i + 1) / (elmt.ports.at(align-side).len() + 1)
if not elmt.auto-ports {
start-margin = 0
dl = elmt.ports-pos.at(with)(len)
}
} else if align-side == ortho-sides.first() {
dl = 0
start-margin = 0
} else {
dl = len
start-margin = 0
}
if axis == 1 {
dl = len - dl
}
let (ctx, with-pos) = coordinate.resolve(ctx, with)
return with-pos.at(axis) - dl + start-margin
}
#let resolve-coordinate(ctx, elmt, coord, axis) = {
if type(coord) == dictionary {
let offset = coord.at("offset", default: none)
let from = coord.at("from", default: none)
let align = coord.at("align", default: none)
let with = coord.at("with", default: none)
if none not in (offset, from) {
if type(offset) != array {
let a = (0, 0)
a.at(axis) = offset
offset = a
}
return resolve-offset(ctx, offset, from, axis)
} else if none not in (align, with) {
return resolve-align(ctx, elmt, align, with, axis)
} else {
panic("Dictionnary must either provide both 'offset' and 'from', or 'align' and 'with'")
}
}
if type(coord) not in (int, float, length) {
panic("Invalid " + "xy".at(axis) + " coordinate: " + repr(coord))
}
return coord
}
#let make-bounds(x, y, w, h) = {
let w2 = w / 2
let h2 = h / 2
return (
bl: (x, y),
tl: (x, y + h),
tr: (x + w, y + h),
br: (x + w, y),
center: (x + w2, y + h2),
b: (x + w2, y),
t: (x + w2, y + h),
l: (x, y + h2),
r: (x + w, y + h2),
)
}
#let render(draw-shape, elmt) = draw.group(name: elmt.id, ctx => {
let width = elmt.size.first()
let height = elmt.size.last()
let x = elmt.pos.first()
let y = elmt.pos.last()
x = resolve-coordinate(ctx, elmt, x, 0)
y = resolve-coordinate(ctx, elmt, y, 1)
let bounds = make-bounds(x, y, width, height)
// Workaround because CeTZ needs to have all draw functions in the body
let func = {}
(func, bounds) = draw-shape(elmt, bounds)
func
if elmt.name != none {
draw.content(
(name: elmt.id, anchor: elmt.name-anchor),
anchor: if elmt.name-anchor in util.valid-anchors {elmt.name-anchor} else {"center"},
padding: 0.5em,
align(center)[*#elmt.name*]
)
}
if elmt.auto-ports {
add-ports(
elmt.id,
bounds,
elmt.ports,
elmt.ports-margins,
debug: elmt.debug.ports
)
}
})
/// Draws an element
/// - draw-shape (function): Draw function
/// - x (number, dictionary): The x position (bottom-left corner).
@ -46,10 +199,9 @@
/// - `ports`: if true, shows dots on all ports of the element
#let elmt(
draw-shape: default-draw-shape,
x: none,
y: none,
w: none,
h: none,
pre-process: default-pre-process,
pos: (0, 0),
size: (1, 1),
name: none,
name-anchor: "center",
ports: (:),
@ -62,84 +214,42 @@
debug: (
ports: false
)
) = draw.get-ctx(ctx => {
let width = w
let height = h
let x = x
let y = y
if x == none { panic("Parameter x must be set") }
if y == none { panic("Parameter y must be set") }
if w == none { panic("Parameter w must be set") }
if h == none { panic("Parameter h must be set") }
if (type(x) == dictionary) {
let offset = x.rel
let to = x.to
let (ctx, to-pos) = coordinate.resolve(ctx, (rel: (offset, 0), to: to))
x = to-pos.at(0)
}
if (type(y) == dictionary) {
let from = y.from
let to = y.to
let (to-side, i) = find-port(ports, to)
let margins = (0%, 0%)
if to-side in ports-margins {
margins = ports-margins.at(to-side)
) = {
for (key, side-ports) in ports.pairs() {
if type(side-ports) == str {
side-ports = ((id: side-ports),)
} else if type(side-ports) == dictionary {
side-ports = (side-ports,)
}
let dy
let top-margin
if to-side in ("east", "west") {
let used-pct = 100% - margins.at(0) - margins.at(1)
let used-height = height * used-pct / 100%
top-margin = height * margins.at(0) / 100%
dy = used-height * (i + 1) / (ports.at(to-side).len() + 1)
if not auto-ports {
top-margin = 0
dy = ports-y.at(to)(height)
for (i, port) in side-ports.enumerate() {
if type(port) == array {
side-ports.at(i) = (
id: port.at(0, default: ""),
name: port.at(1, default: "")
)
} else if type(port) == str {
side-ports.at(i) = (id: port)
}
} else if to-side == "north" {
dy = 0
top-margin = 0
} else if to-side == "south" {
dy = height
top-margin = 0
}
let (ctx, from-pos) = coordinate.resolve(ctx, from)
y = from-pos.at(1) + dy - height + top-margin
ports.at(key) = side-ports
}
let tl = (x, y + height)
let tr = (x + width, y + height)
let br = (x + width, y)
let bl = (x, y)
// Workaround because CeTZ needs to have all draw functions in the body
let func = {}
(func, tl, tr, br, bl) = draw-shape(id, tl, tr, br, bl, fill, stroke)
func
if (name != none) {
draw.content(
(name: id, anchor: name-anchor),
anchor: if name-anchor in util.valid-anchors {name-anchor} else {"center"},
padding: 0.5em,
align(center)[*#name*]
)
}
if auto-ports {
add-ports(
id,
tl, tr, br, bl,
ports,
ports-margins,
debug: debug.ports
)
}
})
return ((
id: id,
draw: render.with(draw-shape),
pre-process: pre-process,
pos: pos,
size: size,
name: name,
name-anchor: name-anchor,
ports: ports,
ports-margins: ports-margins,
fill: fill,
stroke: stroke,
auto-ports: auto-ports,
ports-y: ports-y,
debug: debug
),)
}

View File

@ -34,12 +34,10 @@
angle: if name-rotate {90deg} else {0deg},
name
)
let id = elmt-id + "-port-" + port.at("id")
if debug {
draw.circle(
pos,
name: id,
radius: .1,
stroke: none,
fill: red
@ -49,24 +47,24 @@
draw.hide(draw.circle(
pos,
radius: 0,
stroke: none,
name: id
stroke: none
))
}
draw.anchor(port.id, pos)
}
#let add-ports(
elmt-id,
tl, tr, br, bl,
bounds,
ports,
ports-margins,
debug: false
) = {
let sides = (
"north": (tl, tr),
"east": (tr, br),
"south": (bl, br),
"west": (tl, bl)
"north": (bounds.tl, bounds.tr),
"east": (bounds.tr, bounds.br),
"south": (bounds.bl, bounds.br),
"west": (bounds.tl, bounds.bl)
)
if type(ports) != dictionary {