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,73 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Sequence
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from trimesh import Trimesh
|
|
7
|
+
from trimesh.boolean import boolean_manifold # pyright: ignore[reportUnknownVariableType]
|
|
8
|
+
from typeguard import typechecked
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scadpy.core.part import Part
|
|
12
|
+
from scadpy.d3.solid import Solid
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@typechecked
|
|
16
|
+
def unify_solid_parts(
|
|
17
|
+
parts: Sequence[Part[Trimesh]],
|
|
18
|
+
make_assembly_from_parts: Callable[[Sequence[Part[Trimesh]]], Solid],
|
|
19
|
+
) -> Solid:
|
|
20
|
+
"""Unite a sequence of solid parts and return the resulting solid.
|
|
21
|
+
|
|
22
|
+
Shortcut for :func:`unify_parts`.
|
|
23
|
+
See :func:`unify_parts` for full documentation.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
parts : Sequence[Part[Trimesh]]
|
|
28
|
+
The solid parts to unite.
|
|
29
|
+
make_assembly_from_parts : Callable[[Sequence[Part[Trimesh]]], Solid]
|
|
30
|
+
Factory function to build the resulting Solid from a sequence of parts.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
Solid
|
|
35
|
+
A new solid containing the geometric union of the input parts.
|
|
36
|
+
|
|
37
|
+
Examples
|
|
38
|
+
--------
|
|
39
|
+
>>> from scadpy import cuboid, sphere, unify_solid_parts, Solid
|
|
40
|
+
|
|
41
|
+
>>> unify_solid_parts( # doctest: +SKIP
|
|
42
|
+
... parts=(
|
|
43
|
+
... list(cuboid(4)._parts)
|
|
44
|
+
... + list(sphere(radius=2).translate(2)._parts)
|
|
45
|
+
... ),
|
|
46
|
+
... make_assembly_from_parts=Solid.from_parts,
|
|
47
|
+
... )
|
|
48
|
+
|
|
49
|
+
.. render-example::
|
|
50
|
+
:name: unify_solid_parts_example
|
|
51
|
+
:example: unify_solid_parts(parts=list(cuboid(4)._parts) + list(sphere(radius=2).translate([2, 2, 2])._parts), make_assembly_from_parts=Solid.from_parts)
|
|
52
|
+
:ghost: concat_solid(solids=[cuboid(4), sphere(radius=2).translate(2)])
|
|
53
|
+
"""
|
|
54
|
+
from scadpy import (
|
|
55
|
+
Part,
|
|
56
|
+
are_solid_parts_intersecting,
|
|
57
|
+
get_solid_part_bounds,
|
|
58
|
+
)
|
|
59
|
+
from scadpy.core.part import unify_parts
|
|
60
|
+
|
|
61
|
+
return unify_parts(
|
|
62
|
+
parts=parts,
|
|
63
|
+
get_part_color=lambda p: p.color,
|
|
64
|
+
get_part_magnitude=lambda p: p.geometry.volume, # pyright: ignore[reportAny]
|
|
65
|
+
get_part_bounds=get_solid_part_bounds,
|
|
66
|
+
are_parts_intersecting=are_solid_parts_intersecting,
|
|
67
|
+
get_part_geometry=lambda p: p.geometry,
|
|
68
|
+
unify_geometries=lambda g: boolean_manifold(
|
|
69
|
+
g, operation="union", check_volume=False
|
|
70
|
+
).split(), # pyright: ignore[reportUnknownMemberType]
|
|
71
|
+
make_part_from_geometry=Part[Trimesh].from_geometry,
|
|
72
|
+
make_assembly_from_parts=make_assembly_from_parts,
|
|
73
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"map_solid_to_html",
|
|
3
|
+
"map_solid_to_html_file",
|
|
4
|
+
"map_solid_to_screen",
|
|
5
|
+
"map_solid_to_stl_file",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
from .map_solid_to_html import map_solid_to_html
|
|
9
|
+
from .map_solid_to_html_file import map_solid_to_html_file
|
|
10
|
+
from .map_solid_to_screen import map_solid_to_screen
|
|
11
|
+
from .map_solid_to_stl_file import map_solid_to_stl_file
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from IPython.core.display import HTML
|
|
8
|
+
from typeguard import typechecked
|
|
9
|
+
|
|
10
|
+
from scadpy.color.constants import BLACK, WHITE
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from scadpy import Color, Solid
|
|
14
|
+
|
|
15
|
+
_TEMPLATE = """\
|
|
16
|
+
<div id="{viewer_id}" style="width:100%;aspect-ratio:1/1;"></div>
|
|
17
|
+
<script>
|
|
18
|
+
(function () {{
|
|
19
|
+
// --- Global deduped Three.js loader (shared across all viewers on the page) ---
|
|
20
|
+
if (!window.__scadpy_loader) {{
|
|
21
|
+
var _scripts = [
|
|
22
|
+
'https://cdn.jsdelivr.net/npm/three@0.128/build/three.min.js',
|
|
23
|
+
'https://cdn.jsdelivr.net/npm/three@0.128/examples/js/controls/OrbitControls.js',
|
|
24
|
+
'https://cdn.jsdelivr.net/npm/three@0.128/examples/js/lines/LineSegmentsGeometry.js',
|
|
25
|
+
'https://cdn.jsdelivr.net/npm/three@0.128/examples/js/lines/LineMaterial.js',
|
|
26
|
+
'https://cdn.jsdelivr.net/npm/three@0.128/examples/js/lines/LineSegments2.js',
|
|
27
|
+
];
|
|
28
|
+
var _ready = false, _queue = [], _idx = 0;
|
|
29
|
+
function _next() {{
|
|
30
|
+
if (_idx >= _scripts.length) {{
|
|
31
|
+
_ready = true;
|
|
32
|
+
_queue.forEach(function(f) {{ f(); }});
|
|
33
|
+
_queue = [];
|
|
34
|
+
return;
|
|
35
|
+
}}
|
|
36
|
+
var src = _scripts[_idx++];
|
|
37
|
+
if (document.querySelector('script[src="' + src + '"]')) {{ _next(); return; }}
|
|
38
|
+
var s = document.createElement('script');
|
|
39
|
+
s.src = src; s.onload = _next;
|
|
40
|
+
document.head.appendChild(s);
|
|
41
|
+
}}
|
|
42
|
+
_next();
|
|
43
|
+
window.__scadpy_loader = {{
|
|
44
|
+
onReady: function(f) {{ _ready ? f() : _queue.push(f); }}
|
|
45
|
+
}};
|
|
46
|
+
}}
|
|
47
|
+
|
|
48
|
+
// --- Per-viewer state ---
|
|
49
|
+
var container = document.getElementById('{viewer_id}');
|
|
50
|
+
var renderer = null, animId = null, _controls = null, _scene = null, _camera = null;
|
|
51
|
+
var fg = '{foreground_color}';
|
|
52
|
+
var bg = '{background_color}';
|
|
53
|
+
var parts = {parts_json};
|
|
54
|
+
|
|
55
|
+
function startAnim() {{
|
|
56
|
+
if (!renderer || animId) return;
|
|
57
|
+
(function animate() {{
|
|
58
|
+
animId = requestAnimationFrame(animate);
|
|
59
|
+
_controls.update();
|
|
60
|
+
renderer.render(_scene, _camera);
|
|
61
|
+
}})();
|
|
62
|
+
}}
|
|
63
|
+
|
|
64
|
+
function stopAnim() {{
|
|
65
|
+
if (animId) {{ cancelAnimationFrame(animId); animId = null; }}
|
|
66
|
+
}}
|
|
67
|
+
|
|
68
|
+
function build() {{
|
|
69
|
+
if (renderer) {{ startAnim(); return; }}
|
|
70
|
+
_scene = new THREE.Scene();
|
|
71
|
+
_scene.background = new THREE.Color(bg);
|
|
72
|
+
|
|
73
|
+
var w = container.clientWidth || 400;
|
|
74
|
+
_camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0.001, 100000);
|
|
75
|
+
renderer = new THREE.WebGLRenderer({{antialias: true}});
|
|
76
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
77
|
+
renderer.setSize(w, w);
|
|
78
|
+
container.appendChild(renderer.domElement);
|
|
79
|
+
|
|
80
|
+
_controls = new THREE.OrbitControls(_camera, renderer.domElement);
|
|
81
|
+
_controls.enableDamping = true;
|
|
82
|
+
|
|
83
|
+
var edgeColor;
|
|
84
|
+
_scene.add(new THREE.AmbientLight(0xffffff, 0.45));
|
|
85
|
+
var key = new THREE.DirectionalLight(0xffffff, 0.75);
|
|
86
|
+
key.position.set(2, 3, 4); _scene.add(key);
|
|
87
|
+
var fill = new THREE.DirectionalLight(0xffffff, 0.3);
|
|
88
|
+
fill.position.set(-3, 1, -2); _scene.add(fill);
|
|
89
|
+
var bottom = new THREE.DirectionalLight(0xffffff, 0.15);
|
|
90
|
+
bottom.position.set(0, -1, 0); _scene.add(bottom);
|
|
91
|
+
|
|
92
|
+
for (var i = 0; i < parts.length; i++) {{
|
|
93
|
+
var part = parts[i];
|
|
94
|
+
var geo = new THREE.BufferGeometry();
|
|
95
|
+
geo.setAttribute('position', new THREE.Float32BufferAttribute(part.vertices, 3));
|
|
96
|
+
geo.setIndex(part.faces);
|
|
97
|
+
geo.computeVertexNormals();
|
|
98
|
+
var transparent = part.opacity < 1.0;
|
|
99
|
+
edgeColor = new THREE.Color(part.color).lerp(new THREE.Color(bg), 0.5);
|
|
100
|
+
var mat = new THREE.MeshPhongMaterial({{
|
|
101
|
+
color: part.color,
|
|
102
|
+
opacity: part.opacity,
|
|
103
|
+
transparent: transparent,
|
|
104
|
+
depthWrite: !transparent,
|
|
105
|
+
side: THREE.FrontSide,
|
|
106
|
+
polygonOffset: true,
|
|
107
|
+
polygonOffsetFactor: 1,
|
|
108
|
+
polygonOffsetUnits: 1,
|
|
109
|
+
shininess: 40,
|
|
110
|
+
specular: new THREE.Color(0x222222),
|
|
111
|
+
flatShading: true,
|
|
112
|
+
}});
|
|
113
|
+
_scene.add(new THREE.Mesh(geo, mat));
|
|
114
|
+
var edgesGeo = new THREE.EdgesGeometry(geo, 20);
|
|
115
|
+
var lineGeo = new THREE.LineSegmentsGeometry();
|
|
116
|
+
lineGeo.setPositions(edgesGeo.attributes.position.array);
|
|
117
|
+
var lineMat = new THREE.LineMaterial({{
|
|
118
|
+
color: edgeColor,
|
|
119
|
+
linewidth: 2,
|
|
120
|
+
resolution: new THREE.Vector2(w, w),
|
|
121
|
+
transparent: true,
|
|
122
|
+
opacity: transparent ? part.opacity * 0.5 : 1.0,
|
|
123
|
+
}});
|
|
124
|
+
_scene.add(new THREE.LineSegments2(lineGeo, lineMat));
|
|
125
|
+
var outlineVS = [
|
|
126
|
+
'uniform vec2 resolution;',
|
|
127
|
+
'uniform float outlineWidth;',
|
|
128
|
+
'void main() {{',
|
|
129
|
+
' vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
|
|
130
|
+
' vec4 posN = projectionMatrix * modelViewMatrix * vec4(position + normal, 1.0);',
|
|
131
|
+
' vec2 dir = normalize(posN.xy / posN.w - pos.xy / pos.w);',
|
|
132
|
+
' pos.xy += dir * outlineWidth / (resolution * 0.5);',
|
|
133
|
+
' gl_Position = pos;',
|
|
134
|
+
'}}'
|
|
135
|
+
].join('\\n');
|
|
136
|
+
var outlineFS = [
|
|
137
|
+
'uniform vec3 outlineColor;',
|
|
138
|
+
'void main() {{',
|
|
139
|
+
' gl_FragColor = vec4(outlineColor, 1.0);',
|
|
140
|
+
'}}'
|
|
141
|
+
].join('\\n');
|
|
142
|
+
var outlineMat = new THREE.ShaderMaterial({{
|
|
143
|
+
uniforms: {{
|
|
144
|
+
outlineColor: {{ value: edgeColor }},
|
|
145
|
+
resolution: {{ value: new THREE.Vector2(w, w) }},
|
|
146
|
+
outlineWidth: {{ value: 2.0 }},
|
|
147
|
+
}},
|
|
148
|
+
vertexShader: outlineVS,
|
|
149
|
+
fragmentShader: outlineFS,
|
|
150
|
+
side: THREE.BackSide,
|
|
151
|
+
}});
|
|
152
|
+
var outlineMesh = new THREE.Mesh(geo, outlineMat);
|
|
153
|
+
outlineMesh.renderOrder = -1;
|
|
154
|
+
_scene.add(outlineMesh);
|
|
155
|
+
}}
|
|
156
|
+
|
|
157
|
+
var box = new THREE.Box3().setFromObject(_scene);
|
|
158
|
+
var center = box.getCenter(new THREE.Vector3());
|
|
159
|
+
var size = box.getSize(new THREE.Vector3());
|
|
160
|
+
var dist = Math.max(size.x, size.y, size.z) * 1.2;
|
|
161
|
+
_camera.position.copy(center).add(new THREE.Vector3(dist, dist, dist));
|
|
162
|
+
_controls.target.copy(center);
|
|
163
|
+
_controls.update();
|
|
164
|
+
|
|
165
|
+
var maxDim = Math.max(size.x, size.y, size.z);
|
|
166
|
+
var tgt = maxDim * 0.25 || 1;
|
|
167
|
+
var mag = Math.pow(10, Math.floor(Math.log10(tgt)));
|
|
168
|
+
var niceStep = [1, 2, 5, 10].reduce(function(p, c) {{
|
|
169
|
+
return Math.abs(c * mag - tgt) < Math.abs(p * mag - tgt) ? c : p;
|
|
170
|
+
}}) * mag;
|
|
171
|
+
|
|
172
|
+
var cx = center.x, cy = center.y, cz = center.z;
|
|
173
|
+
var halfGridXZ = Math.ceil(Math.max(size.x, size.z) / 2 / niceStep) * niceStep;
|
|
174
|
+
var halfGridY = Math.ceil(size.y / 2 / niceStep) * niceStep;
|
|
175
|
+
var halfGrid = Math.max(halfGridXZ, halfGridY);
|
|
176
|
+
var divs = Math.round(halfGrid * 2 / niceStep);
|
|
177
|
+
var floorY = cy - halfGrid;
|
|
178
|
+
|
|
179
|
+
var frustum = halfGrid * 1.6;
|
|
180
|
+
_camera.left = -frustum; _camera.right = frustum;
|
|
181
|
+
_camera.top = frustum; _camera.bottom = -frustum;
|
|
182
|
+
_camera.near = dist * 0.01;
|
|
183
|
+
_camera.far = (dist + halfGrid * 2) * 2;
|
|
184
|
+
_camera.updateProjectionMatrix();
|
|
185
|
+
|
|
186
|
+
function makeGrid(gSize, d, opacity) {{
|
|
187
|
+
var g = new THREE.GridHelper(gSize, d, fg, fg);
|
|
188
|
+
g.material.opacity = opacity;
|
|
189
|
+
g.material.transparent = true;
|
|
190
|
+
return g;
|
|
191
|
+
}}
|
|
192
|
+
|
|
193
|
+
function makeWall(opacity) {{
|
|
194
|
+
var n = divs, h = halfGrid, pts = [];
|
|
195
|
+
for (var i = 0; i <= n; i++) {{ var xv = -h + i * niceStep; pts.push(xv, 0, 0, xv, h * 2, 0); }}
|
|
196
|
+
for (var j = 0; j <= n; j++) {{ var yv2 = j * niceStep; pts.push(-h, yv2, 0, h, yv2, 0); }}
|
|
197
|
+
var g = new THREE.BufferGeometry();
|
|
198
|
+
g.setAttribute('position', new THREE.Float32BufferAttribute(pts, 3));
|
|
199
|
+
return new THREE.LineSegments(g, new THREE.LineBasicMaterial({{color: fg, transparent: true, opacity: opacity}}));
|
|
200
|
+
}}
|
|
201
|
+
|
|
202
|
+
var floor = makeGrid(halfGrid * 2, divs, 0.25);
|
|
203
|
+
floor.position.set(cx, floorY, cz); _scene.add(floor);
|
|
204
|
+
var backWall = makeWall(0.1);
|
|
205
|
+
backWall.position.set(cx, floorY, cz - halfGrid); _scene.add(backWall);
|
|
206
|
+
var leftWall = makeWall(0.1);
|
|
207
|
+
leftWall.rotation.y = -Math.PI / 2;
|
|
208
|
+
leftWall.position.set(cx - halfGrid, floorY, cz); _scene.add(leftWall);
|
|
209
|
+
|
|
210
|
+
function makeSprite(text) {{
|
|
211
|
+
var canvas = document.createElement('canvas');
|
|
212
|
+
var cw = Math.max(80, text.length * 26);
|
|
213
|
+
canvas.width = cw; canvas.height = 64;
|
|
214
|
+
var ctx = canvas.getContext('2d');
|
|
215
|
+
ctx.font = 'bold 38px sans-serif';
|
|
216
|
+
ctx.fillStyle = fg; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
217
|
+
ctx.fillText(text, cw / 2, 32);
|
|
218
|
+
var spr = new THREE.Sprite(new THREE.SpriteMaterial({{map: new THREE.CanvasTexture(canvas), transparent: true}}));
|
|
219
|
+
var sh = halfGrid * 0.16;
|
|
220
|
+
spr.scale.set(sh * cw / 64, sh, 1);
|
|
221
|
+
return spr;
|
|
222
|
+
}}
|
|
223
|
+
|
|
224
|
+
var edgeOff = niceStep * 0.5, gy = floorY + niceStep * 0.15;
|
|
225
|
+
var xS = Math.ceil((cx - halfGrid) / niceStep), xE = Math.floor((cx + halfGrid) / niceStep);
|
|
226
|
+
for (var xi = xS; xi <= xE; xi++) {{
|
|
227
|
+
var xv = xi * niceStep;
|
|
228
|
+
var xs = makeSprite(parseFloat(xv.toPrecision(4)).toString());
|
|
229
|
+
xs.position.set(xv, gy, cz - halfGrid - edgeOff); _scene.add(xs);
|
|
230
|
+
}}
|
|
231
|
+
var zS = Math.ceil((cz - halfGrid) / niceStep), zE = Math.floor((cz + halfGrid) / niceStep);
|
|
232
|
+
for (var zi = zS; zi <= zE; zi++) {{
|
|
233
|
+
var zv = zi * niceStep;
|
|
234
|
+
var zs = makeSprite(parseFloat(zv.toPrecision(4)).toString());
|
|
235
|
+
zs.position.set(cx - halfGrid - edgeOff, gy, zv); _scene.add(zs);
|
|
236
|
+
}}
|
|
237
|
+
var yS = Math.ceil((cy - halfGrid) / niceStep), yE = Math.floor((cy + halfGrid) / niceStep);
|
|
238
|
+
for (var yi = yS; yi <= yE; yi++) {{
|
|
239
|
+
var yv = yi * niceStep;
|
|
240
|
+
var ys = makeSprite(parseFloat(yv.toPrecision(4)).toString());
|
|
241
|
+
ys.position.set(cx - halfGrid - edgeOff, yv, cz - halfGrid); _scene.add(ys);
|
|
242
|
+
}}
|
|
243
|
+
|
|
244
|
+
var cornerX = cx - halfGrid, cornerY = floorY, cornerZ = cz - halfGrid;
|
|
245
|
+
var origin = new THREE.Vector3(cornerX, cornerY, cornerZ);
|
|
246
|
+
var axisLen = halfGrid * 2, arrowHead = axisLen * 0.08;
|
|
247
|
+
_scene.add(new THREE.ArrowHelper(new THREE.Vector3(1, 0, 0), origin, axisLen, 0xff4444, arrowHead, arrowHead * 0.6));
|
|
248
|
+
_scene.add(new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), origin, axisLen, 0x44ff44, arrowHead, arrowHead * 0.6));
|
|
249
|
+
_scene.add(new THREE.ArrowHelper(new THREE.Vector3(0, 0, 1), origin, axisLen, 0x4444ff, arrowHead, arrowHead * 0.6));
|
|
250
|
+
|
|
251
|
+
function makeAxisLabel(text, color) {{
|
|
252
|
+
var canvas = document.createElement('canvas');
|
|
253
|
+
canvas.width = 64; canvas.height = 64;
|
|
254
|
+
var ctx = canvas.getContext('2d');
|
|
255
|
+
ctx.font = 'bold 52px sans-serif';
|
|
256
|
+
ctx.fillStyle = color; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
257
|
+
ctx.fillText(text, 32, 32);
|
|
258
|
+
var spr = new THREE.Sprite(new THREE.SpriteMaterial({{map: new THREE.CanvasTexture(canvas)}}));
|
|
259
|
+
var labelSize = axisLen * 0.1;
|
|
260
|
+
spr.scale.set(labelSize, labelSize, 1);
|
|
261
|
+
return spr;
|
|
262
|
+
}}
|
|
263
|
+
|
|
264
|
+
var lx = makeAxisLabel('X', '#ff4444'); lx.position.set(cornerX + axisLen * 1.06, cornerY, cornerZ); _scene.add(lx);
|
|
265
|
+
var ly = makeAxisLabel('Y', '#44ff44'); ly.position.set(cornerX, cornerY + axisLen * 1.06, cornerZ); _scene.add(ly);
|
|
266
|
+
var lz = makeAxisLabel('Z', '#4444ff'); lz.position.set(cornerX, cornerY, cornerZ + axisLen * 1.06); _scene.add(lz);
|
|
267
|
+
|
|
268
|
+
requestAnimationFrame(function() {{
|
|
269
|
+
renderer.render(_scene, _camera);
|
|
270
|
+
startAnim();
|
|
271
|
+
}});
|
|
272
|
+
}}
|
|
273
|
+
|
|
274
|
+
// Build on first visibility, pause/resume animation — canvas is never destroyed
|
|
275
|
+
var observer = new IntersectionObserver(function(entries) {{
|
|
276
|
+
if (entries[0].isIntersecting) {{ window.__scadpy_loader.onReady(build); }}
|
|
277
|
+
else {{ stopAnim(); }}
|
|
278
|
+
}}, {{ threshold: 0.01, rootMargin: '200px' }});
|
|
279
|
+
observer.observe(container);
|
|
280
|
+
}})();
|
|
281
|
+
</script>
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@typechecked
|
|
286
|
+
def map_solid_to_html(
|
|
287
|
+
solid: Solid,
|
|
288
|
+
background_color: Color = WHITE,
|
|
289
|
+
foreground_color: Color = BLACK,
|
|
290
|
+
) -> HTML:
|
|
291
|
+
background_color_hex = "#{:02X}{:02X}{:02X}".format(
|
|
292
|
+
*(int(x * 255) for x in background_color[:-1])
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
parts = []
|
|
296
|
+
for part in solid._parts:
|
|
297
|
+
mesh = part.geometry
|
|
298
|
+
color = part.color
|
|
299
|
+
parts.append({
|
|
300
|
+
"vertices": mesh.vertices.ravel().tolist(),
|
|
301
|
+
"faces": mesh.faces.ravel().tolist(),
|
|
302
|
+
"color": "#{:02X}{:02X}{:02X}".format(
|
|
303
|
+
int(color[0] * 255), int(color[1] * 255), int(color[2] * 255)
|
|
304
|
+
),
|
|
305
|
+
"opacity": float(color[3]),
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
foreground_color_hex = "#{:02X}{:02X}{:02X}".format(
|
|
309
|
+
*(int(x * 255) for x in foreground_color[:-1])
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
html = _TEMPLATE.format(
|
|
313
|
+
viewer_id=f"scadpy-{uuid.uuid4().hex}",
|
|
314
|
+
background_color=background_color_hex,
|
|
315
|
+
foreground_color=foreground_color_hex,
|
|
316
|
+
parts_json=json.dumps(parts),
|
|
317
|
+
)
|
|
318
|
+
return HTML(html)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from scadpy.color.constants import BLACK, WHITE
|
|
6
|
+
from typeguard import typechecked
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from scadpy import Color, Solid
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@typechecked
|
|
13
|
+
def map_solid_to_html_file(
|
|
14
|
+
solid: Solid,
|
|
15
|
+
path: str,
|
|
16
|
+
background_color: Color = WHITE,
|
|
17
|
+
foreground_color: Color = BLACK,
|
|
18
|
+
) -> int:
|
|
19
|
+
"""Save a solid as an HTML file.
|
|
20
|
+
|
|
21
|
+
Shortcut for :func:`map_component_to_html_file`.
|
|
22
|
+
See :func:`map_component_to_html_file` for full documentation.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
solid : Solid
|
|
27
|
+
The solid to save.
|
|
28
|
+
path : str
|
|
29
|
+
The file path where the HTML will be written.
|
|
30
|
+
background_color : Color, default=WHITE
|
|
31
|
+
The background color of the rendered output.
|
|
32
|
+
foreground_color : Color, default=BLACK
|
|
33
|
+
The foreground color (axes, grid) of the rendered output.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
int
|
|
38
|
+
The number of characters written to the file.
|
|
39
|
+
|
|
40
|
+
Examples
|
|
41
|
+
--------
|
|
42
|
+
>>> from scadpy import cuboid, map_solid_to_html_file
|
|
43
|
+
|
|
44
|
+
>>> map_solid_to_html_file( # doctest: +SKIP
|
|
45
|
+
... solid=cuboid(4), path="output.html"
|
|
46
|
+
... )
|
|
47
|
+
"""
|
|
48
|
+
from scadpy import map_component_to_html_file, map_solid_to_html
|
|
49
|
+
|
|
50
|
+
return map_component_to_html_file(
|
|
51
|
+
component=solid,
|
|
52
|
+
path=path,
|
|
53
|
+
to_html=lambda component: map_solid_to_html(
|
|
54
|
+
solid=component,
|
|
55
|
+
background_color=background_color,
|
|
56
|
+
foreground_color=foreground_color,
|
|
57
|
+
),
|
|
58
|
+
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from scadpy.color.constants import BLACK, WHITE
|
|
6
|
+
from typeguard import typechecked
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from scadpy import Color, Solid
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@typechecked
|
|
13
|
+
def map_solid_to_screen(
|
|
14
|
+
solid: Solid,
|
|
15
|
+
background_color: Color = WHITE,
|
|
16
|
+
foreground_color: Color = BLACK,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Display a solid in a Qt-based window.
|
|
19
|
+
|
|
20
|
+
Shortcut for :func:`map_component_to_screen`.
|
|
21
|
+
See :func:`map_component_to_screen` for full documentation.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
solid : Solid
|
|
26
|
+
The solid to display.
|
|
27
|
+
background_color : Color, default=WHITE
|
|
28
|
+
The background color of the rendered window.
|
|
29
|
+
foreground_color : Color, default=BLACK
|
|
30
|
+
The foreground color (axes, grid) of the rendered window.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
None
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
>>> from scadpy import cuboid, map_solid_to_screen
|
|
39
|
+
|
|
40
|
+
>>> map_solid_to_screen(solid=cuboid(4)) # doctest: +SKIP
|
|
41
|
+
"""
|
|
42
|
+
from scadpy import map_component_to_screen, map_solid_to_html
|
|
43
|
+
|
|
44
|
+
map_component_to_screen(
|
|
45
|
+
component=solid,
|
|
46
|
+
to_html=lambda component: map_solid_to_html(
|
|
47
|
+
solid=component,
|
|
48
|
+
background_color=background_color,
|
|
49
|
+
foreground_color=foreground_color,
|
|
50
|
+
),
|
|
51
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
import trimesh
|
|
7
|
+
from typeguard import typechecked
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from scadpy import Solid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@typechecked
|
|
14
|
+
def map_solid_to_stl_file(solid: Solid, path: str | Path) -> int:
|
|
15
|
+
"""Export a solid to an STL file.
|
|
16
|
+
|
|
17
|
+
All parts of the solid are merged into a single mesh before export.
|
|
18
|
+
The resulting file is in binary STL format.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
solid : Solid
|
|
23
|
+
The solid to export.
|
|
24
|
+
path : str or Path
|
|
25
|
+
Destination file path. The ``.stl`` extension is recommended but
|
|
26
|
+
not enforced.
|
|
27
|
+
|
|
28
|
+
Returns
|
|
29
|
+
-------
|
|
30
|
+
int
|
|
31
|
+
The number of bytes written.
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
>>> from scadpy import cuboid, map_solid_to_stl_file
|
|
36
|
+
|
|
37
|
+
>>> map_solid_to_stl_file(solid=cuboid(4), path="output.stl") # doctest: +SKIP
|
|
38
|
+
"""
|
|
39
|
+
meshes = [p.geometry for p in solid._parts if len(p.geometry.faces) > 0]
|
|
40
|
+
if not meshes:
|
|
41
|
+
data = b""
|
|
42
|
+
else:
|
|
43
|
+
combined = trimesh.util.concatenate(meshes)
|
|
44
|
+
data = combined.export(file_type="stl")
|
|
45
|
+
|
|
46
|
+
path = Path(path)
|
|
47
|
+
path.write_bytes(data)
|
|
48
|
+
return len(data)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
"get_solid_bounds",
|
|
3
|
+
"get_solid_part_bounds",
|
|
4
|
+
"get_solid_part_colors",
|
|
5
|
+
"is_solid_empty",
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
from .get_solid_bounds import get_solid_bounds
|
|
9
|
+
from .get_solid_part_bounds import get_solid_part_bounds
|
|
10
|
+
from .get_solid_part_colors import get_solid_part_colors
|
|
11
|
+
from .is_solid_empty import is_solid_empty
|
|
@@ -0,0 +1,37 @@
|
|
|
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 Solid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@typechecked
|
|
14
|
+
def get_solid_bounds(solid: Solid) -> NDArray[np.float64]:
|
|
15
|
+
"""Return the axis-aligned bounding box of the solid.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
solid : Solid
|
|
20
|
+
The solid to compute bounds for.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
NDArray[np.float64]
|
|
25
|
+
1D array ``[min_x, min_y, min_z, max_x, max_y, max_z]``.
|
|
26
|
+
Returns zeros if the solid is empty.
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> from scadpy import cuboid, get_solid_bounds
|
|
31
|
+
|
|
32
|
+
>>> get_solid_bounds(cuboid(2))
|
|
33
|
+
array([-1., -1., -1., 1., 1., 1.])
|
|
34
|
+
"""
|
|
35
|
+
from scadpy import get_component_bounds
|
|
36
|
+
|
|
37
|
+
return get_component_bounds(solid.vertex_coordinates)
|
|
@@ -0,0 +1,37 @@
|
|
|
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 trimesh import Trimesh
|
|
8
|
+
from typeguard import typechecked
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from scadpy.core.part import Part
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@typechecked
|
|
15
|
+
def get_solid_part_bounds(part: Part[Trimesh]) -> NDArray[np.float64]:
|
|
16
|
+
"""Return the 3D bounding box of a solid part as [minx, miny, minz, maxx, maxy, maxz].
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
part : Part[Trimesh]
|
|
21
|
+
The solid part to compute the bounding box of.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
NDArray[np.float64]
|
|
26
|
+
Array of shape (6,) containing [minx, miny, minz, maxx, maxy, maxz].
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> from scadpy import cuboid, get_solid_part_bounds
|
|
31
|
+
>>> bounds = get_solid_part_bounds(part=cuboid(2)._parts[0])
|
|
32
|
+
>>> bounds.shape
|
|
33
|
+
(6,)
|
|
34
|
+
"""
|
|
35
|
+
from scadpy import get_component_bounds
|
|
36
|
+
|
|
37
|
+
return get_component_bounds(part.geometry.vertices)
|
|
@@ -0,0 +1,39 @@
|
|
|
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 Solid
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@typechecked
|
|
14
|
+
def get_solid_part_colors(solid: Solid) -> NDArray[np.float64]:
|
|
15
|
+
"""For each part in the solid, return its RGBA color.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
solid : Solid
|
|
20
|
+
The solid to extract part colors from.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
NDArray[np.float64]
|
|
25
|
+
2D array of shape (n_parts, 4), one RGBA row per part.
|
|
26
|
+
|
|
27
|
+
Examples
|
|
28
|
+
--------
|
|
29
|
+
>>> from scadpy import cuboid, get_solid_part_colors, DEFAULT_OPACITY
|
|
30
|
+
|
|
31
|
+
>>> colors = get_solid_part_colors(cuboid(2))
|
|
32
|
+
>>> colors.shape
|
|
33
|
+
(1, 4)
|
|
34
|
+
>>> bool(colors[0, 3] == DEFAULT_OPACITY)
|
|
35
|
+
True
|
|
36
|
+
"""
|
|
37
|
+
from scadpy import get_assembly_part_colors
|
|
38
|
+
|
|
39
|
+
return get_assembly_part_colors(solid)
|