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,38 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from typeguard import typechecked
6
+
7
+ if TYPE_CHECKING:
8
+ from scadpy import Shape
9
+
10
+
11
+ @typechecked
12
+ def is_shape_empty(shape: Shape) -> bool:
13
+ """
14
+ Return whether the shape has no vertices.
15
+
16
+ Parameters
17
+ ----------
18
+ shape : Shape
19
+ The shape to check.
20
+
21
+ Returns
22
+ -------
23
+ bool
24
+ True if the shape has no vertices, False otherwise.
25
+
26
+ Examples
27
+ --------
28
+ >>> from shapely.geometry import Polygon
29
+ >>> from scadpy import Shape, is_shape_empty
30
+
31
+ >>> is_shape_empty(Shape.from_parts([]))
32
+ True
33
+
34
+ >>> polygon = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
35
+ >>> is_shape_empty(Shape.from_geometry(polygon))
36
+ False
37
+ """
38
+ return len(shape.vertex_coordinates) == 0
@@ -0,0 +1,13 @@
1
+ __all__ = [
2
+ "map_dxf_to_shape",
3
+ "map_geometries_to_shape",
4
+ "map_geometry_to_shape",
5
+ "map_parts_to_shape",
6
+ "map_svg_to_shape",
7
+ ]
8
+
9
+ from .map_dxf_to_shape import map_dxf_to_shape
10
+ from .map_geometries_to_shape import map_geometries_to_shape
11
+ from .map_geometry_to_shape import map_geometry_to_shape
12
+ from .map_parts_to_shape import map_parts_to_shape
13
+ from .map_svg_to_shape import map_svg_to_shape
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, cast
6
+ from urllib.request import Request, urlopen
7
+
8
+ from trimesh import load
9
+ from trimesh.path import Path2D
10
+ from typeguard import typechecked
11
+
12
+ if TYPE_CHECKING:
13
+ from scadpy.d2.shape import Shape
14
+
15
+
16
+ @typechecked
17
+ def map_dxf_to_shape(source: str | Path) -> Shape:
18
+ """Load a 2D shape from a DXF file or URL.
19
+
20
+ Closed paths in the DXF are converted to filled polygons. Open paths
21
+ (lines, arcs, splines that do not form a closed region) are ignored.
22
+ ``source`` can be a local file path or an ``http``/``https`` URL.
23
+
24
+ Parameters
25
+ ----------
26
+ source : str or Path
27
+ Path to a local ``.dxf`` file or an HTTP/HTTPS URL pointing to one.
28
+
29
+ Returns
30
+ -------
31
+ Shape
32
+ A new shape whose parts correspond to the closed filled regions
33
+ found in the DXF.
34
+
35
+ Examples
36
+ --------
37
+ >>> from scadpy import map_dxf_to_shape
38
+
39
+ >>> map_dxf_to_shape("https://raw.githubusercontent.com/mikedh/trimesh/main/models/2D/wrench.dxf") # doctest: +SKIP
40
+
41
+ .. render-example::
42
+ :name: map_dxf_to_shape
43
+ :example: map_dxf_to_shape("https://raw.githubusercontent.com/mikedh/trimesh/main/models/2D/wrench.dxf")
44
+ """
45
+ from scadpy import map_geometries_to_shape
46
+
47
+ if isinstance(source, str) and source.startswith(("http://", "https://")):
48
+ req = Request(source, headers={"User-Agent": "ScadPy"})
49
+ with urlopen(req) as response:
50
+ data = BytesIO(response.read())
51
+ path = cast(Path2D, load(data, file_type="dxf"))
52
+ else:
53
+ path = cast(Path2D, load(source))
54
+
55
+ return map_geometries_to_shape(list(path.polygons_full))
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import TYPE_CHECKING
5
+
6
+ from shapely.geometry import Polygon
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def map_geometries_to_shape(geometries: Sequence[Polygon]) -> Shape:
15
+ """Map a sequence of polygons to a shape.
16
+
17
+ Each polygon is validated and oriented via :func:`map_parts_to_shape`.
18
+
19
+ Parameters
20
+ ----------
21
+ geometries : Sequence[Polygon]
22
+ The polygons to map.
23
+
24
+ Returns
25
+ -------
26
+ Shape
27
+ A new shape containing all valid, oriented polygons.
28
+
29
+ Examples
30
+ --------
31
+ >>> from shapely.geometry import Polygon
32
+ >>> from scadpy import map_geometries_to_shape
33
+
34
+ >>> map_geometries_to_shape( # doctest: +SKIP
35
+ ... [Polygon([(0, 0), (4, 0), (4, 4), (0, 4)])]
36
+ ... )
37
+
38
+ .. render-example::
39
+ :name: map_geometries_to_shape
40
+ :example: map_geometries_to_shape([Polygon([(0, 0), (4, 0), (4, 4), (0, 4)])])
41
+ """
42
+ from scadpy.core.part import Part
43
+ from scadpy.d2.shape import Shape
44
+
45
+ return Shape.from_parts([Part[Polygon].from_geometry(g) for g in geometries])
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from shapely.geometry import Polygon
6
+ from typeguard import typechecked
7
+
8
+ if TYPE_CHECKING:
9
+ from scadpy.d2.shape import Shape
10
+
11
+
12
+ @typechecked
13
+ def map_geometry_to_shape(geometry: Polygon) -> Shape:
14
+ """Map a single polygon to a shape.
15
+
16
+ Shortcut for :func:`map_geometries_to_shape` with a single polygon.
17
+
18
+ Parameters
19
+ ----------
20
+ geometry : Polygon
21
+ The polygon to map.
22
+
23
+ Returns
24
+ -------
25
+ Shape
26
+ A new shape containing the single valid, oriented polygon.
27
+
28
+ Examples
29
+ --------
30
+ >>> from shapely.geometry import Polygon
31
+ >>> from scadpy import map_geometry_to_shape
32
+
33
+ >>> map_geometry_to_shape( # doctest: +SKIP
34
+ ... Polygon([(0, 0), (4, 0), (4, 4), (0, 4)])
35
+ ... )
36
+
37
+ .. render-example::
38
+ :name: map_geometry_to_shape
39
+ :example: map_geometry_to_shape(Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]))
40
+ """
41
+ from scadpy.d2.shape.importers import map_geometries_to_shape
42
+
43
+ return map_geometries_to_shape([geometry])
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Sequence
4
+ from typing import TYPE_CHECKING
5
+
6
+ from shapely.geometry import Polygon
7
+ from shapely.validation import make_valid
8
+ from typeguard import typechecked
9
+
10
+ from scadpy.d2.shape.types.utils import shapely_base_geometry_to_shapely_polygons
11
+
12
+ if TYPE_CHECKING:
13
+ from scadpy import Part, Shape
14
+
15
+
16
+ @typechecked
17
+ def map_parts_to_shape(
18
+ parts: Sequence[Part[Polygon]],
19
+ ) -> Shape:
20
+ """Map a sequence of parts to a shape, repairing and orienting each polygon.
21
+
22
+ Each polygon is validated using :func:`shapely.validation.make_valid`. If a
23
+ polygon is invalid, it is repaired and may be split into multiple valid polygons.
24
+ All resulting polygons are oriented counter-clockwise (exterior) and clockwise
25
+ (holes) using :func:`shapely.geometry.polygon.orient`.
26
+
27
+ Parameters
28
+ ----------
29
+ parts : Sequence[Part[Polygon]]
30
+ The parts to map. Each part holds a Shapely polygon and a color.
31
+
32
+ Returns
33
+ -------
34
+ Shape
35
+ A new shape containing all valid, oriented polygons from the input parts.
36
+
37
+ Examples
38
+ --------
39
+ >>> from shapely.geometry import Polygon
40
+ >>> from scadpy import map_parts_to_shape
41
+ >>> from scadpy.core.part import Part
42
+
43
+ >>> map_parts_to_shape( # doctest: +SKIP
44
+ ... [Part.from_geometry(Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]))]
45
+ ... )
46
+
47
+ .. render-example::
48
+ :name: map_parts_to_shape
49
+ :example: map_parts_to_shape([Part.from_geometry(Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]))])
50
+ """
51
+ from scadpy.core.part import Part
52
+ from scadpy.d2.shape import Shape
53
+
54
+ validated: list[Part[Polygon]] = []
55
+ for part in parts:
56
+ geom = make_valid(part.geometry) if not part.geometry.is_valid else part.geometry
57
+ for polygon in shapely_base_geometry_to_shapely_polygons(geom):
58
+ validated.append(Part.from_geometry(polygon, part.color))
59
+
60
+ shape = Shape()
61
+ shape._parts = validated
62
+ return shape
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ from io import BytesIO
4
+ from pathlib import Path
5
+ from typing import TYPE_CHECKING, cast
6
+ from urllib.request import Request, urlopen
7
+
8
+ from trimesh import load
9
+ from trimesh.path import Path2D
10
+ from typeguard import typechecked
11
+
12
+ if TYPE_CHECKING:
13
+ from scadpy.d2.shape import Shape
14
+
15
+
16
+ @typechecked
17
+ def map_svg_to_shape(source: str | Path) -> Shape:
18
+ """Load a 2D shape from an SVG file or URL.
19
+
20
+ Closed paths in the SVG are converted to filled polygons. Open paths
21
+ and decorative elements (text, images, gradients) are ignored.
22
+ ``source`` can be a local file path or an ``http``/``https`` URL.
23
+
24
+ Parameters
25
+ ----------
26
+ source : str or Path
27
+ Path to a local ``.svg`` file or an HTTP/HTTPS URL pointing to one.
28
+
29
+ Returns
30
+ -------
31
+ Shape
32
+ A new shape whose parts correspond to the closed filled regions
33
+ found in the SVG.
34
+
35
+ Examples
36
+ --------
37
+ >>> from scadpy import map_svg_to_shape
38
+
39
+ >>> map_svg_to_shape("https://upload.wikimedia.org/wikipedia/commons/0/04/Pentagon.svg") # doctest: +SKIP
40
+
41
+ .. render-example::
42
+ :name: map_svg_to_shape
43
+ :example: map_svg_to_shape("https://upload.wikimedia.org/wikipedia/commons/0/04/Pentagon.svg")
44
+ """
45
+ from scadpy import map_geometries_to_shape
46
+
47
+ if isinstance(source, str) and source.startswith(("http://", "https://")):
48
+ req = Request(source, headers={"User-Agent": "ScadPy"})
49
+ with urlopen(req) as response:
50
+ data = BytesIO(response.read())
51
+ path = cast(Path2D, load(data, file_type="svg"))
52
+ else:
53
+ path = cast(Path2D, load(source))
54
+
55
+ return map_geometries_to_shape(list(path.polygons_full))
@@ -0,0 +1,6 @@
1
+ __all__ = ["circle", "polygon", "rectangle", "square"]
2
+
3
+ from .circle import circle
4
+ from .polygon import polygon
5
+ from .rectangle import rectangle
6
+ from .square import square
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import numpy as np
6
+ from typeguard import typechecked
7
+
8
+ if TYPE_CHECKING:
9
+ from scadpy.d2.shape import Shape
10
+
11
+
12
+ @typechecked
13
+ def circle(radius: float, segment_count: int = 64) -> Shape:
14
+ """
15
+ Create a circle approximated by a polygon.
16
+
17
+ Parameters
18
+ ----------
19
+ radius : float
20
+ The radius of the circle. Must be strictly positive.
21
+ segment_count : int, optional
22
+ The number of segments used to approximate the circle.
23
+ Higher values produce a smoother shape. Default is 64.
24
+
25
+ Returns
26
+ -------
27
+ Shape
28
+ A :class:`~scadpy.d2.shape.types.Shape` object representing the approximated circle.
29
+
30
+ Notes
31
+ -----
32
+ - The circle is centered at the origin (0, 0).
33
+ - The more segments, the smoother the approximation.
34
+
35
+ Examples
36
+ --------
37
+ >>> from scadpy import circle
38
+
39
+ >>> # circle of radius 5 with default resolution (64 vertices)
40
+ >>> x = circle(5)
41
+ >>> len(x.vertex_coordinates)
42
+ 64
43
+
44
+ >>> # circle of radius 3 with low resolution
45
+ >>> x = circle(3, segment_count=8)
46
+ >>> coords = x.vertex_coordinates[:4]
47
+ >>> coords.round(2) # doctest: +NORMALIZE_WHITESPACE
48
+ array([[ 3. , 0. ],
49
+ [ 2.12, 2.12],
50
+ [ 0. , 3. ],
51
+ [-2.12, 2.12]])
52
+
53
+ >>> # invalid circle (radius <= 0)
54
+ >>> circle(0)
55
+ Traceback (most recent call last):
56
+ ...
57
+ ValueError: Circle radius must be strictly positive.
58
+ """
59
+ from scadpy.d2.shape import polygon
60
+
61
+ if radius <= 0:
62
+ raise ValueError("Circle radius must be strictly positive.")
63
+ if segment_count < 3:
64
+ raise ValueError("Circle vertex segment_count must be at least 3.")
65
+
66
+ angles = np.linspace(0, 2 * np.pi, segment_count, endpoint=False)
67
+ points = np.column_stack((radius * np.cos(angles), radius * np.sin(angles)))
68
+ return polygon(points=points)
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import TYPE_CHECKING
5
+
6
+ from shapely.geometry import Polygon
7
+ from typeguard import typechecked
8
+
9
+ if TYPE_CHECKING:
10
+ from scadpy.d2.shape import Shape
11
+
12
+
13
+ @typechecked
14
+ def polygon(points: Iterable[Iterable[float]]) -> Shape:
15
+ """
16
+ Create a 2D polygon shape from a sequence of points.
17
+
18
+ This function constructs a :class:`~scadpy.d2.shape.types.Shape` from the given 2D points.
19
+ The polygon is automatically closed by connecting the last point to the first.
20
+ If the polygon is self-intersecting, it may be split into multiple parts.
21
+
22
+ Parameters
23
+ ----------
24
+ points : Iterable[Iterable[float]]
25
+ A sequence of coordinate pairs ``[x, y]`` defining the vertices
26
+ of the polygon. At least three points are required.
27
+
28
+ Returns
29
+ -------
30
+ Shape
31
+ A :class:`~scadpy.d2.shape.types.Shape` object representing the polygon.
32
+
33
+ Notes
34
+ -----
35
+ - The polygon is automatically closed by connecting the last point to the first.
36
+ - The input points are converted into 2D coordinates; extra dimensions are ignored.
37
+ - If the polygon is self-intersecting, it may be split into multiple parts.
38
+
39
+ Examples
40
+ --------
41
+ >>> import numpy as np
42
+ >>> from scadpy import polygon
43
+
44
+ >>> # simple triangle
45
+ >>> p = polygon([[0, 0], [1, 0], [0, 1]])
46
+ >>> p.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
47
+ array([[0., 0.],
48
+ [1., 0.],
49
+ [0., 1.]])
50
+
51
+ >>> # square defined manually
52
+ >>> # equivalent to square(2)
53
+ >>> p = polygon([[-1, -1], [1, -1], [1, 1], [-1, 1]])
54
+ >>> p.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
55
+ array([[-1., -1.],
56
+ [ 1., -1.],
57
+ [ 1., 1.],
58
+ [-1., 1.]])
59
+
60
+ >>> # self-intersecting polygon generate multiple parts
61
+ >>> p = polygon([[0, 0], [2, 2], [0, 2], [2, 0]])
62
+ >>> vtop = p.vertex_to_part[:, np.newaxis]
63
+ >>> stacked = np.hstack([vtop, p.vertex_coordinates])
64
+ >>> stacked # doctest: +NORMALIZE_WHITESPACE
65
+ array([[0., 2., 0.],
66
+ [0., 1., 1.],
67
+ [0., 0., 0.],
68
+ [1., 2., 2.],
69
+ [1., 0., 2.],
70
+ [1., 1., 1.]])
71
+
72
+ >>> # invalid polygon (less than 3 points)
73
+ >>> polygon([[0, 0], [1, 1]])
74
+ Traceback (most recent call last):
75
+ ...
76
+ ValueError: A polygon must have at least 3 points
77
+ """
78
+ from scadpy.d2 import resolve_vector_2d
79
+ from scadpy.d2.shape import Shape
80
+
81
+ coords = [tuple(resolve_vector_2d(p, 0)) for p in points]
82
+ if len(coords) < 3:
83
+ raise ValueError("A polygon must have at least 3 points")
84
+
85
+ polygon = Polygon(coords)
86
+ return Shape.from_geometry(polygon)
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from typing import TYPE_CHECKING
5
+
6
+ from typeguard import typechecked
7
+
8
+ if TYPE_CHECKING:
9
+ from scadpy.d2.shape import Shape
10
+
11
+
12
+ @typechecked
13
+ def rectangle(size: Iterable[float]) -> Shape:
14
+ """
15
+ Create a rectangle centered at the origin.
16
+
17
+ Parameters
18
+ ----------
19
+ size : Iterable[float]
20
+ The dimensions of the rectangle as ``[width, height]``.
21
+ Both values must be strictly positive numbers.
22
+
23
+ Returns
24
+ -------
25
+ Shape
26
+ A :class:`~scadpy.d2.shape.types.Shape` object representing the rectangle.
27
+
28
+ Notes
29
+ -----
30
+ - The rectangle is always centered at the origin (0, 0).
31
+ - The edges of the rectangle are aligned with the X and Y axes.
32
+
33
+ Examples
34
+ --------
35
+ >>> from scadpy import rectangle
36
+
37
+ >>> # rectangle 4 units wide and 2 units tall
38
+ >>> x = rectangle([4, 2])
39
+ >>> x.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
40
+ array([[-2., -1.],
41
+ [ 2., -1.],
42
+ [ 2., 1.],
43
+ [-2., 1.]])
44
+
45
+ >>> # equivalent to square(10)
46
+ >>> x = rectangle([10, 10])
47
+ >>> x.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
48
+ array([[-5., -5.],
49
+ [ 5., -5.],
50
+ [ 5., 5.],
51
+ [-5., 5.]])
52
+
53
+ >>> # if one or no dimension is provided,
54
+ >>> # the missing value defaults to 1.0
55
+ >>> x = rectangle([5])
56
+ >>> x.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
57
+ array([[-2.5, -0.5],
58
+ [ 2.5, -0.5],
59
+ [ 2.5, 0.5],
60
+ [-2.5, 0.5]])
61
+
62
+ >>> # invalid rectangle
63
+ >>> rectangle([0, 5])
64
+ Traceback (most recent call last):
65
+ ...
66
+ ValueError: Rectangle dimensions must be strictly positive.
67
+ """
68
+
69
+ from scadpy.d2 import resolve_vector_2d
70
+ from scadpy.d2.shape import polygon
71
+
72
+ width, height = resolve_vector_2d(size, 1.0) # pyright: ignore[reportAny]
73
+
74
+ if width <= 0 or height <= 0:
75
+ raise ValueError("Rectangle dimensions must be strictly positive.")
76
+
77
+ half_width, half_height = width / 2.0, height / 2.0 # pyright: ignore[reportAny]
78
+
79
+ points = [
80
+ (-half_width, -half_height),
81
+ (half_width, -half_height),
82
+ (half_width, half_height),
83
+ (-half_width, half_height),
84
+ ]
85
+ return polygon(points=points)
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from typeguard import typechecked
6
+
7
+ if TYPE_CHECKING:
8
+ from scadpy.d2.shape import Shape
9
+
10
+
11
+ @typechecked
12
+ def square(size: float) -> Shape:
13
+ """
14
+ Creates a square centered at the origin.
15
+
16
+ This function is a convenience wrapper around
17
+ :func:`~scadpy.d2.shape.primitives.rectangle`, with equal width and height.
18
+
19
+ Parameters
20
+ ----------
21
+ size : float
22
+ The length of each side of the square.
23
+
24
+ Returns
25
+ -------
26
+ Shape
27
+ A :class:`~scadpy.d2.shape.types.Shape` object representing the square.
28
+
29
+ Notes
30
+ -----
31
+ - The square is centered at the origin `(0, 0)`.
32
+ - The square is equivalent to calling ``rectangle([size, size])``.
33
+
34
+ Examples
35
+ --------
36
+ >>> from scadpy import square
37
+
38
+ >>> # square of size 1x1
39
+ >>> x = square(1)
40
+ >>> x.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
41
+ array([[-0.5, -0.5],
42
+ [ 0.5, -0.5],
43
+ [ 0.5, 0.5],
44
+ [-0.5, 0.5]])
45
+
46
+ >>> # square of size 5x5
47
+ >>> x = square(5)
48
+ >>> x.vertex_coordinates # doctest: +NORMALIZE_WHITESPACE
49
+ array([[-2.5, -2.5],
50
+ [ 2.5, -2.5],
51
+ [ 2.5, 2.5],
52
+ [-2.5, 2.5]])
53
+ """
54
+
55
+ from scadpy.d2.shape import rectangle
56
+
57
+ return rectangle(size=[size, size])
@@ -0,0 +1,53 @@
1
+ __all__ = [
2
+ "are_shape_corners_convex",
3
+ "get_shape_corner_angles",
4
+ "get_shape_corner_normals",
5
+ "get_shape_corner_to_incoming_directed_edge",
6
+ "get_shape_corner_to_outgoing_directed_edge",
7
+ "get_shape_corner_to_vertex",
8
+ "get_shape_directed_edge_directions",
9
+ "get_shape_directed_edge_to_corner",
10
+ "get_shape_directed_edge_to_edge",
11
+ "get_shape_directed_edge_to_vertex",
12
+ "get_shape_edge_lengths",
13
+ "get_shape_edge_midpoints",
14
+ "get_shape_edge_normals",
15
+ "get_shape_edge_to_vertex",
16
+ "get_shape_part_vertex_coordinates",
17
+ "get_shape_ring_to_part",
18
+ "get_shape_ring_types",
19
+ "get_shape_vertex_coordinates",
20
+ "get_shape_vertex_to_part",
21
+ "get_shape_vertex_to_ring",
22
+ ]
23
+
24
+ from .corner import (
25
+ are_shape_corners_convex,
26
+ get_shape_corner_angles,
27
+ get_shape_corner_normals,
28
+ get_shape_corner_to_incoming_directed_edge,
29
+ get_shape_corner_to_outgoing_directed_edge,
30
+ get_shape_corner_to_vertex,
31
+ )
32
+ from .directed_edge import (
33
+ get_shape_directed_edge_directions,
34
+ get_shape_directed_edge_to_corner,
35
+ get_shape_directed_edge_to_edge,
36
+ get_shape_directed_edge_to_vertex,
37
+ )
38
+ from .edge import (
39
+ get_shape_edge_lengths,
40
+ get_shape_edge_midpoints,
41
+ get_shape_edge_normals,
42
+ get_shape_edge_to_vertex,
43
+ )
44
+ from .ring import (
45
+ get_shape_ring_to_part,
46
+ get_shape_ring_types,
47
+ )
48
+ from .vertex import (
49
+ get_shape_part_vertex_coordinates,
50
+ get_shape_vertex_coordinates,
51
+ get_shape_vertex_to_part,
52
+ get_shape_vertex_to_ring,
53
+ )
@@ -0,0 +1,15 @@
1
+ __all__ = [
2
+ "are_shape_corners_convex",
3
+ "get_shape_corner_angles",
4
+ "get_shape_corner_normals",
5
+ "get_shape_corner_to_incoming_directed_edge",
6
+ "get_shape_corner_to_outgoing_directed_edge",
7
+ "get_shape_corner_to_vertex",
8
+ ]
9
+
10
+ from .are_shape_corners_convex import are_shape_corners_convex
11
+ from .get_shape_corner_angles import get_shape_corner_angles
12
+ from .get_shape_corner_normals import get_shape_corner_normals
13
+ from .get_shape_corner_to_incoming_directed_edge import get_shape_corner_to_incoming_directed_edge
14
+ from .get_shape_corner_to_outgoing_directed_edge import get_shape_corner_to_outgoing_directed_edge
15
+ from .get_shape_corner_to_vertex import get_shape_corner_to_vertex