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.
- scadpy/__init__.py +5 -0
- scadpy/color/__init__.py +3 -0
- scadpy/color/constants/BEIGE.py +3 -0
- scadpy/color/constants/BLACK.py +3 -0
- scadpy/color/constants/BLUE.py +3 -0
- scadpy/color/constants/BROWN.py +3 -0
- scadpy/color/constants/DARK_GRAY.py +3 -0
- scadpy/color/constants/DEFAULT_COLOR.py +3 -0
- scadpy/color/constants/DEFAULT_OPACITY.py +1 -0
- scadpy/color/constants/GRAY.py +3 -0
- scadpy/color/constants/GREEN.py +3 -0
- scadpy/color/constants/ORANGE.py +3 -0
- scadpy/color/constants/RED.py +3 -0
- scadpy/color/constants/WHITE.py +3 -0
- scadpy/color/constants/YELLOW.py +3 -0
- scadpy/color/constants/__init__.py +29 -0
- scadpy/color/type/__init__.py +3 -0
- scadpy/color/type/color.py +3 -0
- scadpy/color/utils/__init__.py +3 -0
- scadpy/color/utils/get_random_color.py +36 -0
- scadpy/core/__init__.py +3 -0
- scadpy/core/assembly/__init__.py +5 -0
- scadpy/core/assembly/combinations/__init__.py +13 -0
- scadpy/core/assembly/combinations/concat_assemblies.py +50 -0
- scadpy/core/assembly/combinations/exclude_assemblies.py +135 -0
- scadpy/core/assembly/combinations/intersect_assemblies.py +128 -0
- scadpy/core/assembly/combinations/subtract_assemblies.py +151 -0
- scadpy/core/assembly/combinations/unify_assemblies.py +59 -0
- scadpy/core/assembly/topologies/__init__.py +41 -0
- scadpy/core/assembly/topologies/directed_edge/__init__.py +9 -0
- scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_directions.py +70 -0
- scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_edge.py +49 -0
- scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_vertex.py +54 -0
- scadpy/core/assembly/topologies/edge/__init__.py +9 -0
- scadpy/core/assembly/topologies/edge/get_assembly_edge_lengths.py +46 -0
- scadpy/core/assembly/topologies/edge/get_assembly_edge_midpoints.py +51 -0
- scadpy/core/assembly/topologies/edge/get_assembly_edge_normals.py +67 -0
- scadpy/core/assembly/topologies/face_corner/__init__.py +13 -0
- scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_angles.py +72 -0
- scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_normals.py +103 -0
- scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_incoming_directed_edge.py +65 -0
- scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_outgoing_directed_edge.py +65 -0
- scadpy/core/assembly/topologies/face_corner/get_assembly_face_directed_edge_to_corner.py +79 -0
- scadpy/core/assembly/topologies/part/__init__.py +5 -0
- scadpy/core/assembly/topologies/part/get_assembly_part_colors.py +55 -0
- scadpy/core/assembly/topologies/vertex/__init__.py +7 -0
- scadpy/core/assembly/topologies/vertex/get_assembly_vertex_coordinates.py +70 -0
- scadpy/core/assembly/topologies/vertex/get_assembly_vertex_to_part.py +62 -0
- scadpy/core/assembly/transformations/__init__.py +19 -0
- scadpy/core/assembly/transformations/color_assembly.py +24 -0
- scadpy/core/assembly/transformations/mirror_vertex_coordinates.py +68 -0
- scadpy/core/assembly/transformations/pull_vertex_coordinates.py +64 -0
- scadpy/core/assembly/transformations/push_vertex_coordinates.py +64 -0
- scadpy/core/assembly/transformations/resize_vertex_coordinates.py +121 -0
- scadpy/core/assembly/transformations/rotate_vertex_coordinates.py +73 -0
- scadpy/core/assembly/transformations/scale_vertex_coordinates.py +76 -0
- scadpy/core/assembly/transformations/translate_vertex_coordinates.py +70 -0
- scadpy/core/assembly/types/__init__.py +7 -0
- scadpy/core/assembly/types/assembly.py +14 -0
- scadpy/core/assembly/types/topology_filter.py +6 -0
- scadpy/core/assembly/utils/__init__.py +9 -0
- scadpy/core/assembly/utils/lookup_pairs.py +56 -0
- scadpy/core/assembly/utils/resolve_topology_filter.py +84 -0
- scadpy/core/assembly/utils/transform_filtered_parts.py +55 -0
- scadpy/core/component/__init__.py +3 -0
- scadpy/core/component/exporters/__init__.py +7 -0
- scadpy/core/component/exporters/map_component_to_html_file.py +47 -0
- scadpy/core/component/exporters/map_component_to_screen.py +63 -0
- scadpy/core/component/features/__init__.py +5 -0
- scadpy/core/component/features/get_component_bounds.py +38 -0
- scadpy/core/component/utils/__init__.py +9 -0
- scadpy/core/component/utils/blend_component_colors.py +77 -0
- scadpy/core/component/utils/get_intersecting_component_index_groups.py +108 -0
- scadpy/core/part/__init__.py +3 -0
- scadpy/core/part/combinations/__init__.py +11 -0
- scadpy/core/part/combinations/concat_parts.py +48 -0
- scadpy/core/part/combinations/intersect_parts.py +147 -0
- scadpy/core/part/combinations/subtract_parts.py +94 -0
- scadpy/core/part/combinations/unify_parts.py +143 -0
- scadpy/core/part/types/__init__.py +5 -0
- scadpy/core/part/types/part.py +34 -0
- scadpy/core/part/utils/__init__.py +5 -0
- scadpy/core/part/utils/blend_part_colors.py +32 -0
- scadpy/d2/__init__.py +2 -0
- scadpy/d2/shape/__init__.py +9 -0
- scadpy/d2/shape/combinations/__init__.py +21 -0
- scadpy/d2/shape/combinations/are_shape_parts_intersecting.py +49 -0
- scadpy/d2/shape/combinations/concat_shape.py +48 -0
- scadpy/d2/shape/combinations/exclude_shape.py +71 -0
- scadpy/d2/shape/combinations/intersect_shape.py +64 -0
- scadpy/d2/shape/combinations/intersect_shape_parts.py +71 -0
- scadpy/d2/shape/combinations/subtract_shape.py +72 -0
- scadpy/d2/shape/combinations/subtract_shape_parts.py +66 -0
- scadpy/d2/shape/combinations/unify_shape.py +51 -0
- scadpy/d2/shape/combinations/unify_shape_parts.py +74 -0
- scadpy/d2/shape/exporters/__init__.py +17 -0
- scadpy/d2/shape/exporters/map_shape_to_dxf.py +43 -0
- scadpy/d2/shape/exporters/map_shape_to_dxf_file.py +38 -0
- scadpy/d2/shape/exporters/map_shape_to_html.py +117 -0
- scadpy/d2/shape/exporters/map_shape_to_html_file.py +58 -0
- scadpy/d2/shape/exporters/map_shape_to_screen.py +51 -0
- scadpy/d2/shape/exporters/map_shape_to_svg.py +40 -0
- scadpy/d2/shape/exporters/map_shape_to_svg_file.py +38 -0
- scadpy/d2/shape/features/__init__.py +9 -0
- scadpy/d2/shape/features/get_shape_bounds.py +40 -0
- scadpy/d2/shape/features/get_shape_part_bounds.py +37 -0
- scadpy/d2/shape/features/is_shape_empty.py +38 -0
- scadpy/d2/shape/importers/__init__.py +13 -0
- scadpy/d2/shape/importers/map_dxf_to_shape.py +55 -0
- scadpy/d2/shape/importers/map_geometries_to_shape.py +45 -0
- scadpy/d2/shape/importers/map_geometry_to_shape.py +43 -0
- scadpy/d2/shape/importers/map_parts_to_shape.py +62 -0
- scadpy/d2/shape/importers/map_svg_to_shape.py +55 -0
- scadpy/d2/shape/primitives/__init__.py +6 -0
- scadpy/d2/shape/primitives/circle.py +68 -0
- scadpy/d2/shape/primitives/polygon.py +86 -0
- scadpy/d2/shape/primitives/rectangle.py +85 -0
- scadpy/d2/shape/primitives/square.py +57 -0
- scadpy/d2/shape/topologies/__init__.py +53 -0
- scadpy/d2/shape/topologies/corner/__init__.py +15 -0
- scadpy/d2/shape/topologies/corner/are_shape_corners_convex.py +75 -0
- scadpy/d2/shape/topologies/corner/get_shape_corner_angles.py +58 -0
- scadpy/d2/shape/topologies/corner/get_shape_corner_normals.py +82 -0
- scadpy/d2/shape/topologies/corner/get_shape_corner_to_incoming_directed_edge.py +39 -0
- scadpy/d2/shape/topologies/corner/get_shape_corner_to_outgoing_directed_edge.py +39 -0
- scadpy/d2/shape/topologies/corner/get_shape_corner_to_vertex.py +65 -0
- scadpy/d2/shape/topologies/directed_edge/__init__.py +11 -0
- scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_directions.py +44 -0
- scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_corner.py +41 -0
- scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_edge.py +51 -0
- scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_vertex.py +63 -0
- scadpy/d2/shape/topologies/edge/__init__.py +11 -0
- scadpy/d2/shape/topologies/edge/get_shape_edge_lengths.py +43 -0
- scadpy/d2/shape/topologies/edge/get_shape_edge_midpoints.py +46 -0
- scadpy/d2/shape/topologies/edge/get_shape_edge_normals.py +40 -0
- scadpy/d2/shape/topologies/edge/get_shape_edge_to_vertex.py +71 -0
- scadpy/d2/shape/topologies/ring/__init__.py +7 -0
- scadpy/d2/shape/topologies/ring/get_shape_ring_to_part.py +46 -0
- scadpy/d2/shape/topologies/ring/get_shape_ring_types.py +46 -0
- scadpy/d2/shape/topologies/vertex/__init__.py +11 -0
- scadpy/d2/shape/topologies/vertex/get_shape_part_vertex_coordinates.py +62 -0
- scadpy/d2/shape/topologies/vertex/get_shape_vertex_coordinates.py +44 -0
- scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_part.py +42 -0
- scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_ring.py +63 -0
- scadpy/d2/shape/transformations/__init__.py +43 -0
- scadpy/d2/shape/transformations/chamfer_shape.py +259 -0
- scadpy/d2/shape/transformations/color_shape.py +46 -0
- scadpy/d2/shape/transformations/convexify_shape.py +79 -0
- scadpy/d2/shape/transformations/fill_shape.py +68 -0
- scadpy/d2/shape/transformations/fillet_shape.py +289 -0
- scadpy/d2/shape/transformations/grow_shape.py +82 -0
- scadpy/d2/shape/transformations/linear_cut_shape.py +116 -0
- scadpy/d2/shape/transformations/linear_extrude_shape.py +60 -0
- scadpy/d2/shape/transformations/linear_slice_shape.py +144 -0
- scadpy/d2/shape/transformations/mirror_shape.py +53 -0
- scadpy/d2/shape/transformations/pull_shape.py +67 -0
- scadpy/d2/shape/transformations/push_shape.py +67 -0
- scadpy/d2/shape/transformations/radial_extrude_shape.py +285 -0
- scadpy/d2/shape/transformations/radial_slice_shape.py +132 -0
- scadpy/d2/shape/transformations/recoordinate_shape.py +82 -0
- scadpy/d2/shape/transformations/resize_shape.py +91 -0
- scadpy/d2/shape/transformations/rotate_shape.py +63 -0
- scadpy/d2/shape/transformations/scale_shape.py +58 -0
- scadpy/d2/shape/transformations/shrink_shape.py +69 -0
- scadpy/d2/shape/transformations/translate_shape.py +54 -0
- scadpy/d2/shape/types/__init__.py +3 -0
- scadpy/d2/shape/types/shape.py +792 -0
- scadpy/d2/shape/types/utils/__init__.py +5 -0
- scadpy/d2/shape/types/utils/shapely_base_geometry_to_shapely_polygons.py +25 -0
- scadpy/d2/shape/utils/__init__.py +5 -0
- scadpy/d2/shape/utils/shapely_base_geometry_to_shapely_polygons.py +55 -0
- scadpy/d2/utils/__init__.py +3 -0
- scadpy/d2/utils/resolve_vector_2d.py +50 -0
- scadpy/d3/__init__.py +2 -0
- scadpy/d3/solid/__init__.py +8 -0
- scadpy/d3/solid/combinations/__init__.py +21 -0
- scadpy/d3/solid/combinations/are_solid_parts_intersecting.py +51 -0
- scadpy/d3/solid/combinations/concat_solid.py +48 -0
- scadpy/d3/solid/combinations/exclude_solid.py +71 -0
- scadpy/d3/solid/combinations/intersect_solid.py +64 -0
- scadpy/d3/solid/combinations/intersect_solid_parts.py +73 -0
- scadpy/d3/solid/combinations/subtract_solid.py +72 -0
- scadpy/d3/solid/combinations/subtract_solid_parts.py +68 -0
- scadpy/d3/solid/combinations/unify_solid.py +51 -0
- scadpy/d3/solid/combinations/unify_solid_parts.py +73 -0
- scadpy/d3/solid/exporters/__init__.py +11 -0
- scadpy/d3/solid/exporters/map_solid_to_html.py +318 -0
- scadpy/d3/solid/exporters/map_solid_to_html_file.py +58 -0
- scadpy/d3/solid/exporters/map_solid_to_screen.py +51 -0
- scadpy/d3/solid/exporters/map_solid_to_stl_file.py +48 -0
- scadpy/d3/solid/features/__init__.py +11 -0
- scadpy/d3/solid/features/get_solid_bounds.py +37 -0
- scadpy/d3/solid/features/get_solid_part_bounds.py +37 -0
- scadpy/d3/solid/features/get_solid_part_colors.py +39 -0
- scadpy/d3/solid/features/is_solid_empty.py +36 -0
- scadpy/d3/solid/importers/__init__.py +11 -0
- scadpy/d3/solid/importers/map_geometries_to_solid.py +42 -0
- scadpy/d3/solid/importers/map_geometry_to_solid.py +42 -0
- scadpy/d3/solid/importers/map_parts_to_solid.py +66 -0
- scadpy/d3/solid/importers/map_stl_to_solid.py +37 -0
- scadpy/d3/solid/primitives/__init__.py +7 -0
- scadpy/d3/solid/primitives/cone.py +70 -0
- scadpy/d3/solid/primitives/cuboid.py +75 -0
- scadpy/d3/solid/primitives/cylinder.py +73 -0
- scadpy/d3/solid/primitives/polyhedron.py +60 -0
- scadpy/d3/solid/primitives/sphere.py +58 -0
- scadpy/d3/solid/topologies/__init__.py +8 -0
- scadpy/d3/solid/topologies/triangle/__init__.py +5 -0
- scadpy/d3/solid/topologies/triangle/get_solid_triangle_to_vertex.py +49 -0
- scadpy/d3/solid/topologies/vertex/__init__.py +7 -0
- scadpy/d3/solid/topologies/vertex/get_solid_vertex_coordinates.py +39 -0
- scadpy/d3/solid/topologies/vertex/get_solid_vertex_to_part.py +37 -0
- scadpy/d3/solid/transformations/__init__.py +23 -0
- scadpy/d3/solid/transformations/color_solid.py +46 -0
- scadpy/d3/solid/transformations/convexify_solid.py +64 -0
- scadpy/d3/solid/transformations/mirror_solid.py +53 -0
- scadpy/d3/solid/transformations/pull_solid.py +67 -0
- scadpy/d3/solid/transformations/push_solid.py +67 -0
- scadpy/d3/solid/transformations/recoordinate_solid.py +68 -0
- scadpy/d3/solid/transformations/resize_solid.py +92 -0
- scadpy/d3/solid/transformations/rotate_solid.py +93 -0
- scadpy/d3/solid/transformations/scale_solid.py +58 -0
- scadpy/d3/solid/transformations/translate_solid.py +54 -0
- scadpy/d3/solid/types/__init__.py +3 -0
- scadpy/d3/solid/types/solid.py +448 -0
- scadpy/d3/utils/__init__.py +3 -0
- scadpy/d3/utils/resolve_vector_3d.py +50 -0
- scadpy/utils/__init__.py +6 -0
- scadpy/utils/resolve_vector.py +64 -0
- scadpy/utils/x.py +38 -0
- scadpy/utils/y.py +38 -0
- scadpy/utils/z.py +38 -0
- scadpy-0.1.0.dist-info/METADATA +282 -0
- scadpy-0.1.0.dist-info/RECORD +236 -0
- scadpy-0.1.0.dist-info/WHEEL +4 -0
- scadpy-0.1.0.dist-info/licenses/LICENSE.md +43 -0
scadpy/__init__.py
ADDED
scadpy/color/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
DEFAULT_OPACITY = 1
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"BEIGE",
|
|
3
|
+
"BLACK",
|
|
4
|
+
"BLUE",
|
|
5
|
+
"BROWN",
|
|
6
|
+
"DEFAULT_COLOR",
|
|
7
|
+
"DEFAULT_OPACITY",
|
|
8
|
+
"DARK_GRAY",
|
|
9
|
+
"GRAY",
|
|
10
|
+
"GREEN",
|
|
11
|
+
"ORANGE",
|
|
12
|
+
"RED",
|
|
13
|
+
"WHITE",
|
|
14
|
+
"YELLOW",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
from .BEIGE import BEIGE
|
|
18
|
+
from .BLACK import BLACK
|
|
19
|
+
from .BLUE import BLUE
|
|
20
|
+
from .BROWN import BROWN
|
|
21
|
+
from .DEFAULT_COLOR import DEFAULT_COLOR
|
|
22
|
+
from .DARK_GRAY import DARK_GRAY
|
|
23
|
+
from .DEFAULT_OPACITY import DEFAULT_OPACITY
|
|
24
|
+
from .GRAY import GRAY
|
|
25
|
+
from .GREEN import GREEN
|
|
26
|
+
from .ORANGE import ORANGE
|
|
27
|
+
from .RED import RED
|
|
28
|
+
from .WHITE import WHITE
|
|
29
|
+
from .YELLOW import YELLOW
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import random
|
|
4
|
+
from typing import TYPE_CHECKING, cast
|
|
5
|
+
|
|
6
|
+
from typeguard import typechecked
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from scadpy.color import Color
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@typechecked
|
|
13
|
+
def get_random_color() -> Color:
|
|
14
|
+
from scadpy import color
|
|
15
|
+
|
|
16
|
+
color_names: list[str] = getattr(color.constants, "__all__", [])
|
|
17
|
+
color_names = [
|
|
18
|
+
c for c in color_names if c != "DEFAULT_OPACITY" and c != "DEFAULT_COLOR"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
if not color_names:
|
|
22
|
+
raise ValueError("No color constant found.")
|
|
23
|
+
|
|
24
|
+
name = random.choice(color_names)
|
|
25
|
+
color_value = cast(color.Color, getattr(color.constants, name))
|
|
26
|
+
|
|
27
|
+
if not isinstance(color_value, (list, tuple)):
|
|
28
|
+
raise TypeError(f"{name} is not a list or tuple.")
|
|
29
|
+
|
|
30
|
+
if len(color_value) != 4:
|
|
31
|
+
raise TypeError(f"{name} must be RGBA (4 values), got {len(color_value)}.")
|
|
32
|
+
|
|
33
|
+
if not all(0.0 <= c <= 1.0 for c in color_value):
|
|
34
|
+
raise ValueError(f"{name} has values outside [0.0, 1.0] range.")
|
|
35
|
+
|
|
36
|
+
return color_value
|
scadpy/core/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"concat_assemblies",
|
|
3
|
+
"exclude_assemblies",
|
|
4
|
+
"intersect_assemblies",
|
|
5
|
+
"subtract_assemblies",
|
|
6
|
+
"unify_assemblies",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
from .concat_assemblies import concat_assemblies
|
|
10
|
+
from .exclude_assemblies import exclude_assemblies
|
|
11
|
+
from .intersect_assemblies import intersect_assemblies
|
|
12
|
+
from .subtract_assemblies import subtract_assemblies
|
|
13
|
+
from .unify_assemblies import unify_assemblies
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
4
|
+
|
|
5
|
+
from typeguard import typechecked
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@typechecked
|
|
9
|
+
def concat_assemblies[A, P](
|
|
10
|
+
assemblies: Iterable[A],
|
|
11
|
+
get_assembly_parts: Callable[[A], Iterable[P]],
|
|
12
|
+
concat_parts: Callable[[Sequence[P]], A],
|
|
13
|
+
) -> A:
|
|
14
|
+
"""
|
|
15
|
+
Combine multiple assemblies into a single assembly by concatenating all their parts.
|
|
16
|
+
|
|
17
|
+
This function uses dependency injection to remain type-agnostic, allowing it
|
|
18
|
+
to work with any assembly/part domain model by providing appropriate accessor
|
|
19
|
+
and concatenation functions.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
assemblies : Iterable[A]
|
|
24
|
+
Iterable of assembly objects to be combined.
|
|
25
|
+
get_assembly_parts : Callable[[A], Iterable[P]]
|
|
26
|
+
Function that extracts parts from an assembly.
|
|
27
|
+
concat_parts : Callable[[Sequence[P]], A]
|
|
28
|
+
Function that combines a sequence of parts into a new assembly.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
A
|
|
33
|
+
The assembly object created by combining all parts from all input assemblies.
|
|
34
|
+
|
|
35
|
+
Examples
|
|
36
|
+
--------
|
|
37
|
+
>>> from scadpy import concat_assemblies
|
|
38
|
+
>>> assemblies = [
|
|
39
|
+
... ['a', 'b'],
|
|
40
|
+
... ['c'],
|
|
41
|
+
... []
|
|
42
|
+
... ]
|
|
43
|
+
>>> concat_assemblies(
|
|
44
|
+
... assemblies,
|
|
45
|
+
... get_assembly_parts=lambda a: a,
|
|
46
|
+
... concat_parts=lambda parts: ''.join(parts)
|
|
47
|
+
... )
|
|
48
|
+
'abc'
|
|
49
|
+
"""
|
|
50
|
+
return concat_parts([p for a in assemblies for p in get_assembly_parts(a)])
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
from typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typechecked
|
|
11
|
+
def exclude_assemblies[A, P](
|
|
12
|
+
assemblies: Sequence[A],
|
|
13
|
+
get_assembly_parts: Callable[[A], Iterable[P]],
|
|
14
|
+
get_part_bounds: Callable[[P], NDArray[np.float64]],
|
|
15
|
+
are_parts_intersecting: Callable[[P, P], bool],
|
|
16
|
+
subtract_parts: Callable[[P, P], A],
|
|
17
|
+
intersect_parts: Callable[[Sequence[P]], A],
|
|
18
|
+
unify_parts: Callable[[Sequence[P]], A],
|
|
19
|
+
concat_parts: Callable[[Sequence[P]], A],
|
|
20
|
+
) -> A:
|
|
21
|
+
"""
|
|
22
|
+
Compute the symmetric difference (exclusive-or) of multiple assemblies.
|
|
23
|
+
|
|
24
|
+
This function is fully generic and uses dependency injection for all domain-specific
|
|
25
|
+
operations, making it suitable for a wide range of applications (2D, 3D, CAD, etc.).
|
|
26
|
+
The result contains all parts that are present in exactly one assembly, i.e., the union minus all intersections.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
assemblies : Sequence[A]
|
|
31
|
+
Sequence of assembly objects to process.
|
|
32
|
+
get_assembly_parts : Callable[[A], Iterable[P]]
|
|
33
|
+
Function that extracts parts from an assembly.
|
|
34
|
+
get_part_bounds : Callable[[P], NDArray[np.float64]]
|
|
35
|
+
Function to extract the bounding box of a part.
|
|
36
|
+
are_parts_intersecting : Callable[[P, P], bool]
|
|
37
|
+
Function to determine if two parts intersect.
|
|
38
|
+
subtract_parts : Callable[[P, P], A]
|
|
39
|
+
Function to subtract one part from another, returning an assembly.
|
|
40
|
+
intersect_parts : Callable[[Sequence[P]], A]
|
|
41
|
+
Function to compute the intersection of a group of parts.
|
|
42
|
+
unify_parts : Callable[[Sequence[P]], A]
|
|
43
|
+
Function to unify (union) overlapping parts within an assembly.
|
|
44
|
+
concat_parts : Callable[[Sequence[P]], A]
|
|
45
|
+
Function to concatenate a sequence of parts into a new assembly.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
A
|
|
50
|
+
The assembly object containing the symmetric difference of all input assemblies.
|
|
51
|
+
|
|
52
|
+
Examples
|
|
53
|
+
--------
|
|
54
|
+
>>> from scadpy import exclude_assemblies
|
|
55
|
+
|
|
56
|
+
>>> assemblies = [
|
|
57
|
+
... [{'bounds': [0, 0, 2, 2]}],
|
|
58
|
+
... [{'bounds': [1, 1, 3, 3]}, {'bounds': [5, 5, 6, 6]}]
|
|
59
|
+
... ]
|
|
60
|
+
...
|
|
61
|
+
>>> def are_intersecting(p1, p2):
|
|
62
|
+
... b1, b2 = p1['bounds'], p2['bounds']
|
|
63
|
+
... return not (b1[2] <= b2[0] or b2[2] <= b1[0] or
|
|
64
|
+
... b1[3] <= b2[1] or b2[3] <= b1[1])
|
|
65
|
+
...
|
|
66
|
+
>>> def subtract_geometries(g1, g2):
|
|
67
|
+
... if (g2[0] > g1[0] and g2[2] < g1[2]
|
|
68
|
+
... and g2[1] > g1[1] and g2[3] < g1[3]):
|
|
69
|
+
... return [
|
|
70
|
+
... [g1[0], g1[1], g2[0], g1[3]], # left
|
|
71
|
+
... [g2[2], g1[1], g1[2], g1[3]], # right
|
|
72
|
+
... [g2[0], g1[1], g2[2], g2[1]], # bottom
|
|
73
|
+
... [g2[0], g2[3], g2[2], g1[3]], # top
|
|
74
|
+
... ]
|
|
75
|
+
... return [g1]
|
|
76
|
+
...
|
|
77
|
+
>>> def subtract_parts(p1, p2):
|
|
78
|
+
... geometries = subtract_geometries(
|
|
79
|
+
... p1['bounds'], p2['bounds']
|
|
80
|
+
... )
|
|
81
|
+
... return [{'bounds': g} for g in geometries]
|
|
82
|
+
...
|
|
83
|
+
>>> result = exclude_assemblies(
|
|
84
|
+
... assemblies,
|
|
85
|
+
... get_assembly_parts=lambda a: a,
|
|
86
|
+
... get_part_bounds=lambda p: p['bounds'],
|
|
87
|
+
... are_parts_intersecting=are_intersecting,
|
|
88
|
+
... subtract_parts=subtract_parts,
|
|
89
|
+
... intersect_parts= lambda parts: parts,
|
|
90
|
+
... unify_parts= lambda parts: parts,
|
|
91
|
+
... concat_parts= lambda parts: parts
|
|
92
|
+
... )
|
|
93
|
+
>>> result == [
|
|
94
|
+
... {'bounds': [0, 0, 2, 2]},
|
|
95
|
+
... {'bounds': [1, 1, 3, 3]},
|
|
96
|
+
... {'bounds': [5, 5, 6, 6]}
|
|
97
|
+
... ]
|
|
98
|
+
True
|
|
99
|
+
"""
|
|
100
|
+
from scadpy.core.assembly import subtract_assemblies, unify_assemblies
|
|
101
|
+
|
|
102
|
+
if len(assemblies) == 0:
|
|
103
|
+
return concat_parts([])
|
|
104
|
+
|
|
105
|
+
result = assemblies[0]
|
|
106
|
+
for assembly in assemblies[1:]:
|
|
107
|
+
result = unify_assemblies(
|
|
108
|
+
assemblies=[
|
|
109
|
+
subtract_assemblies(
|
|
110
|
+
to_be_subtracted=result,
|
|
111
|
+
to_subtract=assembly,
|
|
112
|
+
get_assembly_parts=get_assembly_parts,
|
|
113
|
+
get_part_bounds=get_part_bounds,
|
|
114
|
+
are_parts_intersecting=are_parts_intersecting,
|
|
115
|
+
subtract_parts=subtract_parts,
|
|
116
|
+
intersect_parts=intersect_parts,
|
|
117
|
+
unify_parts=unify_parts,
|
|
118
|
+
concat_parts=concat_parts,
|
|
119
|
+
),
|
|
120
|
+
subtract_assemblies(
|
|
121
|
+
to_be_subtracted=assembly,
|
|
122
|
+
to_subtract=result,
|
|
123
|
+
get_assembly_parts=get_assembly_parts,
|
|
124
|
+
get_part_bounds=get_part_bounds,
|
|
125
|
+
are_parts_intersecting=are_parts_intersecting,
|
|
126
|
+
subtract_parts=subtract_parts,
|
|
127
|
+
intersect_parts=intersect_parts,
|
|
128
|
+
unify_parts=unify_parts,
|
|
129
|
+
concat_parts=concat_parts,
|
|
130
|
+
),
|
|
131
|
+
],
|
|
132
|
+
get_assembly_parts=get_assembly_parts,
|
|
133
|
+
unify_parts=unify_parts,
|
|
134
|
+
)
|
|
135
|
+
return result
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
from typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typechecked
|
|
11
|
+
def intersect_assemblies[A, P](
|
|
12
|
+
assemblies: Sequence[A],
|
|
13
|
+
get_assembly_parts: Callable[[A], Iterable[P]],
|
|
14
|
+
get_part_bounds: Callable[[P], NDArray[np.float64]],
|
|
15
|
+
are_parts_intersecting: Callable[[P, P], bool],
|
|
16
|
+
intersect_parts: Callable[[Sequence[P]], A],
|
|
17
|
+
unify_parts: Callable[[Sequence[P]], A],
|
|
18
|
+
concat_parts: Callable[[Sequence[P]], A],
|
|
19
|
+
) -> A:
|
|
20
|
+
"""
|
|
21
|
+
Compute the intersection of multiple assemblies, keeping only intersections that involve at least one part from each assembly.
|
|
22
|
+
|
|
23
|
+
This function is fully generic and uses dependency injection for all domain-specific
|
|
24
|
+
operations, making it suitable for a wide range of applications (2D, 3D, CAD, etc.).
|
|
25
|
+
Only groups of parts that include at least one part from each input assembly are intersected and included in the result.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
assemblies : Sequence[A]
|
|
30
|
+
Sequence of assembly objects to intersect.
|
|
31
|
+
get_assembly_parts : Callable[[A], Iterable[P]]
|
|
32
|
+
Function that extracts parts from an assembly.
|
|
33
|
+
get_part_bounds : Callable[[P], NDArray[np.float64]]
|
|
34
|
+
Function to extract the bounding box of a part.
|
|
35
|
+
are_parts_intersecting : Callable[[P, P], bool]
|
|
36
|
+
Function to determine if two parts intersect.
|
|
37
|
+
intersect_parts : Callable[[Sequence[P]], A]
|
|
38
|
+
Function to compute the intersection of a group of parts.
|
|
39
|
+
unify_parts : Callable[[Sequence[P]], A]
|
|
40
|
+
Function to unify (union) overlapping parts within an assembly.
|
|
41
|
+
concat_parts : Callable[[Sequence[P]], A]
|
|
42
|
+
Function to concatenate a sequence of parts into a new assembly.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
A
|
|
47
|
+
The assembly object containing all intersections involving at least one part from each input assembly.
|
|
48
|
+
|
|
49
|
+
Examples
|
|
50
|
+
--------
|
|
51
|
+
>>> from scadpy import intersect_assemblies
|
|
52
|
+
|
|
53
|
+
>>> assemblies = [
|
|
54
|
+
... [{'bounds': [0, 0, 2, 2]}],
|
|
55
|
+
... [{'bounds': [1, 1, 3, 3]}, {'bounds': [5, 5, 6, 6]}]
|
|
56
|
+
... ]
|
|
57
|
+
...
|
|
58
|
+
>>> def are_intersecting(p1, p2):
|
|
59
|
+
... b1, b2 = p1['bounds'], p2['bounds']
|
|
60
|
+
... return not (b1[2] <= b2[0] or b2[2] <= b1[0] or
|
|
61
|
+
... b1[3] <= b2[1] or b2[3] <= b1[1])
|
|
62
|
+
...
|
|
63
|
+
>>> def intersect_geometries(geometries):
|
|
64
|
+
... if len(geometries) == 1:
|
|
65
|
+
... return [geometries[0]]
|
|
66
|
+
... minx = max(g[0] for g in geometries)
|
|
67
|
+
... miny = max(g[1] for g in geometries)
|
|
68
|
+
... maxx = min(g[2] for g in geometries)
|
|
69
|
+
... maxy = min(g[3] for g in geometries)
|
|
70
|
+
... if minx < maxx and miny < maxy:
|
|
71
|
+
... return [[minx, miny, maxx, maxy]]
|
|
72
|
+
... return []
|
|
73
|
+
...
|
|
74
|
+
>>> def intersect_parts(parts):
|
|
75
|
+
... geometries = [p['bounds'] for p in parts]
|
|
76
|
+
... for g in intersect_geometries(geometries):
|
|
77
|
+
... return [{'bounds': g}]
|
|
78
|
+
...
|
|
79
|
+
>>> intersect_assemblies(
|
|
80
|
+
... assemblies,
|
|
81
|
+
... get_assembly_parts=lambda a: a,
|
|
82
|
+
... get_part_bounds=lambda p: p['bounds'],
|
|
83
|
+
... are_parts_intersecting=are_intersecting,
|
|
84
|
+
... intersect_parts=intersect_parts,
|
|
85
|
+
... unify_parts=lambda p: p,
|
|
86
|
+
... concat_parts=lambda p: p
|
|
87
|
+
... )
|
|
88
|
+
[{'bounds': [1, 1, 2, 2]}]
|
|
89
|
+
"""
|
|
90
|
+
from scadpy.core.assembly import concat_assemblies, unify_assemblies
|
|
91
|
+
from scadpy.core.component import get_intersecting_component_index_groups
|
|
92
|
+
|
|
93
|
+
unified_assemblies = [
|
|
94
|
+
unify_assemblies(
|
|
95
|
+
[a],
|
|
96
|
+
get_assembly_parts=get_assembly_parts,
|
|
97
|
+
unify_parts=unify_parts,
|
|
98
|
+
)
|
|
99
|
+
for a in assemblies
|
|
100
|
+
]
|
|
101
|
+
part_with_assembly_index: list[tuple[P, int]] = [
|
|
102
|
+
(p, i) for i, a in enumerate(unified_assemblies) for p in get_assembly_parts(a)
|
|
103
|
+
]
|
|
104
|
+
parts = [p for p, _ in part_with_assembly_index]
|
|
105
|
+
assembly_indices = [i for _, i in part_with_assembly_index]
|
|
106
|
+
|
|
107
|
+
intersecting_part_index_groups: list[list[int]] = (
|
|
108
|
+
get_intersecting_component_index_groups(
|
|
109
|
+
parts,
|
|
110
|
+
get_component_bounds=get_part_bounds,
|
|
111
|
+
are_components_intersecting=are_parts_intersecting,
|
|
112
|
+
)
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# keep only groups that have at least one part from each assembly
|
|
116
|
+
num_assemblies = len(assemblies)
|
|
117
|
+
intersected_assemblies: list[A] = []
|
|
118
|
+
for group in intersecting_part_index_groups:
|
|
119
|
+
assembly_ids_in_group = {assembly_indices[idx] for idx in group}
|
|
120
|
+
if len(assembly_ids_in_group) == num_assemblies:
|
|
121
|
+
group_parts = [parts[idx] for idx in group]
|
|
122
|
+
intersected_assemblies.append(intersect_parts(group_parts))
|
|
123
|
+
|
|
124
|
+
return concat_assemblies(
|
|
125
|
+
assemblies=intersected_assemblies,
|
|
126
|
+
get_assembly_parts=get_assembly_parts,
|
|
127
|
+
concat_parts=concat_parts,
|
|
128
|
+
)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
from numpy.typing import NDArray
|
|
7
|
+
from typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typechecked
|
|
11
|
+
def subtract_assemblies[A, P](
|
|
12
|
+
to_be_subtracted: A,
|
|
13
|
+
to_subtract: A,
|
|
14
|
+
get_assembly_parts: Callable[[A], Iterable[P]],
|
|
15
|
+
get_part_bounds: Callable[[P], NDArray[np.float64]],
|
|
16
|
+
are_parts_intersecting: Callable[[P, P], bool],
|
|
17
|
+
subtract_parts: Callable[[P, P], A],
|
|
18
|
+
intersect_parts: Callable[[Sequence[P]], A],
|
|
19
|
+
unify_parts: Callable[[Sequence[P]], A],
|
|
20
|
+
concat_parts: Callable[[Sequence[P]], A],
|
|
21
|
+
) -> A:
|
|
22
|
+
"""
|
|
23
|
+
Subtract the geometry of all parts in one assembly from all parts in another assembly.
|
|
24
|
+
|
|
25
|
+
This function is fully generic and uses dependency injection for all domain-specific
|
|
26
|
+
operations, making it suitable for a wide range of applications (2D, 3D, CAD, etc.).
|
|
27
|
+
For each part in the first assembly, all intersecting parts from the second assembly are subtracted.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
to_be_subtracted : A
|
|
32
|
+
The assembly whose parts will be subtracted from.
|
|
33
|
+
to_subtract : A
|
|
34
|
+
The assembly whose parts will be subtracted.
|
|
35
|
+
get_assembly_parts : Callable[[A], Iterable[P]]
|
|
36
|
+
Function that extracts parts from an assembly.
|
|
37
|
+
get_part_bounds : Callable[[P], NDArray[np.float64]]
|
|
38
|
+
Function to extract the bounding box of a part.
|
|
39
|
+
are_parts_intersecting : Callable[[P, P], bool]
|
|
40
|
+
Function to determine if two parts intersect.
|
|
41
|
+
subtract_parts : Callable[[P, P], A]
|
|
42
|
+
Function to subtract one part from another, returning an assembly.
|
|
43
|
+
intersect_parts : Callable[[Sequence[P]], A]
|
|
44
|
+
Function to compute the intersection of a group of parts.
|
|
45
|
+
unify_parts : Callable[[Sequence[P]], A]
|
|
46
|
+
Function to unify (union) overlapping parts within an assembly.
|
|
47
|
+
concat_parts : Callable[[Sequence[P]], A]
|
|
48
|
+
Function to concatenate a sequence of parts into a new assembly.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
A
|
|
53
|
+
The assembly object containing all subtracted parts.
|
|
54
|
+
|
|
55
|
+
Examples
|
|
56
|
+
--------
|
|
57
|
+
>>> from scadpy import subtract_assemblies
|
|
58
|
+
|
|
59
|
+
>>> assembly1 = [{'bounds': [0, 0, 3, 3]}]
|
|
60
|
+
>>> assembly2 = [{'bounds': [1, 1, 2, 2]}]
|
|
61
|
+
...
|
|
62
|
+
>>> def are_intersecting(p1, p2):
|
|
63
|
+
... b1, b2 = p1['bounds'], p2['bounds']
|
|
64
|
+
... return not (b1[2] <= b2[0] or b2[2] <= b1[0] or
|
|
65
|
+
... b1[3] <= b2[1] or b2[3] <= b1[1])
|
|
66
|
+
...
|
|
67
|
+
>>> def subtract_geometries(g1, g2):
|
|
68
|
+
... if (g2[0] > g1[0] and g2[2] < g1[2]
|
|
69
|
+
... and g2[1] > g1[1] and g2[3] < g1[3]):
|
|
70
|
+
... return [
|
|
71
|
+
... [g1[0], g1[1], g2[0], g1[3]], # left
|
|
72
|
+
... [g2[2], g1[1], g1[2], g1[3]], # right
|
|
73
|
+
... [g2[0], g1[1], g2[2], g2[1]], # bottom
|
|
74
|
+
... [g2[0], g2[3], g2[2], g1[3]], # top
|
|
75
|
+
... ]
|
|
76
|
+
... return [g1]
|
|
77
|
+
...
|
|
78
|
+
>>> def subtract_parts(p1, p2):
|
|
79
|
+
... geometries = subtract_geometries(
|
|
80
|
+
... p1['bounds'], p2['bounds']
|
|
81
|
+
... )
|
|
82
|
+
... return [{'bounds': g} for g in geometries]
|
|
83
|
+
...
|
|
84
|
+
>>> result = subtract_assemblies(
|
|
85
|
+
... assembly1, assembly2,
|
|
86
|
+
... get_assembly_parts=lambda a: a,
|
|
87
|
+
... get_part_bounds=lambda p: p['bounds'],
|
|
88
|
+
... are_parts_intersecting=are_intersecting,
|
|
89
|
+
... subtract_parts=subtract_parts,
|
|
90
|
+
... intersect_parts=lambda p: p,
|
|
91
|
+
... unify_parts=lambda p: p,
|
|
92
|
+
... concat_parts=lambda p: p
|
|
93
|
+
... )
|
|
94
|
+
>>> result == [
|
|
95
|
+
... {'bounds': [0, 0, 1, 3]},
|
|
96
|
+
... {'bounds': [2, 0, 3, 3]},
|
|
97
|
+
... {'bounds': [1, 0, 2, 1]},
|
|
98
|
+
... {'bounds': [1, 2, 2, 3]}
|
|
99
|
+
... ]
|
|
100
|
+
True
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
from scadpy.core.assembly import (
|
|
104
|
+
concat_assemblies,
|
|
105
|
+
intersect_assemblies,
|
|
106
|
+
unify_assemblies,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
unified_to_subtract = unify_assemblies(
|
|
110
|
+
[to_subtract],
|
|
111
|
+
get_assembly_parts=get_assembly_parts,
|
|
112
|
+
unify_parts=unify_parts,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
parts_to_be_subtracted = get_assembly_parts(to_be_subtracted)
|
|
116
|
+
parts_to_subtract = get_assembly_parts(unified_to_subtract)
|
|
117
|
+
|
|
118
|
+
subtracted_assemblies: list[A] = []
|
|
119
|
+
for part_to_be_subtracted in parts_to_be_subtracted:
|
|
120
|
+
intersecting_parts_to_subtract = [
|
|
121
|
+
part
|
|
122
|
+
for part in parts_to_subtract
|
|
123
|
+
if are_parts_intersecting(part_to_be_subtracted, part)
|
|
124
|
+
]
|
|
125
|
+
if not intersecting_parts_to_subtract:
|
|
126
|
+
subtracted_assemblies.append(concat_parts([part_to_be_subtracted]))
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
partially_subtracted_assemblies: list[A] = []
|
|
130
|
+
for part_to_subtract in intersecting_parts_to_subtract:
|
|
131
|
+
partially_subtracted_assemblies.append(
|
|
132
|
+
subtract_parts(part_to_be_subtracted, part_to_subtract)
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
subtracted_assemblies += [
|
|
136
|
+
intersect_assemblies(
|
|
137
|
+
assemblies=partially_subtracted_assemblies,
|
|
138
|
+
get_assembly_parts=get_assembly_parts,
|
|
139
|
+
get_part_bounds=get_part_bounds,
|
|
140
|
+
are_parts_intersecting=are_parts_intersecting,
|
|
141
|
+
intersect_parts=intersect_parts,
|
|
142
|
+
unify_parts=unify_parts,
|
|
143
|
+
concat_parts=concat_parts,
|
|
144
|
+
)
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
return concat_assemblies(
|
|
148
|
+
assemblies=subtracted_assemblies,
|
|
149
|
+
get_assembly_parts=get_assembly_parts,
|
|
150
|
+
concat_parts=concat_parts,
|
|
151
|
+
)
|