deformable 0.1.0__py3-none-win_amd64.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.
deformable/__init__.py ADDED
@@ -0,0 +1,191 @@
1
+ """deformable: finite element deformation and fracture of tetrahedral meshes, from Python.
2
+
3
+ A pure-Python package over libdeformable, the shared library of the C SDK (declared in deformable.h), loaded with
4
+ ctypes. It needs no third-party package: only the shared library, which the platform wheel carries in the package
5
+ directory. The library is looked up through the DEFORMABLE_LIBRARY environment variable, then in the package
6
+ directory, then in the target/release folder of the Cargo workspace the package sits in (deformable.library).
7
+
8
+ It runs on Python 3.9 and later, so it also runs inside Blender, whose bundled Python is 3.10 or later since
9
+ Blender 3.1: put the package and the shared library on Blender's module path, or set DEFORMABLE_LIBRARY.
10
+
11
+ The host flow::
12
+
13
+ import deformable
14
+
15
+ cage = deformable.TriangleSurface(positions, triangles)
16
+ mesh = deformable.tetrahedralize(cage, deformable.TetrahedralizeParameters(maximum_edge_length=0.5)).mesh
17
+ with deformable.Simulation() as simulation:
18
+ material = simulation.add_material(deformable.MaterialParameters.default())
19
+ cube = simulation.add_object(mesh, material)
20
+ for frame in range(25):
21
+ simulation.step(frame / 24, substeps=10)
22
+ positions = simulation.node_positions(cube)
23
+
24
+ Conventions:
25
+
26
+ - Errors. A failed call raises DeformableError with the library's status, message and element indices. Arguments the
27
+ package refuses before calling the library raise ValueError or TypeError; a closed handle raises ValueError.
28
+ - Arrays. Vertex and index data may be lists or tuples of groups, flat sequences of numbers, array.array, memoryview
29
+ or any other buffer (deformable.conversion). Results are lists of Vector3 and of index tuples, except the skinned
30
+ output of SurfaceEmbedding.deform (DeformedSurface), which is flat array.array copies: 32-bit floats for positions
31
+ and normals, bytes for the hidden faces.
32
+ - Handles. Simulation, SurfaceEmbedding and CancelFlag own a library handle, freed by close(), at the end of a with
33
+ block, or when the object is collected; close() waits for calls in flight on the object from other threads, and
34
+ later calls raise ValueError. Object and material handles are ints counting up from 0.
35
+ - Units. Lengths cross in host units; time, forces, gravity, masses and material values in simulation units. The
36
+ simulation's scale converts host lengths to simulation lengths.
37
+ - License. The library checks the license of this device and user before engine work; deformable.license reads it
38
+ and activates a license key. A call the trial or license does not allow raises LicenseError, a DeformableError
39
+ whose message is written for the user: LicenseRequiredError ("Trial ended — Buy license") and
40
+ TrialLimitExceededError ("Trial limit: 2500 tetrahedrons") are its two most common kinds.
41
+ - Threads. Library calls release the global interpreter lock. Calls on one simulation from several threads run one
42
+ at a time; tools, embeddings and cancel flags may be used from several threads at once.
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ from . import library, license
48
+ from .cancel_flag import CancelFlag
49
+ from .embedding import (
50
+ DeformedSurface,
51
+ PreparationParameters,
52
+ SkinningOptions,
53
+ SurfaceEmbedding,
54
+ SurfaceEmbeddingArrays,
55
+ embed_surface,
56
+ )
57
+ from .errors import DeformableError, LicenseError, LicenseRequiredError, Status, TrialLimitExceededError
58
+ from .library import LibraryNotFoundError
59
+ from .license import LicenseState, LicenseStatus
60
+ from .material_regions import MaterialRegion, material_region_indices
61
+ from .mesh_verification import MeshChecks, MeshIssue, meshes_intersect, verify_surface
62
+ from .meshes import (
63
+ FaceHole,
64
+ PolygonMesh,
65
+ SurfaceMesh,
66
+ TetrahedralMesh,
67
+ TriangleSurface,
68
+ boundary_faces,
69
+ face_corners,
70
+ )
71
+ from .simulation import (
72
+ ConvexRegion,
73
+ DrivenVertex,
74
+ FaceEvent,
75
+ GlueParameters,
76
+ MaterialParameters,
77
+ MaterialPreset,
78
+ ObjectOptions,
79
+ Plane,
80
+ Simulation,
81
+ SimulationStatistics,
82
+ StepReport,
83
+ TetrahedronState,
84
+ )
85
+ from .tools import (
86
+ Anisotropy,
87
+ AutocageParameters,
88
+ DensityRegion,
89
+ PixelGrid,
90
+ SplinterImageParameters,
91
+ Tetrahedralization,
92
+ TetrahedralizeParameters,
93
+ TriangulateParameters,
94
+ Triangulation,
95
+ autocage,
96
+ extrude,
97
+ span_layers,
98
+ splinters_from_image,
99
+ tetrahedralize,
100
+ triangulate,
101
+ )
102
+ from .values import (
103
+ IDENTITY,
104
+ NODE_REPLACED,
105
+ AffineTransform,
106
+ ClipMode,
107
+ ExtrudeDirection,
108
+ MeshIssueKind,
109
+ SelfIntersectionCheck,
110
+ StepOutcome,
111
+ Vector2,
112
+ Vector3,
113
+ )
114
+
115
+ __version__ = "0.1.0"
116
+
117
+
118
+ def library_version() -> str:
119
+ """The version of the loaded shared library, major.minor.patch."""
120
+ return library.load().deformable_version().decode("ascii")
121
+
122
+
123
+ __all__ = [
124
+ "IDENTITY",
125
+ "NODE_REPLACED",
126
+ "AffineTransform",
127
+ "Anisotropy",
128
+ "AutocageParameters",
129
+ "CancelFlag",
130
+ "ClipMode",
131
+ "ConvexRegion",
132
+ "DeformableError",
133
+ "DeformedSurface",
134
+ "DensityRegion",
135
+ "DrivenVertex",
136
+ "ExtrudeDirection",
137
+ "FaceEvent",
138
+ "FaceHole",
139
+ "GlueParameters",
140
+ "LibraryNotFoundError",
141
+ "LicenseError",
142
+ "LicenseRequiredError",
143
+ "LicenseState",
144
+ "LicenseStatus",
145
+ "MaterialParameters",
146
+ "MaterialPreset",
147
+ "MaterialRegion",
148
+ "MeshChecks",
149
+ "MeshIssue",
150
+ "MeshIssueKind",
151
+ "ObjectOptions",
152
+ "PixelGrid",
153
+ "Plane",
154
+ "PolygonMesh",
155
+ "PreparationParameters",
156
+ "SelfIntersectionCheck",
157
+ "Simulation",
158
+ "SimulationStatistics",
159
+ "SkinningOptions",
160
+ "SplinterImageParameters",
161
+ "Status",
162
+ "StepOutcome",
163
+ "StepReport",
164
+ "SurfaceEmbedding",
165
+ "SurfaceEmbeddingArrays",
166
+ "SurfaceMesh",
167
+ "Tetrahedralization",
168
+ "TetrahedralizeParameters",
169
+ "TetrahedralMesh",
170
+ "TetrahedronState",
171
+ "TrialLimitExceededError",
172
+ "TriangleSurface",
173
+ "TriangulateParameters",
174
+ "Triangulation",
175
+ "Vector2",
176
+ "Vector3",
177
+ "autocage",
178
+ "boundary_faces",
179
+ "embed_surface",
180
+ "extrude",
181
+ "face_corners",
182
+ "library_version",
183
+ "license",
184
+ "material_region_indices",
185
+ "meshes_intersect",
186
+ "span_layers",
187
+ "splinters_from_image",
188
+ "tetrahedralize",
189
+ "triangulate",
190
+ "verify_surface",
191
+ ]
@@ -0,0 +1,43 @@
1
+ """The flag another thread sets to stop a step or a tool."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ import ctypes
7
+
8
+ from . import native
9
+ from .handles import OwnedHandle
10
+
11
+
12
+ class CancelFlag(OwnedHandle):
13
+ """A thread-safe cancel flag, not set when created.
14
+
15
+ Library calls release the global interpreter lock, so one Python thread can set the flag while another runs
16
+ Simulation.step or a tool that was given it; that call stops at its next check.
17
+ """
18
+
19
+ _destroy_name = "deformable_cancel_flag_destroy"
20
+ _description = "cancel flag"
21
+
22
+ def __init__(self) -> None:
23
+ super().__init__("deformable_cancel_flag_create")
24
+
25
+ def set(self) -> None:
26
+ """Sets the flag."""
27
+ self._call("deformable_cancel_flag_set", 1)
28
+
29
+ def clear(self) -> None:
30
+ """Clears the flag."""
31
+ self._call("deformable_cancel_flag_set", 0)
32
+
33
+ def is_set(self) -> bool:
34
+ """True while the flag is set."""
35
+ cancelled = native.Boolean()
36
+ self._call("deformable_cancel_flag_is_set", ctypes.byref(cancelled))
37
+ return bool(cancelled.value)
38
+
39
+
40
+ def cancel_handle(cancel: CancelFlag | None) -> contextlib.AbstractContextManager[int | None]:
41
+ """The handle a call takes for an optional cancel flag, NULL when there is none; the flag stays open until the
42
+ block ends."""
43
+ return contextlib.nullcontext() if cancel is None else cancel._use()
@@ -0,0 +1,176 @@
1
+ """Python values to the ctypes arrays the library borrows, and the arrays it lends back to Python values.
2
+
3
+ Array inputs take any of these forms:
4
+
5
+ - a sequence of groups, such as a list of (x, y, z) tuples or of Vector3;
6
+ - a flat sequence of numbers, group after group;
7
+ - any buffer: array.array, memoryview, bytes, or another object with the buffer protocol, flat or with one row per
8
+ group. A contiguous buffer that already holds the C element type is copied as it is; any other is read number by
9
+ number, so array.array("f") vertex data from Blender's foreach_get works as well.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import array
15
+ import ctypes
16
+ import numbers
17
+ import sys
18
+ from collections.abc import Iterable, Sequence
19
+ from typing import Any, Callable
20
+
21
+ from . import native
22
+ from .values import AffineTransform, Vector2, Vector3
23
+
24
+ UNSIGNED_32_LIMIT = 1 << 32
25
+
26
+ # The buffer formats whose items have the C type's size and byte order, so the bytes can be copied as they are.
27
+ _byte_order_prefix = "<" if sys.byteorder == "little" else ">"
28
+ _NATIVE_FORMATS = {
29
+ ctypes.c_double: {"d", "@d", "=d", _byte_order_prefix + "d"},
30
+ ctypes.c_uint32: {"I", "@I", "=I", _byte_order_prefix + "I"},
31
+ ctypes.c_uint8: {"B", "@B", "=B", "<B", ">B", "c"},
32
+ }
33
+
34
+
35
+ def unsigned(value: int, limit: int = UNSIGNED_32_LIMIT) -> int:
36
+ """value as an index or count, refused when ctypes would wrap it into another number."""
37
+ value = int(value)
38
+ if not 0 <= value < limit:
39
+ raise ValueError(f"{value} is outside the unsigned range [0, {limit})")
40
+ return value
41
+
42
+
43
+ def size(value: int) -> int:
44
+ """value as a size_t count."""
45
+ return unsigned(value, 1 << (8 * ctypes.sizeof(ctypes.c_size_t)))
46
+
47
+
48
+ def _flatten(values: Iterable[Any]) -> list:
49
+ flat = []
50
+ for value in values:
51
+ if isinstance(value, numbers.Number):
52
+ flat.append(value)
53
+ elif isinstance(value, str):
54
+ # A one-character string iterates to itself, so it would recurse forever.
55
+ raise TypeError(f"expected a number or a group of numbers, not the string {value!r}")
56
+ else:
57
+ flat.extend(_flatten(value))
58
+ return flat
59
+
60
+
61
+ def _buffer(values: Any) -> memoryview | None:
62
+ if isinstance(values, (list, tuple)):
63
+ return None
64
+ try:
65
+ return memoryview(values)
66
+ except TypeError:
67
+ return None
68
+
69
+
70
+ def scalar_array(values: Any, element_type, group: int = 1, name: str = "values"):
71
+ """A ctypes array of element_type holding values, whose length must be a multiple of group."""
72
+ view = _buffer(values)
73
+ if view is not None and view.c_contiguous and view.format in _NATIVE_FORMATS.get(element_type, ()):
74
+ data = view.tobytes()
75
+ array = (element_type * (len(data) // ctypes.sizeof(element_type))).from_buffer_copy(data)
76
+ else:
77
+ flat = _flatten(view.tolist() if view is not None else values)
78
+ if element_type in (ctypes.c_uint32, ctypes.c_uint8):
79
+ limit = 1 << (8 * ctypes.sizeof(element_type))
80
+ for value in flat:
81
+ unsigned(value, limit)
82
+ array = (element_type * len(flat))(*flat)
83
+ if len(array) % group:
84
+ raise ValueError(f"{name} holds {len(array)} numbers, not a whole number of groups of {group}")
85
+ return array
86
+
87
+
88
+ def grouped_array(values: Any, group_type, element_type, group: int, name: str):
89
+ """A ctypes array of group_type (a structure or array of group element_type values) over values."""
90
+ flat = scalar_array(values, element_type, group, name)
91
+ return (group_type * (len(flat) // group)).from_buffer(flat)
92
+
93
+
94
+ def vector3_array(values: Any, name: str = "positions"):
95
+ """DeformableVector3 values."""
96
+ return grouped_array(values, native.Vector3, ctypes.c_double, 3, name)
97
+
98
+
99
+ def vector2_array(values: Any, name: str = "uvs"):
100
+ """DeformableVector2 values."""
101
+ return grouped_array(values, native.Vector2, ctypes.c_double, 2, name)
102
+
103
+
104
+ def index_array(values: Any, name: str = "indices"):
105
+ """uint32_t values."""
106
+ return scalar_array(values, ctypes.c_uint32, 1, name)
107
+
108
+
109
+ def index_group_array(values: Any, group: int, name: str):
110
+ """uint32_t[group] values, such as tetrahedrons (4) or triangles (3)."""
111
+ return grouped_array(values, native.Index * group, ctypes.c_uint32, group, name)
112
+
113
+
114
+ def vector3(value: Sequence[float]) -> native.Vector3:
115
+ """One DeformableVector3."""
116
+ x, y, z = value
117
+ return native.Vector3(x, y, z)
118
+
119
+
120
+ def affine_transform(transform: AffineTransform) -> native.AffineTransform:
121
+ """One DeformableAffineTransform."""
122
+ linear, translation = transform
123
+ return native.AffineTransform(
124
+ native.Matrix3((native.Vector3 * 3)(*(vector3(row) for row in linear))), vector3(translation)
125
+ )
126
+
127
+
128
+ def structure_array(values: Iterable[Any], element_type, convert: Callable[[Any], Any]):
129
+ """A ctypes array of element_type with convert applied to each value."""
130
+ values = list(values)
131
+ structures = (element_type * len(values))()
132
+ for index, value in enumerate(values):
133
+ structures[index] = convert(value)
134
+ return structures
135
+
136
+
137
+ def lent_array(pointer, element_type, count: int):
138
+ """The count elements of element_type a library pointer lends, as a ctypes array over the library's memory."""
139
+ if count == 0:
140
+ return (element_type * 0)()
141
+ return ctypes.cast(pointer, ctypes.POINTER(element_type * count)).contents
142
+
143
+
144
+ def read_scalars(pointer, element_type, count: int) -> list:
145
+ """count numbers of element_type lent by the library."""
146
+ return list(lent_array(pointer, element_type, count))
147
+
148
+
149
+ def read_array(pointer, type_code: str, count: int) -> array.array:
150
+ """count numbers lent by the library, copied at once into an array.array of type_code, whose items must have the
151
+ C type's size. A host takes it as it is: Blender's foreach_set reads it without a per-item conversion."""
152
+ values = array.array(type_code)
153
+ if count:
154
+ values.frombytes(ctypes.string_at(pointer, count * values.itemsize))
155
+ return values
156
+
157
+
158
+ def read_groups(pointer, element_type, count: int, group: int) -> list[tuple]:
159
+ """count groups of group numbers of element_type lent by the library, as tuples."""
160
+ flat = read_scalars(pointer, element_type, count * group)
161
+ return list(zip(*(flat[offset::group] for offset in range(group))))
162
+
163
+
164
+ def read_vector3_list(pointer, count: int) -> list[Vector3]:
165
+ """count DeformableVector3 values lent by the library."""
166
+ return [Vector3._make(group) for group in read_groups(pointer, ctypes.c_double, count, 3)]
167
+
168
+
169
+ def read_vector2_list(pointer, count: int) -> list[Vector2]:
170
+ """count DeformableVector2 values lent by the library."""
171
+ return [Vector2._make(group) for group in read_groups(pointer, ctypes.c_double, count, 2)]
172
+
173
+
174
+ def from_vector3(value: native.Vector3) -> Vector3:
175
+ """A DeformableVector3 as a Vector3."""
176
+ return Vector3(value.x, value.y, value.z)
Binary file
@@ -0,0 +1,159 @@
1
+ """Render surfaces: preparation against the rest mesh, the embedding it gives, and per-frame skinning."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import array
6
+ import ctypes
7
+ import dataclasses
8
+ from collections.abc import Sequence
9
+ from typing import Any
10
+
11
+ from . import conversion, library, native
12
+ from .errors import check
13
+ from .handles import OwnedHandle, temporary_handle
14
+ from .meshes import SurfaceMesh, polygon_mesh_view, read_surface_mesh, surface_mesh_view, tetrahedral_mesh_view
15
+ from .simulation import TetrahedronState
16
+ from .values import AffineTransform, ClipMode
17
+
18
+
19
+ @dataclasses.dataclass(frozen=True)
20
+ class PreparationParameters:
21
+ """The surface preparation settings."""
22
+
23
+ clip_mode: ClipMode | int
24
+ """The clip mode."""
25
+ uv_scale: float
26
+ """Scale of the planar UVs of inner and cap faces; the random offsets of inner faces are not
27
+ scaled."""
28
+ splinter_cage: Any = None
29
+ """None, or the splinter cage polygon mesh in the surface's space, already moved by the host's splinter
30
+ transform; with vertices and a clipping mode the surface takes the splinter path."""
31
+
32
+ def _native(self) -> native.PreparationParameters:
33
+ cage = None if self.splinter_cage is None else ctypes.pointer(polygon_mesh_view(self.splinter_cage))
34
+ return native.PreparationParameters(int(self.clip_mode), self.uv_scale, cage)
35
+
36
+
37
+ @dataclasses.dataclass(frozen=True)
38
+ class SkinningOptions:
39
+ """Per-frame skinning options."""
40
+
41
+ show_inner_faces: bool = False
42
+ """Show every inner face, exposed or not."""
43
+ start_splintered: bool = False
44
+ """With splinter data, show the cap faces from the start instead of once a tetrahedron of theirs is flagged."""
45
+
46
+
47
+ @dataclasses.dataclass(frozen=True)
48
+ class DeformedSurface:
49
+ """A skinned surface in the object space of the host's shape, as flat arrays a host hands to its mesh API as they
50
+ are (Blender's foreach_set takes them without a per-item conversion)."""
51
+
52
+ positions: array.array
53
+ """x, y and z of the deformed position of each output vertex, mapped by the inverse world transform, flat 32-bit
54
+ floats (array("f"))."""
55
+ normals: array.array
56
+ """x, y and z of the deformed normal of each corner, embed(p + n) - embed(p) mapped by the linear part of the
57
+ inverse world transform and made unit length, a zero vector left zero, flat 32-bit floats (array("f")); empty
58
+ without normals."""
59
+ hidden_faces: array.array
60
+ """1 for each face the host hides (an inner or cap face not shown yet), 0 for the others (array("b"))."""
61
+
62
+
63
+ @dataclasses.dataclass(frozen=True)
64
+ class SurfaceEmbeddingArrays:
65
+ """The arrays of a surface embedding."""
66
+
67
+ surface: SurfaceMesh
68
+ """The prepared surface in rest space: output vertices, polygons and per-corner channels."""
69
+ corner_flags: list[int]
70
+ """The flag word of each corner: bits 0-28 the controlling tetrahedron, bits 29-30 the tetrahedron face of an
71
+ inner face, bit 31 set on the corners of inner faces."""
72
+ source_faces: list[int]
73
+ """The input face each output face was cut from, -1 for inner and cap faces."""
74
+ vertex_tetrahedrons: list[int]
75
+ """The controlling tetrahedron of each output vertex."""
76
+ vertex_weights: list[tuple[float, float, float, float]]
77
+ """The barycentric weights of each output vertex in its tetrahedron."""
78
+ normal_weights: list[tuple[float, float, float, float]]
79
+ """The weights of p + n for each corner in its vertex's tetrahedron; none without normals."""
80
+ brick_tetrahedrons: list[int]
81
+ """The controlling tetrahedron of each brick of the splinter path; none without splinter data."""
82
+ vertex_cell_tetrahedrons: list[int]
83
+ """The cell tetrahedron each output vertex follows once its cell has broken loose; none without splinter data."""
84
+
85
+
86
+ class SurfaceEmbedding(OwnedHandle):
87
+ """A prepared surface in rest space, made by embed_surface. Reading and skinning never change it, so several
88
+ threads may use one embedding at the same time."""
89
+
90
+ _destroy_name = "deformable_surface_embedding_destroy"
91
+ _description = "surface embedding"
92
+
93
+ def arrays(self) -> SurfaceEmbeddingArrays:
94
+ """A copy of the embedding's arrays."""
95
+ view = native.SurfaceEmbeddingView()
96
+ read_indices = conversion.read_scalars
97
+ # The view points into the embedding, so it is copied before close() may free it.
98
+ with self._use() as address:
99
+ check(library.load().deformable_surface_embedding_view(address, ctypes.byref(view)))
100
+ return SurfaceEmbeddingArrays(
101
+ read_surface_mesh(view.surface),
102
+ read_indices(view.corner_flags, ctypes.c_uint32, view.corner_flag_count),
103
+ read_indices(view.source_faces, ctypes.c_int32, view.source_face_count),
104
+ read_indices(view.vertex_tetrahedrons, ctypes.c_uint32, view.vertex_tetrahedron_count),
105
+ conversion.read_groups(view.vertex_weights, ctypes.c_double, view.vertex_weight_count, 4),
106
+ conversion.read_groups(view.normal_weights, ctypes.c_double, view.normal_weight_count, 4),
107
+ read_indices(view.brick_tetrahedrons, ctypes.c_uint32, view.brick_tetrahedron_count),
108
+ read_indices(view.vertex_cell_tetrahedrons, ctypes.c_uint32, view.vertex_cell_tetrahedron_count),
109
+ )
110
+
111
+ def deform(
112
+ self,
113
+ states: Sequence[TetrahedronState],
114
+ world_transform: AffineTransform,
115
+ options: SkinningOptions | None = None,
116
+ ) -> DeformedSurface:
117
+ """Skins the surface from the current tetrahedron states, one per rest tetrahedron, into the object space of
118
+ the host's shape: positions and unit normals are mapped by the inverse of world_transform, the shape's world
119
+ matrix (IDENTITY when the states are in object space).
120
+
121
+ Splinter vertices switch to their cell tetrahedron, and inner and cap faces are hidden per options.
122
+ """
123
+ options = SkinningOptions() if options is None else options
124
+ functions = library.load()
125
+ state_array = conversion.structure_array(states, native.TetrahedronState, TetrahedronState._native)
126
+ transform = conversion.affine_transform(world_transform)
127
+ skinning = native.SkinningOptions(bool(options.show_inner_faces), bool(options.start_splintered))
128
+ with temporary_handle("deformable_deformed_surface_destroy") as deformed:
129
+ check(functions.deformable_deformed_surface_create(ctypes.byref(deformed)))
130
+ self._call(
131
+ "deformable_surface_embedding_deform",
132
+ state_array,
133
+ len(state_array),
134
+ ctypes.byref(transform),
135
+ ctypes.byref(skinning),
136
+ deformed,
137
+ )
138
+ view = native.DeformedSurfaceView()
139
+ check(functions.deformable_deformed_surface_view(deformed, ctypes.byref(view)))
140
+ return DeformedSurface(
141
+ conversion.read_array(view.positions, "f", 3 * view.position_count),
142
+ conversion.read_array(view.normals, "f", 3 * view.normal_count),
143
+ conversion.read_array(view.hidden_faces, "b", view.face_count),
144
+ )
145
+
146
+
147
+ def embed_surface(rest: Any, surface: SurfaceMesh, parameters: PreparationParameters) -> SurfaceEmbedding:
148
+ """Prepares a render surface against the rest tetrahedral mesh: clip mode, inner faces with random planar UVs ×
149
+ uv_scale, the splinter path when a splinter cage is given, then the barycentric embedding."""
150
+ handle = native.Handle()
151
+ check(
152
+ library.load().deformable_embed_surface(
153
+ ctypes.byref(tetrahedral_mesh_view(rest)),
154
+ ctypes.byref(surface_mesh_view(surface)),
155
+ ctypes.byref(parameters._native()),
156
+ ctypes.byref(handle),
157
+ )
158
+ )
159
+ return SurfaceEmbedding._adopt(handle)