Skip to content

Add some circuits #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Kreijstal opened this issue Apr 21, 2025 · 8 comments
Closed

Add some circuits #44

Kreijstal opened this issue Apr 21, 2025 · 8 comments

Comments

@Kreijstal
Copy link
Contributor

Kreijstal commented Apr 21, 2025

I would do a PR but it seems actions are failing and it is confusing, do we have to also upload the SVG shouldn't that be part of the actions themselves?

Details
#import "@preview/cetz:0.3.4" as cetz
#set page(width: auto, height: auto, margin: 8pt)
#let connect-orthogonal(
  start_anchor, 
  end_anchor,   
  style: "hv", 
  ..styling    
) = {
  assert(style in ("hv", "vh"), message: "Orthogonal connection style must be 'hv' or 'vh'.")

  let corner = if style == "hv" {
    (start_anchor, "|-", end_anchor)
  } else { 
    (start_anchor, "-|", end_anchor)
  }

  cetz.draw.line(start_anchor, corner, end_anchor, ..styling)
}

#let nmos_transistor(
  position, name,
  label: none, label_pos: "D", label_anchor: "north-west",
  label_offset: (0.05, 0.05),
  label_size: 8pt,
  show_pin_labels: false, pin_label_size: 7pt,
  scale: 1.0, rotate: 0deg, width: 0.9, height: 1.2,
  gate_lead_factor: 0.3, bulk_lead_factor: 0.3, gate_pos_factor: 0.3,
  channel_pos_factor: 0.4, gate_v_extent_factor: 0.35, channel_v_extent_factor: 0.35,
  thick_factor: 2.0, arrow_scale: 0.8, arrow_fill: black,
  ..styling
) = {
  cetz.draw.group(name: name, ..styling, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)
    let center_y = height / 2
    let gate_line_x = gate_pos_factor * width
    let channel_line_x = channel_pos_factor * width
    let gate_v_extent = height * gate_v_extent_factor
    let channel_v_extent = height * channel_v_extent_factor
    let drain_term_rel = (width, height)
    let source_term_rel = (width, 0)
    let gate_term_rel = (-gate_lead_factor * width, center_y)
    let bulk_term_rel = (width + bulk_lead_factor * width, center_y)
    let gate_line_top = (gate_line_x, center_y + gate_v_extent)
    let gate_line_bottom = (gate_line_x, center_y - gate_v_extent)
    let gate_conn_pt = (gate_line_x, center_y)
    let channel_top = (channel_line_x, center_y + channel_v_extent)
    let channel_bottom = (channel_line_x, center_y - channel_v_extent)
    let bulk_conn_pt = (channel_line_x, center_y)
    let drain_horiz = (width, center_y + channel_v_extent)
    let source_horiz = (width, center_y - channel_v_extent)

    cetz.draw.anchor("G", gate_term_rel)
    cetz.draw.anchor("D", drain_term_rel)
    cetz.draw.anchor("S", source_term_rel)
    cetz.draw.anchor("B", bulk_term_rel)
    cetz.draw.anchor("center", (width/2, height/2))
    cetz.draw.anchor("bulk_conn", bulk_conn_pt)
    cetz.draw.anchor("gate_conn", gate_conn_pt)
    cetz.draw.anchor("north", (width/2, height))
    cetz.draw.anchor("south", (width/2, 0))
    cetz.draw.anchor("east", (width, height/2))
    cetz.draw.anchor("west", (0, height/2))
    cetz.draw.anchor("north-east", (width, height))
    cetz.draw.anchor("south-west", (0, 0))
    cetz.draw.anchor("default", gate_term_rel) 

    cetz.draw.line(channel_bottom, channel_top, ..styling, thickness: 0.6pt * thick_factor)
    cetz.draw.line(gate_line_bottom, gate_line_top, ..styling, thickness: 0.6pt * thick_factor)
    cetz.draw.line(gate_term_rel, gate_conn_pt, ..styling)
    cetz.draw.line(drain_term_rel, drain_horiz, ..styling)
    cetz.draw.line(drain_horiz, channel_top, ..styling)
    cetz.draw.line(source_horiz, source_term_rel, ..styling)
    cetz.draw.line(
      channel_bottom, source_horiz, ..styling,
      mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale)
    )
    cetz.draw.line(bulk_conn_pt, bulk_term_rel, ..styling)

    if show_pin_labels {

      cetz.draw.content((rel: (-0.05, 0), to: "G"), text(size: pin_label_size, $G$), anchor: "east")
      cetz.draw.content((rel: (0, -0.05), to: "D"), text(size: pin_label_size, $D$), anchor: "north")
      cetz.draw.content((rel: (0, 0.05), to: "S"), text(size: pin_label_size, $S$), anchor: "south")
      cetz.draw.content((rel: (0.05, 0), to: "B"), text(size: pin_label_size, $B$), anchor: "west")
    }
    if label != none {
       cetz.draw.content(

         (rel: label_offset, to: label_pos),

         text(size: label_size, label),

         anchor: label_anchor
       )
    }
  })
}

