Skip to content
Open
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
60 changes: 43 additions & 17 deletions src/paths/paths.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,18 @@ mutable struct Node{T} <: GeometryEntity{T}
end
convert(::Type{Node{T}}, n::Node{T}) where {T} = n
function convert(::Type{Node{T}}, n::Node{S}) where {S, T}
return Node{T}(convert(Segment{T}, n.seg), n.sty)
# Breaks linked list but can at least have dummies on either side
# to preserve info about whether the node started/ended a path
cur = Node{T}(convert(Segment{T}, n.seg), n.sty)
if n.prev !== n
cur.prev = Node{T}(convert(Segment{T}, n.prev.seg), n.prev.sty)
cur.prev.next = cur
end
if n.next !== n
cur.next = Node{T}(convert(Segment{T}, n.next.seg), n.next.sty)
cur.next.prev = cur
end
return cur
end
convert(::Type{GeometryEntity{T}}, n::Node) where {T} = convert(Node{T}, n)

Expand Down Expand Up @@ -362,14 +373,19 @@ end
Type for abstracting an arbitrary styled path in the plane. Iterating returns
[`Paths.Node`](@ref) objects.

Convenience constructors for `Path{T}` object:
The most common convenience constructors:

Path(p0::Point=zero(Point{typeof(1.0UPREFERRED)}); α0=0.0°, name=uniquename("path"), metadata=UNDEF_META)
Path(p0x::Coordinate, p0y::Coordinate; α0=0.0°, name=uniquename("path"), metadata=UNDEF_META)

