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
|
@@ -0,0 +1,121 @@
|
|
|
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 resize_vertex_coordinates(
|
|
12
|
+
vertex_coordinates: NDArray[np.float64],
|
|
13
|
+
size: Iterable[float | None],
|
|
14
|
+
n_dims: int,
|
|
15
|
+
auto: bool = False,
|
|
16
|
+
pivot: float | Iterable[float] | None = None,
|
|
17
|
+
vertex_filter: NDArray[np.bool_] | None = None,
|
|
18
|
+
) -> NDArray[np.float64]:
|
|
19
|
+
"""
|
|
20
|
+
Resize vertex coordinates to fit target dimensions.
|
|
21
|
+
|
|
22
|
+
Computes per-axis scale factors from the bounding box of
|
|
23
|
+
``vertex_coordinates`` and ``size``, then delegates to
|
|
24
|
+
:func:`scale_vertex_coordinates`.
|
|
25
|
+
|
|
26
|
+
``None`` entries in ``size`` mark axes to leave unchanged. When
|
|
27
|
+
``auto=True``, those axes are scaled proportionally to the average
|
|
28
|
+
ratio of the defined axes instead.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
vertex_coordinates : NDArray[np.float64]
|
|
33
|
+
2D array of shape ``(n_vertices, n_dims)``.
|
|
34
|
+
size : Iterable[float | None]
|
|
35
|
+
Target dimensions. ``None`` for an axis means "leave unchanged"
|
|
36
|
+
(or "scale proportionally" when ``auto=True``). Broadcast rules
|
|
37
|
+
from :func:`resolve_vector` apply: a shorter iterable is padded
|
|
38
|
+
with ``None`` (no resize on missing axes).
|
|
39
|
+
n_dims : int
|
|
40
|
+
Number of coordinate dimensions. Used to broadcast ``size`` and
|
|
41
|
+
``pivot`` to the correct length via :func:`resolve_vector`.
|
|
42
|
+
auto : bool, default=False
|
|
43
|
+
If ``True``, axes with ``None`` are scaled proportionally to the
|
|
44
|
+
average ratio of the defined axes.
|
|
45
|
+
pivot : float | Iterable[float] | None, default=None
|
|
46
|
+
The point relative to which scaling is applied. Defaults to the
|
|
47
|
+
center of the bounding box of ``vertex_coordinates``.
|
|
48
|
+
vertex_filter : NDArray[np.bool_] | None, default=None
|
|
49
|
+
Boolean array selecting which vertices are resized. If ``None``,
|
|
50
|
+
all vertices are resized.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
NDArray[np.float64]
|
|
55
|
+
Array of shape ``(n_vertices, n_dims)``, one row per vertex.
|
|
56
|
+
|
|
57
|
+
Examples
|
|
58
|
+
--------
|
|
59
|
+
>>> import numpy as np
|
|
60
|
+
>>> from scadpy import resize_vertex_coordinates
|
|
61
|
+
|
|
62
|
+
Resize a 4×2 rectangle to 6×6:
|
|
63
|
+
|
|
64
|
+
>>> coordinates = np.array(
|
|
65
|
+
... [[0., 0.], [4., 0.], [4., 2.], [0., 2.]], dtype=np.float64
|
|
66
|
+
... )
|
|
67
|
+
>>> result = resize_vertex_coordinates(coordinates, size=[6, 6], n_dims=2)
|
|
68
|
+
>>> (result.max(axis=0) - result.min(axis=0)).tolist()
|
|
69
|
+
[6.0, 6.0]
|
|
70
|
+
|
|
71
|
+
Freeze the Y axis (``None``):
|
|
72
|
+
|
|
73
|
+
>>> result = resize_vertex_coordinates(coordinates, size=[6, None], n_dims=2)
|
|
74
|
+
>>> (result.max(axis=0) - result.min(axis=0)).tolist()
|
|
75
|
+
[6.0, 2.0]
|
|
76
|
+
|
|
77
|
+
Scale the Y axis proportionally with ``auto=True``:
|
|
78
|
+
|
|
79
|
+
>>> result = resize_vertex_coordinates(
|
|
80
|
+
... coordinates, size=[6, None], n_dims=2, auto=True
|
|
81
|
+
... )
|
|
82
|
+
>>> (result.max(axis=0) - result.min(axis=0)).tolist()
|
|
83
|
+
[6.0, 3.0]
|
|
84
|
+
"""
|
|
85
|
+
from scadpy import resolve_vector, scale_vertex_coordinates
|
|
86
|
+
|
|
87
|
+
# Convert None → nan so resolve_vector can broadcast to n_dims.
|
|
88
|
+
# resolve_vector replaces nan with its default_value; using nan as
|
|
89
|
+
# default_value preserves the sentinel for frozen axes.
|
|
90
|
+
size_as_floats = [float("nan") if s is None else float(s) for s in size]
|
|
91
|
+
size_array = resolve_vector(size_as_floats, float("nan"), n_dims)
|
|
92
|
+
|
|
93
|
+
if len(vertex_coordinates) == 0:
|
|
94
|
+
return vertex_coordinates
|
|
95
|
+
|
|
96
|
+
bounds_min = vertex_coordinates.min(axis=0)
|
|
97
|
+
bounds_max = vertex_coordinates.max(axis=0)
|
|
98
|
+
current_size = bounds_max - bounds_min
|
|
99
|
+
|
|
100
|
+
if pivot is None:
|
|
101
|
+
effective_pivot = (bounds_min + bounds_max) / 2
|
|
102
|
+
else:
|
|
103
|
+
effective_pivot = np.array(resolve_vector(pivot, 0, n_dims))
|
|
104
|
+
|
|
105
|
+
defined_scales = [
|
|
106
|
+
s / c
|
|
107
|
+
for s, c in zip(size_array, current_size)
|
|
108
|
+
if not np.isnan(s) and c != 0
|
|
109
|
+
]
|
|
110
|
+
auto_scale = sum(defined_scales) / len(defined_scales) if defined_scales else 1.0
|
|
111
|
+
|
|
112
|
+
scale_array = np.ones(n_dims, dtype=np.float64)
|
|
113
|
+
for i, (s, c) in enumerate(zip(size_array, current_size)):
|
|
114
|
+
if not np.isnan(s):
|
|
115
|
+
scale_array[i] = s / c if c != 0 else 1.0
|
|
116
|
+
elif auto:
|
|
117
|
+
scale_array[i] = auto_scale
|
|
118
|
+
|
|
119
|
+
return scale_vertex_coordinates(
|
|
120
|
+
vertex_coordinates, scale_array, effective_pivot, vertex_filter
|
|
121
|
+
)
|
|
@@ -0,0 +1,73 @@
|
|
|
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 rotate_vertex_coordinates(
|
|
12
|
+
vertex_coordinates: NDArray[np.float64],
|
|
13
|
+
rotation_matrix: NDArray[np.float64],
|
|
14
|
+
pivot: float | Iterable[float] = 0,
|
|
15
|
+
vertex_filter: NDArray[np.bool_] | None = None,
|
|
16
|
+
) -> NDArray[np.float64]:
|
|
17
|
+
"""
|
|
18
|
+
Rotate vertex coordinates using a precomputed rotation matrix and a pivot point.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
vertex_coordinates : NDArray[np.float64]
|
|
23
|
+
2D array of shape (n_vertices, dimensions).
|
|
24
|
+
rotation_matrix : NDArray[np.float64]
|
|
25
|
+
Square rotation matrix of shape (dimensions, dimensions).
|
|
26
|
+
pivot : float | Iterable[float], default=0
|
|
27
|
+
The point around which rotation is applied. If a single float is provided,
|
|
28
|
+
it will be broadcast to all coordinate dimensions. Defaults to the origin.
|
|
29
|
+
vertex_filter : NDArray[np.bool_] | None, default=None
|
|
30
|
+
Boolean array selecting which vertices are rotated. If ``None``, all vertices
|
|
31
|
+
are rotated.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
NDArray[np.float64]
|
|
36
|
+
Array of shape (n_vertices, dimensions), one row per vertex.
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
--------
|
|
40
|
+
>>> import numpy as np
|
|
41
|
+
>>> from shapely.geometry import Polygon
|
|
42
|
+
>>> from scadpy import rotate_vertex_coordinates, Shape
|
|
43
|
+
|
|
44
|
+
>>> polygon = Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
|
|
45
|
+
>>> shape = Shape.from_geometries([polygon])
|
|
46
|
+
>>> angle = np.deg2rad(90)
|
|
47
|
+
>>> R = np.array([
|
|
48
|
+
... [np.cos(angle), -np.sin(angle)],
|
|
49
|
+
... [np.sin(angle), np.cos(angle)],
|
|
50
|
+
... ])
|
|
51
|
+
>>> rotate_vertex_coordinates(
|
|
52
|
+
... shape.vertex_coordinates,
|
|
53
|
+
... R,
|
|
54
|
+
... pivot=[1, 1],
|
|
55
|
+
... ).round(10) # doctest: +NORMALIZE_WHITESPACE
|
|
56
|
+
array([[2., 0.],
|
|
57
|
+
[2., 2.],
|
|
58
|
+
[0., 2.],
|
|
59
|
+
[0., 0.]])
|
|
60
|
+
"""
|
|
61
|
+
from scadpy import resolve_vector
|
|
62
|
+
|
|
63
|
+
dimensions: int = vertex_coordinates.shape[1]
|
|
64
|
+
pivot_array = np.array(resolve_vector(pivot, 0, dimensions))
|
|
65
|
+
|
|
66
|
+
if vertex_filter is None:
|
|
67
|
+
return (vertex_coordinates - pivot_array) @ rotation_matrix.T + pivot_array
|
|
68
|
+
|
|
69
|
+
result = np.array(vertex_coordinates)
|
|
70
|
+
result[vertex_filter] = (
|
|
71
|
+
(vertex_coordinates[vertex_filter] - pivot_array) @ rotation_matrix.T + pivot_array
|
|
72
|
+
)
|
|
73
|
+
return result
|
|
@@ -0,0 +1,76 @@
|
|
|
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 scale_vertex_coordinates(
|
|
12
|
+
vertex_coordinates: NDArray[np.float64],
|
|
13
|
+
scale: float | Iterable[float],
|
|
14
|
+
pivot: float | Iterable[float] = 0,
|
|
15
|
+
vertex_filter: NDArray[np.bool_] | None = None,
|
|
16
|
+
) -> NDArray[np.float64]:
|
|
17
|
+
"""
|
|
18
|
+
Scale vertex coordinates by a given factor or vector, relative to a pivot.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
vertex_coordinates : NDArray[np.float64]
|
|
23
|
+
2D array of shape (n_vertices, dimensions).
|
|
24
|
+
scale : float | Iterable[float]
|
|
25
|
+
The scaling factor(s) to apply. Its length should match the number of coordinate dimensions.
|
|
26
|
+
If a single float is provided, it will be broadcast to all coordinate dimensions.
|
|
27
|
+
pivot : float | Iterable[float], default=0
|
|
28
|
+
The point relative to which scaling is performed. If a single float is provided, it will be broadcast to all coordinate dimensions.
|
|
29
|
+
Defaults to 0 (the origin).
|
|
30
|
+
vertex_filter : NDArray[np.bool_] | None, default=None
|
|
31
|
+
Boolean array selecting which vertices are scaled. If ``None``, all vertices are scaled.
|
|
32
|
+
|
|
33
|
+
Returns
|
|
34
|
+
-------
|
|
35
|
+
NDArray[np.float64]
|
|
36
|
+
Array of shape (n_vertices, dimensions), one row per vertex.
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
--------
|
|
40
|
+
>>> from shapely.geometry import Polygon
|
|
41
|
+
>>> from scadpy import scale_vertex_coordinates, Shape
|
|
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
|
+
>>> scale_vertex_coordinates(
|
|
52
|
+
... shape.vertex_coordinates,
|
|
53
|
+
... scale=[10, 0.5],
|
|
54
|
+
... pivot=[1, 2]
|
|
55
|
+
... ) # doctest: +NORMALIZE_WHITESPACE
|
|
56
|
+
array([[ -9. , 1. ],
|
|
57
|
+
[ 11. , 1. ],
|
|
58
|
+
[ 11. , 2. ],
|
|
59
|
+
...
|
|
60
|
+
[111. , 6. ],
|
|
61
|
+
[111. , 7. ],
|
|
62
|
+
[ 91. , 7. ]])
|
|
63
|
+
"""
|
|
64
|
+
from scadpy import resolve_vector
|
|
65
|
+
|
|
66
|
+
dimensions: int = vertex_coordinates.shape[1]
|
|
67
|
+
|
|
68
|
+
pivot_array = np.array(resolve_vector(pivot, 0, dimensions))
|
|
69
|
+
scale_array = np.array(resolve_vector(scale, 1, dimensions))
|
|
70
|
+
|
|
71
|
+
if vertex_filter is None:
|
|
72
|
+
return pivot_array + (vertex_coordinates - pivot_array) * scale_array
|
|
73
|
+
|
|
74
|
+
result = np.array(vertex_coordinates)
|
|
75
|
+
result[vertex_filter] = pivot_array + (vertex_coordinates[vertex_filter] - pivot_array) * scale_array
|
|
76
|
+
return result
|
|
@@ -0,0 +1,70 @@
|
|
|
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 translate_vertex_coordinates(
|
|
12
|
+
vertex_coordinates: NDArray[np.float64],
|
|
13
|
+
translation: float | Iterable[float],
|
|
14
|
+
vertex_filter: NDArray[np.bool_] | None = None,
|
|
15
|
+
) -> NDArray[np.float64]:
|
|
16
|
+
"""
|
|
17
|
+
Translate vertex coordinates by a given vector.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
vertex_coordinates : NDArray[np.float64]
|
|
22
|
+
2D array of shape (n_vertices, dimensions).
|
|
23
|
+
translation : float | Iterable[float]
|
|
24
|
+
The translation vector to apply. Its length should match the number of coordinate dimensions.
|
|
25
|
+
If a single float is provided, it will be broadcast to all coordinate dimensions.
|
|
26
|
+
vertex_filter : NDArray[np.bool_] | None, default=None
|
|
27
|
+
Boolean array selecting which vertices are translated. If ``None``, all vertices
|
|
28
|
+
are translated.
|
|
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 translate_vertex_coordinates, Shape
|
|
39
|
+
|
|
40
|
+
>>> polygon1 = Polygon(
|
|
41
|
+
... shell=[(0, 0), (2, 0), (2, 2), (0, 2)],
|
|
42
|
+
... holes=[[(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)]]
|
|
43
|
+
... )
|
|
44
|
+
>>> polygon2 = Polygon(
|
|
45
|
+
... shell=[(10, 10), (12, 10), (12, 12), (10, 12)]
|
|
46
|
+
... )
|
|
47
|
+
>>> shape = Shape.from_geometries([polygon1, polygon2])
|
|
48
|
+
>>> translate_vertex_coordinates(
|
|
49
|
+
... shape.vertex_coordinates,
|
|
50
|
+
... translation=[10, 20]
|
|
51
|
+
... ) # doctest: +NORMALIZE_WHITESPACE
|
|
52
|
+
array([[10. , 20. ],
|
|
53
|
+
[12. , 20. ],
|
|
54
|
+
[12. , 22. ],
|
|
55
|
+
...
|
|
56
|
+
[22. , 30. ],
|
|
57
|
+
[22. , 32. ],
|
|
58
|
+
[20. , 32. ]])
|
|
59
|
+
"""
|
|
60
|
+
from scadpy import resolve_vector
|
|
61
|
+
|
|
62
|
+
dimensions: int = vertex_coordinates.shape[1]
|
|
63
|
+
translation_array = np.array(resolve_vector(translation, 0, dimensions))
|
|
64
|
+
|
|
65
|
+
if vertex_filter is None:
|
|
66
|
+
return vertex_coordinates + translation_array
|
|
67
|
+
|
|
68
|
+
result = np.array(vertex_coordinates)
|
|
69
|
+
result[vertex_filter] += translation_array
|
|
70
|
+
return result
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from scadpy.core.part import Part
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Assembly[G](ABC):
|
|
12
|
+
def __init__(self, *args: Any, **kwargs: Any): # pyright: ignore[reportExplicitAny, reportAny]
|
|
13
|
+
super().__init__(*args, **kwargs)
|
|
14
|
+
self._parts: Sequence[Part[G]] = []
|
|
@@ -0,0 +1,56 @@
|
|
|
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 lookup_pairs(
|
|
10
|
+
queries: NDArray[np.int64],
|
|
11
|
+
haystack: NDArray[np.int64],
|
|
12
|
+
) -> NDArray[np.int64]:
|
|
13
|
+
"""
|
|
14
|
+
For each queried pair, return its index in the haystack.
|
|
15
|
+
|
|
16
|
+
Each pair ``(a, b)`` is encoded as ``a * n + b`` where ``n`` is
|
|
17
|
+
``max(haystack) + 1``, then looked up via binary search. This is
|
|
18
|
+
fully vectorized with no Python loops.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
queries : NDArray[np.int64]
|
|
23
|
+
2D array of shape ``(n_queries, 2)``. Pairs to look up.
|
|
24
|
+
haystack : NDArray[np.int64]
|
|
25
|
+
2D array of shape ``(n_items, 2)``. The reference pairs.
|
|
26
|
+
Each pair must appear exactly once.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
NDArray[np.int64]
|
|
31
|
+
1D array of shape ``(n_queries,)``. Each entry is the index
|
|
32
|
+
in ``haystack`` of the corresponding query pair.
|
|
33
|
+
|
|
34
|
+
Examples
|
|
35
|
+
--------
|
|
36
|
+
>>> import numpy as np
|
|
37
|
+
>>> from scadpy import lookup_pairs
|
|
38
|
+
|
|
39
|
+
>>> haystack = np.array(
|
|
40
|
+
... [[0, 1], [1, 0], [1, 2], [2, 1], [2, 0], [0, 2]],
|
|
41
|
+
... dtype=np.int64,
|
|
42
|
+
... )
|
|
43
|
+
>>> queries = np.array([[2, 0], [0, 1], [1, 2]], dtype=np.int64)
|
|
44
|
+
>>> lookup_pairs(queries, haystack)
|
|
45
|
+
array([4, 0, 2])
|
|
46
|
+
"""
|
|
47
|
+
if len(queries) == 0:
|
|
48
|
+
return np.empty(0, dtype=np.int64)
|
|
49
|
+
|
|
50
|
+
n = int(haystack.max()) + 1
|
|
51
|
+
haystack_keys = haystack[:, 0] * n + haystack[:, 1]
|
|
52
|
+
sort_order = np.argsort(haystack_keys)
|
|
53
|
+
haystack_keys_sorted = haystack_keys[sort_order]
|
|
54
|
+
|
|
55
|
+
query_keys = queries[:, 0] * n + queries[:, 1]
|
|
56
|
+
return sort_order[np.searchsorted(haystack_keys_sorted, query_keys)]
|
|
@@ -0,0 +1,84 @@
|
|
|
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 import TopologyFilter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@typechecked
|
|
14
|
+
def resolve_topology_filter[A](
|
|
15
|
+
assembly: A,
|
|
16
|
+
count: int,
|
|
17
|
+
topology_filter: TopologyFilter[A] | None,
|
|
18
|
+
) -> NDArray[np.bool_] | None:
|
|
19
|
+
"""
|
|
20
|
+
Resolve a topology filter into a boolean mask.
|
|
21
|
+
|
|
22
|
+
A topology filter can be either a precomputed boolean mask or a callable
|
|
23
|
+
that derives one from the assembly. This function normalizes both forms
|
|
24
|
+
into a concrete mask and validates its length against the expected
|
|
25
|
+
topology count (vertices, edges, parts, etc.).
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
assembly : A
|
|
30
|
+
The assembly to pass to the filter if it is a callable.
|
|
31
|
+
topology_filter : TopologyFilter[A] | None
|
|
32
|
+
A boolean mask, a callable that produces one from the assembly,
|
|
33
|
+
or None. If None, the function returns None immediately.
|
|
34
|
+
count : int
|
|
35
|
+
The expected length of the resulting mask. Should match the number
|
|
36
|
+
of topological elements being filtered (e.g. vertex_count, edge_count,
|
|
37
|
+
part count).
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
NDArray[np.bool_] | None
|
|
42
|
+
The resolved boolean mask, or None if topology_filter is None.
|
|
43
|
+
|
|
44
|
+
Raises
|
|
45
|
+
------
|
|
46
|
+
ValueError
|
|
47
|
+
If the resolved mask length does not match the expected count.
|
|
48
|
+
|
|
49
|
+
Examples
|
|
50
|
+
--------
|
|
51
|
+
>>> import numpy as np
|
|
52
|
+
>>> from scadpy import resolve_topology_filter
|
|
53
|
+
|
|
54
|
+
>>> # none passthrough
|
|
55
|
+
>>> resolve_topology_filter("any", 4, None) is None
|
|
56
|
+
True
|
|
57
|
+
|
|
58
|
+
>>> # direct mask
|
|
59
|
+
>>> mask = np.array([True, False, True, False])
|
|
60
|
+
>>> resolve_topology_filter("any", 4, mask)
|
|
61
|
+
array([ True, False, True, False])
|
|
62
|
+
|
|
63
|
+
>>> # callable filter
|
|
64
|
+
>>> resolve_topology_filter(
|
|
65
|
+
... "hello", 1, lambda s: np.array([len(s) > 3])
|
|
66
|
+
... )
|
|
67
|
+
array([ True])
|
|
68
|
+
|
|
69
|
+
>>> # mismatched count
|
|
70
|
+
>>> resolve_topology_filter(
|
|
71
|
+
... "any", 5, np.array([True, False])
|
|
72
|
+
... ) # doctest: +ELLIPSIS
|
|
73
|
+
Traceback (most recent call last):
|
|
74
|
+
...
|
|
75
|
+
ValueError: Topology filter length (2) does not match...
|
|
76
|
+
"""
|
|
77
|
+
if topology_filter is None:
|
|
78
|
+
return None
|
|
79
|
+
mask = topology_filter(assembly) if callable(topology_filter) else topology_filter
|
|
80
|
+
if len(mask) != count:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
f"Topology filter length ({len(mask)}) does not match expected count ({count})."
|
|
83
|
+
)
|
|
84
|
+
return mask
|
|
@@ -0,0 +1,55 @@
|
|
|
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 typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from scadpy import Part, TopologyFilter
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@typechecked
|
|
14
|
+
def transform_filtered_parts[A, G](
|
|
15
|
+
assembly: A,
|
|
16
|
+
parts: Sequence[Part[G]],
|
|
17
|
+
part_filter: TopologyFilter[A] | None,
|
|
18
|
+
transform: Callable[[Sequence[Part[G]]], Sequence[Part[G]]],
|
|
19
|
+
concat_parts: Callable[[Sequence[Part[G]]], A],
|
|
20
|
+
) -> A:
|
|
21
|
+
"""Apply a transformation to a filtered subset of parts, keeping the rest unchanged.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
assembly : A
|
|
26
|
+
The assembly used to evaluate *part_filter*.
|
|
27
|
+
parts : Sequence[Part[G]]
|
|
28
|
+
All parts of the assembly.
|
|
29
|
+
part_filter : TopologyFilter[A] | None
|
|
30
|
+
Optional filter selecting which parts to transform. If ``None``, all
|
|
31
|
+
parts are transformed.
|
|
32
|
+
transform : Callable[[Sequence[Part[G]]], Sequence[Part[G]]]
|
|
33
|
+
Function applied to the selected parts.
|
|
34
|
+
concat_parts : Callable[[Sequence[Part[G]]], A]
|
|
35
|
+
Function that assembles transformed and untouched parts into a new
|
|
36
|
+
assembly of type *A*.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
A
|
|
41
|
+
New assembly with the filtered parts transformed and the rest unchanged.
|
|
42
|
+
"""
|
|
43
|
+
from scadpy import resolve_topology_filter
|
|
44
|
+
|
|
45
|
+
mask = resolve_topology_filter(
|
|
46
|
+
assembly=assembly, count=len(parts), topology_filter=part_filter
|
|
47
|
+
)
|
|
48
|
+
if mask is None:
|
|
49
|
+
mask = np.ones(len(parts), dtype=bool)
|
|
50
|
+
|
|
51
|
+
selected = [p for p, m in zip(parts, mask) if m]
|
|
52
|
+
unselected = [p for p, m in zip(parts, mask) if not m]
|
|
53
|
+
|
|
54
|
+
transformed = transform(selected)
|
|
55
|
+
return concat_parts([*transformed, *unselected])
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
from IPython.core.display import HTML
|
|
7
|
+
from typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@typechecked
|
|
11
|
+
def map_component_to_html_file[Component](
|
|
12
|
+
component: Component, path: str, to_html: Callable[[Component], HTML]
|
|
13
|
+
) -> int:
|
|
14
|
+
"""
|
|
15
|
+
Export a component to an HTML file.
|
|
16
|
+
|
|
17
|
+
This function uses dependency injection for the HTML conversion, making it
|
|
18
|
+
suitable for any component type that can be represented as HTML. The HTML
|
|
19
|
+
is written to the specified file path.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
component : Component
|
|
24
|
+
The component to export.
|
|
25
|
+
path : str
|
|
26
|
+
The file path where the HTML will be written.
|
|
27
|
+
to_html : Callable[[Component], HTML]
|
|
28
|
+
Function that converts the component to an IPython HTML object.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
int
|
|
33
|
+
The number of characters written to the file.
|
|
34
|
+
|
|
35
|
+
Examples
|
|
36
|
+
--------
|
|
37
|
+
>>> from IPython.core.display import HTML
|
|
38
|
+
>>> from scadpy import map_component_to_html_file
|
|
39
|
+
...
|
|
40
|
+
>>> map_component_to_html_file(
|
|
41
|
+
... "Hello, World!",
|
|
42
|
+
... "test.html",
|
|
43
|
+
... to_html=lambda c: HTML(f"<h1>{c}</h1>")
|
|
44
|
+
... ) > 0 # doctest: +SKIP
|
|
45
|
+
True
|
|
46
|
+
"""
|
|
47
|
+
return open(path, "w", encoding="utf-8").write(cast(str, to_html(component).data))
|