#let pmos_transistor(
  position, name,
  label: none, label_pos: "S", label_anchor: "south-west",
  label_offset: (0.05, 0.05),
  label_size: 8pt,
  show_pin_labels: false, pin_label_size: 7pt,
  show_gate_bubble: true, bubble_radius_factor: 0.08,
  scale: 1.0, rotate: 0deg, width: 0.9, height: 1.2,
  gate_lead_factor: 0.3, bulk_lead_factor: 0.3, gate_pos_factor: 0.3,
  channel_pos_factor: 0.4, gate_v_extent_factor: 0.35, channel_v_extent_factor: 0.35,
  thick_factor: 2.0, arrow_scale: 0.8, arrow_fill: black,
  ..styling
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)
    let center_y = height / 2
    let gate_line_x = gate_pos_factor * width
    let channel_line_x = channel_pos_factor * width
    let gate_v_extent = height * gate_v_extent_factor
    let channel_v_extent = height * channel_v_extent_factor
    let source_term_rel = (width, height) 
    let drain_term_rel = (width, 0)      
    let gate_term_rel = (-gate_lead_factor * width, center_y)
    let bulk_term_rel = (width + bulk_lead_factor * width, center_y)
    let gate_line_top = (gate_line_x, center_y + gate_v_extent)
    let gate_line_bottom = (gate_line_x, center_y - gate_v_extent)
    let gate_conn_pt = (gate_line_x, center_y)
    let channel_top = (channel_line_x, center_y + channel_v_extent)
    let channel_bottom = (channel_line_x, center_y - channel_v_extent)
    let bulk_conn_pt = (channel_line_x, center_y)
    let source_horiz = (width, center_y + channel_v_extent) 
    let drain_horiz = (width, center_y - channel_v_extent)  
    let bubble_radius = height * bubble_radius_factor

    cetz.draw.anchor("G", gate_term_rel)
    cetz.draw.anchor("S", source_term_rel) 
    cetz.draw.anchor("D", drain_term_rel)  
    cetz.draw.anchor("B", bulk_term_rel)
    cetz.draw.anchor("center", (width/2, height/2))
    cetz.draw.anchor("bulk_conn", bulk_conn_pt)
    cetz.draw.anchor("gate_conn", gate_conn_pt)
    cetz.draw.anchor("north", (width/2, height)) 
    cetz.draw.anchor("south", (width/2, 0))    
    cetz.draw.anchor("east", (width, height/2))
    cetz.draw.anchor("west", (0, height/2))
    cetz.draw.anchor("north-east", (width, height)) 
    cetz.draw.anchor("south-east", (width, 0))    
    cetz.draw.anchor("south-west", (0, 0))
    cetz.draw.anchor("default", gate_term_rel) 

    cetz.draw.line(channel_bottom, channel_top, ..styling, thickness: 0.6pt * thick_factor)
    cetz.draw.line(gate_line_bottom, gate_line_top, ..styling, thickness: 0.6pt * thick_factor)
    cetz.draw.line(gate_term_rel, gate_conn_pt, ..styling)
    cetz.draw.line(drain_term_rel, drain_horiz, ..styling)
    cetz.draw.line(drain_horiz, channel_bottom, ..styling)
    cetz.draw.line(source_term_rel, source_horiz, ..styling)
    cetz.draw.line(
        source_horiz, channel_top, ..styling,
        mark: (end: "stealth", fill: arrow_fill, scale: arrow_scale)
    )
    cetz.draw.line(bulk_conn_pt, bulk_term_rel, ..styling)

    if show_gate_bubble {

        let bubble_center = (rel: (-bubble_radius, 0), to: gate_conn_pt)
        cetz.draw.circle(bubble_center, radius: bubble_radius, ..styling, fill: white)
    }

    if show_pin_labels {

      cetz.draw.content((rel: (-0.05, 0), to: "G"), text(size: pin_label_size, $G$), anchor: "east")
      cetz.draw.content((rel: (0.05, 0), to: "S"), text(size: pin_label_size, $S$), anchor: "west") 
      cetz.draw.content((rel: (0.05, 0), to: "D"), text(size: pin_label_size, $D$), anchor: "west") 
      cetz.draw.content((rel: (0.05, 0), to: "B"), text(size: pin_label_size, $B$), anchor: "west")
    }
    if label != none {
       cetz.draw.content(

         (rel: label_offset, to: label_pos),

         text(size: label_size, label),

         anchor: label_anchor
       )
    }
  })
}

#let gnd_symbol(
  position, name,
  label: none, label_pos: "north", label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,
  scale: 1.0, rotate: 0deg, lead_length: 0.3, bar_width: 0.5,
  bar_spacing: 0.05, bar_width_factors: (1.0, 0.7, 0.4),
  ..styling
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)
    let lead_end_y = -lead_length
    let bar1_y = lead_end_y
    let bar2_y = lead_end_y - bar_spacing
    let bar3_y = lead_end_y - 2 * bar_spacing
    assert(bar_width_factors.len() == 3, message: "bar_width_factors must have 3 elements.")
    let bar1_half_w = bar_width * bar_width_factors.at(0) / 2
    let bar2_half_w = bar_width * bar_width_factors.at(1) / 2
    let bar3_half_w = bar_width * bar_width_factors.at(2) / 2
    let south_y = bar3_y

    cetz.draw.anchor("T", (0, 0)) 
    cetz.draw.anchor("north", (0, 0)) 
    cetz.draw.anchor("south", (0, south_y)) 
    cetz.draw.anchor("west", (-bar1_half_w, bar1_y)) 
    cetz.draw.anchor("east", (bar1_half_w, bar1_y)) 
    cetz.draw.anchor("center", (0, (bar1_y + bar3_y) / 2)) 
    cetz.draw.anchor("default", (0, 0)) 

    cetz.draw.line((0, 0), (0, lead_end_y), ..styling)
    cetz.draw.line((-bar1_half_w, bar1_y), (bar1_half_w, bar1_y), ..styling)
    cetz.draw.line((-bar2_half_w, bar2_y), (bar2_half_w, bar2_y), ..styling)
    cetz.draw.line((-bar3_half_w, bar3_y), (bar3_half_w, bar3_y), ..styling)

    if label != none {
      cetz.draw.content(

        (rel: label_offset, to: label_pos),

        text(size: label_size, label),

        anchor: label_anchor
      )
    }
  })
}

