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/simulation.py
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""The simulation a host drives frame by frame, with its value types.
|
|
2
|
+
|
|
3
|
+
Lengths cross in host units; time, forces, gravity, masses and material values in simulation units. The simulation's
|
|
4
|
+
scale converts host lengths to simulation lengths.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ctypes
|
|
10
|
+
import dataclasses
|
|
11
|
+
import numbers
|
|
12
|
+
from collections.abc import Callable, Sequence
|
|
13
|
+
from typing import Any, NamedTuple
|
|
14
|
+
|
|
15
|
+
from . import conversion, library, native
|
|
16
|
+
from .cancel_flag import CancelFlag, cancel_handle
|
|
17
|
+
from .errors import Status, check
|
|
18
|
+
from .handles import OwnedHandle
|
|
19
|
+
from .meshes import tetrahedral_mesh_view
|
|
20
|
+
from .values import StepOutcome, Vector3
|
|
21
|
+
|
|
22
|
+
_UNIFORM = 0
|
|
23
|
+
_PER_TETRAHEDRON = 1
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclasses.dataclass(frozen=True)
|
|
27
|
+
class MaterialParameters:
|
|
28
|
+
"""Material values as a host gives them, before the library clamps them."""
|
|
29
|
+
|
|
30
|
+
youngs_modulus: float
|
|
31
|
+
"""Young's modulus E, in pascals in simulation units."""
|
|
32
|
+
poissons_ratio: float
|
|
33
|
+
"""Poisson's ratio ν, unitless."""
|
|
34
|
+
density: float
|
|
35
|
+
"""Mass per unit volume; a tetrahedron weighs density·V."""
|
|
36
|
+
toughness: float
|
|
37
|
+
"""Fracture threshold on half the largest separation-tensor eigenvalue; 0 or negative means unbreakable."""
|
|
38
|
+
youngs_damping: float
|
|
39
|
+
"""Stiffness-proportional damping; the engine uses alpha = youngs_damping / youngs_modulus."""
|
|
40
|
+
yield_strain: float
|
|
41
|
+
"""Plastic flow starts when the elastic strain norm exceeds this."""
|
|
42
|
+
maximum_yield: float
|
|
43
|
+
"""Bound on the plastic strain norm; 0 resets the plastic strain every solve."""
|
|
44
|
+
creep_fraction: float
|
|
45
|
+
"""Fraction, 0 to 1, of the elastic strain added to the plastic strain once per solve, not per second, while its
|
|
46
|
+
norm exceeds yield_strain; 0 disables plasticity."""
|
|
47
|
+
wake_up_radius: float
|
|
48
|
+
"""Radius of the impact wake flood fill, in simulation units."""
|
|
49
|
+
friction: float
|
|
50
|
+
"""Coulomb friction coefficient."""
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def default(cls, **changes: float) -> MaterialParameters:
|
|
54
|
+
"""The values a new material starts from, with the given fields changed."""
|
|
55
|
+
structure = native.MaterialParameters()
|
|
56
|
+
check(library.load().deformable_material_parameters_default(ctypes.byref(structure)))
|
|
57
|
+
return dataclasses.replace(cls._from_native(structure), **changes)
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def presets(cls) -> list[MaterialPreset]:
|
|
61
|
+
"""The presets the library ships, in its order: from the softest to the metals."""
|
|
62
|
+
functions = library.load()
|
|
63
|
+
return [cls._preset_at(functions, index) for index in range(functions.deformable_material_preset_count())]
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def preset(cls, name: str) -> MaterialParameters | None:
|
|
67
|
+
"""The preset with this name, or None. The name must match exactly, case included ("unbreakable_rubber")."""
|
|
68
|
+
if not isinstance(name, str):
|
|
69
|
+
raise TypeError(f"a preset name is a str, not {type(name).__name__}")
|
|
70
|
+
if "\0" in name:
|
|
71
|
+
return None
|
|
72
|
+
# Lone surrogates pass through as bytes that are not UTF-8, which no preset name matches.
|
|
73
|
+
encoded = ctypes.create_string_buffer(name.encode("utf-8", errors="surrogatepass"))
|
|
74
|
+
structure = native.MaterialParameters()
|
|
75
|
+
code = library.load().deformable_material_preset_find(encoded, ctypes.byref(structure))
|
|
76
|
+
if code == Status.INVALID_ARGUMENT:
|
|
77
|
+
return None
|
|
78
|
+
check(code)
|
|
79
|
+
return cls._from_native(structure)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def _preset_at(cls, functions: ctypes.CDLL, index: int) -> MaterialPreset:
|
|
83
|
+
name = ctypes.POINTER(ctypes.c_char)()
|
|
84
|
+
structure = native.MaterialParameters()
|
|
85
|
+
check(functions.deformable_material_preset(index, ctypes.byref(name), ctypes.byref(structure)))
|
|
86
|
+
return MaterialPreset(ctypes.string_at(name).decode("utf-8"), cls._from_native(structure))
|
|
87
|
+
|
|
88
|
+
@classmethod
|
|
89
|
+
def _from_native(cls, structure: native.MaterialParameters) -> MaterialParameters:
|
|
90
|
+
return cls(**{field.name: getattr(structure, field.name) for field in dataclasses.fields(cls)})
|
|
91
|
+
|
|
92
|
+
def _native(self) -> native.MaterialParameters:
|
|
93
|
+
return native.MaterialParameters(**dataclasses.asdict(self))
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class MaterialPreset(NamedTuple):
|
|
97
|
+
"""A material preset the library ships: its name and its values."""
|
|
98
|
+
|
|
99
|
+
name: str
|
|
100
|
+
"""The preset's lowercase snake_case name, such as "concrete" or "unbreakable_rubber"."""
|
|
101
|
+
parameters: MaterialParameters
|
|
102
|
+
"""The values, each inside its clamp range, so add_material keeps them as they are."""
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclasses.dataclass(frozen=True)
|
|
106
|
+
class ObjectOptions:
|
|
107
|
+
"""The per-object settings."""
|
|
108
|
+
|
|
109
|
+
self_collision: bool
|
|
110
|
+
"""The object's colliders test its own faces against each other."""
|
|
111
|
+
disabled: bool
|
|
112
|
+
"""The object is created disabled."""
|
|
113
|
+
|
|
114
|
+
@classmethod
|
|
115
|
+
def default(cls, **changes: bool) -> ObjectOptions:
|
|
116
|
+
"""The object defaults (self-collision on, enabled), with the given fields changed."""
|
|
117
|
+
structure = native.ObjectOptions()
|
|
118
|
+
check(library.load().deformable_object_options_default(ctypes.byref(structure)))
|
|
119
|
+
values = {"self_collision": bool(structure.self_collision), "disabled": bool(structure.disabled)}
|
|
120
|
+
return cls(**{**values, **changes})
|
|
121
|
+
|
|
122
|
+
def _native(self) -> native.ObjectOptions:
|
|
123
|
+
return native.ObjectOptions(bool(self.self_collision), bool(self.disabled))
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class GlueParameters(NamedTuple):
|
|
127
|
+
"""The spring and damping of glue; a zero spring constant or damping coefficient takes 4905 or 2452.5, each on
|
|
128
|
+
its own."""
|
|
129
|
+
|
|
130
|
+
spring_constant: float
|
|
131
|
+
"""The spring constant k."""
|
|
132
|
+
damping_coefficient: float
|
|
133
|
+
"""The damping coefficient c."""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Plane(NamedTuple):
|
|
137
|
+
"""A bounding plane of a convex region: the inside is normal · p + offset <= 0."""
|
|
138
|
+
|
|
139
|
+
normal: Sequence[float]
|
|
140
|
+
"""The outward normal n, of any length."""
|
|
141
|
+
offset: float
|
|
142
|
+
"""The offset d."""
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclasses.dataclass(frozen=True)
|
|
146
|
+
class ConvexRegion:
|
|
147
|
+
"""A convex region: the points inside all of its planes; with no plane it holds every point."""
|
|
148
|
+
|
|
149
|
+
planes: Sequence[Plane]
|
|
150
|
+
"""The bounding planes, each a Plane or a (normal, offset) pair."""
|
|
151
|
+
|
|
152
|
+
def contains(self, point: Sequence[float]) -> bool:
|
|
153
|
+
"""True when normal · point + offset <= 0 for every plane: the test a host runs for passive regions."""
|
|
154
|
+
inside = native.Boolean()
|
|
155
|
+
check(
|
|
156
|
+
library.load().deformable_convex_region_contains(
|
|
157
|
+
ctypes.byref(self._native()), conversion.vector3(point), ctypes.byref(inside)
|
|
158
|
+
)
|
|
159
|
+
)
|
|
160
|
+
return bool(inside.value)
|
|
161
|
+
|
|
162
|
+
def _native(self) -> native.ConvexRegion:
|
|
163
|
+
planes = conversion.structure_array(
|
|
164
|
+
self.planes, native.Plane, lambda plane: native.Plane(conversion.vector3(plane[0]), plane[1])
|
|
165
|
+
)
|
|
166
|
+
return native.ConvexRegion(planes, len(planes))
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class DrivenVertex(NamedTuple):
|
|
170
|
+
"""A vertex of an object driven to a target, in host units."""
|
|
171
|
+
|
|
172
|
+
vertex: int
|
|
173
|
+
"""The vertex, an index into the object's nodes."""
|
|
174
|
+
target: Sequence[float]
|
|
175
|
+
"""Where the vertex is at the end of the next step, in host units."""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class StepReport(NamedTuple):
|
|
179
|
+
"""The result of a Simulation.step call."""
|
|
180
|
+
|
|
181
|
+
substeps_run: int
|
|
182
|
+
"""The number of substeps simulated."""
|
|
183
|
+
outcome: StepOutcome
|
|
184
|
+
"""What the call did."""
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@dataclasses.dataclass(frozen=True)
|
|
188
|
+
class TetrahedronState:
|
|
189
|
+
"""The output record of one tetrahedron."""
|
|
190
|
+
|
|
191
|
+
corners: tuple[Vector3, Vector3, Vector3, Vector3]
|
|
192
|
+
"""The current corner positions in host units."""
|
|
193
|
+
island: int
|
|
194
|
+
"""The id of the tetrahedron's island, or 0 while it has none."""
|
|
195
|
+
strain_measure: float
|
|
196
|
+
"""The squared norm of the total co-rotated strain, plastic part included."""
|
|
197
|
+
flags: int
|
|
198
|
+
"""The exposed-face bits 0-3 reported so far (sticky), plus NODE_REPLACED."""
|
|
199
|
+
|
|
200
|
+
def face_centroid(self, face: int) -> Vector3:
|
|
201
|
+
"""The centroid of face face (0-3, the face opposite corner face)."""
|
|
202
|
+
return self._face_query("deformable_tetrahedron_state_face_centroid", face)
|
|
203
|
+
|
|
204
|
+
def face_normal(self, face: int) -> Vector3:
|
|
205
|
+
"""The outward unit normal of face face (0-3) of a positively oriented (left-handed) state; zero for a face of
|
|
206
|
+
zero area."""
|
|
207
|
+
return self._face_query("deformable_tetrahedron_state_face_normal", face)
|
|
208
|
+
|
|
209
|
+
def _face_query(self, function_name: str, face: int) -> Vector3:
|
|
210
|
+
result = native.Vector3()
|
|
211
|
+
function = getattr(library.load(), function_name)
|
|
212
|
+
check(function(ctypes.byref(self._native()), conversion.unsigned(face), ctypes.byref(result)))
|
|
213
|
+
return conversion.from_vector3(result)
|
|
214
|
+
|
|
215
|
+
def _native(self) -> native.TetrahedronState:
|
|
216
|
+
corners = (native.Vector3 * 4)(*(conversion.vector3(corner) for corner in self.corners))
|
|
217
|
+
return native.TetrahedronState(corners, self.island, self.strain_measure, self.flags)
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
def _from_native(cls, structure: native.TetrahedronState) -> TetrahedronState:
|
|
221
|
+
corners = tuple(conversion.from_vector3(corner) for corner in structure.corners)
|
|
222
|
+
return cls(corners, structure.island, structure.strain_measure, structure.flags)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class FaceEvent(NamedTuple):
|
|
226
|
+
"""A tetrahedron fracture changed in the last step and the faces it now shares with no other tetrahedron."""
|
|
227
|
+
|
|
228
|
+
tetrahedron: int
|
|
229
|
+
"""The tetrahedron's index in its object's mesh."""
|
|
230
|
+
exposed_faces: int
|
|
231
|
+
"""Bit k is set when face k, opposite corner k, is exposed; original boundary faces count as exposed."""
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class SimulationStatistics(NamedTuple):
|
|
235
|
+
"""The solver, contact and island counters."""
|
|
236
|
+
|
|
237
|
+
systems: int
|
|
238
|
+
"""Systems solved so far."""
|
|
239
|
+
conjugate_gradient_updates: int
|
|
240
|
+
"""Conjugate gradient solution updates summed over every solve so far."""
|
|
241
|
+
contacts: int
|
|
242
|
+
"""Live contacts, glue contacts included."""
|
|
243
|
+
islands: int
|
|
244
|
+
"""Islands, awake and asleep."""
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def tetrahedron_materials(materials: int | Sequence[int] | Any) -> native.TetrahedronMaterials:
|
|
248
|
+
"""One material handle for every tetrahedron, or one handle per tetrahedron in mesh order."""
|
|
249
|
+
if isinstance(materials, numbers.Integral):
|
|
250
|
+
return native.TetrahedronMaterials(_UNIFORM, conversion.unsigned(materials), None, 0)
|
|
251
|
+
per_tetrahedron = conversion.index_array(materials, "materials")
|
|
252
|
+
return native.TetrahedronMaterials(_PER_TETRAHEDRON, 0, per_tetrahedron, len(per_tetrahedron))
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class Simulation(OwnedHandle):
|
|
256
|
+
"""One scene: gravity (0, -9.81, 0), scale 1 and min(available parallelism, 16) workers when created.
|
|
257
|
+
|
|
258
|
+
Use it in a with block, or call close(), to free it at a known point. It may move between threads; calls on one
|
|
259
|
+
simulation from several threads run one at a time, and close() waits for the call in flight. Object and material
|
|
260
|
+
handles are ints counting up from 0 in creation order.
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
_destroy_name = "deformable_simulation_destroy"
|
|
264
|
+
_description = "simulation"
|
|
265
|
+
_one_call_at_a_time = True
|
|
266
|
+
|
|
267
|
+
def __init__(self) -> None:
|
|
268
|
+
super().__init__("deformable_simulation_create")
|
|
269
|
+
|
|
270
|
+
def set_scale(self, scale: float) -> None:
|
|
271
|
+
"""Sets the host length unit → simulation unit factor, in [0.001, 1000]; it applies to later conversions."""
|
|
272
|
+
self._call("deformable_simulation_set_scale", scale)
|
|
273
|
+
|
|
274
|
+
def set_gravity(self, gravity: Sequence[float]) -> None:
|
|
275
|
+
"""Sets the gravity in simulation units, not scaled."""
|
|
276
|
+
self._call("deformable_simulation_set_gravity", conversion.vector3(gravity))
|
|
277
|
+
|
|
278
|
+
def set_worker_count(self, count: int) -> None:
|
|
279
|
+
"""Sets the worker count, at least 1 and including the calling thread; results do not depend on it."""
|
|
280
|
+
self._call("deformable_simulation_set_worker_count", conversion.size(count))
|
|
281
|
+
|
|
282
|
+
def add_material(self, parameters: MaterialParameters) -> int:
|
|
283
|
+
"""Creates a material, each value clamped to its range, and returns its handle."""
|
|
284
|
+
material = native.Index()
|
|
285
|
+
self._call("deformable_simulation_add_material", ctypes.byref(parameters._native()), ctypes.byref(material))
|
|
286
|
+
return material.value
|
|
287
|
+
|
|
288
|
+
def set_material(self, material: int, parameters: MaterialParameters) -> None:
|
|
289
|
+
"""Rewrites a material's values; the objects using it take them before the next step's substeps."""
|
|
290
|
+
self._call(
|
|
291
|
+
"deformable_simulation_set_material", conversion.unsigned(material), ctypes.byref(parameters._native())
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
def add_object(self, mesh: Any, materials: int | Sequence[int] | Any, options: ObjectOptions | None = None) -> int:
|
|
295
|
+
"""Creates an object from a tetrahedral mesh in world space and host units, and returns its handle.
|
|
296
|
+
|
|
297
|
+
materials is one material handle for every tetrahedron or one per tetrahedron; options defaults to
|
|
298
|
+
ObjectOptions.default(). A degenerate tetrahedron or a face shared by three tetrahedrons raises a
|
|
299
|
+
DeformableError naming it, and nothing is created then.
|
|
300
|
+
"""
|
|
301
|
+
options = ObjectOptions.default() if options is None else options
|
|
302
|
+
object_handle = native.Index()
|
|
303
|
+
self._call(
|
|
304
|
+
"deformable_simulation_add_object",
|
|
305
|
+
ctypes.byref(tetrahedral_mesh_view(mesh)),
|
|
306
|
+
ctypes.byref(tetrahedron_materials(materials)),
|
|
307
|
+
ctypes.byref(options._native()),
|
|
308
|
+
ctypes.byref(object_handle),
|
|
309
|
+
)
|
|
310
|
+
return object_handle.value
|
|
311
|
+
|
|
312
|
+
def set_object_material(self, object_handle: int, materials: int | Sequence[int] | Any) -> None:
|
|
313
|
+
"""Reassigns an object's materials before the next step's substeps; it rebuilds islands and wakes nothing."""
|
|
314
|
+
self._call(
|
|
315
|
+
"deformable_simulation_set_object_material",
|
|
316
|
+
conversion.unsigned(object_handle),
|
|
317
|
+
ctypes.byref(tetrahedron_materials(materials)),
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
def remove_object(self, object_handle: int) -> None:
|
|
321
|
+
"""Deletes an object; its contacts and glue are freed and its handle becomes unknown."""
|
|
322
|
+
self._call("deformable_simulation_remove_object", conversion.unsigned(object_handle))
|
|
323
|
+
|
|
324
|
+
def set_object_disabled(self, object_handle: int, disabled: bool) -> None:
|
|
325
|
+
"""Disables or enables an object; a disabled object keeps its outputs and ignores forces and driving."""
|
|
326
|
+
self._call("deformable_simulation_set_object_disabled", conversion.unsigned(object_handle), bool(disabled))
|
|
327
|
+
|
|
328
|
+
def set_self_collision(self, object_handle: int, enabled: bool) -> None:
|
|
329
|
+
"""Sets or clears an object's self-collision; an island takes the change at its next rebuild."""
|
|
330
|
+
self._call("deformable_simulation_set_self_collision", conversion.unsigned(object_handle), bool(enabled))
|
|
331
|
+
|
|
332
|
+
def set_never_sleep(self, object_handle: int, node: int, enabled: bool) -> None:
|
|
333
|
+
"""Sets or clears the never-sleep flag of a node: its island never sleeps, and setting it wakes the node."""
|
|
334
|
+
self._call(
|
|
335
|
+
"deformable_simulation_set_never_sleep",
|
|
336
|
+
conversion.unsigned(object_handle),
|
|
337
|
+
conversion.unsigned(node),
|
|
338
|
+
bool(enabled),
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
def set_glue_all(self, parameters: GlueParameters | None) -> None:
|
|
342
|
+
"""Glues each new object to every other enabled object where they overlap; None turns glue all off."""
|
|
343
|
+
glue = None if parameters is None else ctypes.byref(native.GlueParameters(*parameters))
|
|
344
|
+
self._call("deformable_simulation_set_glue_all", glue)
|
|
345
|
+
|
|
346
|
+
def add_glue_region(self, region: ConvexRegion, members: Sequence[int], parameters: GlueParameters) -> None:
|
|
347
|
+
"""Glues each new member to every other enabled member through their tetrahedrons with a vertex inside every
|
|
348
|
+
plane of region; plane offsets are in host units."""
|
|
349
|
+
member_handles = conversion.index_array(members, "members")
|
|
350
|
+
self._call(
|
|
351
|
+
"deformable_simulation_add_glue_region",
|
|
352
|
+
ctypes.byref(region._native()),
|
|
353
|
+
member_handles,
|
|
354
|
+
len(member_handles),
|
|
355
|
+
ctypes.byref(native.GlueParameters(*parameters)),
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
def drive(self, object_handle: int, vertices: Sequence[DrivenVertex]) -> None:
|
|
359
|
+
"""Drives the listed vertices toward their targets (host units) from the next step on, until the next call
|
|
360
|
+
for the object; previously driven vertices not listed are released with their last driven velocity."""
|
|
361
|
+
driven = conversion.structure_array(
|
|
362
|
+
vertices,
|
|
363
|
+
native.DrivenVertex,
|
|
364
|
+
lambda vertex: native.DrivenVertex(conversion.unsigned(vertex[0]), conversion.vector3(vertex[1])),
|
|
365
|
+
)
|
|
366
|
+
self._call("deformable_simulation_drive", conversion.unsigned(object_handle), driven, len(driven))
|
|
367
|
+
|
|
368
|
+
def add_force(self, object_handle: int, node: int, force: Sequence[float]) -> None:
|
|
369
|
+
"""Queues a force in simulation units on a node for every substep of the next step; calls add up."""
|
|
370
|
+
self._call(
|
|
371
|
+
"deformable_simulation_add_force",
|
|
372
|
+
conversion.unsigned(object_handle),
|
|
373
|
+
conversion.unsigned(node),
|
|
374
|
+
conversion.vector3(force),
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def step(self, time: float, substeps: int, cancel: CancelFlag | None = None) -> StepReport:
|
|
378
|
+
"""Advances to time (seconds) in substeps equal substeps. The first call only records the time.
|
|
379
|
+
|
|
380
|
+
A cancel flag set from another thread stops the step between two substeps, after at least one.
|
|
381
|
+
"""
|
|
382
|
+
report = native.StepReport()
|
|
383
|
+
with cancel_handle(cancel) as cancel_address:
|
|
384
|
+
self._call(
|
|
385
|
+
"deformable_simulation_step", time, conversion.unsigned(substeps), cancel_address, ctypes.byref(report)
|
|
386
|
+
)
|
|
387
|
+
return StepReport(report.substeps_run, StepOutcome(report.outcome))
|
|
388
|
+
|
|
389
|
+
def skip_to(self, time: float) -> None:
|
|
390
|
+
"""Scene disable: records time as the last time without stepping, and drops the queued forces."""
|
|
391
|
+
self._call("deformable_simulation_skip_to", time)
|
|
392
|
+
|
|
393
|
+
def node_positions(self, object_handle: int) -> list[Vector3]:
|
|
394
|
+
"""The current node positions of an object in host units; fracture adds nodes."""
|
|
395
|
+
return self._read_lent(
|
|
396
|
+
"deformable_simulation_node_positions", object_handle, native.Vector3, conversion.read_vector3_list
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
def node_velocities(self, object_handle: int) -> list[Vector3]:
|
|
400
|
+
"""The current node velocities of an object in host units per second, in node-position order."""
|
|
401
|
+
return self._read_lent(
|
|
402
|
+
"deformable_simulation_node_velocities", object_handle, native.Vector3, conversion.read_vector3_list
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
def node_masses(self, object_handle: int) -> list[float]:
|
|
406
|
+
"""The lumped node masses of an object in simulation units."""
|
|
407
|
+
return self._read_lent(
|
|
408
|
+
"deformable_simulation_node_masses",
|
|
409
|
+
object_handle,
|
|
410
|
+
ctypes.c_double,
|
|
411
|
+
lambda pointer, count: conversion.read_scalars(pointer, ctypes.c_double, count),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
def tetrahedron_nodes(self, object_handle: int) -> list[tuple[int, int, int, int]]:
|
|
415
|
+
"""The current four nodes of each tetrahedron of an object, in mesh order."""
|
|
416
|
+
return self._read_lent(
|
|
417
|
+
"deformable_simulation_tetrahedron_nodes",
|
|
418
|
+
object_handle,
|
|
419
|
+
native.TetrahedronIndices,
|
|
420
|
+
lambda pointer, count: conversion.read_groups(pointer, ctypes.c_uint32, count, 4),
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
def tetrahedron_states(self, object_handle: int) -> list[TetrahedronState]:
|
|
424
|
+
"""The output record of each tetrahedron of an object, in mesh order."""
|
|
425
|
+
return self._read_lent(
|
|
426
|
+
"deformable_simulation_tetrahedron_states",
|
|
427
|
+
object_handle,
|
|
428
|
+
native.TetrahedronState,
|
|
429
|
+
lambda pointer, count: [
|
|
430
|
+
TetrahedronState._from_native(state)
|
|
431
|
+
for state in conversion.lent_array(pointer, native.TetrahedronState, count)
|
|
432
|
+
],
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
def face_events(self, object_handle: int) -> list[FaceEvent]:
|
|
436
|
+
"""The exposed-face events of an object from the last step, one per tetrahedron fracture changed."""
|
|
437
|
+
return self._read_lent(
|
|
438
|
+
"deformable_simulation_face_events",
|
|
439
|
+
object_handle,
|
|
440
|
+
native.FaceEvent,
|
|
441
|
+
lambda pointer, count: [
|
|
442
|
+
FaceEvent(event.tetrahedron, event.exposed_faces)
|
|
443
|
+
for event in conversion.lent_array(pointer, native.FaceEvent, count)
|
|
444
|
+
],
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def awake_tetrahedron_count(self) -> int:
|
|
448
|
+
"""The number of tetrahedrons in awake islands."""
|
|
449
|
+
count = native.Count()
|
|
450
|
+
self._call("deformable_simulation_awake_tetrahedron_count", ctypes.byref(count))
|
|
451
|
+
return count.value
|
|
452
|
+
|
|
453
|
+
def statistics(self) -> SimulationStatistics:
|
|
454
|
+
"""The solver, contact and island counters."""
|
|
455
|
+
statistics = native.SimulationStatistics()
|
|
456
|
+
self._call("deformable_simulation_statistics", ctypes.byref(statistics))
|
|
457
|
+
return SimulationStatistics(
|
|
458
|
+
statistics.systems, statistics.conjugate_gradient_updates, statistics.contacts, statistics.islands
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
def _read_lent(self, function_name: str, object_handle: int, element_type, read: Callable[[Any, int], Any]) -> Any:
|
|
462
|
+
"""Copies an array the simulation lends with read(pointer, count) before another call can change it."""
|
|
463
|
+
pointer = ctypes.POINTER(element_type)()
|
|
464
|
+
count = native.Count()
|
|
465
|
+
with self._use() as address:
|
|
466
|
+
function = getattr(library.load(), function_name)
|
|
467
|
+
check(function(address, conversion.unsigned(object_handle), ctypes.byref(pointer), ctypes.byref(count)))
|
|
468
|
+
return read(pointer, count.value)
|