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/core/assembly/topologies/face_corner/get_assembly_face_corner_to_incoming_directed_edge.py
ADDED
|
@@ -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
|
+
)
|
scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_outgoing_directed_edge.py
ADDED
|
@@ -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,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,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
|