#let resistor(
  position, name,
  label: none, label_pos: "south", label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0, rotate: 0deg,
  width: 0.8,
  height: 0.3,
  zigs: 3,
  lead_extension: 0.3,
  ..styling
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale) 
    cetz.draw.rotate(rotate) 

    let half_width = width / 2
    let half_height = height / 2
    let lead_start_x = -half_width - lead_extension
    let lead_end_x = half_width + lead_extension
    let zig_start_x = -half_width
    let zig_end_x = half_width 

    let num_segments = zigs * 2

    let segment_h_dist = width / num_segments

    let sgn = 1 

    cetz.draw.line(
      (lead_start_x, 0),
      (zig_start_x, 0),

      (rel: (segment_h_dist / 2, half_height * sgn)),

      ..for _ in range(num_segments - 1) {
        sgn *= -1

        ((rel: (segment_h_dist, half_height * 2 * sgn)),)
      },

      (rel: (segment_h_dist / 2, half_height)),

      (lead_end_x, 0),
      ..styling
    )

    cetz.draw.anchor("L", (lead_start_x, 0)) 
    cetz.draw.anchor("R", (lead_end_x, 0)) 
    cetz.draw.anchor("center", (0, 0))
    cetz.draw.anchor("north", (0, half_height))
    cetz.draw.anchor("south", (0, -half_height))
    cetz.draw.anchor("east", (lead_end_x, 0)) 
    cetz.draw.anchor("west", (lead_start_x, 0)) 

    cetz.draw.anchor("T", (0, half_height)) 
    cetz.draw.anchor("B", (0, -half_height)) 
    cetz.draw.anchor("default", (lead_start_x, 0)) 

    if label != none {
      cetz.draw.content(

        (rel: label_offset, to: label_pos),

        text(size: label_size, label),

        anchor: label_anchor

      )
    }
  })
}

#let capacitor(
  position, name,
  label: none, label_pos: "south", label_anchor: "north",
  label_offset: (0, -0.1),
  label_size: 8pt,
  scale: 1.0, rotate: 0deg,
  plate_height: 0.6,
  plate_gap: 0.2,
  lead_extension: 0.5,
  ..styling
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale) 
    cetz.draw.rotate(rotate) 

    let half_gap = plate_gap / 2
    let half_height = plate_height / 2
    let lead_start_x = -half_gap - lead_extension
    let lead_end_x = half_gap + lead_extension
    let plate_left_x = -half_gap
    let plate_right_x = half_gap

    cetz.draw.line( (lead_start_x, 0), (plate_left_x, 0), ..styling )
    cetz.draw.line( (plate_left_x, -half_height), (plate_left_x, half_height), ..styling )

    cetz.draw.line( (plate_right_x, half_height), (plate_right_x, -half_height), ..styling )
    cetz.draw.line( (plate_right_x, 0), (lead_end_x, 0), ..styling )

    cetz.draw.anchor("L", (lead_start_x, 0)) 
    cetz.draw.anchor("R", (lead_end_x, 0)) 
    cetz.draw.anchor("center", (0, 0))
    cetz.draw.anchor("north", (0, half_height)) 
    cetz.draw.anchor("south", (0, -half_height)) 
    cetz.draw.anchor("east", (lead_end_x, 0)) 
    cetz.draw.anchor("west", (lead_start_x, 0)) 

    cetz.draw.anchor("T", (0, half_height)) 
    cetz.draw.anchor("B", (0, -half_height)) 
    cetz.draw.anchor("default", (lead_start_x, 0)) 

    if label != none {
      cetz.draw.content(

        (rel: label_offset, to: label_pos),

        text(size: label_size, label),

        anchor: label_anchor

      )
    }
  })
}
#let voltage_source(
  position, name,

  label: none,
  annotation_label_pos: "left",
  annotation_label_anchor: auto,
  annotation_label_offset: auto,
  annotation_label_size: 8pt,

  show_voltage_annotation: true,
  voltage_arrow_pos: "left",
  voltage_arrow_dir: "down",
  voltage_arrow_length_factor: 2,
  voltage_arrow_offset_factor: 0.7,
  arrow_scale: 1.0,
  arrow_fill: black,
  stroke_thickness: 0.6pt,

  scale: 1.0, rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  ..styling
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate) 

    let circle_top_y = radius
    let circle_bottom_y = -radius
    let top_lead_end_y = circle_top_y + lead_length
    let bottom_lead_end_y = circle_bottom_y - lead_length

    cetz.draw.circle((0, 0), radius: radius, ..styling)

    cetz.draw.line((0, circle_top_y), (0, top_lead_end_y), ..styling)

    cetz.draw.line((0, circle_bottom_y), (0, bottom_lead_end_y), ..styling)

    if show_voltage_annotation {

      let arrow_x = if voltage_arrow_pos == "left" {
         -radius * (1 + voltage_arrow_offset_factor)
      } else { 
         radius * (1 + voltage_arrow_offset_factor)
      }
      let arrow_len = radius * voltage_arrow_length_factor
      let arrow_half_len = arrow_len / 2

      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" {
        (arrow_half_len, -arrow_half_len)
      } else { 
        (-arrow_half_len, arrow_half_len)
      }
      let arrow_start = (arrow_x, arrow_start_y)
      let arrow_end = (arrow_x, arrow_end_y)
      let arrow_mid_pt = (arrow_x, 0) 

      cetz.draw.line(
        arrow_start, arrow_end,
        ..styling,
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4,
          fill: arrow_fill,
          stroke: (paint: black, thickness: stroke_thickness)
        )
      )

      if label != none { 

        let (default_anchor, default_offset) = if annotation_label_pos == "left" {
           ("east", (-0.05, 0)) 
        } else { 
           ("west", (0.05, 0))  
        }

        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }

        cetz.draw.content(
          (rel:(0.1*scale*arrow_x/calc.abs(arrow_x),0),to:arrow_mid_pt), 
          text(size: annotation_label_size, fill: arrow_fill, label),
          anchor: final_anchor,
          offset: final_offset
        )
      }
    }

    cetz.draw.anchor("T", (0, top_lead_end_y))    
    cetz.draw.anchor("B", (0, bottom_lead_end_y)) 
    cetz.draw.anchor("center", (0, 0))          
    cetz.draw.anchor("north", (0, top_lead_end_y)) 
    cetz.draw.anchor("south", (0, bottom_lead_end_y)) 
    cetz.draw.anchor("east", (radius, 0))         
    cetz.draw.anchor("west", (-radius, 0))        
  })
}

