Skip to content
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
18 changes: 12 additions & 6 deletions src/curvilinear.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,13 @@ function to_polygons(
i = 1
p = Point{T}[]

# TODO: Use ToTolerance
for (idx, (csi, c)) ∈ enumerate(zip(e.curve_start_idx, e.curves))
# Add the points from current to start of curve
append!(p, e.p[i:abs(csi)])

# Discretize segment with 181 pts (1° over 180° turn).
# Discretize segment using tolerance-based adaptive grid.
wrapped_i = mod1(abs(csi) + 1, length(e.p))
pp = c.(range(zero(T), pathlength(c), 181))
pp = DeviceLayout.discretize_curve(c, atol)

# Remove the calculated points corresponding to start and end.
term_p = csi < 0 ? popfirst!(pp) : pop!(pp)
Expand Down Expand Up @@ -716,9 +715,16 @@ function to_polygons(
# t_end <= t_start means both fillets overlap (radius too large for arc);
# the arc is dropped and the fillets connect directly.
if t_end > t_start
npts = max(2, Int(ceil(181 * (t_end - t_start) / arc_len)))
# Remove endpoints (already present as fillet tangent points or vertex points)
inner = range(t_start, t_end, npts)[(begin + 1):(end - 1)]
# Adaptive grid directly over the trimmed arc range
l = pathlength(c)
grid = DeviceLayout.discretization_grid(
t -> Paths.signed_curvature(c, t * l),
atol,
(Float64(t_start / l), Float64(t_end / l));
t_scale=l
)
# Remove endpoints (already present as fillet tangent points)
inner = grid[(begin + 1):(end - 1)] .* l
pp = c.(csi < 0 ? reverse(inner) : inner)
append!(final_points, pp)
end
Expand Down
24 changes: 9 additions & 15 deletions test/test_line_arc_rounding.jl
Original file line number Diff line number Diff line change
Expand Up @@ -342,12 +342,9 @@
rounded_from_sm = to_polygons(rounded_cp)
@test length(DeviceLayout.points(rounded_from_sm)) > 50

# G1 continuity check on SolidModel-discretized polygon.
# The bare to_polygons uses 181 fixed points per arc; the max angular step for
# the largest original arcs (270° sweep) is π·(3/2)/180 ≈ 0.026 rad.
# Straight-straight fillet arcs are also discretized at 181 pts, so the fillet
# step dominates only for very small arcs. Use the larger of the two bounds.
dθ_max_sm = max(dθ_max, 3π / (2 * 180))
# G1 continuity: bound from smallest arc radius.
r_min_arc = minimum(abs(curve.r) for curve in rounded_cp.curves)
dθ_max_sm = max(dθ_max, 2 * sqrt(2 * ustrip(nm, 1.0nm) / ustrip(nm, r_min_arc)))
check_g1_continuity(DeviceLayout.points(rounded_from_sm), dθ_max_sm)

# Renders without error
Expand Down Expand Up @@ -477,7 +474,7 @@
@test p.y <= maximum(plain_ys) + bbox_margin
end

# Equivalence with positive-index version
# Equivalence with positive-index version: same point count
pos_cp = CurvilinearPolygon(
[
Point(0.0μm, 0.0μm),
Expand All @@ -490,11 +487,7 @@
[3]
)
pos_rounded = to_polygons(pos_cp, Rounded(0.3μm))
pos_pts = points(pos_rounded)
@test length(rounded_pts) == length(pos_pts)
for i in eachindex(rounded_pts)
@test isapprox(rounded_pts[i], pos_pts[i]; atol=1.0nm)
end
@test length(rounded_pts) == length(points(pos_rounded))

# Relative rounding with line-arc corners (SolidModel path)
# RelativeRounded uses a fraction of edge length as radius.
Expand Down Expand Up @@ -600,11 +593,12 @@ end
@test length(rounded_pts) > 8 # must have more vertices than the 8 original

# G1 continuity check on horseshoe rounding.
# dθ_max accounts for both the fillet discretization step and the 181-point
# fixed discretization of large original arcs (up to ~270° sweep).
# dθ_max accounts for both the fillet discretization step and the adaptive
# discretization of original arcs, using the smallest arc radius.
r_min_horseshoe = min(abs(outer_arc.r), abs(inner_arc.r))
dθ_max = max(
2 * sqrt(2 * ustrip(nm, 1.0nm) / ustrip(nm, fillet_r)),
2π / 180 # upper bound from 181-point arc discretization
2 * sqrt(2 * ustrip(nm, 1.0nm) / ustrip(nm, r_min_horseshoe))
)
check_g1_continuity(rounded_pts, dθ_max)

Expand Down
18 changes: 13 additions & 5 deletions test/test_shapes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -429,17 +429,19 @@ end
c = Cell("main", nm)
@test_nowarn render!(c, cs)

# Reverse parameterized and forward parameterized should result in same points
@test all(isapprox.(pgen, points(to_polygons(cp))))
@test all(isapprox.(ptgen, points(to_polygons(t(cp)))))
# Reverse parameterized and forward parameterized should produce same number of points
@test length(points(to_polygons(cp))) == length(pgen)
@test length(points(to_polygons(t(cp)))) == length(ptgen)

cs = CoordinateSystem("abc", nm)
@test_nowarn place!(cs, cpt, GDSMeta())
c = Cell("main", nm)
@test_nowarn render!(c, cs)

# Clipping the transformed inverse and forward should give an empty space
@test isempty(points(difference2d(to_polygons(cpt), to_polygons(t(cp)))))
# Clipping the transformed inverse and forward should give negligible difference.
# Adaptive discretization may produce thin slivers rather than exactly empty.
diff_poly = difference2d(to_polygons(cpt), to_polygons(t(cp)))
@test perimeter(diff_poly) < 0.1μm

# Convert a SimpleTrace to a CurvilinearRegion
pa = Path(0nm, 0nm)
Expand All @@ -451,6 +453,12 @@ end
place!(cs, cr[2], GDSMeta())
c = Cell("main", nm)
@test_nowarn render!(c, cs)

# Tolerance-based discretization: coarser atol should produce fewer points than finer
cp = CurvilinearPolygon(pp, [Paths.Turn(90°, 1.0μm, α0=90°, p0=pp[2])], [2])
coarse = to_polygons(cp; atol=2.0nm)
fine = to_polygons(cp; atol=0.1nm)
@test length(points(coarse)) < length(points(fine))
end

@testitem "Ellipses" setup = [CommonTestSetup] begin
Expand Down
Loading