ScadPy 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. scadpy/__init__.py +5 -0
  2. scadpy/color/__init__.py +3 -0
  3. scadpy/color/constants/BEIGE.py +3 -0
  4. scadpy/color/constants/BLACK.py +3 -0
  5. scadpy/color/constants/BLUE.py +3 -0
  6. scadpy/color/constants/BROWN.py +3 -0
  7. scadpy/color/constants/DARK_GRAY.py +3 -0
  8. scadpy/color/constants/DEFAULT_COLOR.py +3 -0
  9. scadpy/color/constants/DEFAULT_OPACITY.py +1 -0
  10. scadpy/color/constants/GRAY.py +3 -0
  11. scadpy/color/constants/GREEN.py +3 -0
  12. scadpy/color/constants/ORANGE.py +3 -0
  13. scadpy/color/constants/RED.py +3 -0
  14. scadpy/color/constants/WHITE.py +3 -0
  15. scadpy/color/constants/YELLOW.py +3 -0
  16. scadpy/color/constants/__init__.py +29 -0
  17. scadpy/color/type/__init__.py +3 -0
  18. scadpy/color/type/color.py +3 -0
  19. scadpy/color/utils/__init__.py +3 -0
  20. scadpy/color/utils/get_random_color.py +36 -0
  21. scadpy/core/__init__.py +3 -0
  22. scadpy/core/assembly/__init__.py +5 -0
  23. scadpy/core/assembly/combinations/__init__.py +13 -0
  24. scadpy/core/assembly/combinations/concat_assemblies.py +50 -0
  25. scadpy/core/assembly/combinations/exclude_assemblies.py +135 -0
  26. scadpy/core/assembly/combinations/intersect_assemblies.py +128 -0
  27. scadpy/core/assembly/combinations/subtract_assemblies.py +151 -0
  28. scadpy/core/assembly/combinations/unify_assemblies.py +59 -0
  29. scadpy/core/assembly/topologies/__init__.py +41 -0
  30. scadpy/core/assembly/topologies/directed_edge/__init__.py +9 -0
  31. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_directions.py +70 -0
  32. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_edge.py +49 -0
  33. scadpy/core/assembly/topologies/directed_edge/get_assembly_directed_edge_to_vertex.py +54 -0
  34. scadpy/core/assembly/topologies/edge/__init__.py +9 -0
  35. scadpy/core/assembly/topologies/edge/get_assembly_edge_lengths.py +46 -0
  36. scadpy/core/assembly/topologies/edge/get_assembly_edge_midpoints.py +51 -0
  37. scadpy/core/assembly/topologies/edge/get_assembly_edge_normals.py +67 -0
  38. scadpy/core/assembly/topologies/face_corner/__init__.py +13 -0
  39. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_angles.py +72 -0
  40. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_normals.py +103 -0
  41. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_incoming_directed_edge.py +65 -0
  42. scadpy/core/assembly/topologies/face_corner/get_assembly_face_corner_to_outgoing_directed_edge.py +65 -0
  43. scadpy/core/assembly/topologies/face_corner/get_assembly_face_directed_edge_to_corner.py +79 -0
  44. scadpy/core/assembly/topologies/part/__init__.py +5 -0
  45. scadpy/core/assembly/topologies/part/get_assembly_part_colors.py +55 -0
  46. scadpy/core/assembly/topologies/vertex/__init__.py +7 -0
  47. scadpy/core/assembly/topologies/vertex/get_assembly_vertex_coordinates.py +70 -0
  48. scadpy/core/assembly/topologies/vertex/get_assembly_vertex_to_part.py +62 -0
  49. scadpy/core/assembly/transformations/__init__.py +19 -0
  50. scadpy/core/assembly/transformations/color_assembly.py +24 -0
  51. scadpy/core/assembly/transformations/mirror_vertex_coordinates.py +68 -0
  52. scadpy/core/assembly/transformations/pull_vertex_coordinates.py +64 -0
  53. scadpy/core/assembly/transformations/push_vertex_coordinates.py +64 -0
  54. scadpy/core/assembly/transformations/resize_vertex_coordinates.py +121 -0
  55. scadpy/core/assembly/transformations/rotate_vertex_coordinates.py +73 -0
  56. scadpy/core/assembly/transformations/scale_vertex_coordinates.py +76 -0
  57. scadpy/core/assembly/transformations/translate_vertex_coordinates.py +70 -0
  58. scadpy/core/assembly/types/__init__.py +7 -0
  59. scadpy/core/assembly/types/assembly.py +14 -0
  60. scadpy/core/assembly/types/topology_filter.py +6 -0
  61. scadpy/core/assembly/utils/__init__.py +9 -0
  62. scadpy/core/assembly/utils/lookup_pairs.py +56 -0
  63. scadpy/core/assembly/utils/resolve_topology_filter.py +84 -0
  64. scadpy/core/assembly/utils/transform_filtered_parts.py +55 -0
  65. scadpy/core/component/__init__.py +3 -0
  66. scadpy/core/component/exporters/__init__.py +7 -0
  67. scadpy/core/component/exporters/map_component_to_html_file.py +47 -0
  68. scadpy/core/component/exporters/map_component_to_screen.py +63 -0
  69. scadpy/core/component/features/__init__.py +5 -0
  70. scadpy/core/component/features/get_component_bounds.py +38 -0
  71. scadpy/core/component/utils/__init__.py +9 -0
  72. scadpy/core/component/utils/blend_component_colors.py +77 -0
  73. scadpy/core/component/utils/get_intersecting_component_index_groups.py +108 -0
  74. scadpy/core/part/__init__.py +3 -0
  75. scadpy/core/part/combinations/__init__.py +11 -0
  76. scadpy/core/part/combinations/concat_parts.py +48 -0
  77. scadpy/core/part/combinations/intersect_parts.py +147 -0
  78. scadpy/core/part/combinations/subtract_parts.py +94 -0
  79. scadpy/core/part/combinations/unify_parts.py +143 -0
  80. scadpy/core/part/types/__init__.py +5 -0
  81. scadpy/core/part/types/part.py +34 -0
  82. scadpy/core/part/utils/__init__.py +5 -0
  83. scadpy/core/part/utils/blend_part_colors.py +32 -0
  84. scadpy/d2/__init__.py +2 -0
  85. scadpy/d2/shape/__init__.py +9 -0
  86. scadpy/d2/shape/combinations/__init__.py +21 -0
  87. scadpy/d2/shape/combinations/are_shape_parts_intersecting.py +49 -0
  88. scadpy/d2/shape/combinations/concat_shape.py +48 -0
  89. scadpy/d2/shape/combinations/exclude_shape.py +71 -0
  90. scadpy/d2/shape/combinations/intersect_shape.py +64 -0
  91. scadpy/d2/shape/combinations/intersect_shape_parts.py +71 -0
  92. scadpy/d2/shape/combinations/subtract_shape.py +72 -0
  93. scadpy/d2/shape/combinations/subtract_shape_parts.py +66 -0
  94. scadpy/d2/shape/combinations/unify_shape.py +51 -0
  95. scadpy/d2/shape/combinations/unify_shape_parts.py +74 -0
  96. scadpy/d2/shape/exporters/__init__.py +17 -0
  97. scadpy/d2/shape/exporters/map_shape_to_dxf.py +43 -0
  98. scadpy/d2/shape/exporters/map_shape_to_dxf_file.py +38 -0
  99. scadpy/d2/shape/exporters/map_shape_to_html.py +117 -0
  100. scadpy/d2/shape/exporters/map_shape_to_html_file.py +58 -0
  101. scadpy/d2/shape/exporters/map_shape_to_screen.py +51 -0
  102. scadpy/d2/shape/exporters/map_shape_to_svg.py +40 -0
  103. scadpy/d2/shape/exporters/map_shape_to_svg_file.py +38 -0
  104. scadpy/d2/shape/features/__init__.py +9 -0
  105. scadpy/d2/shape/features/get_shape_bounds.py +40 -0
  106. scadpy/d2/shape/features/get_shape_part_bounds.py +37 -0
  107. scadpy/d2/shape/features/is_shape_empty.py +38 -0
  108. scadpy/d2/shape/importers/__init__.py +13 -0
  109. scadpy/d2/shape/importers/map_dxf_to_shape.py +55 -0
  110. scadpy/d2/shape/importers/map_geometries_to_shape.py +45 -0
  111. scadpy/d2/shape/importers/map_geometry_to_shape.py +43 -0
  112. scadpy/d2/shape/importers/map_parts_to_shape.py +62 -0
  113. scadpy/d2/shape/importers/map_svg_to_shape.py +55 -0
  114. scadpy/d2/shape/primitives/__init__.py +6 -0
  115. scadpy/d2/shape/primitives/circle.py +68 -0
  116. scadpy/d2/shape/primitives/polygon.py +86 -0
  117. scadpy/d2/shape/primitives/rectangle.py +85 -0
  118. scadpy/d2/shape/primitives/square.py +57 -0
  119. scadpy/d2/shape/topologies/__init__.py +53 -0
  120. scadpy/d2/shape/topologies/corner/__init__.py +15 -0
  121. scadpy/d2/shape/topologies/corner/are_shape_corners_convex.py +75 -0
  122. scadpy/d2/shape/topologies/corner/get_shape_corner_angles.py +58 -0
  123. scadpy/d2/shape/topologies/corner/get_shape_corner_normals.py +82 -0
  124. scadpy/d2/shape/topologies/corner/get_shape_corner_to_incoming_directed_edge.py +39 -0
  125. scadpy/d2/shape/topologies/corner/get_shape_corner_to_outgoing_directed_edge.py +39 -0
  126. scadpy/d2/shape/topologies/corner/get_shape_corner_to_vertex.py +65 -0
  127. scadpy/d2/shape/topologies/directed_edge/__init__.py +11 -0
  128. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_directions.py +44 -0
  129. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_corner.py +41 -0
  130. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_edge.py +51 -0
  131. scadpy/d2/shape/topologies/directed_edge/get_shape_directed_edge_to_vertex.py +63 -0
  132. scadpy/d2/shape/topologies/edge/__init__.py +11 -0
  133. scadpy/d2/shape/topologies/edge/get_shape_edge_lengths.py +43 -0
  134. scadpy/d2/shape/topologies/edge/get_shape_edge_midpoints.py +46 -0
  135. scadpy/d2/shape/topologies/edge/get_shape_edge_normals.py +40 -0
  136. scadpy/d2/shape/topologies/edge/get_shape_edge_to_vertex.py +71 -0
  137. scadpy/d2/shape/topologies/ring/__init__.py +7 -0
  138. scadpy/d2/shape/topologies/ring/get_shape_ring_to_part.py +46 -0
  139. scadpy/d2/shape/topologies/ring/get_shape_ring_types.py +46 -0
  140. scadpy/d2/shape/topologies/vertex/__init__.py +11 -0
  141. scadpy/d2/shape/topologies/vertex/get_shape_part_vertex_coordinates.py +62 -0
  142. scadpy/d2/shape/topologies/vertex/get_shape_vertex_coordinates.py +44 -0
  143. scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_part.py +42 -0
  144. scadpy/d2/shape/topologies/vertex/get_shape_vertex_to_ring.py +63 -0
  145. scadpy/d2/shape/transformations/__init__.py +43 -0
  146. scadpy/d2/shape/transformations/chamfer_shape.py +259 -0
  147. scadpy/d2/shape/transformations/color_shape.py +46 -0
  148. scadpy/d2/shape/transformations/convexify_shape.py +79 -0
  149. scadpy/d2/shape/transformations/fill_shape.py +68 -0
  150. scadpy/d2/shape/transformations/fillet_shape.py +289 -0
  151. scadpy/d2/shape/transformations/grow_shape.py +82 -0
  152. scadpy/d2/shape/transformations/linear_cut_shape.py +116 -0
  153. scadpy/d2/shape/transformations/linear_extrude_shape.py +60 -0
  154. scadpy/d2/shape/transformations/linear_slice_shape.py +144 -0
  155. scadpy/d2/shape/transformations/mirror_shape.py +53 -0
  156. scadpy/d2/shape/transformations/pull_shape.py +67 -0
  157. scadpy/d2/shape/transformations/push_shape.py +67 -0
  158. scadpy/d2/shape/transformations/radial_extrude_shape.py +285 -0
  159. scadpy/d2/shape/transformations/radial_slice_shape.py +132 -0
  160. scadpy/d2/shape/transformations/recoordinate_shape.py +82 -0
  161. scadpy/d2/shape/transformations/resize_shape.py +91 -0
  162. scadpy/d2/shape/transformations/rotate_shape.py +63 -0
  163. scadpy/d2/shape/transformations/scale_shape.py +58 -0
  164. scadpy/d2/shape/transformations/shrink_shape.py +69 -0
  165. scadpy/d2/shape/transformations/translate_shape.py +54 -0
  166. scadpy/d2/shape/types/__init__.py +3 -0
  167. scadpy/d2/shape/types/shape.py +792 -0
  168. scadpy/d2/shape/types/utils/__init__.py +5 -0
  169. scadpy/d2/shape/types/utils/shapely_base_geometry_to_shapely_polygons.py +25 -0
  170. scadpy/d2/shape/utils/__init__.py +5 -0
  171. scadpy/d2/shape/utils/shapely_base_geometry_to_shapely_polygons.py +55 -0
  172. scadpy/d2/utils/__init__.py +3 -0
  173. scadpy/d2/utils/resolve_vector_2d.py +50 -0
  174. scadpy/d3/__init__.py +2 -0
  175. scadpy/d3/solid/__init__.py +8 -0
  176. scadpy/d3/solid/combinations/__init__.py +21 -0
  177. scadpy/d3/solid/combinations/are_solid_parts_intersecting.py +51 -0
  178. scadpy/d3/solid/combinations/concat_solid.py +48 -0
  179. scadpy/d3/solid/combinations/exclude_solid.py +71 -0
  180. scadpy/d3/solid/combinations/intersect_solid.py +64 -0
  181. scadpy/d3/solid/combinations/intersect_solid_parts.py +73 -0
  182. scadpy/d3/solid/combinations/subtract_solid.py +72 -0
  183. scadpy/d3/solid/combinations/subtract_solid_parts.py +68 -0
  184. scadpy/d3/solid/combinations/unify_solid.py +51 -0
  185. scadpy/d3/solid/combinations/unify_solid_parts.py +73 -0
  186. scadpy/d3/solid/exporters/__init__.py +11 -0
  187. scadpy/d3/solid/exporters/map_solid_to_html.py +318 -0
  188. scadpy/d3/solid/exporters/map_solid_to_html_file.py +58 -0
  189. scadpy/d3/solid/exporters/map_solid_to_screen.py +51 -0
  190. scadpy/d3/solid/exporters/map_solid_to_stl_file.py +48 -0
  191. scadpy/d3/solid/features/__init__.py +11 -0
  192. scadpy/d3/solid/features/get_solid_bounds.py +37 -0
  193. scadpy/d3/solid/features/get_solid_part_bounds.py +37 -0
  194. scadpy/d3/solid/features/get_solid_part_colors.py +39 -0
  195. scadpy/d3/solid/features/is_solid_empty.py +36 -0
  196. scadpy/d3/solid/importers/__init__.py +11 -0
  197. scadpy/d3/solid/importers/map_geometries_to_solid.py +42 -0
  198. scadpy/d3/solid/importers/map_geometry_to_solid.py +42 -0
  199. scadpy/d3/solid/importers/map_parts_to_solid.py +66 -0
  200. scadpy/d3/solid/importers/map_stl_to_solid.py +37 -0
  201. scadpy/d3/solid/primitives/__init__.py +7 -0
  202. scadpy/d3/solid/primitives/cone.py +70 -0
  203. scadpy/d3/solid/primitives/cuboid.py +75 -0
  204. scadpy/d3/solid/primitives/cylinder.py +73 -0
  205. scadpy/d3/solid/primitives/polyhedron.py +60 -0
  206. scadpy/d3/solid/primitives/sphere.py +58 -0
  207. scadpy/d3/solid/topologies/__init__.py +8 -0
  208. scadpy/d3/solid/topologies/triangle/__init__.py +5 -0
  209. scadpy/d3/solid/topologies/triangle/get_solid_triangle_to_vertex.py +49 -0
  210. scadpy/d3/solid/topologies/vertex/__init__.py +7 -0
  211. scadpy/d3/solid/topologies/vertex/get_solid_vertex_coordinates.py +39 -0
  212. scadpy/d3/solid/topologies/vertex/get_solid_vertex_to_part.py +37 -0
  213. scadpy/d3/solid/transformations/__init__.py +23 -0
  214. scadpy/d3/solid/transformations/color_solid.py +46 -0
  215. scadpy/d3/solid/transformations/convexify_solid.py +64 -0
  216. scadpy/d3/solid/transformations/mirror_solid.py +53 -0
  217. scadpy/d3/solid/transformations/pull_solid.py +67 -0
  218. scadpy/d3/solid/transformations/push_solid.py +67 -0
  219. scadpy/d3/solid/transformations/recoordinate_solid.py +68 -0
  220. scadpy/d3/solid/transformations/resize_solid.py +92 -0
  221. scadpy/d3/solid/transformations/rotate_solid.py +93 -0
  222. scadpy/d3/solid/transformations/scale_solid.py +58 -0
  223. scadpy/d3/solid/transformations/translate_solid.py +54 -0
  224. scadpy/d3/solid/types/__init__.py +3 -0
  225. scadpy/d3/solid/types/solid.py +448 -0
  226. scadpy/d3/utils/__init__.py +3 -0
  227. scadpy/d3/utils/resolve_vector_3d.py +50 -0
  228. scadpy/utils/__init__.py +6 -0
  229. scadpy/utils/resolve_vector.py +64 -0
  230. scadpy/utils/x.py +38 -0
  231. scadpy/utils/y.py +38 -0
  232. scadpy/utils/z.py +38 -0
  233. scadpy-0.1.0.dist-info/METADATA +282 -0
  234. scadpy-0.1.0.dist-info/RECORD +236 -0
  235. scadpy-0.1.0.dist-info/WHEEL +4 -0
  236. scadpy-0.1.0.dist-info/licenses/LICENSE.md +43 -0
@@ -0,0 +1,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)