#let node(
  position,
  name,
  label:none,

  radius: 0.05,
  label_size: 8pt,

  label_offset: (0, 0),
  label_anchor: "center",
  fill: white,
  text_fill: black,
  scale: 1.0,
  rotate: 0deg,
  ..styling
) = {
  cetz.draw.group(name: name, ..styling, {

    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)

    cetz.draw.circle((0, 0), radius: radius, ..styling, fill: fill)

    if label != none {
        cetz.draw.content(
          (rel: label_offset, to: (0, 0)),
          text(size: label_size, fill: text_fill, label),
          anchor: label_anchor
        )
      }

    cetz.draw.anchor("center", (0, 0))
    cetz.draw.anchor("default", (0, 0))
    cetz.draw.anchor("north", (0, radius))
    cetz.draw.anchor("south", (0, -radius))
    cetz.draw.anchor("east", (radius, 0))
    cetz.draw.anchor("west", (-radius, 0))
    let diag_offset = radius * calc.cos(45deg)
    cetz.draw.anchor("north-east", (diag_offset, diag_offset))
    cetz.draw.anchor("north-west", (-diag_offset, diag_offset))
    cetz.draw.anchor("south-east", (diag_offset, -diag_offset))
    cetz.draw.anchor("south-west", (-diag_offset, -diag_offset))
  })
}

#let vdd_symbol(
  position,
  name,
  label: none,

  label_pos: "north",
  label_anchor: "south",
  label_offset: (0, 0.1),
  label_size: 8pt,

  scale: 1.0,
  rotate: 0deg,
  stem_length: 0.3,
  bar_width: 0.5,

  text_fill: black,
  ..styling
) = {
  cetz.draw.group(name: name, {

    cetz.draw.set-origin(position) 
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)

    let stem_top_y = stem_length
    let bar_half_width = bar_width / 2
    let bar_left_x = -bar_half_width
    let bar_right_x = bar_half_width

    cetz.draw.line((0, 0), (0, stem_top_y), ..styling)

    cetz.draw.line((bar_left_x, stem_top_y), (bar_right_x, stem_top_y), ..styling)

    cetz.draw.anchor("B", (0, 0))           
    cetz.draw.anchor("south", (0, 0))       
    cetz.draw.anchor("default", (0, 0))     
    cetz.draw.anchor("T", (0, stem_top_y))  
    cetz.draw.anchor("north", (0, stem_top_y)) 
    cetz.draw.anchor("TL", (bar_left_x, stem_top_y)) 
    cetz.draw.anchor("TR", (bar_right_x, stem_top_y)) 

    cetz.draw.anchor("west", (bar_left_x, stem_top_y)) 
    cetz.draw.anchor("east", (bar_right_x, stem_top_y)) 
    cetz.draw.anchor("center", (0, stem_top_y / 2)) 

    if label != none {
      cetz.draw.content(

        (rel: label_offset, to: label_pos),

        text(size: label_size, fill: text_fill, label),

        anchor: label_anchor
      )
    }
  })
}

#let current_source(
  position, name,
  label: none,
  label_pos: "west", 
  label_anchor: "west", 
  label_offset: (0.1, 0), 
  label_size: 8pt,
  scale: 1.0, rotate: 0deg,
  radius: 0.3,
  lead_length: 0.3,
  arrow_dir: "up", 
  arrow_scale: 1.0,
  arrow_fill: black,

  ..styling
) = {

  cetz.draw.group(name: name, ..styling, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)

    let circle_top_y = radius
    let circle_bottom_y = -radius
    let top_lead_end_y = circle_top_y + lead_length
    let bottom_lead_end_y = circle_bottom_y - lead_length

    cetz.draw.circle((0, 0), radius: radius, ..styling)

    cetz.draw.line((0, circle_top_y), (0, top_lead_end_y), ..styling)
    cetz.draw.line((0, circle_bottom_y), (0, bottom_lead_end_y), ..styling)

    let arrow_v_extent = radius * 0.7 
    assert(arrow_dir in ("up", "down"), message: "Arrow direction must be 'up' or 'down'.")
    let (arrow_start_y, arrow_end_y) = if arrow_dir == "up" {
      (-arrow_v_extent, arrow_v_extent)
    } else { 
      (arrow_v_extent, -arrow_v_extent)
    }
    let arrow_start = (0, arrow_start_y)
    let arrow_end = (0, arrow_end_y)

    cetz.draw.line(
      arrow_start, arrow_end,
      ..styling, 
      mark: (
        end: "stealth",
        scale: arrow_scale * 0.4, 
        fill: arrow_fill,

      )
    )

    cetz.draw.anchor("T", (0, top_lead_end_y))     
    cetz.draw.anchor("B", (0, bottom_lead_end_y))  
    cetz.draw.anchor("center", (0, 0))
    cetz.draw.anchor("north", (0, top_lead_end_y))
    cetz.draw.anchor("south", (0, bottom_lead_end_y))
    cetz.draw.anchor("east", (radius, 0))
    cetz.draw.anchor("west", (-radius, 0))
    cetz.draw.anchor("default", (0, top_lead_end_y)) 

    if label != none {
      cetz.draw.content(
        (rel: label_offset, to: label_pos),
        text(size: label_size, label),
        anchor: label_anchor
      )
    }
  })
}



