diff --git a/src/paths/paths.jl b/src/paths/paths.jl index 5a61582b..df8b6a91 100644 --- a/src/paths/paths.jl +++ b/src/paths/paths.jl @@ -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) @@ -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 @@ -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}[] @@ -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) diff --git a/test/test_entity.jl b/test/test_entity.jl index 17a6baeb..c9220d77 100644 --- a/test/test_entity.jl +++ b/test/test_entity.jl @@ -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 diff --git a/test/tests.jl b/test/tests.jl index ec4e09b4..2379e22d 100644 --- a/test/tests.jl +++ b/test/tests.jl @@ -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) @@ -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)