Path{T}(p0::Point=zero(Point{T}), α0::typeof(1.0°)=0.0°, metadata::Meta=UNDEF_META)
Path{T}(name::String, p0::Point{T}=zero(Point{T}), α0=0.0°, meta::Meta=UNDEF_META, nodes::Vector{Node{T}}=Node{T}[])
Path(p0::Point=zero(Point{typeof(1.0UPREFERRED)}); α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Path(p0x::Coordinate, p0y::Coordinate; α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Path(u::CoordinateUnits; α0=0.0, name=uniquename("path"), metadata=UNDEF_META)
Paths constructed via those constructors with `p0` in `Length` units will have coordinate type
`typeof(1.0UPREFERRED)`.
For explicit control over the coordinate type, use one of the other constructors:

Path{T}(p0::Point=zero(Point{T}), α0=0.0°, metadata::Meta=UNDEF_META)
Path{T}(name::String, p0::Point=zero(Point{T}), α0=0.0°, metadata::Meta=UNDEF_META, nodes::Vector{Node{T}}=Node{T}[])
Path(v::Vector{Node{T}}; name=uniquename("path"), metadata=UNDEF_META) where {T}
Path(u::CoordinateUnits, p0=zero(Point{typeof(1.0u)}); α0=0.0°, name=uniquename("path"), metadata=UNDEF_META)
"""
mutable struct Path{T} <: AbstractComponent{T}
name::String
Expand All @@ -381,7 +397,7 @@ mutable struct Path{T} <: AbstractComponent{T}

Path{T}(
name::String,
p0::Point{T}=zero(Point{T}),
p0::Point=zero(Point{T}),
α0=0.0°,
meta::Meta=UNDEF_META,
nodes::Vector{Node{T}}=Node{T}[]
Expand Down Expand Up @@ -477,26 +493,36 @@ function show(io::IO, x::Node)
return print(io, "$(segment(x)) styled as $(style(x))")
end

Path{T}(p0::Point{T}=zero(Point{T}), α0=0.0°, meta::Meta=UNDEF_META) where {T} =
Path{T}(p0::Point=zero(Point{T}), α0=0.0°, meta::Meta=UNDEF_META) where {T} =
Path{T}(uniquename("path"), p0, α0, meta, Node{T}[])

# Return "preferred" path length coordinate type
# for consistency with non-explicit unit choice
_preferred_coordinatetype(x::Length) =
1.0UPREFERRED isa Length ? typeof(1.0UPREFERRED) : typeof(x)
_preferred_coordinatetype(::Coordinate) = Float64

function Path(
p0::Point{T}=zero(Point{typeof(1.0UPREFERRED)});
α0=0.0°,
name=uniquename("path"),
metadata=UNDEF_META
) where {T}
return Path{float(T)}(name, float.(p0), α0, metadata)
S = _preferred_coordinatetype(oneunit(T))
return Path{S}(name, p0, α0, metadata)
end

Path(p0x::T, p0y::T; kwargs...) where {T <: Coordinate} =
Path(Point{float(T)}(p0x, p0y); kwargs...)

Path(p0x::S, p0y::T; kwargs...) where {S <: Coordinate, T <: Coordinate} =
Path(promote(p0x, p0y)...; kwargs...)
Path(p0x::Coordinate, p0y::Coordinate; kwargs...) = Path(Point(p0x, p0y); kwargs...)

function Path(u::DeviceLayout.CoordinateUnits; kwargs...)
return Path(Point(0.0u, 0.0u); kwargs...)
function Path(
u::DeviceLayout.CoordinateUnits,
p0=zero(Point{typeof(1.0u)});
α0=0.0°,
name=uniquename("path"),
metadata=UNDEF_META
)
T = typeof(1.0u)
return Path{T}(name, p0, α0, metadata)
end
function Path(v::Vector{Node{T}}; name=uniquename("path"), metadata=UNDEF_META) where {T}
isempty(v) && return Path{T}(zero(Point{T}), 0.0°, v)
Expand Down
5 changes: 4 additions & 1 deletion test/test_entity.jl
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@
@test Paths.style(elements(cs)[3]).length == 100μm # Make sure taper got reconciled
flathalo = halo(elements(cs), 2μm)
@test eltype(flathalo) <: Paths.Node
@test all(Paths.style.(elements(halopath)) .== Paths.style.(flathalo))
@test all(
Paths.width.(Paths.style.(elements(halopath)), 0nm) .≈
Paths.width.(Paths.style.(flathalo), 0nm)
)
@test Paths.style(flathalo[4]).length == 100μm

# Issue: Corner transformation
Expand Down
40 changes: 16 additions & 24 deletions test/tests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -511,32 +511,24 @@ end
@test typeof(Path(Point(0, 0))) == Path{Float64}
@test α0(Path()) == 0.0° == 0.0 == 0.0rad

@test typeof(Path(0.0μm, 0.0μm)) == Path{typeof(1.0μm)}
@test typeof(Path(0.0μm2μm, 0.0μm2μm)) == Path{typeof(1.0μm2μm)}
@test typeof(Path(0.0μm2μm, 0.0nm2μm)) == Path{typeof(1.0μm2μm)}
@test typeof(Path(0.0μm2nm, 0.0μm2nm)) == Path{typeof(1.0μm2nm)}
@test typeof(Path(0.0μm2nm, 0.0nm2nm)) == Path{typeof(1.0nm2nm)}
@test typeof(Path(0μm, 0μm)) == Path{typeof(1.0μm)}
@test typeof(Path(0μm2μm, 0μm2μm)) == Path{typeof(1.0μm2μm)}
@test typeof(Path(0μm2μm, 0nm2μm)) == Path{typeof(1.0μm2μm)}
@test typeof(Path(0μm2nm, 0μm2nm)) == Path{typeof(1.0μm2nm)}
@test typeof(Path(0μm2nm, 0nm2nm)) == Path{typeof(1.0nm2nm)}

@test typeof(Path(Point(0.0μm, 0.0μm))) == Path{typeof(1.0μm)}
@test typeof(Path(Point(0.0μm2μm, 0.0μm2μm))) == Path{typeof(1.0μm2μm)}
@test typeof(Path(Point(0.0μm2μm, 0.0nm2μm))) == Path{typeof(1.0μm2μm)}
@test typeof(Path(Point(0.0μm2nm, 0.0μm2nm))) == Path{typeof(1.0μm2nm)}
@test typeof(Path(Point(0.0μm2nm, 0.0nm2nm))) == Path{typeof(1.0nm2nm)}
@test typeof(Path(Point(0μm, 0μm))) == Path{typeof(1.0μm)}
@test typeof(Path(Point(0μm2μm, 0μm2μm))) == Path{typeof(1.0μm2μm)}
@test typeof(Path(Point(0μm2μm, 0nm2μm))) == Path{typeof(1.0μm2μm)}
@test typeof(Path(Point(0μm2nm, 0μm2nm))) == Path{typeof(1.0μm2nm)}
@test typeof(Path(Point(0μm2nm, 0nm2nm))) == Path{typeof(1.0nm2nm)}
# FreeUnits (plain Unitful) normalize to preferred type (#93)
up = DeviceLayout.UPREFERRED
@test typeof(Path(0.0μm, 0.0μm)) == Path{typeof(1.0up)}
@test typeof(Path(0.0mm, 0.0mm)) == Path{typeof(1.0up)}
# ContextUnits preserve their preferred context
@test typeof(Path(0.0μm2μm, 0.0μm2μm)) == Path{typeof(1.0up)}
@test typeof(Path(0.0μm2μm, 0.0nm2μm)) == Path{typeof(1.0up)}
# Unit constructor preserves explicit unit choice (matches Cell("name", μm))
@test typeof(Path(μm)) == Path{typeof(1.0μm)}
@test typeof(Path(μm2nm, Point(0.0nm2nm, 0.0nm2nm))) == Path{typeof(1.0μm2nm)}
@test typeof(Path(μm2nm, Point(0, 0)μm2μm)) == Path{typeof(1.0μm2nm)}
@test typeof(Path(μm, Point(0, 0)μm2μm)) == Path{typeof(1.0μm)}

@test α0(Path(Point(0.0μm, 0.0μm); α0=90°)) == 90.0° == π * rad / 2 == π / 2
@test α0(Path(Point(0.0μm, 0.0μm); α0=π / 2)) == 90.0° == π * rad / 2 == π / 2

@test typeof(Path(μm)) == Path{typeof(1.0μm)}
# Coordinate values preserved after normalization
@test getx(Path(1.0mm, 2.0mm).p0) ≈ 1.0mm

@test_throws DimensionError Path(0, 0μm)
@test_throws DimensionError Path(0nm, 0)
Expand Down Expand Up @@ -684,8 +676,8 @@ end
splice!(pa, 1, split(pa[1], π / 4 * 10μm))
splice!(pa, 3, split(pa[3], π / 4 * 10μm))

@test (α1(pa[2].seg) == π / 2) && (α1(pa[1].seg) == α0(pa[2].seg))
@test (α1(pa[4].seg) == 0) && (α1(pa[3].seg) == α0(pa[4].seg))
@test (α1(pa[2].seg) π / 2) && (α1(pa[1].seg) α0(pa[2].seg))
@test (α1(pa[4].seg) 0) && (α1(pa[3].seg) α0(pa[4].seg))

# Splitting bsplines near endpoints (MR !198)
pa = Path(0μm, 0μm)
Expand Down
Loading