Skip to content

fix: Default to preferred coordinate type for builtin components #73

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

Merged
merged 2 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ The format of this changelog is based on
[Keep a Changelog](https://keepachangelog.com/), and this project adheres to
[Semantic Versioning](https://semver.org/).

## Upcoming

- `SolidModels.check_overlap` now skips empty groups
- Built-in components `Spacer`, `ArrowAnnotation`, and `WeatherVane` now default to coordinate type `typeof(1.0UPREFERRED)` if no coordinate type is specified in the constructor
- Minor documentation improvements

## 1.4.0 (2025-07-01)

- Added `SolidModels.check_overlap(::SolidModel)` for checking overlap of physical groups in a `SolidModel`
Expand Down
34 changes: 17 additions & 17 deletions src/schematics/components/builtin_components.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ A component with empty geometry and an 8-point compass of hooks at the origin.
@compdef struct WeatherVane{T} <: AbstractComponent{T}
name::String = "vane"
end
WeatherVane(; kwargs...) = WeatherVane{typeof(1.0UPREFERRED)}(; kwargs...)
hooks(::WeatherVane{T}) where {T} = compass(p0=zero(Point{T}))

"""
Spacer{T} <: AbstractComponent{T}

Provides an 8-point [compass](@ref SchematicDrivenLayout.compass) of hooks at each of two points separated by `p1`.
A component with empty geometry and an 8-point [compass](@ref SchematicDrivenLayout.compass) of hooks at each of two points separated by `p1`.

# Parameters

- `name`: The name of the spacer
- `p1`: The endpoint of the spacer

# Hooks
Expand All @@ -33,6 +35,7 @@ Provides an 8-point [compass](@ref SchematicDrivenLayout.compass) of hooks at ea
name::String = "spacer"
p1::Point{T} = zero(Point{T})
end
Spacer(; kwargs...) = Spacer{typeof(1.0UPREFERRED)}(; kwargs...)
function hooks(s::Spacer{T}) where {T}
return merge(compass("p0_", p0=zero(Point{T})), compass("p1_", p0=s.p1))
end
Expand Down Expand Up @@ -62,6 +65,7 @@ An arrow with a given length and width along with a text annotation in the layer
textsize::T = 25 * DeviceLayout.onemicron(T)
meta = SemanticMeta(:annotation)
end
ArrowAnnotation(; kwargs...) = ArrowAnnotation{typeof(1.0UPREFERRED)}(; kwargs...)

# Draw an arrow from left to right
function _geometry!(cs::CoordinateSystem, ac::ArrowAnnotation)
Expand Down Expand Up @@ -94,19 +98,22 @@ end

"""
struct GDSComponent{T} <: AbstractComponent{T}
name::String
cell::Cell{T}
hooks::NamedTuple
meta::DeviceLayout.Meta
end
GDSComponent(cell:Cell, hooks=compass(), parameters=(;))
GDSComponent([name::String=uniquename(cellname),]
filename::String,
cellname::String,
hooks=compass(),
parameters=(;))

A component with geometry corresponding to an explicit `Cell`.

The `meta` field does not affect metadata inside the
`Cell`, but can still be used by a `LayoutTarget` to decide whether the component should
be rendered or not.
The `Cell` can be provided to the constructor directly, or as the path to a `.gds` file
together with the name of a top-level cell in that file.

Hooks are supplied by the user, with a default of [`compass()`](@ref).

Users can specify their own `NamedTuple` of `parameters`. These parameters have no
effect on geometry or hooks.
"""
struct GDSComponent{T} <: AbstractComponent{T}
name::String
Expand All @@ -119,13 +126,6 @@ struct GDSComponent{T} <: AbstractComponent{T}
end
end

"""
GDSComponent(filename::String, cellname::String, hooks=compass(), meta=GDSMeta())

Construct a GDSComponent using only the necessary fields `filename` and `cellname`.

The component will have a unique name based on `cellname`.
"""
GDSComponent(
filename::String,
cellname::String,
Expand All @@ -152,7 +152,7 @@ function GDSComponent(
return GDSComponent{coordinatetype(l_cell)}(name, l_cell, hooks, parameters)
end

DEFAULT_GDSCOMPONENT_PARAMS = (;)
const DEFAULT_GDSCOMPONENT_PARAMS = (;)
default_parameters(::Type{<:GDSComponent}) = DEFAULT_GDSCOMPONENT_PARAMS

function _geometry!(cs::CoordinateSystem, l::GDSComponent)
Expand Down
25 changes: 10 additions & 15 deletions test/test_schematicdriven.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ allowed_rotation_angles(::TestDirectionalComponent) = [0, pi / 2]

@testset "Built-in components" begin
# Component works like any GeometryStructure
ar = SchematicDrivenLayout.ArrowAnnotation{typeof(1.0nm)}(width=1μm)
ar = SchematicDrivenLayout.ArrowAnnotation(width=1μm)
cs = CoordinateSystem("test", nm)
place!(cs, ar)
cs2 = CoordinateSystem("test2", nm)
Expand All @@ -34,7 +34,7 @@ allowed_rotation_angles(::TestDirectionalComponent) = [0, pi / 2]
@test ar2.width == ar.width
@test ar2.length == 1000.0nm
@test keys(SchematicDrivenLayout.non_default_parameters(ar2)) ==
(:name, :length, :width, :textsize) # textsize default is 25.0μm != 25000.0nm...
(:name, :length, :width)

# Can find references within component
c = Cell("test", nm)
Expand All @@ -47,7 +47,7 @@ allowed_rotation_angles(::TestDirectionalComponent) = [0, pi / 2]
addref!(cs2, ref2)
@test transformation(cs2, ref) ≈ transformation(ref2) ∘ transformation(ref)

sp = Spacer{typeof(1.0nm)}(; p1=Point(1mm, 1mm))
sp = Spacer(; p1=Point(1mm, 1mm))
tr = transformation(hooks(sp).p1_south, hooks(ar).nock)
@test isapprox_angle(rotation(tr), -π / 2)

Expand All @@ -59,6 +59,9 @@ allowed_rotation_angles(::TestDirectionalComponent) = [0, pi / 2]

# Default preference NoUnits
@test coordinatetype(Component) == typeof(1.0nm2nm)

wv = WeatherVane()
@test in_direction(hooks(wv, :east)) == 0°
end

@variant CutoutFlipchipArrow SchematicDrivenLayout.ArrowAnnotation{typeof(1.0nm)} map_meta =
Expand All @@ -81,7 +84,7 @@ end
@test level(cs.element_metadata[1]) == 2
@test level(cs.element_metadata[2]) == 1
manually_flipped = map_metadata(
SchematicDrivenLayout.ArrowAnnotation{typeof(1.0nm)}(; name=uniquename("arrow")),
SchematicDrivenLayout.ArrowAnnotation(; name=uniquename("arrow")),
facing
)
cs2 = geometry(manually_flipped)
Expand Down Expand Up @@ -719,20 +722,12 @@ end

### No reordering of nodes or changing root of rendering tree
g = SchematicGraph("spacers")
n1 = add_node!(g, Spacer{typeof(1.0nm)}(name="first", p1=Point(10μm, 0μm)))
n2 = fuse!(
g,
n1 => :p1_east,
Spacer{typeof(1.0nm)}(name="second", p1=Point(10μm, 0μm)) => :p1_south
)
n1 = add_node!(g, Spacer(name="first", p1=Point(10μm, 0μm)))
n2 = fuse!(g, n1 => :p1_east, Spacer(name="second", p1=Point(10μm, 0μm)) => :p1_south)
bcc = BasicCompositeComponent(g)
g2 = SchematicGraph("test")
bccn = add_node!(g2, bcc)
fuse!(
g2,
bccn => :_2_p0_north,
Spacer{typeof(1.0nm)}(name="third", p1=Point(10μm, 0μm)) => :p1_south
)
fuse!(g2, bccn => :_2_p0_north, Spacer(name="third", p1=Point(10μm, 0μm)) => :p1_south)
g3 = flatten(g2)
floorplan3 = plan(g3; log_dir=nothing)
@test name.(components(g3)) == ["first", "second", "third"]
Expand Down