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 +191 -0
- deformable/cancel_flag.py +43 -0
- deformable/conversion.py +176 -0
- deformable/deformable.dll +0 -0
- deformable/embedding.py +159 -0
- deformable/errors.py +150 -0
- deformable/handles.py +97 -0
- deformable/library.py +410 -0
- deformable/license.py +154 -0
- deformable/material_regions.py +48 -0
- deformable/mesh_verification.py +187 -0
- deformable/meshes.py +179 -0
- deformable/native.py +401 -0
- deformable/simulation.py +468 -0
- deformable/tools.py +340 -0
- deformable/values.py +116 -0
- deformable-0.1.0.dist-info/METADATA +14 -0
- deformable-0.1.0.dist-info/RECORD +19 -0
- deformable-0.1.0.dist-info/WHEEL +4 -0
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()
|
deformable/conversion.py
ADDED
|
@@ -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
|
deformable/embedding.py
ADDED
|
@@ -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)
|