feat(geometry): add arc segment support to PolySlab geometry#3220
feat(geometry): add arc segment support to PolySlab geometry#3220weiliangjin2021 wants to merge 1 commit intodevelopfrom
Conversation
c3f10b5 to
fe09c6f
Compare
Add bulge/arc support - Finite bulge validation (reject inf/nan) - Synchronized vertex+bulge canonicalization (winding reversal, zero-length edge handling) - Adjoint guard for non-zero bulge polyslabs
fe09c6f to
dd4cec3
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| return [self.make_shapely_polygon(self.reference_polygon)] | ||
| # Use discretized polygon for arc segments | ||
| poly_vertices = self._discretized_reference_polygon | ||
| return [self.make_shapely_polygon(poly_vertices)] |
There was a problem hiding this comment.
Side intersections not updated for arc segments
Medium Severity
_intersections_side uses self.reference_polygon (straight-edged vertices) for the vertical sidewall case, while inside and _intersections_normal were both updated to use self._discretized_reference_polygon. This means side-view cross-sections of a PolySlab with arc segments will show straight edges instead of curves, producing incorrect geometry in visualizations and any downstream computations relying on intersections_2d with a non-normal axis.
Additional Locations (2)
Diff CoverageDiff: origin/develop...HEAD, staged and unstaged changes
Summary
tidy3d/components/geometry/polyslab.pyLines 1279-1287 1279 chord_vec = p1 - p0
1280 chord_length = np.linalg.norm(chord_vec)
1281
1282 if math.isclose(chord_length, 0):
! 1283 continue
1284
1285 # θ = 4 * atan(|bulge|)
1286 theta = 4.0 * np.arctan(abs(b))Lines 1287-1295 1287
1288 # Radius: r = chord / (2 * sin(θ/2))
1289 half_theta = theta / 2.0
1290 if math.isclose(half_theta, 0) or math.isclose(np.sin(half_theta), 0):
! 1291 continue
1292 radius = chord_length / (2.0 * np.sin(half_theta))
1293
1294 # Circular segment area: A = r² * (θ - sin(θ)) / 2
1295 segment_area = (radius**2) * (theta - np.sin(theta)) / 2.0Lines 1298-1306 1298 # negative bulge subtracts area
1299 if b > 0:
1300 base_area += segment_area
1301 else:
! 1302 base_area -= segment_area
1303
1304 return base_area
1305
1306 @staticmethodLines 1363-1371 1363 chord_vec = p1 - p0
1364 chord_length = np.linalg.norm(chord_vec)
1365
1366 if math.isclose(chord_length, 0):
! 1367 raise SetupError("Arc endpoints are coincident; cannot define arc.")
1368
1369 if math.isclose(bulge, 0):
1370 # Degenerate case: straight line, no arc
1371 return {Lines 1449-1457 1449 Shape (num_points, 2).
1450 """
1451 if math.isclose(bulge, 0):
1452 # Straight line: just return endpoint
! 1453 return np.array([p1])
1454
1455 arc = PolySlab._arc_from_bulge(p0, p1, bulge)
1456 center = arc["center"]
1457 radius = arc["radius"]Lines 1508-1516 1508
1509 # r = chord / (2 * sin(θ/2))
1510 half_theta = theta / 2.0
1511 if math.isclose(half_theta, 0):
! 1512 return chord_length
1513 radius = chord_length / (2.0 * np.sin(half_theta))
1514
1515 # arc_length = r * |θ|
1516 return radius * thetaLines 1537-1545 1537 p1 = np.asarray(p1, dtype=float)
1538
1539 if math.isclose(bulge, 0):
1540 # Straight line
! 1541 return np.minimum(p0, p1), np.maximum(p0, p1)
1542
1543 # Use discretized arc points to compute bounds (robust approach)
1544 arc_points = PolySlab._discretize_arc(p0, p1, bulge)
1545 all_points = np.vstack([p0, arc_points])Lines 1946-1955 1946 length = z_max - z_min
1947
1948 # Use arc-aware area calculation when arc segments present
1949 if self._has_arc_segments:
! 1950 top_area = abs(self._area_with_arcs(self.top_polygon, self._bulges))
! 1951 base_area = abs(self._area_with_arcs(self.base_polygon, self._bulges))
1952 else:
1953 top_area = abs(self._area(self.top_polygon))
1954 base_area = abs(self._area(self.base_polygon))Lines 1965-1976 1965 base_polygon = self.base_polygon
1966
1967 # Use arc-aware calculations when arc segments present
1968 if self._has_arc_segments:
! 1969 top_area = abs(self._area_with_arcs(top_polygon, self._bulges))
! 1970 base_area = abs(self._area_with_arcs(base_polygon, self._bulges))
! 1971 top_perim = self._perimeter_with_arcs(top_polygon, self._bulges)
! 1972 base_perim = self._perimeter_with_arcs(base_polygon, self._bulges)
1973 else:
1974 top_area = abs(self._area(top_polygon))
1975 base_area = abs(self._area(base_polygon))
1976 top_perim = self._perimeter(top_polygon) |


Note
Medium Risk
Changes core geometry computations (
inside, intersections, bounds, area/perimeter) and adds new validation constraints, which could subtly affect existing workflows; functionality is gated behindbulgesbut touches commonly used code paths.Overview
Adds first-class arc-edge support to
PolySlabvia an optionalbulgesarray (bulge = tan(θ/4)), including canonicalization of vertices/bulges, arc discretization for Shapely operations, and arc-awarebounds,inside/intersection geometry, and area/perimeter calculations (affecting volume/surface area).Introduces new validation rules for
bulges(length match, finite values, no self-intersecting arc polygons, and rejecting zero-length edges with nonzero bulge) and temporarily restricts arc polyslabs to vertical sidewalls with nodilation; adjoint derivative computation now explicitly raisesNotImplementedErrorwhen arcs are present. Schemas and tests are updated to serialize/validatebulgesacross simulation types and cover the new behaviors.Written by Cursor Bugbot for commit dd4cec3. This will update automatically on new commits. Configure here.