#let voltage_points(
  position, name,

  // Annotation Label (e.g., V_GS)
  label: none, // The main label for the voltage difference (e.g., $V_{GS}$)
  annotation_label_pos: "left", // "left" or "right" relative to arrow
  annotation_label_anchor: auto, // Auto anchors based on position
  annotation_label_offset: auto, // Auto offsets based on position
  annotation_label_size: 8pt,
  annotation_text_fill: black, // Color for the annotation label

  // Annotation Arrow
  show_voltage_annotation: true, // Show the arrow and its label
  voltage_arrow_pos: "left", // "left" or "right" of the points
  voltage_arrow_dir: "down", // "up" or "down"
  arrow_length_factor: 1.0, // Multiplies point_separation for arrow length
  arrow_offset: 0.3,        // Absolute distance from points' vertical axis
  arrow_scale: 1.0,         // For arrowhead size
  arrow_fill: black,        // Fill color for the arrow
  arrow_stroke: black,      // Stroke color for the arrow
  arrow_stroke_thickness: 0.6pt, // Thickness for the arrow line/head

  // Point Appearance
  point_separation: 0.6,    // Vertical distance between the two points
  point_radius: 0.05,       // Radius of the small circles at the points
  point_fill: black,        // Fill color for the points
  point_stroke: none,       // Stroke for the points (default none)

  // Point Labels (e.g., +, -, G, S)
  show_point_labels: false, // Show labels next to the individual points
  top_label: $[+]$,         // Label for the top point
  bottom_label: $[-]$,      // Label for the bottom point
  point_label_size: 7pt,    // Font size for point labels
  point_text_fill: black,   // Color for point labels
  top_label_offset: (0, 0.05), // Offset for top label relative to top point
  top_label_anchor: "south", // Anchor point for top label
  bottom_label_offset: (0, -0.05), // Offset for bottom label relative to bottom point
  bottom_label_anchor: "north", // Anchor point for bottom label

  // General
  scale: 1.0, rotate: 0deg,
  ..styling // Applied to points (stroke) and arrow (stroke)
) = {
  cetz.draw.group(name: name, {
    cetz.draw.set-origin(position)
    cetz.draw.scale(scale)
    cetz.draw.rotate(rotate)

    let half_sep = point_separation / 2
    let top_pos = (0, half_sep)
    let bottom_pos = (0, -half_sep)

    // Draw the points
    cetz.draw.circle(top_pos, radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)
    cetz.draw.circle(bottom_pos, radius: point_radius, fill: point_fill, stroke: point_stroke, ..styling)

    // Anchors
    cetz.draw.anchor("T", top_pos)      // Top point
    cetz.draw.anchor("B", bottom_pos)     // Bottom point
    cetz.draw.anchor("center", (0, 0))    // Midpoint between T and B
    cetz.draw.anchor("north", top_pos)    // Alias for T
    cetz.draw.anchor("south", bottom_pos)   // Alias for B
    // Define east/west based on the arrow offset direction if annotation shown? Or just center?
    cetz.draw.anchor("east", (point_radius, 0))  // Nominal east
    cetz.draw.anchor("west", (-point_radius, 0)) // Nominal west
    cetz.draw.anchor("default", top_pos) // Default connection point

    // Voltage Annotation Arrow + Label
    if show_voltage_annotation {
      let arrow_x = if voltage_arrow_pos == "left" {
         -arrow_offset
      } else { // "right"
         arrow_offset
      }
      let arrow_len = point_separation * arrow_length_factor
      let arrow_half_len = arrow_len / 2

      let (arrow_start_y, arrow_end_y) = if voltage_arrow_dir == "down" {
        (arrow_half_len, -arrow_half_len)
      } else { // "up"
        (-arrow_half_len, arrow_half_len)
      }
      let arrow_start = (arrow_x, arrow_start_y)
      let arrow_end = (arrow_x, arrow_end_y)
      let arrow_mid_pt = (arrow_x, 0)

      cetz.draw.line(
        arrow_start, arrow_end,
        stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness),
        ..styling, // Pass general styling (might override stroke)
        mark: (
          end: "stealth",
          scale: arrow_scale * 0.4, // Scale factor for arrowhead
          fill: arrow_fill,
          stroke: (paint: arrow_stroke, thickness: arrow_stroke_thickness) // Ensure arrowhead stroke matches line
        )
      )

      // Draw the annotation label (e.g., V_GS)
      if label != none {
        let (default_anchor, default_offset) = if annotation_label_pos == "left" {
           ("east", (-0.05, 0))
        } else { // "right"
           ("west", (0.05, 0))
        }

        let final_anchor = if annotation_label_anchor == auto { default_anchor } else { annotation_label_anchor }
        let final_offset = if annotation_label_offset == auto { default_offset } else { annotation_label_offset }

        // Adjust offset relative to arrow direction slightly
        let arrow_dir_sgn = if voltage_arrow_dir == "down" { 1 } else { -1 }
        let adjusted_offset = (final_offset.at(0), final_offset.at(1) )//- 0.02 * arrow_dir_sgn)


        cetz.draw.content(
          arrow_mid_pt, // Place relative to arrow midpoint
          text(size: annotation_label_size, fill: annotation_text_fill, label),
          anchor: final_anchor,
          offset: adjusted_offset
        )
      }
    }

    // Point Labels (+, -, G, S, etc.)
    if show_point_labels {
       cetz.draw.content(
         (rel: top_label_offset, to: "T"),
         text(size: point_label_size, fill: point_text_fill, top_label),
         anchor: top_label_anchor
       )
       cetz.draw.content(
         (rel: bottom_label_offset, to: "B"),
         text(size: point_label_size, fill: point_text_fill, bottom_label),
         anchor: bottom_label_anchor
       )
    }
  })
}


