ScadPy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. scadpy/__init__.py +5 -0
  2. scadpy/color/__init__.py +3 -0
  3. scadpy/color/constants/BEIGE.py +3 -0
  4. scadpy/color/constants/BLACK.py +3 -0
  5. scadpy/color/constants/BLUE.py +3 -0
  6. scadpy/color/constants/BROWN.py +3 -0
  7. scadpy/color/constants/DARK_GRAY.py +3 -0
  8. scadpy/color/constants/DEFAULT_COLOR.py +3 -0
  9. scadpy/color/constants/DEFAULT_OPACITY.py +1 -0
  10. scadpy/color/constants/GRAY.py +3 -0
  11. scadpy/color/constants/GREEN.py +3 -0
  12. scadpy/color/constants/ORANGE.py +3 -0
  13. scadpy/color/constants/RED.py +3 -0
  14. scadpy/color/constants/WHITE.py +3 -0
  15. scadpy/color/constants/YELLOW.py +3 -0
  16. scadpy/color/constants/__init__.py +29 -0
  17. scadpy/color/type/__init__.py +3 -0
  18. scadpy/color/type/color.py +3 -0
  19. scadpy/color/utils/__init__.py +3 -0
  20. scadpy/color/utils/get_random_color.py +36 -0
  21. scadpy/core/__init__.py +3 -0
  22. scadpy/core/assembly/__init__.py +5 -0
  23. scadpy/core/assembly/combinations/__init__.py +13 -0
  24. scadpy/core/assembly/combinations/concat_assemblies.py +50 -0
  25. scadpy/core/assembly/combinations/exclude_assemblies.py +135 -0
  26. scadpy/core/assembly/combinations/intersect_assemblies.py +128 -0
  27. scadpy/core/assembly/combinations/subtract_assemblies.py +151 -0
  28. scadpy/core/assembly/combinations/unify_assemblies.py +59 -0
  29. scadpy/core/assembly/topologies/__init__.py +41 -0
  30. scadpy/core/assembly/topologies/directed_edge/__init__.py +9 -0
  31. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_directions.py +70 -0
  32. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_edge.py +49 -0
  33. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_vertex.py +54 -0
  34. scadpy/core/assembly/topologies/edge/__init__.py +9 -0
  35. scadpy/core/assembly/topologies/edge/get_assembly_edge_lengths.py +46 -0
  36. scadpy/core/assembly/topologies/edge/get_assembly_edge_midpoints.py +51 -0
  37. scadpy/core/assembly/topologies/edge/get_assembly_edge_normals.py +67 -0
  38. scadpy/core/assembly/topologies/face_corner/__init__.py +13 -0
  39. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_angles.py +72 -0
  40. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_normals.py +103 -0
  41. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_incoming_directed_edge.py +65 -0
  42. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_outgoing_directed_edge.py +65 -0
  43. scadpy/core/assembly/topologies/face_corner/get_assembly_face_directed_edge_to_corner.py +79 -0
  44. scadpy/core/assembly/topologies/part/__init__.py +5 -0
  45. scadpy/core/assembly/topologies/part/get_assembly_part_colors.py +55 -0
  46. scadpy/core/assembly/topologies/vertex/__init__.py +7 -0
  47. scadpy/core/assembly/topologies/vertex/get_assembly_vertex_coordinates.py +70 -0
  48. scadpy/core/assembly/topologies/vertex/get_assembly_vertex_to_part.py +62 -0
  49. scadpy/core/assembly/transformations/__init__.py +19 -0
  50. scadpy/core/assembly/transformations/color_assembly.py +24 -0
  51. scadpy/core/assembly/transformations/mirror_vertex_coordinates.py +68 -0
  52. scadpy/core/assembly/transformations/pull_vertex_coordinates.py +64 -0
  53. scadpy/core/assembly/transformations/push_vertex_coordinates.py +64 -0
  54. scadpy/core/assembly/transformations/resize_vertex_coordinates.py +121 -0
  55. scadpy/core/assembly/transformations/rotate_vertex_coordinates.py +73 -0
  56. scadpy/core/assembly/transformations/scale_vertex_coordinates.py +76 -0
  57. scadpy/core/assembly/transformations/translate_vertex_coordinates.py +70 -0
  58. scadpy/core/assembly/types/__init__.py +7 -0
  59. scadpy/core/assembly/types/assembly.py +14 -0
  60. scadpy/core/assembly/types/topology_filter.py +6 -0
  61. scadpy/core/assembly/utils/__init__.py +9 -0
  62. scadpy/core/assembly/utils/lookup_pairs.py +56 -0
  63. scadpy/core/assembly/utils/resolve_topology_filter.py +84 -0
  64. scadpy/core/assembly/utils/transform_filtered_parts.py +55 -0
  65. scadpy/core/component/__init__.py +3 -0
  66. scadpy/core/component/exporters/__init__.py +7 -0
  67. scadpy/core/component/exporters/map_component_to_html_file.py +47 -0
  68. scadpy/core/component/exporters/map_component_to_screen.py +63 -0
  69. scadpy/core/component/features/__init__.py +5 -0
  70. scadpy/core/component/features/get_component_bounds.py +38 -0
  71. scadpy/core/component/utils/__init__.py +9 -0
  72. scadpy/core/component/utils/blend_component_colors.py +77 -0
  73. scadpy/core/component/utils/get_intersecting_component_index_groups.py +108 -0
  74. scadpy/core/part/__init__.py +3 -0
  75. scadpy/core/part/combinations/__init__.py +11 -0
  76. scadpy/core/part/combinations/concat_parts.py +48 -0
  77. scadpy/core/part/combinations/intersect_parts.py +147 -0
  78. scadpy/core/part/combinations/subtract_parts.py +94 -0
  79. scadpy/core/part/combinations/unify_parts.py +143 -0
  80. scadpy/core/part/types/__init__.py +5 -0
  81. scadpy/core/part/types/part.py +34 -0
  82. scadpy/core/part/utils/__init__.py +5 -0
  83. scadpy/core/part/utils/blend_part_colors.py +32 -0
  84. scadpy/d2/__init__.py +2 -0
  85. scadpy/d2/shape/__init__.py +9 -0
  86. scadpy/d2/shape/combinations/__init__.py +21 -0
  87. scadpy/d2/shape/combinations/are_shape_parts_intersecting.py +49 -0
  88. scadpy/d2/shape/combinations/concat_shape.py +48 -0
  89. scadpy/d2/shape/combinations/exclude_shape.py +71 -0
  90. scadpy/d2/shape/combinations/intersect_shape.py +64 -0
  91. scadpy/d2/shape/combinations/intersect_shape_parts.py +71 -0
  92. scadpy/d2/shape/combinations/subtract_shape.py +72 -0
  93. scadpy/d2/shape/combinations/subtract_shape_parts.py +66 -0
  94. scadpy/d2/shape/combinations/unify_shape.py +51 -0
  95. scadpy/d2/shape/combinations/unify_shape_parts.py +74 -0
  96. scadpy/d2/shape/exporters/__init__.py +17 -0
  97. scadpy/d2/shape/exporters/map_shape_to_dxf.py +43 -0
  98. scadpy/d2/shape/exporters/map_shape_to_dxf_file.py +38 -0
  99. scadpy/d2/shape/exporters/map_shape_to_html.py +117 -0
  100. scadpy/d2/shape/exporters/map_shape_to_html_file.py +58 -0
  101. scadpy/d2/shape/exporters/map_shape_to_screen.py +51 -0
  102. scadpy/d2/shape/exporters/map_shape_to_svg.py +40 -0
  103. scadpy/d2/shape/exporters/map_shape_to_svg_file.py +38 -0
  104. scadpy/d2/shape/features/__init__.py +9 -0
  105. scadpy/d2/shape/features/get_shape_bounds.py +40 -0
  106. scadpy/d2/shape/features/get_shape_part_bounds.py +37 -0
  107. scadpy/d2/shape/features/is_shape_empty.py +38 -0
  108. scadpy/d2/shape/importers/__init__.py +13 -0
  109. scadpy/d2/shape/importers/map_dxf_to_shape.py +55 -0
  110. scadpy/d2/shape/importers/map_geometries_to_shape.py +45 -0
  111. scadpy/d2/shape/importers/map_geometry_to_shape.py +43 -0
  112. scadpy/d2/shape/importers/map_parts_to_shape.py +62 -0
  113. scadpy/d2/shape/importers/map_svg_to_shape.py +55 -0
  114. scadpy/d2/shape/primitives/__init__.py +6 -0
  115. scadpy/d2/shape/primitives/circle.py +68 -0
  116. scadpy/d2/shape/primitives/polygon.py +86 -0
  117. scadpy/d2/shape/primitives/rectangle.py +85 -0
  118. scadpy/d2/shape/primitives/square.py +57 -0
  119. scadpy/d2/shape/topologies/__init__.py +53 -0
  120. scadpy/d2/shape/topologies/corner/__init__.py +15 -0
  121. scadpy/d2/shape/topologies/corner/are_shape_corners_convex.py +75 -0
  122. scadpy/d2/shape/topologies/corner/get_shape_corner_angles.py +58 -0
  123. scadpy/d2/shape/topologies/corner/get_shape_corner_normals.py +82 -0
  124. scadpy/d2/shape/topologies/corner/get_shape_corner_to_incoming_directed_edge.py +39 -0
  125. scadpy/d2/shape/topologies/corner/get_shape_corner_to_outgoing_directed_edge.py +39 -0
  126. scadpy/d2/shape/topologies/corner/get_shape_corner_to_vertex.py +65 -0
  127. scadpy/d2/shape/topologies/directed_edge/__init__.py +11 -0
  128. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_directions.py +44 -0
  129. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_corner.py +41 -0
  130. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_edge.py +51 -0
  131. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_vertex.py +63 -0
  132. scadpy/d2/shape/topologies/edge/__init__.py +11 -0
  133. scadpy/d2/shape/topologies/edge/get_shape_edge_lengths.py +43 -0
  134. scadpy/d2/shape/topologies/edge/get_shape_edge_midpoints.py +46 -0
  135. scadpy/d2/shape/topologies/edge/get_shape_edge_normals.py +40 -0
  136. scadpy/d2/shape/topologies/edge/get_shape_edge_to_vertex.py +71 -0
  137. scadpy/d2/shape/topologies/ring/__init__.py +7 -0
  138. scadpy/d2/shape/topologies/ring/get_shape_ring_to_part.py +46 -0
  139. scadpy/d2/shape/topologies/ring/get_shape_ring_types.py +46 -0
  140. scadpy/d2/shape/topologies/vertex/__init__.py +11 -0
  141. scadpy/d2/shape/topologies/vertex/get_shape_part_vertex_coordinates.py +62 -0
  142. scadpy/d2/shape/topologies/vertex/get_shape_vertex_coordinates.py +44 -0
  143. scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_part.py +42 -0
  144. scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_ring.py +63 -0
  145. scadpy/d2/shape/transformations/__init__.py +43 -0
  146. scadpy/d2/shape/transformations/chamfer_shape.py +259 -0
  147. scadpy/d2/shape/transformations/color_shape.py +46 -0
  148. scadpy/d2/shape/transformations/convexify_shape.py +79 -0
  149. scadpy/d2/shape/transformations/fill_shape.py +68 -0
  150. scadpy/d2/shape/transformations/fillet_shape.py +289 -0
  151. scadpy/d2/shape/transformations/grow_shape.py +82 -0
  152. scadpy/d2/shape/transformations/linear_cut_shape.py +116 -0
  153. scadpy/d2/shape/transformations/linear_extrude_shape.py +60 -0
  154. scadpy/d2/shape/transformations/linear_slice_shape.py +144 -0
  155. scadpy/d2/shape/transformations/mirror_shape.py +53 -0
  156. scadpy/d2/shape/transformations/pull_shape.py +67 -0
  157. scadpy/d2/shape/transformations/push_shape.py +67 -0
  158. scadpy/d2/shape/transformations/radial_extrude_shape.py +285 -0
  159. scadpy/d2/shape/transformations/radial_slice_shape.py +132 -0
  160. scadpy/d2/shape/transformations/recoordinate_shape.py +82 -0
  161. scadpy/d2/shape/transformations/resize_shape.py +91 -0
  162. scadpy/d2/shape/transformations/rotate_shape.py +63 -0
  163. scadpy/d2/shape/transformations/scale_shape.py +58 -0
  164. scadpy/d2/shape/transformations/shrink_shape.py +69 -0
  165. scadpy/d2/shape/transformations/translate_shape.py +54 -0
  166. scadpy/d2/shape/types/__init__.py +3 -0
  167. scadpy/d2/shape/types/shape.py +792 -0
  168. scadpy/d2/shape/types/utils/__init__.py +5 -0
  169. scadpy/d2/shape/types/utils/shapely_base_geometry_to_shapely_polygons.py +25 -0
  170. scadpy/d2/shape/utils/__init__.py +5 -0
  171. scadpy/d2/shape/utils/shapely_base_geometry_to_shapely_polygons.py +55 -0
  172. scadpy/d2/utils/__init__.py +3 -0
  173. scadpy/d2/utils/resolve_vector_2d.py +50 -0
  174. scadpy/d3/__init__.py +2 -0
  175. scadpy/d3/solid/__init__.py +8 -0
  176. scadpy/d3/solid/combinations/__init__.py +21 -0
  177. scadpy/d3/solid/combinations/are_solid_parts_intersecting.py +51 -0
  178. scadpy/d3/solid/combinations/concat_solid.py +48 -0
  179. scadpy/d3/solid/combinations/exclude_solid.py +71 -0
  180. scadpy/d3/solid/combinations/intersect_solid.py +64 -0
  181. scadpy/d3/solid/combinations/intersect_solid_parts.py +73 -0
  182. scadpy/d3/solid/combinations/subtract_solid.py +72 -0
  183. scadpy/d3/solid/combinations/subtract_solid_parts.py +68 -0
  184. scadpy/d3/solid/combinations/unify_solid.py +51 -0
  185. scadpy/d3/solid/combinations/unify_solid_parts.py +73 -0
  186. scadpy/d3/solid/exporters/__init__.py +11 -0
  187. scadpy/d3/solid/exporters/map_solid_to_html.py +318 -0
  188. scadpy/d3/solid/exporters/map_solid_to_html_file.py +58 -0
  189. scadpy/d3/solid/exporters/map_solid_to_screen.py +51 -0
  190. scadpy/d3/solid/exporters/map_solid_to_stl_file.py +48 -0
  191. scadpy/d3/solid/features/__init__.py +11 -0
  192. scadpy/d3/solid/features/get_solid_bounds.py +37 -0
  193. scadpy/d3/solid/features/get_solid_part_bounds.py +37 -0
  194. scadpy/d3/solid/features/get_solid_part_colors.py +39 -0
  195. scadpy/d3/solid/features/is_solid_empty.py +36 -0
  196. scadpy/d3/solid/importers/__init__.py +11 -0
  197. scadpy/d3/solid/importers/map_geometries_to_solid.py +42 -0
  198. scadpy/d3/solid/importers/map_geometry_to_solid.py +42 -0
  199. scadpy/d3/solid/importers/map_parts_to_solid.py +66 -0
  200. scadpy/d3/solid/importers/map_stl_to_solid.py +37 -0
  201. scadpy/d3/solid/primitives/__init__.py +7 -0
  202. scadpy/d3/solid/primitives/cone.py +70 -0
  203. scadpy/d3/solid/primitives/cuboid.py +75 -0
  204. scadpy/d3/solid/primitives/cylinder.py +73 -0
  205. scadpy/d3/solid/primitives/polyhedron.py +60 -0
  206. scadpy/d3/solid/primitives/sphere.py +58 -0
  207. scadpy/d3/solid/topologies/__init__.py +8 -0
  208. scadpy/d3/solid/topologies/triangle/__init__.py +5 -0
  209. scadpy/d3/solid/topologies/triangle/get_solid_triangle_to_vertex.py +49 -0
  210. scadpy/d3/solid/topologies/vertex/__init__.py +7 -0
  211. scadpy/d3/solid/topologies/vertex/get_solid_vertex_coordinates.py +39 -0
  212. scadpy/d3/solid/topologies/vertex/get_solid_vertex_to_part.py +37 -0
  213. scadpy/d3/solid/transformations/__init__.py +23 -0
  214. scadpy/d3/solid/transformations/color_solid.py +46 -0
  215. scadpy/d3/solid/transformations/convexify_solid.py +64 -0
  216. scadpy/d3/solid/transformations/mirror_solid.py +53 -0
  217. scadpy/d3/solid/transformations/pull_solid.py +67 -0
  218. scadpy/d3/solid/transformations/push_solid.py +67 -0
  219. scadpy/d3/solid/transformations/recoordinate_solid.py +68 -0
  220. scadpy/d3/solid/transformations/resize_solid.py +92 -0
  221. scadpy/d3/solid/transformations/rotate_solid.py +93 -0
  222. scadpy/d3/solid/transformations/scale_solid.py +58 -0
  223. scadpy/d3/solid/transformations/translate_solid.py +54 -0
  224. scadpy/d3/solid/types/__init__.py +3 -0
  225. scadpy/d3/solid/types/solid.py +448 -0
  226. scadpy/d3/utils/__init__.py +3 -0
  227. scadpy/d3/utils/resolve_vector_3d.py +50 -0
  228. scadpy/utils/__init__.py +6 -0
  229. scadpy/utils/resolve_vector.py +64 -0
  230. scadpy/utils/x.py +38 -0
  231. scadpy/utils/y.py +38 -0
  232. scadpy/utils/z.py +38 -0
  233. scadpy-0.1.0.dist-info/METADATA +282 -0
  234. scadpy-0.1.0.dist-info/RECORD +236 -0
  235. scadpy-0.1.0.dist-info/WHEEL +4 -0
  236. scadpy-0.1.0.dist-info/licenses/LICENSE.md +43 -0
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def are_shape_corners_convex(
15
+ shape: Shape,
16
+ ) -> NDArray[np.bool_]:
17
+ """
18
+ For each corner in the shape, return whether it is convex.
19
+
20
+ A corner is convex if the shape turns left at that vertex on an exterior
21
+ ring (counter-clockwise winding). Concave corners turn right. Interior
22
+ rings have their orientation inverted accordingly.
23
+
24
+ Use :func:`get_shape_corner_angles` to get the magnitude of the turning
25
+ angle independently of convexity.
26
+
27
+ Parameters
28
+ ----------
29
+ shape : Shape
30
+ The shape to extract corner convexity from.
31
+
32
+ Returns
33
+ -------
34
+ NDArray[np.bool_]
35
+ 1D boolean array of shape (n_corners,). True if convex, False if concave.
36
+
37
+ Examples
38
+ --------
39
+ >>> from scadpy import are_shape_corners_convex, polygon
40
+
41
+ >>> # arrow: 5 convex corners (tip and sides)
42
+ >>> # + 2 concave corners (the tail notch)
43
+ >>> arrow = polygon(
44
+ ... [(0, 1), (3, 0), (5, 2), (3, 4),
45
+ ... (0, 3), (1, 2.5), (1, 1.5)]
46
+ ... )
47
+ >>> are_shape_corners_convex(arrow).tolist()
48
+ [True, True, True, True, True, False, False]
49
+ """
50
+ corner_to_vertex = shape.corner_to_vertex
51
+ coords = shape.vertex_coordinates
52
+
53
+ if len(corner_to_vertex) == 0:
54
+ return np.empty(0, dtype=np.bool_)
55
+
56
+ prev_coords = coords[corner_to_vertex[:, 0]]
57
+ curr_coords = coords[corner_to_vertex[:, 1]]
58
+ next_coords = coords[corner_to_vertex[:, 2]]
59
+
60
+ v_in = curr_coords - prev_coords
61
+ v_out = next_coords - curr_coords
62
+
63
+ # 2D cross product: positive = left turn (convex on CCW ring)
64
+ cross = v_in[:, 0] * v_out[:, 1] - v_in[:, 1] * v_out[:, 0]
65
+
66
+ is_convex = cross > 0
67
+
68
+ # interior rings (CW in shapely) have inverted orientation — flip convexity
69
+ ring_types = shape.ring_types
70
+ vertex_to_ring = shape.vertex_to_ring
71
+ corner_ring_indices = vertex_to_ring[corner_to_vertex[:, 1]]
72
+ is_interior = ring_types[corner_ring_indices] == "interior"
73
+ is_convex = np.where(is_interior, ~is_convex, is_convex)
74
+
75
+ return is_convex
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_corner_angles(
15
+ shape: Shape,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ For each corner in the shape, return its interior angle in degrees.
19
+
20
+ The angle is always positive, in the range (0°, 180°). It represents
21
+ the turning angle at the corner, regardless of whether the corner is
22
+ convex or concave. Use :func:`are_shape_corners_convex` to distinguish
23
+ convex corners from concave ones.
24
+
25
+ The angle is computed as the absolute value of the signed angle from the
26
+ incoming edge to the outgoing edge at each corner, using the 2D cross
27
+ product to determine orientation.
28
+
29
+ Parameters
30
+ ----------
31
+ shape : Shape
32
+ The shape to extract corner angles from.
33
+
34
+ Returns
35
+ -------
36
+ NDArray[np.float64]
37
+ 1D array of shape (n_corners,), one angle per corner, in degrees.
38
+ All values are in the range (0°, 180°).
39
+
40
+ Examples
41
+ --------
42
+ >>> from scadpy import get_shape_corner_angles, polygon
43
+
44
+ >>> # arrow: 5 convex corners + 2 concave corners,
45
+ >>> # all with different angles
46
+ >>> arrow = polygon(
47
+ ... [(0, 1), (3, 0), (5, 2), (3, 4),
48
+ ... (0, 3), (1, 2.5), (1, 1.5)]
49
+ ... )
50
+ >>> get_shape_corner_angles(arrow).round(2).tolist()
51
+ [135.0, 63.43, 90.0, 63.43, 135.0, 63.43, 63.43]
52
+ """
53
+ from scadpy.core.assembly import get_assembly_face_corner_angles
54
+
55
+ return get_assembly_face_corner_angles(
56
+ corner_to_vertex=shape.corner_to_vertex,
57
+ vertex_coordinates=shape.vertex_coordinates,
58
+ )
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_corner_normals(
15
+ shape: Shape,
16
+ epsilon: float = 1e-10,
17
+ ) -> NDArray[np.float64]:
18
+ """
19
+ For each corner in the shape, return its outward unit normal.
20
+
21
+ The normal is the bisector of the outward edge normals at the corner,
22
+ oriented to point away from the filled material. It points outward for
23
+ convex corners and inward for concave ones, consistently with
24
+ :func:`are_shape_corners_convex`.
25
+
26
+ Each edge normal is the 90° CW rotation of the edge direction, which
27
+ points outward for CCW (exterior) rings. The bisector is then the
28
+ normalized average of the two adjacent edge normals. Its sign is
29
+ corrected using :func:`are_shape_corners_convex`, which already accounts
30
+ for ring orientation (interior vs exterior), so no separate handling
31
+ is needed here.
32
+
33
+ For degenerate 180° corners (straight edges) where the bisector
34
+ vanishes, the normal falls back to the outward edge normal of the
35
+ incoming edge (90° CW rotation).
36
+
37
+ Parameters
38
+ ----------
39
+ shape : Shape
40
+ The shape to extract corner normals from.
41
+ epsilon : float, optional
42
+ Threshold below which the bisector norm is considered degenerate
43
+ (straight 180° corner). Defaults to ``1e-10``.
44
+
45
+ Returns
46
+ -------
47
+ NDArray[np.float64]
48
+ 2D array of shape (n_corners, 2). Each row is a unit vector ``[nx, ny]``.
49
+
50
+ Examples
51
+ --------
52
+ >>> from scadpy import (
53
+ ... get_shape_corner_normals,
54
+ ... are_shape_corners_convex,
55
+ ... polygon,
56
+ ... )
57
+
58
+ >>> # arrow: 5 convex corners (normals point outward)
59
+ >>> # + 2 concave corners (normals point inward,
60
+ >>> # into the tail notch — x component is positive)
61
+ >>> arrow = polygon(
62
+ ... [(0, 1), (3, 0), (5, 2), (3, 4),
63
+ ... (0, 3), (1, 2.5), (1, 1.5)]
64
+ ... )
65
+ >>> normals = get_shape_corner_normals(arrow)
66
+ >>> normals.round(4) # doctest: +NORMALIZE_WHITESPACE
67
+ array([[-0.9975, -0.0709],
68
+ [ 0.2298, -0.9732],
69
+ [ 1. , 0. ],
70
+ [ 0.2298, 0.9732],
71
+ [-0.9975, 0.0709],
72
+ [ 0.8507, 0.5257],
73
+ [ 0.8507, -0.5257]])
74
+ """
75
+ from scadpy.core.assembly import get_assembly_face_corner_normals
76
+
77
+ return get_assembly_face_corner_normals(
78
+ corner_to_vertex=shape.corner_to_vertex,
79
+ vertex_coordinates=shape.vertex_coordinates,
80
+ are_corners_convex=shape.are_corners_convex,
81
+ epsilon=epsilon,
82
+ )
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_corner_to_incoming_directed_edge(
15
+ shape: Shape,
16
+ ) -> NDArray[np.int64]:
17
+ """
18
+ For each corner in the shape, return the index of its incoming directed edge.
19
+
20
+ See :func:`get_assembly_face_corner_to_incoming_directed_edge` for full documentation.
21
+
22
+ Examples
23
+ --------
24
+ >>> from scadpy import (
25
+ ... get_shape_corner_to_incoming_directed_edge, polygon
26
+ ... )
27
+
28
+ >>> # triangle: corners (2,0,1), (0,1,2), (1,2,0)
29
+ >>> # incoming: 2→0, 0→1, 1→2
30
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
31
+ >>> get_shape_corner_to_incoming_directed_edge(triangle)
32
+ array([4, 0, 2])
33
+ """
34
+ from scadpy.core.assembly import get_assembly_face_corner_to_incoming_directed_edge
35
+
36
+ return get_assembly_face_corner_to_incoming_directed_edge(
37
+ corner_to_vertex=shape.corner_to_vertex,
38
+ directed_edge_to_vertex=shape.directed_edge_to_vertex,
39
+ )
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_corner_to_outgoing_directed_edge(
15
+ shape: Shape,
16
+ ) -> NDArray[np.int64]:
17
+ """
18
+ For each corner in the shape, return the index of its outgoing directed edge.
19
+
20
+ See :func:`get_assembly_face_corner_to_outgoing_directed_edge` for full documentation.
21
+
22
+ Examples
23
+ --------
24
+ >>> from scadpy import (
25
+ ... get_shape_corner_to_outgoing_directed_edge, polygon
26
+ ... )
27
+
28
+ >>> # triangle: corners (2,0,1), (0,1,2), (1,2,0)
29
+ >>> # outgoing: 0→1, 1→2, 2→0
30
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
31
+ >>> get_shape_corner_to_outgoing_directed_edge(triangle)
32
+ array([0, 2, 4])
33
+ """
34
+ from scadpy.core.assembly import get_assembly_face_corner_to_outgoing_directed_edge
35
+
36
+ return get_assembly_face_corner_to_outgoing_directed_edge(
37
+ corner_to_vertex=shape.corner_to_vertex,
38
+ directed_edge_to_vertex=shape.directed_edge_to_vertex,
39
+ )
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_corner_to_vertex(
15
+ shape: Shape,
16
+ ) -> NDArray[np.int64]:
17
+ """
18
+ For each corner in the shape, return its three vertex indices (prev, curr, next).
19
+
20
+ A corner is defined by three consecutive vertices on a ring. Each ring of ``n``
21
+ vertices yields ``n`` corners (the ring is treated as cyclic).
22
+
23
+ Parameters
24
+ ----------
25
+ shape : Shape
26
+ The shape to extract corner vertex indices from.
27
+
28
+ Returns
29
+ -------
30
+ NDArray[np.int64]
31
+ 2D array of shape (n_corners, 3). Each row contains the indices
32
+ ``[prev, curr, next]`` into the shape's global vertex array.
33
+
34
+ Examples
35
+ --------
36
+ >>> from scadpy import get_shape_corner_to_vertex, polygon
37
+
38
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
39
+ >>> get_shape_corner_to_vertex( # doctest: +NORMALIZE_WHITESPACE
40
+ ... triangle
41
+ ... )
42
+ array([[2, 0, 1],
43
+ [0, 1, 2],
44
+ [1, 2, 0]])
45
+ """
46
+ vertex_to_ring = shape.vertex_to_ring
47
+
48
+ if len(vertex_to_ring) == 0:
49
+ return np.empty((0, 3), dtype=np.int64)
50
+
51
+ indices = np.arange(len(vertex_to_ring), dtype=np.int64)
52
+
53
+ # for each vertex, prev and next are within the same ring (cyclic)
54
+ # shift by +1 and -1 within each ring using modular arithmetic per ring
55
+ ring_starts = np.searchsorted(vertex_to_ring, np.arange(vertex_to_ring[-1] + 1))
56
+ ring_sizes = np.diff(np.append(ring_starts, len(vertex_to_ring)))
57
+
58
+ # offset of each vertex within its ring
59
+ offsets = indices - ring_starts[vertex_to_ring]
60
+ ring_size_per_vertex = ring_sizes[vertex_to_ring]
61
+
62
+ prev_indices = ring_starts[vertex_to_ring] + (offsets - 1) % ring_size_per_vertex
63
+ next_indices = ring_starts[vertex_to_ring] + (offsets + 1) % ring_size_per_vertex
64
+
65
+ return np.stack([prev_indices, indices, next_indices], axis=1)
@@ -0,0 +1,11 @@
1
+ __all__ = [
2
+ "get_shape_directed_edge_directions",
3
+ "get_shape_directed_edge_to_corner",
4
+ "get_shape_directed_edge_to_edge",
5
+ "get_shape_directed_edge_to_vertex",
6
+ ]
7
+
8
+ from .get_shape_directed_edge_directions import get_shape_directed_edge_directions
9
+ from .get_shape_directed_edge_to_corner import get_shape_directed_edge_to_corner
10
+ from .get_shape_directed_edge_to_edge import get_shape_directed_edge_to_edge
11
+ from .get_shape_directed_edge_to_vertex import get_shape_directed_edge_to_vertex
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_directed_edge_directions(
15
+ shape: Shape,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ For each directed edge in the shape, return its unit direction vector.
19
+
20
+ See :func:`get_assembly_directed_edge_directions` for full documentation.
21
+
22
+ Examples
23
+ --------
24
+ >>> from scadpy import get_shape_directed_edge_directions, square
25
+
26
+ >>> # unit square: 4 edges → 8 directed edges,
27
+ >>> # forward then backward interleaved
28
+ >>> square_shape = square(1)
29
+ >>> get_shape_directed_edge_directions(square_shape).round(4)
30
+ array([[ 1., 0.],
31
+ [-1., 0.],
32
+ [ 0., 1.],
33
+ [ 0., -1.],
34
+ [-1., 0.],
35
+ [ 1., 0.],
36
+ [ 0., -1.],
37
+ [ 0., 1.]])
38
+ """
39
+ from scadpy.core.assembly import get_assembly_directed_edge_directions
40
+
41
+ return get_assembly_directed_edge_directions(
42
+ directed_edge_to_vertex=shape.directed_edge_to_vertex,
43
+ vertex_coordinates=shape.vertex_coordinates,
44
+ )
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_directed_edge_to_corner(
15
+ shape: Shape,
16
+ ) -> NDArray[np.int64]:
17
+ """
18
+ For each directed edge in the shape, return its source and target corner indices.
19
+
20
+ See :func:`get_assembly_face_directed_edge_to_corner` for full documentation.
21
+
22
+ Examples
23
+ --------
24
+ >>> from scadpy import get_shape_directed_edge_to_corner, polygon
25
+
26
+ >>> # triangle: corners (2,0,1)=0, (0,1,2)=1, (1,2,0)=2
27
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
28
+ >>> get_shape_directed_edge_to_corner(triangle)
29
+ array([[0, 1],
30
+ [1, 0],
31
+ [1, 2],
32
+ [2, 1],
33
+ [2, 0],
34
+ [0, 2]])
35
+ """
36
+ from scadpy.core.assembly import get_assembly_face_directed_edge_to_corner
37
+
38
+ return get_assembly_face_directed_edge_to_corner(
39
+ corner_to_outgoing_directed_edge=shape.corner_to_outgoing_directed_edge,
40
+ corner_to_incoming_directed_edge=shape.corner_to_incoming_directed_edge,
41
+ )
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_directed_edge_to_edge(
15
+ shape: Shape,
16
+ ) -> NDArray[np.int64]:
17
+ """
18
+ For each directed edge in the shape, return the index of its parent undirected edge.
19
+
20
+ Since directed edges are interleaved (``directed_edge 2i`` and
21
+ ``directed_edge 2i+1`` both belong to ``edge i``), the mapping is:
22
+ ``edge index = directed_edge index // 2``.
23
+
24
+ Parameters
25
+ ----------
26
+ shape : Shape
27
+ The shape to extract directed edge-to-edge indices from.
28
+
29
+ Returns
30
+ -------
31
+ NDArray[np.int64]
32
+ 1D array of shape ``(2 * n_edges,)``. Each entry is the index of
33
+ the parent undirected edge.
34
+
35
+ Examples
36
+ --------
37
+ >>> from scadpy import get_shape_directed_edge_to_edge, polygon, square
38
+
39
+ >>> # triangle: 3 edges → 6 directed edges
40
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
41
+ >>> get_shape_directed_edge_to_edge(triangle)
42
+ array([0, 0, 1, 1, 2, 2])
43
+
44
+ >>> # square: 4 edges → 8 directed edges
45
+ >>> square_shape = square(1)
46
+ >>> get_shape_directed_edge_to_edge(square_shape)
47
+ array([0, 0, 1, 1, 2, 2, 3, 3])
48
+ """
49
+ from scadpy.core.assembly import get_assembly_directed_edge_to_edge
50
+
51
+ return get_assembly_directed_edge_to_edge(n_edges=len(shape.edge_to_vertex))
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+
14
+ @typechecked
15
+ def get_shape_directed_edge_to_vertex(
16
+ shape: Shape,
17
+ ) -> NDArray[np.int64]:
18
+ """
19
+ For each directed edge in the shape, return the indices of its start and end vertices.
20
+
21
+ Each undirected edge ``i`` gives rise to two directed edges, interleaved:
22
+
23
+ - ``directed_edge 2i`` : forward → ``[start, end]`` (ring winding order)
24
+ - ``directed_edge 2i+1`` : backward → ``[end, start]``
25
+
26
+ Parameters
27
+ ----------
28
+ shape : Shape
29
+ The shape to extract directed edge-to-vertex indices from.
30
+
31
+ Returns
32
+ -------
33
+ NDArray[np.int64]
34
+ 2D array of shape ``(2 * n_edges, 2)``. Each row is
35
+ ``[start_vertex, end_vertex]`` for the directed edge.
36
+
37
+ Examples
38
+ --------
39
+ >>> from scadpy import (
40
+ ... get_shape_directed_edge_to_vertex, polygon, square
41
+ ... )
42
+
43
+ >>> # triangle: 3 edges → 6 directed edges
44
+ >>> # (forward/backward interleaved)
45
+ >>> triangle = polygon([(0, 0), (1, 0), (0.5, 1)])
46
+ >>> get_shape_directed_edge_to_vertex(triangle)
47
+ array([[0, 1],
48
+ [1, 0],
49
+ [1, 2],
50
+ [2, 1],
51
+ [2, 0],
52
+ [0, 2]])
53
+
54
+ >>> # square: 4 edges → 8 directed edges
55
+ >>> square_shape = square(1)
56
+ >>> get_shape_directed_edge_to_vertex(square_shape).shape
57
+ (8, 2)
58
+ """
59
+ from scadpy.core.assembly import get_assembly_directed_edge_to_vertex
60
+
61
+ return get_assembly_directed_edge_to_vertex(
62
+ edge_to_vertex=shape.edge_to_vertex,
63
+ )
@@ -0,0 +1,11 @@
1
+ __all__ = [
2
+ "get_shape_edge_lengths",
3
+ "get_shape_edge_midpoints",
4
+ "get_shape_edge_normals",
5
+ "get_shape_edge_to_vertex",
6
+ ]
7
+
8
+ from .get_shape_edge_lengths import get_shape_edge_lengths
9
+ from .get_shape_edge_midpoints import get_shape_edge_midpoints
10
+ from .get_shape_edge_normals import get_shape_edge_normals
11
+ from .get_shape_edge_to_vertex import get_shape_edge_to_vertex
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_edge_lengths(
15
+ shape: Shape,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ For each edge in the shape, return its length.
19
+
20
+ Parameters
21
+ ----------
22
+ shape : Shape
23
+ The shape to extract edge lengths from.
24
+
25
+ Returns
26
+ -------
27
+ NDArray[np.float64]
28
+ 1D array of shape ``(n_edges,)``, one length per edge.
29
+
30
+ Examples
31
+ --------
32
+ >>> from scadpy import get_shape_edge_lengths, square
33
+
34
+ >>> square_shape = square(2)
35
+ >>> get_shape_edge_lengths(square_shape)
36
+ array([2., 2., 2., 2.])
37
+ """
38
+ from scadpy.core.assembly import get_assembly_edge_lengths
39
+
40
+ return get_assembly_edge_lengths(
41
+ edge_to_vertex=shape.edge_to_vertex,
42
+ vertex_coordinates=shape.vertex_coordinates,
43
+ )
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def get_shape_edge_midpoints(
15
+ shape: Shape,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ For each edge in the shape, return the midpoint between its two vertices.
19
+
20
+ Parameters
21
+ ----------
22
+ shape : Shape
23
+ The shape to extract edge midpoints from.
24
+
25
+ Returns
26
+ -------
27
+ NDArray[np.float64]
28
+ 2D array of shape ``(n_edges, 2)``, one midpoint per edge.
29
+
30
+ Examples
31
+ --------
32
+ >>> from scadpy import get_shape_edge_midpoints, square
33
+
34
+ >>> square_shape = square(2)
35
+ >>> get_shape_edge_midpoints(square_shape)
36
+ array([[ 0., -1.],
37
+ [ 1., 0.],
38
+ [ 0., 1.],
39
+ [-1., 0.]])
40
+ """
41
+ from scadpy.core.assembly import get_assembly_edge_midpoints
42
+
43
+ return get_assembly_edge_midpoints(
44
+ edge_to_vertex=shape.edge_to_vertex,
45
+ vertex_coordinates=shape.vertex_coordinates,
46
+ )