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,65 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+ from typeguard import typechecked
6
+
7
+
8
+ @typechecked
9
+ def get_assembly_face_corner_to_incoming_directed_edge(
10
+ corner_to_vertex: NDArray[np.int64],
11
+ directed_edge_to_vertex: NDArray[np.int64],
12
+ ) -> NDArray[np.int64]:
13
+ """
14
+ For each corner, return the index of its incoming directed edge.
15
+
16
+ The incoming directed edge of corner ``(prev, curr, next)`` is
17
+ ``prev → curr``.
18
+
19
+ Parameters
20
+ ----------
21
+ corner_to_vertex : NDArray[np.int64]
22
+ 2D array of shape ``(n_corners, 3)``. Each row is
23
+ ``[prev_vertex, curr_vertex, next_vertex]``.
24
+ directed_edge_to_vertex : NDArray[np.int64]
25
+ 2D array of shape ``(n_directed_edges, 2)``. Each row is
26
+ ``[start_vertex, end_vertex]``.
27
+
28
+ Returns
29
+ -------
30
+ NDArray[np.int64]
31
+ 1D array of shape ``(n_corners,)``. Each entry is the index of
32
+ the incoming directed edge for that corner.
33
+
34
+ Examples
35
+ --------
36
+ >>> import numpy as np
37
+ >>> from scadpy import (
38
+ ... get_assembly_face_corner_to_incoming_directed_edge,
39
+ ... )
40
+
41
+ >>> # triangle: directed edges
42
+ >>> # [0→1]=0, [1→0]=1, [1→2]=2, [2→1]=3, [2→0]=4, [0→2]=5
43
+ >>> directed_edge_to_vertex = np.array(
44
+ ... [[0, 1], [1, 0], [1, 2], [2, 1], [2, 0], [0, 2]],
45
+ ... dtype=np.int64,
46
+ ... )
47
+ >>> # corners: (2,0,1), (0,1,2), (1,2,0)
48
+ >>> # incoming: 2→0, 0→1, 1→2
49
+ >>> corner_to_vertex = np.array(
50
+ ... [[2, 0, 1], [0, 1, 2], [1, 2, 0]], dtype=np.int64
51
+ ... )
52
+ >>> get_assembly_face_corner_to_incoming_directed_edge(
53
+ ... corner_to_vertex, directed_edge_to_vertex
54
+ ... )
55
+ array([4, 0, 2])
56
+ """
57
+ if len(corner_to_vertex) == 0:
58
+ return np.empty(0, dtype=np.int64)
59
+
60
+ from scadpy.core.assembly.utils import lookup_pairs
61
+
62
+ return lookup_pairs(
63
+ queries=corner_to_vertex[:, 0:2],
64
+ haystack=directed_edge_to_vertex,
65
+ )
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+ from typeguard import typechecked
6
+
7
+
8
+ @typechecked
9
+ def get_assembly_face_corner_to_outgoing_directed_edge(
10
+ corner_to_vertex: NDArray[np.int64],
11
+ directed_edge_to_vertex: NDArray[np.int64],
12
+ ) -> NDArray[np.int64]:
13
+ """
14
+ For each corner, return the index of its outgoing directed edge.
15
+
16
+ The outgoing directed edge of corner ``(prev, curr, next)`` is
17
+ ``curr → next``.
18
+
19
+ Parameters
20
+ ----------
21
+ corner_to_vertex : NDArray[np.int64]
22
+ 2D array of shape ``(n_corners, 3)``. Each row is
23
+ ``[prev_vertex, curr_vertex, next_vertex]``.
24
+ directed_edge_to_vertex : NDArray[np.int64]
25
+ 2D array of shape ``(n_directed_edges, 2)``. Each row is
26
+ ``[start_vertex, end_vertex]``.
27
+
28
+ Returns
29
+ -------
30
+ NDArray[np.int64]
31
+ 1D array of shape ``(n_corners,)``. Each entry is the index of
32
+ the outgoing directed edge for that corner.
33
+
34
+ Examples
35
+ --------
36
+ >>> import numpy as np
37
+ >>> from scadpy import (
38
+ ... get_assembly_face_corner_to_outgoing_directed_edge,
39
+ ... )
40
+
41
+ >>> # triangle: directed edges
42
+ >>> # [0→1]=0, [1→0]=1, [1→2]=2, [2→1]=3, [2→0]=4, [0→2]=5
43
+ >>> directed_edge_to_vertex = np.array(
44
+ ... [[0, 1], [1, 0], [1, 2], [2, 1], [2, 0], [0, 2]],
45
+ ... dtype=np.int64,
46
+ ... )
47
+ >>> # corners: (2,0,1), (0,1,2), (1,2,0)
48
+ >>> # outgoing: 0→1, 1→2, 2→0
49
+ >>> corner_to_vertex = np.array(
50
+ ... [[2, 0, 1], [0, 1, 2], [1, 2, 0]], dtype=np.int64
51
+ ... )
52
+ >>> get_assembly_face_corner_to_outgoing_directed_edge(
53
+ ... corner_to_vertex, directed_edge_to_vertex
54
+ ... )
55
+ array([0, 2, 4])
56
+ """
57
+ if len(corner_to_vertex) == 0:
58
+ return np.empty(0, dtype=np.int64)
59
+
60
+ from scadpy.core.assembly.utils import lookup_pairs
61
+
62
+ return lookup_pairs(
63
+ queries=corner_to_vertex[:, 1:3],
64
+ haystack=directed_edge_to_vertex,
65
+ )
@@ -0,0 +1,79 @@
1
+ from __future__ import annotations
2
+
3
+ import numpy as np
4
+ from numpy.typing import NDArray
5
+ from typeguard import typechecked
6
+
7
+
8
+ @typechecked
9
+ def get_assembly_face_directed_edge_to_corner(
10
+ corner_to_outgoing_directed_edge: NDArray[np.int64],
11
+ corner_to_incoming_directed_edge: NDArray[np.int64],
12
+ ) -> NDArray[np.int64]:
13
+ """
14
+ For each directed edge, return the indices of its source and target corners.
15
+
16
+ The source corner of a directed edge ``curr → next`` is the corner
17
+ ``(prev, curr, next)`` — the one that *emits* the directed edge as outgoing.
18
+ The target corner is the corner ``(curr, next, next_next)`` — the one that
19
+ *receives* it as incoming.
20
+
21
+ Parameters
22
+ ----------
23
+ corner_to_outgoing_directed_edge : NDArray[np.int64]
24
+ 1D array of shape ``(n_corners,)``. Each entry is the index of the
25
+ outgoing directed edge for that corner.
26
+ corner_to_incoming_directed_edge : NDArray[np.int64]
27
+ 1D array of shape ``(n_corners,)``. Each entry is the index of the
28
+ incoming directed edge for that corner.
29
+
30
+ Returns
31
+ -------
32
+ NDArray[np.int64]
33
+ 2D array of shape ``(n_directed_edges, 2)``. Each row is
34
+ ``[source_corner, target_corner]``. Column 0 is the corner that emits
35
+ the directed edge (outgoing), column 1 is the corner that receives it
36
+ (incoming).
37
+
38
+ Examples
39
+ --------
40
+ >>> import numpy as np
41
+ >>> from scadpy import get_assembly_face_directed_edge_to_corner
42
+
43
+ >>> # triangle: 3 corners, 3 edges → 6 directed edges
44
+ >>> # corners: (2,0,1)=0, (0,1,2)=1, (1,2,0)=2
45
+ >>> # outgoing: corner 0 → de 0 (0→1),
46
+ >>> # corner 1 → de 2 (1→2), corner 2 → de 4 (2→0)
47
+ >>> # incoming: corner 0 → de 4 (2→0),
48
+ >>> # corner 1 → de 0 (0→1), corner 2 → de 2 (1→2)
49
+ >>> corner_to_outgoing = np.array([0, 2, 4], dtype=np.int64)
50
+ >>> corner_to_incoming = np.array([4, 0, 2], dtype=np.int64)
51
+ >>> get_assembly_face_directed_edge_to_corner(
52
+ ... corner_to_outgoing, corner_to_incoming
53
+ ... )
54
+ array([[0, 1],
55
+ [1, 0],
56
+ [1, 2],
57
+ [2, 1],
58
+ [2, 0],
59
+ [0, 2]])
60
+ """
61
+ if len(corner_to_outgoing_directed_edge) == 0:
62
+ return np.empty((0, 2), dtype=np.int64)
63
+
64
+ n_corners = len(corner_to_outgoing_directed_edge)
65
+ n_directed_edges = n_corners * 2
66
+ source_corner = np.empty(n_directed_edges, dtype=np.int64)
67
+ target_corner = np.empty(n_directed_edges, dtype=np.int64)
68
+
69
+ corner_indices = np.arange(n_corners, dtype=np.int64)
70
+
71
+ # Forward directed edges: source = corner that emits (outgoing), target = corner that receives (incoming)
72
+ source_corner[corner_to_outgoing_directed_edge] = corner_indices
73
+ target_corner[corner_to_incoming_directed_edge] = corner_indices
74
+
75
+ # Backward directed edges (index ^ 1): source/target are swapped vs forward
76
+ source_corner[corner_to_outgoing_directed_edge ^ 1] = target_corner[corner_to_outgoing_directed_edge]
77
+ target_corner[corner_to_outgoing_directed_edge ^ 1] = corner_indices
78
+
79
+ return np.stack([source_corner, target_corner], axis=1)
@@ -0,0 +1,5 @@
1
+ __all__ = [
2
+ "get_assembly_part_colors",
3
+ ]
4
+
5
+ from .get_assembly_part_colors import get_assembly_part_colors
@@ -0,0 +1,55 @@
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.core.assembly import Assembly
11
+
12
+
13
+ @typechecked
14
+ def get_assembly_part_colors[G](
15
+ assembly: Assembly[G],
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ For each part in the assembly, return its color (r, g, b, a).
19
+
20
+ Parameters
21
+ ----------
22
+ assembly : VertexableAssembly[G]
23
+ The assembly object to extract part colors from.
24
+
25
+ Returns
26
+ -------
27
+ NDArray[np.float64]
28
+ 2D array of shape (n_parts, 4), one row per part.
29
+
30
+ Examples
31
+ --------
32
+ >>> from shapely.geometry import Polygon
33
+ >>> from scadpy import (
34
+ ... BLUE, RED, get_assembly_part_colors, Part, Shape
35
+ ... )
36
+ ...
37
+ >>> polygon1 = Polygon(
38
+ ... shell=[(0, 0), (2, 0), (2, 2), (0, 2)],
39
+ ... holes=[[(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]]
40
+ ... )
41
+ >>> polygon2 = Polygon(
42
+ ... shell=[(10, 10), (12, 10), (12, 12), (10, 12)]
43
+ ... )
44
+ >>> get_assembly_part_colors(
45
+ ... Shape.from_parts([
46
+ ... Part[Polygon].from_geometry(polygon1, BLUE),
47
+ ... Part[Polygon].from_geometry(polygon2, RED)
48
+ ... ]),
49
+ ... ) # doctest: +NORMALIZE_WHITESPACE
50
+ array([[0.1, 0.3, 0.9, 1. ],
51
+ [0.9, 0.1, 0.1, 1. ]])
52
+ """
53
+ if not assembly._parts:
54
+ return np.empty((0, 4), dtype=np.float64)
55
+ return np.array([p.color for p in assembly._parts])
@@ -0,0 +1,7 @@
1
+ __all__ = [
2
+ "get_assembly_vertex_coordinates",
3
+ "get_assembly_vertex_to_part",
4
+ ]
5
+
6
+ from .get_assembly_vertex_coordinates import get_assembly_vertex_coordinates
7
+ from .get_assembly_vertex_to_part import get_assembly_vertex_to_part
@@ -0,0 +1,70 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.core.part import Part
11
+
12
+
13
+ # @typechecked is intentionally omitted: typeguard v4 cannot validate Callable types
14
+ # that contain generic type variables (e.g. Part[G]) at runtime.
15
+ def get_assembly_vertex_coordinates[G](
16
+ parts: Sequence[Part[G]],
17
+ get_part_vertex_coordinates: Callable[[Part[G]], NDArray[np.float64]],
18
+ dimensions: int,
19
+ ) -> NDArray[np.float64]:
20
+ """
21
+ For each vertex in the assembly, return its coordinates.
22
+
23
+ Parameters
24
+ ----------
25
+ parts : Sequence[Part[G]]
26
+ The parts of the assembly.
27
+ get_part_vertex_coordinates : Callable[[Part[G]], NDArray[np.float64]]
28
+ Function that extracts vertex coordinates from a single part.
29
+ dimensions : int
30
+ Number of spatial dimensions (used when ``parts`` is empty).
31
+
32
+ Returns
33
+ -------
34
+ NDArray[np.float64]
35
+ 2D array of shape (n_vertices, dimensions), one row per vertex.
36
+
37
+ Examples
38
+ --------
39
+ >>> from shapely.geometry import Polygon
40
+ >>> from scadpy import (
41
+ ... get_assembly_vertex_coordinates,
42
+ ... get_shape_part_vertex_coordinates,
43
+ ... Shape,
44
+ ... )
45
+
46
+ >>> polygon1 = Polygon(
47
+ ... shell=[(0, 0), (2, 0), (2, 2), (0, 2)],
48
+ ... holes=[[(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]]
49
+ ... )
50
+ >>> polygon2 = Polygon(
51
+ ... shell=[(10, 10), (12, 10), (12, 12), (10, 12)]
52
+ ... )
53
+ >>> shape = Shape.from_geometries([polygon1, polygon2])
54
+ >>> get_assembly_vertex_coordinates(
55
+ ... shape._parts,
56
+ ... get_shape_part_vertex_coordinates,
57
+ ... 2,
58
+ ... ) # doctest: +NORMALIZE_WHITESPACE
59
+ array([[ 0. , 0. ],
60
+ [ 2. , 0. ],
61
+ [ 2. , 2. ],
62
+ ...
63
+ [12. , 10. ],
64
+ [12. , 12. ],
65
+ [10. , 12. ]])
66
+ """
67
+ if not parts:
68
+ return np.empty((0, dimensions), dtype=np.float64)
69
+
70
+ return np.vstack([get_part_vertex_coordinates(p) for p in parts])
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Sequence
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+ from numpy.typing import NDArray
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.core.part import Part
11
+
12
+
13
+ # @typechecked is intentionally omitted: typeguard v4 cannot validate Callable types
14
+ # that contain generic type variables (e.g. Part[G]) at runtime.
15
+ def get_assembly_vertex_to_part[G](
16
+ parts: Sequence[Part[G]],
17
+ get_part_vertex_coordinates: Callable[[Part[G]], NDArray[np.float64]],
18
+ ) -> NDArray[np.int64]:
19
+ """
20
+ For each vertex in the assembly, return its part index.
21
+
22
+ Parameters
23
+ ----------
24
+ parts : Sequence[Part[G]]
25
+ The parts of the assembly.
26
+ get_part_vertex_coordinates : Callable[[Part[G]], NDArray[np.float64]]
27
+ Function that extracts vertex coordinates from a single part.
28
+
29
+ Returns
30
+ -------
31
+ NDArray[np.int64]
32
+ 1D array of shape (n_vertices,), one element per vertex.
33
+
34
+ Examples
35
+ --------
36
+ >>> from shapely.geometry import Polygon
37
+ >>> from scadpy import (
38
+ ... get_assembly_vertex_to_part,
39
+ ... get_shape_part_vertex_coordinates,
40
+ ... Shape,
41
+ ... )
42
+
43
+ >>> polygon1 = Polygon(
44
+ ... shell=[(0, 0), (2, 0), (2, 2), (0, 2)],
45
+ ... holes=[[(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]]
46
+ ... )
47
+ >>> polygon2 = Polygon(
48
+ ... shell=[(10, 10), (12, 10), (12, 12), (10, 12)]
49
+ ... )
50
+ >>> shape = Shape.from_geometries([polygon1, polygon2])
51
+ >>> get_assembly_vertex_to_part(
52
+ ... shape._parts,
53
+ ... get_shape_part_vertex_coordinates,
54
+ ... ) # doctest: +NORMALIZE_WHITESPACE
55
+ array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1])
56
+ """
57
+ if not parts:
58
+ return np.array([], dtype=np.int64)
59
+ return np.concatenate([
60
+ np.full(len(get_part_vertex_coordinates(p)), i, dtype=np.int64)
61
+ for i, p in enumerate(parts)
62
+ ])
@@ -0,0 +1,19 @@
1
+ __all__ = [
2
+ "color_assembly",
3
+ "mirror_vertex_coordinates",
4
+ "pull_vertex_coordinates",
5
+ "push_vertex_coordinates",
6
+ "resize_vertex_coordinates",
7
+ "rotate_vertex_coordinates",
8
+ "scale_vertex_coordinates",
9
+ "translate_vertex_coordinates",
10
+ ]
11
+
12
+ from .color_assembly import color_assembly
13
+ from .mirror_vertex_coordinates import mirror_vertex_coordinates
14
+ from .pull_vertex_coordinates import pull_vertex_coordinates
15
+ from .push_vertex_coordinates import push_vertex_coordinates
16
+ from .resize_vertex_coordinates import resize_vertex_coordinates
17
+ from .rotate_vertex_coordinates import rotate_vertex_coordinates
18
+ from .scale_vertex_coordinates import scale_vertex_coordinates
19
+ from .translate_vertex_coordinates import translate_vertex_coordinates
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable, Iterable, Sequence
4
+ from typing import TYPE_CHECKING
5
+
6
+ from typeguard import typechecked
7
+
8
+ if TYPE_CHECKING:
9
+ from scadpy.color import Color
10
+ from scadpy.core.part import Part
11
+
12
+
13
+ @typechecked
14
+ def color_assembly[A, G](
15
+ assembly: A,
16
+ color: Color,
17
+ get_assembly_parts: Callable[[A], Iterable[Part[G]]],
18
+ concat_parts: Callable[[Sequence[Part[G]]], A],
19
+ ) -> A:
20
+ from scadpy.core.part import Part
21
+
22
+ return concat_parts(
23
+ [Part[G].from_geometry(p.geometry, color) for p in get_assembly_parts(assembly)]
24
+ )
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+
10
+ @typechecked
11
+ def mirror_vertex_coordinates(
12
+ vertex_coordinates: NDArray[np.float64],
13
+ normal: float | Iterable[float],
14
+ pivot: float | Iterable[float] = 0,
15
+ ) -> NDArray[np.float64]:
16
+ """
17
+ Mirror vertex coordinates across a line (2D) or plane (3D) defined by a normal vector and a pivot point.
18
+
19
+ Parameters
20
+ ----------
21
+ vertex_coordinates : NDArray[np.float64]
22
+ 2D array of shape (n_vertices, dimensions).
23
+ normal : float | Iterable[float]
24
+ The normal vector of the mirror line (2D) or plane (3D). Does not need to be normalized.
25
+ If a single float is provided, it will be broadcast to all coordinate dimensions.
26
+ pivot : float | Iterable[float], default=0
27
+ The point through which the mirror line/plane passes. If a single float is provided, it will be broadcast to all coordinate dimensions.
28
+ Defaults to 0 (the origin).
29
+
30
+ Returns
31
+ -------
32
+ NDArray[np.float64]
33
+ Array of shape (n_vertices, dimensions), one row per vertex.
34
+
35
+ Examples
36
+ --------
37
+ >>> from shapely.geometry import Polygon
38
+ >>> from scadpy import mirror_vertex_coordinates, Shape
39
+
40
+ >>> polygon = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
41
+ >>> shape = Shape.from_geometries([polygon])
42
+ >>> mirror_vertex_coordinates(
43
+ ... shape.vertex_coordinates,
44
+ ... normal=[1, 0], # Mirror across y-axis
45
+ ... pivot=[1, 0]
46
+ ... ) # doctest: +NORMALIZE_WHITESPACE
47
+ array([[2., 0.],
48
+ [0., 0.],
49
+ [0., 2.],
50
+ [2., 2.]])
51
+ """
52
+ from scadpy import resolve_vector
53
+
54
+ dimensions: int = vertex_coordinates.shape[1]
55
+
56
+ normal = resolve_vector(normal, 0, dimensions)
57
+ pivot = resolve_vector(pivot, 0, dimensions)
58
+
59
+ normal = normal / np.linalg.norm(normal)
60
+
61
+ # vector from pivot to each vertex
62
+ v = vertex_coordinates - pivot
63
+ # project v onto normal
64
+ projection = np.dot(v, normal)
65
+ # reflection formula
66
+ mirrored = vertex_coordinates - 2 * projection[:, np.newaxis] * normal
67
+
68
+ return mirrored
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+
10
+ @typechecked
11
+ def pull_vertex_coordinates(
12
+ vertex_coordinates: NDArray[np.float64],
13
+ distance: float,
14
+ pivot: float | Iterable[float] = 0,
15
+ vertex_filter: NDArray[np.bool_] | None = None,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ Move selected vertices toward a pivot point by at most ``distance`` units.
19
+
20
+ Each selected vertex is translated in the direction of the pivot by at most
21
+ ``distance``. Vertices already closer than ``distance`` to the pivot are moved
22
+ exactly to the pivot.
23
+
24
+ Parameters
25
+ ----------
26
+ vertex_coordinates : NDArray[np.float64]
27
+ 2D array of shape (n_vertices, dimensions).
28
+ distance : float
29
+ The maximum distance each vertex is moved toward the pivot.
30
+ pivot : float | Iterable[float], default=0
31
+ The point vertices are pulled toward. If a single float is provided,
32
+ it is broadcast to all coordinate dimensions. Defaults to the origin.
33
+ vertex_filter : NDArray[np.bool_] | None, default=None
34
+ Boolean array selecting which vertices are moved. If ``None``, all
35
+ vertices are moved.
36
+
37
+ Returns
38
+ -------
39
+ NDArray[np.float64]
40
+ Array of shape (n_vertices, dimensions), one row per vertex.
41
+
42
+ See Also
43
+ --------
44
+ push_vertex_coordinates : Move vertices away from a pivot point.
45
+ """
46
+ from scadpy import resolve_vector
47
+
48
+ dimensions: int = vertex_coordinates.shape[1]
49
+ pivot_array = resolve_vector(pivot, 0, dimensions)
50
+
51
+ coords = vertex_coordinates if vertex_filter is None else vertex_coordinates[vertex_filter]
52
+ vectors = pivot_array - coords
53
+ lengths = np.linalg.norm(vectors, axis=1, keepdims=True)
54
+ directions = np.divide(
55
+ vectors, lengths, out=np.zeros_like(vectors), where=lengths != 0
56
+ )
57
+ translations = directions * np.minimum(distance, lengths)
58
+
59
+ if vertex_filter is None:
60
+ return vertex_coordinates + translations
61
+
62
+ result = np.array(vertex_coordinates)
63
+ result[vertex_filter] += translations
64
+ return result
@@ -0,0 +1,64 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+
5
+ import numpy as np
6
+ from numpy.typing import NDArray
7
+ from typeguard import typechecked
8
+
9
+
10
+ @typechecked
11
+ def push_vertex_coordinates(
12
+ vertex_coordinates: NDArray[np.float64],
13
+ distance: float,
14
+ pivot: float | Iterable[float] = 0,
15
+ vertex_filter: NDArray[np.bool_] | None = None,
16
+ ) -> NDArray[np.float64]:
17
+ """
18
+ Move selected vertices away from a pivot point by ``distance`` units.
19
+
20
+ Each selected vertex is translated in the direction away from the pivot by
21
+ exactly ``distance``. Vertices located exactly at the pivot are not moved
22
+ (undefined direction).
23
+
24
+ Parameters
25
+ ----------
26
+ vertex_coordinates : NDArray[np.float64]
27
+ 2D array of shape (n_vertices, dimensions).
28
+ distance : float
29
+ The distance each vertex is moved away from the pivot.
30
+ pivot : float | Iterable[float], default=0
31
+ The point vertices are pushed away from. If a single float is provided,
32
+ it is broadcast to all coordinate dimensions. Defaults to the origin.
33
+ vertex_filter : NDArray[np.bool_] | None, default=None
34
+ Boolean array selecting which vertices are moved. If ``None``, all
35
+ vertices are moved.
36
+
37
+ Returns
38
+ -------
39
+ NDArray[np.float64]
40
+ Array of shape (n_vertices, dimensions), one row per vertex.
41
+
42
+ See Also
43
+ --------
44
+ pull_vertex_coordinates : Move vertices toward a pivot point.
45
+ """
46
+ from scadpy import resolve_vector
47
+
48
+ dimensions: int = vertex_coordinates.shape[1]
49
+ pivot_array = resolve_vector(pivot, 0, dimensions)
50
+
51
+ coords = vertex_coordinates if vertex_filter is None else vertex_coordinates[vertex_filter]
52
+ vectors = coords - pivot_array
53
+ lengths = np.linalg.norm(vectors, axis=1, keepdims=True)
54
+ directions = np.divide(
55
+ vectors, lengths, out=np.zeros_like(vectors), where=lengths != 0
56
+ )
57
+ translations = directions * distance
58
+
59
+ if vertex_filter is None:
60
+ return vertex_coordinates + translations
61
+
62
+ result = np.array(vertex_coordinates)
63
+ result[vertex_filter] += translations
64
+ return result