#let wire_hop(
  wire1_start, wire1_end,
  wire2_start, wire2_end,
  hopping_wire: 1,
  hop_radius: 0.15,
  hop_direction: 1, 
  ..styling
) = {

  assert(hopping_wire in (1, 2), message: "hopping_wire must be 1 or 2.")
  assert(hop_direction in (1, -1), message: "hop_direction must be 1 or -1.")

  let intersection = cetz.intersection.line-line(
     wire1_start, wire1_end,
     wire2_start, wire2_end
  )
  assert(intersection != none, message: "Wires do not intersect, cannot hop.")

  let (straight_start, straight_end, hopping_start, hopping_end) = if hopping_wire == 1 {
    (wire2_start, wire2_end, wire1_start, wire1_end)
  } else {
    (wire1_start, wire1_end, wire2_start, wire2_end)
  }

  cetz.draw.line(straight_start, straight_end, ..styling)

  let hop_vec = cetz.vector.sub(hopping_end, hopping_start)
  let wire_angle = calc.atan2(..hop_vec) 

  let hop_unit_vec = cetz.vector.norm(hop_vec)
  assert(hop_unit_vec != none, message: "Cannot get unit vector for zero-length hopping wire.")

  let offset_vec_neg = cetz.vector.scale(hop_unit_vec, -hop_radius)
  let offset_vec_pos = cetz.vector.scale(hop_unit_vec,  hop_radius)

  let arc_start_point = (rel: offset_vec_neg, to: intersection)
  let arc_end_point   = (rel: offset_vec_pos, to: intersection)

  let arc_start_angle = wire_angle
  let arc_stop_angle = wire_angle + (180deg *hop_direction)

  cetz.draw.line(hopping_start, arc_start_point, ..styling)

  cetz.draw.arc(
    (rel:cetz.vector.scale((calc.cos(arc_start_angle),calc.sin(arc_start_angle)),hop_radius*2),to:arc_start_point),
    start: arc_start_angle,
    stop: arc_stop_angle,
    radius: hop_radius,
    ..styling
  )

  cetz.draw.line(arc_end_point, hopping_end, ..styling)

}
#cetz.canvas({
   let default_stroke = (stroke: (thickness:.6pt))

  let x_vin = 0
  let x_r1 = 1.
  let x_m1 = 1.9
  let x_out_comps = x_m1 + 0.9 
  let x_cl = x_out_comps + 2.0 
  let x_vout = x_cl + 1.0 

  let y_gate = 1.6 
  let y_m1_base = 1.0 
  let y_m1_s = y_m1_base 
  let y_m1_d = y_m1_base + 1.2 
  let y_m1_b = y_m1_base + 0.6 
  let y_vdd = y_m1_d + 1.5 
  let y_gnd = -1 
  let y_cl_center = y_m1_s - 0.95 

  voltage_source((x_vin, y_gate -1), "Vin",
    label: $V_"in"$,
    voltage_arrow_pos: "left",
    voltage_arrow_dir: "down",
    annotation_label_pos: "left",
    radius: 0.4,
    lead_length: 0.2,
    ..default_stroke
  )

  resistor((x_r1, y_gate ), "R1", label: $R_1$, label_pos: "north", label_offset: (0, 0.4), width: 1.0, ..default_stroke)

  nmos_transistor((x_m1, y_m1_base), "M1", label: $M_1$, label_pos: "east", label_anchor: "west", label_offset: (0.3, 0.3),..default_stroke)

  vdd_symbol((x_out_comps, y_vdd), "Vdd", label: $V_"DD"$, ..default_stroke)

  resistor((x_out_comps, y_m1_s -1), "R2", rotate: 90deg, label: $R_2$, label_pos: "west", label_offset: (0.5, 0.5), ..default_stroke)

  capacitor((x_cl, y_cl_center), "CL", rotate: 90deg, label: $C_L$, label_pos: "east", label_offset: (-0.2, -0.5), ..default_stroke)

  gnd_symbol((x_vin, y_gnd), "GND_Vin", ..default_stroke)
  gnd_symbol((x_m1 + 1.27, y_m1_b), "GND_M1B", ..default_stroke)
  gnd_symbol((x_out_comps, y_gnd), "GND_R2", ..default_stroke)
  gnd_symbol((x_cl, y_gnd), "GND_CL", ..default_stroke)

  node((x_vout, y_m1_b), "VoutNode", label:$V_"out"$,  label_offset: (0.15, 0), label_anchor: "west", ..default_stroke)

  connect-orthogonal("Vin.T", "R1.L", style: "hv", ..default_stroke) 
  connect-orthogonal("Vin.B", "GND_Vin.T", style: "hv", ..default_stroke)
  connect-orthogonal("R1.R", "M1.G", style: "hv", ..default_stroke) 
  connect-orthogonal("M1.D", "Vdd.B", style: "hv", ..default_stroke) 
  connect-orthogonal("M1.B", "GND_M1B.T", style: "hv", ..default_stroke)
  connect-orthogonal("M1.S", "R2.R", style: "hv", ..default_stroke)
   connect-orthogonal("GND_R2.T", "R2.L", style: "hv", ..default_stroke) 
   connect-orthogonal("GND_CL.T", "CL.L", style: "hv", ..default_stroke) 
  connect-orthogonal("CL.R", "M1.S", style: "hv", ..default_stroke) 
  connect-orthogonal("VoutNode", "M1.S", style: "hv", ..default_stroke) 
})

maybe you like timed event graphs

Details
#import "@preview/cetz:0.3.4"

#set page(width: auto, height: auto, margin: 8pt)

#let transition(coords, name: none, content: none, color: black) = {

  cetz.draw.group(name: name, {
    let top_pos = (rel: (0, 0.5), to: coords)
    let bottom_pos = (rel: (0, -0.5), to: coords)

    cetz.draw.line(top_pos, bottom_pos, stroke: (thickness: 4pt, paint: color))

    cetz.draw.anchor("default", coords)
    cetz.draw.anchor("center", coords)
    cetz.draw.anchor("left", coords)
    cetz.draw.anchor("right", coords)
    cetz.draw.anchor("top", top_pos)
    cetz.draw.anchor("bottom", bottom_pos)

    if content != none {
       cetz.draw.content((rel:(-0,-0.8),to:coords))[ 
         #set text( size: 15pt)
         #content
       ]
    }
  })
}

#let place(coords, name: none, content: none, token: false) = {

  cetz.draw.group(name: name, {
    let radius = 0.4 

    cetz.draw.arc(coords, start: 0deg, stop: 360deg, radius: radius)

    cetz.draw.anchor("default", coords)
    cetz.draw.anchor("center", coords)
    cetz.draw.anchor("north", (rel: (0, radius), to: coords))
    cetz.draw.anchor("south", (rel: (0, -radius), to: coords))
    cetz.draw.anchor("east", (rel: (radius, 0), to: coords))
    cetz.draw.anchor("west", (rel: (-radius, 0), to: coords))
    cetz.draw.anchor("left", (rel: (-2*radius, 0), to: coords)) 
    cetz.draw.anchor("right", (rel: (0, 0), to: coords)) 
    let diag = radius * calc.cos(45deg)
    cetz.draw.anchor("north-east", (rel: (diag, diag), to: coords))
    cetz.draw.anchor("north-west", (rel: (-diag, diag), to: coords))
    cetz.draw.anchor("south-east", (rel: (diag, -diag), to: coords))
    cetz.draw.anchor("south-west", (rel: (-diag, -diag), to: coords))

    if content != none {
       cetz.draw.arc((rel:(-.1,0),to:coords), start: 0deg, stop: 360deg, radius: 0.4 -0.1) 
    }

    if token {
       cetz.draw.arc((rel:(-.35,0),to:coords), start: 0deg, stop: 360deg, radius: 0.4 -0.35,fill:black) 
    }

    if content != none { 
       cetz.draw.content((rel:(-0.35,-0.5),to:coords))[ 
         #set text( size: 15pt)
         #content
       ]
    }
  })
}
#let calculate_middle_and_scaled_orthogonal(a, b, e) = {
  let middle_point = (0,0)
  middle_point.at(0) = (a.at(0) + b.at(0)) / 2
  middle_point.at(1) = (a.at(1) + b.at(1)) / 2

  let orthogonal_vector = (0,0)
  orthogonal_vector.at(0) = a.at(1) - b.at(1)
  orthogonal_vector.at(1) = b.at(0) - a.at(0)

  let scaled_orthogonal_vector = cetz.vector.scale(orthogonal_vector, e)

  return cetz.vector.add(middle_point, scaled_orthogonal_vector)
}
#let line(a,b,mark:none,bend:0)={
  cetz.draw.bezier(a,b,((a,b)=>calculate_middle_and_scaled_orthogonal(a,b,bend),a,b),mark:mark)
}
#let line2(a,b,mark:none,bend:0)={
  let s=((a,b)=>calculate_middle_and_scaled_orthogonal(a,b,bend),a,b)
  cetz.draw.line(a,s)
  cetz.draw.line(s,b,mark:mark)
}

 #cetz.canvas({
  cetz.draw.group(ctx => {

    cetz.draw.scale(1);
    cetz.draw.translate((-0.2, 0.3, 0));
    cetz.draw.set-origin((3, 0.3));

transition((0, 0), name: "t0", content: $T_frak(I)$);
transition((4, 0), name: "t2in", content: $T_(2_frak(I))$);
transition((8, 0), name: "t2out", content: $T_(2_frak(O))$);
transition((12, 0), name: "t1in", content: $T_(1_frak(I))$);
transition((16, 0), name: "t1out", content: $T_(1_frak(O))$);
transition((20, 0), name: "t6", content: $T_frak(O)$);

place((rel: (2.3, 0), to: "t0.right"), name: "p02", content: $τ_(02)$);
place((rel: (2.3, -1), to: "t2in.right"), name: "pr2", content: $ρ_(2)$);
place((rel: (2.3, 1), to: "t2in.right"), name: "p22", content: $τ_(22)$);
place((rel: (2.3, 0), to: "t2out.right"), name: "p21", content: $τ_(21)$);
place((rel: (2.3, -1), to: "t1in.right"), name: "pr1", content: $ρ_(1)$);
place((rel: (2.3, 1), to: "t1in.right"), name: "p11", content: $τ_(11)$);
place((rel: (2.3, 0), to: "t1out.right"), name: "p16", content: $τ_(16)$);
place((rel: (3, -4), to: "t2out.right"), name: "p61", content: $τ_(60)$,token:true);

cetz.draw.line("t0.right", "p02.left", mark: (end: ">"));
cetz.draw.line("p02.right", "t2in.left", mark: (end: ">"));
cetz.draw.line("t2in.right", "pr2.left", mark: (end: ">"));
cetz.draw.line("t2in.right", "p22.left", mark: (end: ">"), bend: 0.3);
cetz.draw.line("pr2.right", "t2out.left", mark: (end: ">"));
cetz.draw.line("p22.right", "t2out.left", mark: (end: ">", bend: 30));
cetz.draw.line("t2out.right", "p21.left", mark: (end: ">"));
cetz.draw.line("p21.right", "t1in.left", mark: (end: ">"));
cetz.draw.line("t1in.right", "pr1.left", mark: (end: ">"));
cetz.draw.line("t1in.right", "p11.left", mark: (end: ">"), bend: 30);
cetz.draw.line("pr1.right", "t1out.left", mark: (end: ">"));
cetz.draw.line("p11.right", "t1out.left", mark: (end: ">"), bend: 30);
cetz.draw.line("t1out.right", "p16.left", mark: (end: ">"));
cetz.draw.line("p16.right", "t6.left", mark: (end: ">"));
line2("t6.right", "p61.right", mark: (end: ">"), bend: 0.2);
line2("p61.left", "t0.left", mark: (end: ">"), bend: 0.18);

  });

});
@janosh
Copy link
Owner

janosh commented Apr 21, 2025

No need to push any assets, just the typst or latex source code and a YAML file with metadata like title, tags, description, author. both circuits and event graphs are very welcome! 👍

@Kreijstal
Copy link
Contributor Author

I guess I am happy with that for now, how often does the website update?

@janosh
Copy link
Owner

janosh commented Apr 21, 2025

usually after every commit/merge but it's currently failing because of an incompatibility issue in one of the imported packages: janosh/svelte-multiselect#295. was planning to work on that last weekend but didn't manage, hopefully next one

@Kreijstal
Copy link
Contributor Author

I saw your changes to my functions thank you, for grouping them like that, I have a patch for the hop function

Details

#let wire_hop(
  wire1_start, wire1_end,
  wire2_start, wire2_end,
  hopping_wire: 1,
  hop_radius: 0.15,
  hop_direction: 1, 
  ..styling
) = {

  assert(hopping_wire in (1, 2), message: "hopping_wire must be 1 or 2.")
  assert(hop_direction in (1, -1), message: "hop_direction must be 1 or -1.")
  
cetz.draw.get-ctx(ctx=>{
let a1=cetz.coordinate.resolve(ctx,wire1_start).at(1)
let a2=cetz.coordinate.resolve(ctx,wire1_end).at(1)
let b1=cetz.coordinate.resolve(ctx,wire2_start).at(1)
let b2=cetz.coordinate.resolve(ctx,wire2_end).at(1)
  let intersection = cetz.intersection.line-line(
    (a1.at(0),a1.at(1)), (a2.at(0),a2.at(1)), 
     (b1.at(0),b1.at(1)), (b2.at(0),b2.at(1)), 
  )
//cetz.draw.content((0,0))[#intersection]
     //cetz.coordinate.resolve(ctx,wire2_end)
  //  /*
  assert(intersection != none, message: "Wires do not intersect, cannot hop.")

  let (straight_start, straight_end, hopping_start, hopping_end) = if hopping_wire == 1 {
    (b1, b2, a1, a2)
  } else {
    (a1, a2, b1, b2)
  }

  cetz.draw.line(straight_start, straight_end, ..styling)

  let hop_vec = cetz.vector.sub(hopping_end, hopping_start)
 // cetz.draw.content((0,0))[#hop_vec]
 // /*
  let wire_angle = calc.atan2(hop_vec.at(0),hop_vec.at(1)) 

  let hop_unit_vec = cetz.vector.norm(hop_vec)
  assert(hop_unit_vec != none, message: "Cannot get unit vector for zero-length hopping wire.")

  let offset_vec_neg = cetz.vector.scale(hop_unit_vec, -hop_radius)
  let offset_vec_pos = cetz.vector.scale(hop_unit_vec,  hop_radius)

  let arc_start_point = (rel: offset_vec_neg, to: intersection)
  let arc_end_point   = (rel: offset_vec_pos, to: intersection)

  let arc_start_angle = wire_angle
  let arc_stop_angle = wire_angle + (180deg *hop_direction)

  cetz.draw.line(hopping_start, arc_start_point, ..styling)

  cetz.draw.arc(
    (rel:cetz.vector.scale((calc.cos(arc_start_angle),calc.sin(arc_start_angle)),hop_radius*2),to:arc_start_point),
    start: arc_start_angle,
    stop: arc_stop_angle,
    radius: hop_radius,
    ..styling
  )

  cetz.draw.line(arc_end_point, hopping_end, ..styling)
   //  */
  
})


}

this makes hop accept anchors instead of points, example:
assuming you used something like components.typ

#import "./components.typ" as comp 

#cetz.canvas({

  let default_stroke = (stroke: (thickness: 0.6pt))
  let node_style = (fill: black, radius: 0.05pt)

  let x_in_labels = -1
  let x_transistors = 0
  let x_out_label = 2

  let y_vdd = 4.4
  let y_m3_base = 3.0  
  let y_m2_base = 1.0  
  let y_m1_base = -0.5 
  let y_gnd = -1.5

  let pt_vin_label = (x_in_labels, 0)    
  let pt_vbias_label = (x_in_labels, 1.6)  
  let pt_vout_label = (x_out_label+1, 2.2) 
  let pt_vdd_connect = (x_transistors, y_vdd)
  let pt_gnd_connect = (x_transistors, y_gnd)

  comp.vdd_symbol(pt_vdd_connect, "VddSym", label: $V_"DD"$, label_offset:(0, 0.3), ..default_stroke)
  comp.gnd_symbol(pt_gnd_connect, "GndSym", ..default_stroke)

  comp.nmos_transistor((x_transistors, y_m1_base), "M1", label: $M_1$, label_pos: "west", label_offset: (0.5, 0.3), ..default_stroke,bulk_lead_factor:-0.6)

  comp.nmos_transistor((x_transistors, y_m2_base), "M2", label: $M_2$, label_pos: "west", label_offset: (0.5, 0.3), ..default_stroke,bulk_lead_factor:-0.6)

  comp.pmos_transistor((x_transistors+1.8, y_m3_base), "M3", label: $M_3$, label_pos: "west", label_offset: (-1.0, -0.0), ..default_stroke,width:-0.9,bulk_lead_factor:-0.6)

  comp.node(pt_vin_label, "VinNode", label: $V_"in"$, label_anchor: "east", label_offset: (-0.2, 0))
  comp.node(pt_vbias_label, "VbiasNode", label: $V_"bias"$, label_anchor: "east", label_offset: (-0.2, 0))
  comp.node(pt_vout_label, "VoutNode", label: $V_"out"$, label_anchor: "west", label_offset: (0.2, 0))

  comp.connect-orthogonal("VinNode", "M1.G", style: "hv", ..default_stroke)

  comp.connect-orthogonal("VbiasNode", "M2.G", style: "hv", ..default_stroke)

  comp.connect-orthogonal("VddSym.B", "M3.S", style: "vh", ..default_stroke)

  comp.connect-orthogonal("M1.S", "GndSym.T", style: "vh", ..default_stroke)

  comp.connect-orthogonal("M1.D", "M2.S", style: "vh", ..default_stroke)

  comp.connect-orthogonal("M2.D", "M3.D", style: "vh", ..default_stroke)

  comp.wire_hop(
    (rel:(0,0),to:"M3.G"), (rel:(1.16,0),to:"M1.D"),     
    "M2.D", "VoutNode", 
    hopping_wire: 1,    
    hop_radius: 0.1,    
    hop_direction: 1,   
    ..default_stroke
  )
  comp.connect-orthogonal("M1.D", (rel:(1.16,0),to:"M1.D"), style: "vh", ..default_stroke)

})

would look like

Image

note: pmos needs a hack
let bubble_center = (rel: (-bubble_radius*width/calc.abs(width), 0), to: gate_conn_pt) something like that

@janosh
Copy link
Owner

janosh commented Apr 21, 2025

looks great! we could think about starting a Typst component system in this repo e.g. in a new folder typst/circuits, typst/chemistry, etc. if you think that would help with reusing functions across diagrams. a PR for that and the PMOS diagram would be very welcome 👍

@Kreijstal
Copy link
Contributor Author

Kreijstal commented Apr 21, 2025

problem with that is that examples are no longer copy pasteable. no?

aka to play for example on typst web

@janosh
Copy link
Owner

janosh commented Apr 21, 2025

yes, that's true. i was thinking about how hard it would be to inline local imports on the website such that code can still be copy-pasted without having to piece together different parts of the repo. could be quite hard. which is why i'm not strongly advocating for that but don't want to block it either without trying. still, would be less work for now to keep self-contained diagrams

@Kreijstal
Copy link
Contributor Author

I think imho the best deal is, we, for now keep every diagram self contained (even if it comes at the cost of repetitions), and when there is enough repetition, it is possible to build a symbol gallery as a library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants