2025-12-01

This commit is contained in:
2026-03-17 14:58:51 -06:00
parent 183e865f8b
commit 4b82b57113
6846 changed files with 954887 additions and 162606 deletions
@@ -0,0 +1,37 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from . import ffengine
from .aabb import AABB, AABB_t
from .fluidsimulation import FluidSimulation, MarkerParticle_t, DiffuseParticle_t
from .meshobject import MeshObject
from .meshfluidsource import MeshFluidSource
from .forcefieldgrid import ForceFieldGrid
from .forcefield import ForceField
from .forcefieldpoint import ForceFieldPoint
from .forcefieldsurface import ForceFieldSurface
from .forcefieldvolume import ForceFieldVolume
from .forcefieldcurve import ForceFieldCurve
from .trianglemesh import TriangleMesh, TriangleMesh_t
from .gridindex import GridIndex, GridIndex_t
from .vector3 import Vector3, Vector3_t
from . import mixbox
@@ -0,0 +1,237 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .vector3 import Vector3, Vector3_t
from .gridindex import GridIndex
from . import method_decorators as decorators
import ctypes
class AABB_t(ctypes.Structure):
_fields_ = [("position", Vector3_t),
("width", ctypes.c_float),
("height", ctypes.c_float),
("depth", ctypes.c_float)]
class AABB(object):
def __init__(self, *args):
if len(args) == 4 and isinstance(args[0], Vector3):
self.position = args[0]
self.width = args[1]
self.height = args[2]
self.depth = args[3]
elif len(args) == 6:
self._position = Vector3(args[0], args[1], args[2])
self.width = args[3]
self.height = args[4]
self.depth = args[5]
elif len(args) == 0:
self.position = Vector3()
self.width = 0.0
self.height = 0.0
self.depth = 0.0
else:
errmsg = "AABB must be initialized with types:\n"
errmsg += "x: " + (str(float) + "\n" +
"y: " + str(float) + "\n" +
"z: " + str(float) + "\n" +
"width: " + str(float) + "\n" +
"height: " + str(float) + "\n" +
"depth: " + str(float) + "\n\n" +
"or\n\n" +
"position: " + (str(Vector3)) + "\n" +
"width: " + str(float) + "\n" +
"height: " + str(float) + "\n" +
"depth: " + str(float))
raise TypeError(errmsg)
def __str__(self):
return (str(self.position) + " " + str(self.width) + " " +
str(self.height) + " " +
str(self.depth))
@classmethod
@decorators.check_type(Vector3)
def from_corners(cls, pmin = Vector3(), pmax = Vector3()):
minx = min(pmin.x, pmax.x)
miny = min(pmin.y, pmax.y)
minz = min(pmin.z, pmax.z)
maxx = max(pmin.x, pmax.x)
maxy = max(pmin.y, pmax.y)
maxz = max(pmin.z, pmax.z)
width = maxx - minx
height = maxy - miny
depth = maxz - minz
return cls(minx, miny, minz, width, height, depth)
@classmethod
def from_points(cls, point_list):
if len(point_list) == 0:
return cls()
minx, miny, minz = point_list[0]
maxx, maxy, maxz = point_list[0]
for p in point_list:
minx = min(p.x, minx);
miny = min(p.y, miny);
minz = min(p.z, minz);
maxx = max(p.x, maxx);
maxy = max(p.y, maxy);
maxz = max(p.z, maxz);
eps = 1e-9;
width = maxx - minx + eps;
height = maxy - miny + eps;
depth = maxz - minz + eps;
return cls(minx, miny, minz, width, height, depth)
@classmethod
def from_struct(cls, cstruct):
return cls(Vector3.from_struct(cstruct.position),
float(cstruct.width),
float(cstruct.height),
float(cstruct.depth))
def to_struct(self):
return AABB_t(Vector3_t(self.x, self.y, self.z),
self.width, self.height, self.depth)
@classmethod
def from_grid_index(cls, grid_index = GridIndex(), dx = 0.0):
return cls(grid_index.i*dx, grid_index.j*dx, grid_index.k*dx, dx, dx, dx)
@property
def x(self):
return self._position.x
@property
def y(self):
return self._position.y
@property
def z(self):
return self._position.z
@property
def width(self):
return self._width
@property
def height(self):
return self._height
@property
def depth(self):
return self._depth
@property
def position(self):
return self._position
@x.setter
def x(self, value):
self._position.x = value
@y.setter
def y(self, value):
self._position.y = value
@z.setter
def z(self, value):
self._position.z = value
@width.setter
def width(self, value):
self._width = float(value)
@height.setter
def height(self, value):
self._height = float(value)
@depth.setter
def depth(self, value):
self._depth = float(value)
@position.setter
@decorators.check_type(Vector3)
def position(self, vector):
self._position = vector
def expand(self, v):
h = 0.5 * v;
self.position -= Vector3(h, h, h);
self.width += v;
self.height += v;
self.depth += v;
@decorators.xyz_or_vector
def contains_point(self, x, y, z):
return (x >= self.x and y >= self.y and z >= self.z and
x < self.x + self.width and
y < self.y + self.height and
z < self.z + self.depth)
def get_min_point(self):
return self.position
def get_max_point(self):
return self.position + Vector3(self.width, self.height, self.depth)
def get_intersection(self, bbox):
minp1 = self.get_min_point()
minp2 = bbox.get_min_point()
maxp1 = self.get_max_point()
maxp2 = bbox.get_max_point()
if minp1.x > maxp2.x or minp1.y > maxp2.y or minp1.z > maxp2.z:
return AABB()
interminx = max(minp1.x, minp2.x)
interminy = max(minp1.y, minp2.y)
interminz = max(minp1.z, minp2.z)
intermaxx = min(maxp1.x, maxp2.x)
intermaxy = min(maxp1.y, maxp2.y)
intermaxz = min(maxp1.z, maxp2.z)
return AABB.from_corners(Vector3(interminx, interminy, interminz),
Vector3(intermaxx, intermaxy, intermaxz))
def get_union(self, bbox):
minp1 = self.get_min_point()
minp2 = bbox.get_min_point()
maxp1 = self.get_max_point()
maxp2 = bbox.get_max_point()
if minp1.x > maxp2.x or minp1.y > maxp2.y or minp1.z > maxp2.z:
return AABB()
unionminx = min(minp1.x, minp2.x)
unionminy = min(minp1.y, minp2.y)
unionminz = min(minp1.z, minp2.z)
unionmaxx = max(maxp1.x, maxp2.x)
unionmaxy = max(maxp1.y, maxp2.y)
unionmaxz = max(maxp1.z, maxp2.z)
return AABB.from_corners(Vector3(unionminx, unionminy, unionminz),
Vector3(unionmaxx, unionmaxy, unionmaxz))
@@ -0,0 +1,112 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
import array
from gridindex import GridIndex
import method_decorators as decorators
class Array3d:
__metaclass__ = ABCMeta
def __init__(self, isize, jsize, ksize):
self.width, self.height, self.depth = isize, jsize, ksize
self._num_elements = isize*jsize*ksize
@abstractmethod
def _init_grid(self, data):
pass
def fill(self, value):
for i in range(self._num_elements):
self._grid[i] = value
@decorators.ijk_or_gridindex
def __call__(self, i, j, k):
if not self._is_index_in_range(i, j, k) and self._out_of_range_value != None:
return self._out_of_range_value
return self._grid[self._get_flat_index(i, j, k)]
def __iter__(self):
i = j = k = 0
for v in self._grid:
yield i, j, k, v
i += 1
if i >= self.width:
i = 0
j += 1
if j >= self.height:
j = 0
k += 1
@decorators.ijk_or_gridindex
def get(self, i, j, k):
return self(i, j, k)
@decorators.ijk_or_gridindex_and_value
def set(self, i, j, k, value):
self._grid[self._get_flat_index(i, j, k)] = value
@decorators.ijk_or_gridindex_and_value
def add(self, i, j, k, value):
self._grid[self._get_flat_index(i, j, k)] += value
def get_num_elements(self):
return self._num_elements
def set_out_of_range_value(self, value = None):
self._out_of_range_value = value
def get_out_of_range_value(self):
return self._out_of_range_value
def _is_index_in_range(self, i, j, k):
return (i >= 0 and j >= 0 and k >= 0 and
i < self.width and j < self.height and k < self.depth)
def _get_flat_index(self, i, j, k):
return i + self.width*(j + self.height*k)
class Array3di(Array3d):
def __init__(self, isize, jsize, ksize, default_value = int()):
Array3d.__init__(self, isize, jsize, ksize)
self._init_grid(default_value)
def _init_grid(self, default_value):
self._grid = array.array('i', [default_value]*self.get_num_elements())
class Array3df(Array3d):
def __init__(self, isize, jsize, ksize, default_value = float()):
Array3d.__init__(self, isize, jsize, ksize)
self._init_grid(default_value)
def _init_grid(self, default_value):
self._grid = array.array('f', [default_value]*self.get_num_elements())
class Array3dd(Array3d):
def __init__(self, isize, jsize, ksize, default_value = float()):
Array3d.__init__(self, isize, jsize, ksize)
self._init_grid(default_value)
def _init_grid(self, default_value):
self._grid = array.array('d', [default_value]*self.get_num_elements())
@@ -0,0 +1,138 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import ctypes
import os
import platform
class LibraryLoadError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
# The FFEngineLib class loads the FLIP Fluids addon simulation engine. The engine
# is a dynamic library which contains methods to process simulation calculations.
# The simulation engine is written in C and C++ and is controlled through Python
# using the built-in ctypes module (https://docs.python.org/3/library/ctypes.html).
#
# The files in src/engine/ffengine contain Python bindings for the fluid simulation
# objects and methods. The Python bindings use ctypes to call corresponding C bindings
# found in src/engine/c_bindings. The C bindings call C++ methods found in src/engine.
#
# To begin following how the simulator is run from Python to C to C++, refer to the
# baking script located at src/addon/bake.py starting at the bake(...) method. The
# arguments passed to bake(...) are generated and formed in the addon within the Bake
# Operators found in src/addon/operators/bake_operators.py as well as the Export
# Operators found in src/addon/operators/export_operators.py.
class FFEngineLib():
def __init__(self):
self._lib = None
def __getattr__(self, name):
if self.__dict__['_lib'] is None:
self._lib = self._load_library("ffengine")
return getattr(self._lib, name)
def _load_library(self, name):
libname_release_prefix = "libffengine"
system = platform.system()
if system == "Windows":
library_extension = ".dll"
elif system == "Darwin":
library_extension = ".dylib"
elif system == "Linux":
library_extension = ".so"
else:
raise LibraryLoadError("Unable to recognize system: " + system)
libdir = os.path.join(os.path.dirname(__file__), "lib")
libnames= [f for f in os.listdir(libdir) if os.path.isfile(os.path.join(libdir, f))]
libnames_release = [n for n in libnames if n.startswith(libname_release_prefix) and n.endswith(library_extension)]
# Sorting the library names by length is not necessary, but sorting from
# longest name to shortest will bypass a possible user-error if the user does not
# completely remove the previous installation before installing a new version.
# A version update required a possible increase in the length of the library
# name. This sort will ensure that the longer library name (newer version)
# is used before the shorter named file (older version) that could remain
# from an incorrect install or compile process.
libnames_release.sort(key=len, reverse=True)
libpaths_release = [os.path.join(libdir, n) for n in libnames_release]
# The addon requires a functioning ffengine library version
missing_libraries = []
if not libpaths_release:
missing_libraries.append(libname_release_prefix + library_extension)
if missing_libraries:
err_msg = "Cannot find fluid engine libraries: "
for libname in missing_libraries:
err_msg += "<" + libname + "> "
raise LibraryLoadError(err_msg)
# The addon may be packaged with multiple versions of a library for the OS, not
# all of which may be compatible with the specific OS version. Choose the first
# library that loads without error.
# Refer to the LIBRARY_SUFFIX variable in the CMakeLists.txt file for generating a
# library with a suffix added to the name.
loaded_library = None
failed_libraries = []
for libpath in libpaths_release:
try:
loaded_library = ctypes.cdll.LoadLibrary(libpath)
break
except:
failed_libraries.append(libpath)
loaded_library = None
pass
# Additional notes on the error message:
# (1) Blender 2.80 and later are 64-bit and require a library that has been
# built as 64-bit. Make sure you are using a 64-bit compiler for these versions.
# Blender 2.79 distributes both 32-bit and 64-bit versions, so make sure your
# your compiler matches the target version of Blender 2.79.
# (2) This resolves possible errors due to incorrect installation of the addon and
# possible conflicts between Blender versions (such as multiple daily builds).
# Refer to this document for addon installation troubleshooting:
# https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Addon-Installation-Troubleshooting
if loaded_library is None:
failed_libraries_string = ""
for libpath in failed_libraries:
failed_libraries_string += "<" + libpath + "> "
msg = "Unable to load fluid engine libraries: " + failed_libraries_string
msg += " (1) Make sure that you are using a 64-bit version of Python/Blender"
msg += " if built for 64-bit and likewise if built for 32-bit."
msg += " (2) Try clearing your Blender user settings (make a backup first!)."
msg += " (3) Contact the developers if you think that this is an error."
raise LibraryLoadError(msg)
return loaded_library
ffengine = FFEngineLib()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,231 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from . import pybindings as pb
from . import method_decorators as decorators
from .trianglemesh import TriangleMesh_t
class ForceField():
__metaclass__ = ABCMeta
@abstractmethod
def __init__():
pass
def __call__(self):
return self._obj
def update_mesh_static(self, mesh):
mesh_struct = mesh.to_struct()
libfunc = lib.ForceField_update_mesh_static
args = [c_void_p, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct])
def update_mesh_animated(self, mesh_previous, mesh_current, mesh_next):
mesh_struct_previous = mesh_previous.to_struct()
mesh_struct_current = mesh_current.to_struct()
mesh_struct_next = mesh_next.to_struct()
libfunc = lib.ForceField_update_mesh_animated
args = [c_void_p, TriangleMesh_t, TriangleMesh_t, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct_previous,
mesh_struct_current,
mesh_struct_next])
@property
def enable(self):
libfunc = lib.ForceField_is_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable.setter
def enable(self, boolval):
if boolval:
libfunc = lib.ForceField_enable
else:
libfunc = lib.ForceField_disable
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def strength(self):
libfunc = lib.ForceField_get_strength
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@strength.setter
def strength(self, value):
libfunc = lib.ForceField_set_strength
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def falloff_power(self):
libfunc = lib.ForceField_get_falloff_power
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@falloff_power.setter
def falloff_power(self, value):
libfunc = lib.ForceField_set_falloff_power
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def max_force_limit_factor(self):
libfunc = lib.ForceField_get_max_force_limit_factor
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@max_force_limit_factor.setter
def max_force_limit_factor(self, value):
libfunc = lib.ForceField_set_max_force_limit_factor
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def enable_min_distance(self):
libfunc = lib.ForceField_is_min_distance_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_min_distance.setter
def enable_min_distance(self, boolval):
if boolval:
libfunc = lib.ForceField_enable_min_distance
else:
libfunc = lib.ForceField_disable_min_distance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def min_distance(self):
libfunc = lib.ForceField_get_min_distance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@min_distance.setter
def min_distance(self, value):
libfunc = lib.ForceField_set_min_distance
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def enable_max_distance(self):
libfunc = lib.ForceField_is_max_distance_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_max_distance.setter
def enable_max_distance(self, boolval):
if boolval:
libfunc = lib.ForceField_enable_max_distance
else:
libfunc = lib.ForceField_disable_max_distance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def max_distance(self):
libfunc = lib.ForceField_get_max_distance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@max_distance.setter
def max_distance(self, value):
libfunc = lib.ForceField_set_max_distance
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def enable_frontfacing(self):
libfunc = lib.ForceField_is_frontfacing_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_frontfacing.setter
def enable_frontfacing(self, boolval):
if boolval:
libfunc = lib.ForceField_enable_frontfacing
else:
libfunc = lib.ForceField_disable_frontfacing
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def enable_backfacing(self):
libfunc = lib.ForceField_is_backfacing_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_backfacing.setter
def enable_backfacing(self, boolval):
if boolval:
libfunc = lib.ForceField_enable_backfacing
else:
libfunc = lib.ForceField_disable_backfacing
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def enable_edgefacing(self):
libfunc = lib.ForceField_is_edgefacing_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_edgefacing.setter
def enable_edgefacing(self, boolval):
if boolval:
libfunc = lib.ForceField_enable_edgefacing
else:
libfunc = lib.ForceField_disable_edgefacing
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def gravity_scale(self):
libfunc = lib.ForceField_get_gravity_scale
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@gravity_scale.setter
def gravity_scale(self, value):
libfunc = lib.ForceField_set_gravity_scale
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def gravity_scale_width(self):
libfunc = lib.ForceField_get_gravity_scale
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@gravity_scale_width.setter
def gravity_scale_width(self, value):
libfunc = lib.ForceField_set_gravity_scale_width
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@@ -0,0 +1,87 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from .forcefield import ForceField
from . import pybindings as pb
from . import method_decorators as decorators
class ForceFieldCurve(ForceField):
def __init__(self):
libfunc = lib.ForceFieldCurve_new
args = [c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [])
def __del__(self):
try:
libfunc = lib.ForceFieldCurve_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
def __call__(self):
return self._obj
@property
def flow_strength(self):
libfunc = lib.ForceFieldCurve_get_flow_strength
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@flow_strength.setter
def flow_strength(self, value):
libfunc = lib.ForceFieldCurve_set_flow_strength
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def spin_strength(self):
libfunc = lib.ForceFieldCurve_get_spin_strength
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@spin_strength.setter
def spin_strength(self, value):
libfunc = lib.ForceFieldCurve_set_spin_strength
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def enable_endcaps(self):
libfunc = lib.ForceFieldCurve_is_endcaps_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_endcaps.setter
def enable_endcaps(self, boolval):
if boolval:
libfunc = lib.ForceFieldCurve_enable_endcaps
else:
libfunc = lib.ForceFieldCurve_disable_endcaps
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@@ -0,0 +1,59 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from . import pybindings as pb
from . import method_decorators as decorators
class ForceFieldGrid():
def __init__(self, c_pointer=None):
if c_pointer is not None:
self._obj = c_pointer
self._is_owner = False
else:
libfunc = lib.ForceFieldGrid_new
args = [c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [])
self._is_owner = True
def __del__(self):
if not self._is_owner:
return
try:
libfunc = lib.MeshObject_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
def __call__(self):
return self._obj
def add_force_field(self, field):
libfunc = lib.ForceFieldGrid_add_force_field
args = [c_void_p, c_void_p, c_void_p]
pb.init_lib_func(libfunc, args, None)
pb.execute_lib_func(libfunc, [self(), field()])
@@ -0,0 +1,45 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from .forcefield import ForceField
from . import pybindings as pb
from . import method_decorators as decorators
class ForceFieldPoint(ForceField):
def __init__(self):
libfunc = lib.ForceFieldPoint_new
args = [c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [])
def __del__(self):
try:
libfunc = lib.ForceFieldPoint_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
@@ -0,0 +1,45 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from .forcefield import ForceField
from . import pybindings as pb
from . import method_decorators as decorators
class ForceFieldSurface(ForceField):
def __init__(self):
libfunc = lib.ForceFieldSurface_new
args = [c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [])
def __del__(self):
try:
libfunc = lib.ForceFieldSurface_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
@@ -0,0 +1,45 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from abc import ABCMeta, abstractmethod
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from .forcefield import ForceField
from . import pybindings as pb
from . import method_decorators as decorators
class ForceFieldVolume(ForceField):
def __init__(self):
libfunc = lib.ForceFieldVolume_new
args = [c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [])
def __del__(self):
try:
libfunc = lib.ForceFieldVolume_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
@@ -0,0 +1,85 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import array
import ctypes
class GridIndex_t(ctypes.Structure):
_fields_ = [("i", ctypes.c_int),
("j", ctypes.c_int),
("k", ctypes.c_int)]
class GridIndex(object):
def __init__(self, i = 0, j = 0, k = 0):
if isinstance(i, GridIndex):
self._values = array.array('i', [i.i, i.j, i.k])
else:
self._values = array.array('i', [i, j, k])
def __str__(self):
return str(self.i) + " " + str(self.j) + " " + str(self.k)
def __getitem__(self, key):
if key < 0 or key > 2:
raise IndexError("Index must be in range [0, 2]")
if not isinstance(key, int):
raise TypeError("Index must be an integer")
return self._values[key]
def __setitem__(self, key, value):
if key < 0 or key > 2:
raise IndexError("Index must be in range [0, 2]")
if not isinstance(key, int):
raise TypeError("Index must be an integer")
self._values[key] = value
def __iter__(self):
yield self._values[0]
yield self._values[1]
yield self._values[2]
@property
def i(self):
return self._values[0]
@property
def j(self):
return self._values[1]
@property
def k(self):
return self._values[2]
@i.setter
def i(self, value):
self._values[0] = value
@j.setter
def j(self, value):
self._values[1] = value
@k.setter
def k(self, value):
self._values[2] = value
@@ -0,0 +1,298 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from . import pybindings as pb
from . import method_decorators as decorators
from .trianglemesh import TriangleMesh_t
class MeshFluidSource():
def __init__(self, i, j, k, dx):
libfunc = lib.MeshFluidSource_new
args = [c_int, c_int, c_int, c_double, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [i, j, k, dx])
def __del__(self):
try:
libfunc = lib.MeshFluidSource_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
def __call__(self):
return self._obj
def update_mesh_static(self, mesh):
mesh_struct = mesh.to_struct()
libfunc = lib.MeshFluidSource_update_mesh_static
args = [c_void_p, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct])
def update_mesh_animated(self, mesh_previous, mesh_current, mesh_next):
mesh_struct_previous = mesh_previous.to_struct()
mesh_struct_current = mesh_current.to_struct()
mesh_struct_next = mesh_next.to_struct()
libfunc = lib.MeshFluidSource_update_mesh_animated
args = [c_void_p, TriangleMesh_t, TriangleMesh_t, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct_previous,
mesh_struct_current,
mesh_struct_next])
@property
def enable(self):
libfunc = lib.MeshFluidSource_is_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable.setter
def enable(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_enable
else:
libfunc = lib.MeshFluidSource_disable
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def substep_emissions(self):
libfunc = lib.MeshFluidSource_get_substep_emissions
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@substep_emissions.setter
def substep_emissions(self, n):
libfunc = lib.MeshFluidSource_set_substep_emissions
pb.init_lib_func(libfunc, [c_void_p, c_int, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), int(n)])
@property
def inflow(self):
libfunc = lib.MeshFluidSource_is_inflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@inflow.setter
def inflow(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_set_inflow
else:
libfunc = lib.MeshFluidSource_set_outflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def outflow(self):
libfunc = lib.MeshFluidSource_is_inflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@outflow.setter
def outflow(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_set_outflow
else:
libfunc = lib.MeshFluidSource_set_inflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def fluid_outflow(self):
libfunc = lib.MeshFluidSource_is_fluid_outflow_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@fluid_outflow.setter
def fluid_outflow(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_enable_fluid_outflow
else:
libfunc = lib.MeshFluidSource_disable_fluid_outflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def diffuse_outflow(self):
libfunc = lib.MeshFluidSource_is_diffuse_outflow_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@diffuse_outflow.setter
def diffuse_outflow(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_enable_diffuse_outflow
else:
libfunc = lib.MeshFluidSource_disable_diffuse_outflow
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
def get_velocity(self):
libfunc = lib.MeshFluidSource_get_velocity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], Vector3_t)
cvect = pb.execute_lib_func(libfunc, [self()])
return Vector3.from_struct(cvect)
@decorators.xyz_or_vector
def set_velocity(self, vx, vy, vz):
libfunc = lib.MeshFluidSource_set_velocity
pb.init_lib_func(
libfunc,
[c_void_p, c_double, c_double, c_double, c_void_p], None
)
pb.execute_lib_func(libfunc, [self(), vx, vy, vz])
@property
def enable_append_object_velocity(self):
libfunc = lib.MeshFluidSource_is_append_object_velocity_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_append_object_velocity.setter
def enable_append_object_velocity(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_enable_append_object_velocity
else:
libfunc = lib.MeshFluidSource_disable_append_object_velocity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def object_velocity_influence(self):
libfunc = lib.MeshFluidSource_get_object_velocity_influence
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@object_velocity_influence.setter
def object_velocity_influence(self, value):
libfunc = lib.MeshFluidSource_set_object_velocity_influence
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def priority(self):
libfunc = lib.MeshFluidSource_get_priority
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@priority.setter
def priority(self, n):
libfunc = lib.MeshFluidSource_set_priority
pb.init_lib_func(libfunc, [c_void_p, c_int, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), int(n)])
@property
def enable_constrained_fluid_velocity(self):
libfunc = lib.MeshFluidSource_is_constrained_fluid_velocity_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_constrained_fluid_velocity.setter
def enable_constrained_fluid_velocity(self, boolval):
if boolval:
libfunc = lib.MeshFluidSource_enable_constrained_fluid_velocity
else:
libfunc = lib.MeshFluidSource_disable_constrained_fluid_velocity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def outflow_inverse(self):
libfunc = lib.MeshFluidSource_is_outflow_inversed
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@outflow_inverse.setter
def outflow_inverse(self, boolval):
do_inverse = (boolval and not self.outflow_inverse) or (not boolval and self.outflow_inverse)
if do_inverse:
libfunc = lib.MeshFluidSource_outflow_inverse
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def source_id(self):
libfunc = lib.MeshFluidSource_get_source_id
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@source_id.setter
def source_id(self, n):
libfunc = lib.MeshFluidSource_set_source_id
pb.init_lib_func(libfunc, [c_void_p, c_int, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), int(n)])
@property
def viscosity(self):
libfunc = lib.MeshFluidSource_get_viscosity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@viscosity.setter
def viscosity(self, v):
libfunc = lib.MeshFluidSource_set_viscosity
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
@property
def lifetime(self):
libfunc = lib.MeshFluidSource_get_lifetime
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@lifetime.setter
def lifetime(self, v):
libfunc = lib.MeshFluidSource_set_lifetime
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
@property
def lifetime_variance(self):
libfunc = lib.MeshFluidSource_get_lifetime_variance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@lifetime_variance.setter
def lifetime_variance(self, v):
libfunc = lib.MeshFluidSource_set_lifetime_variance
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
def get_source_color(self):
libfunc = lib.MeshFluidSource_get_source_color
pb.init_lib_func(libfunc, [c_void_p, c_void_p], Vector3_t)
cvect = pb.execute_lib_func(libfunc, [self()])
return Vector3.from_struct(cvect)
@decorators.xyz_or_vector
def set_source_color(self, r, g, b):
libfunc = lib.MeshFluidSource_set_source_color
pb.init_lib_func(
libfunc,
[c_void_p, c_double, c_double, c_double, c_void_p], None
)
pb.execute_lib_func(libfunc, [self(), r, g, b])
@@ -0,0 +1,273 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from ctypes import c_void_p, c_char_p, c_int, c_float, c_double, byref
from .ffengine import ffengine as lib
from . import pybindings as pb
from . import method_decorators as decorators
from .trianglemesh import TriangleMesh_t
class MeshObject():
def __init__(self, i, j, k, dx):
libfunc = lib.MeshObject_new
args = [c_int, c_int, c_int, c_double, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
self._obj = pb.execute_lib_func(libfunc, [i, j, k, dx])
def __del__(self):
try:
libfunc = lib.MeshObject_destroy
pb.init_lib_func(libfunc, [c_void_p], None)
libfunc(self._obj)
except:
pass
def __call__(self):
return self._obj
def update_mesh_static(self, mesh):
mesh_struct = mesh.to_struct()
libfunc = lib.MeshObject_update_mesh_static
args = [c_void_p, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct])
def update_mesh_animated(self, mesh_previous, mesh_current, mesh_next):
mesh_struct_previous = mesh_previous.to_struct()
mesh_struct_current = mesh_current.to_struct()
mesh_struct_next = mesh_next.to_struct()
libfunc = lib.MeshObject_update_mesh_animated
args = [c_void_p, TriangleMesh_t, TriangleMesh_t, TriangleMesh_t, c_void_p]
pb.init_lib_func(libfunc, args, c_void_p)
pb.execute_lib_func(libfunc, [self(), mesh_struct_previous,
mesh_struct_current,
mesh_struct_next])
@property
def enable(self):
libfunc = lib.MeshObject_is_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable.setter
def enable(self, boolval):
if boolval:
libfunc = lib.MeshObject_enable
else:
libfunc = lib.MeshObject_disable
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def inverse(self):
libfunc = lib.MeshObject_is_inversed
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@inverse.setter
def inverse(self, boolval):
do_inverse = (boolval and not self.inverse) or (not boolval and self.inverse)
if do_inverse:
libfunc = lib.MeshObject_inverse
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def friction(self):
libfunc = lib.MeshObject_get_friction
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@friction.setter
@decorators.check_ge_zero
@decorators.check_le(1.0)
def friction(self, value):
libfunc = lib.MeshObject_set_friction
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def velocity_scale(self):
libfunc = lib.MeshObject_get_velocity_scale
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@velocity_scale.setter
def velocity_scale(self, value):
libfunc = lib.MeshObject_set_velocity_scale
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def whitewater_influence(self):
libfunc = lib.MeshObject_get_whitewater_influence
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@whitewater_influence.setter
@decorators.check_ge_zero
def whitewater_influence(self, value):
libfunc = lib.MeshObject_set_whitewater_influence
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def dust_emission_strength(self):
libfunc = lib.MeshObject_get_dust_emission_strength
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@dust_emission_strength.setter
@decorators.check_ge_zero
def dust_emission_strength(self, value):
libfunc = lib.MeshObject_set_dust_emission_strength
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def sheeting_strength(self):
libfunc = lib.MeshObject_get_sheeting_strength
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@sheeting_strength.setter
@decorators.check_ge_zero
def sheeting_strength(self, value):
libfunc = lib.MeshObject_set_sheeting_strength
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def mesh_expansion(self):
libfunc = lib.MeshObject_get_mesh_expansion
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@mesh_expansion.setter
def mesh_expansion(self, value):
libfunc = lib.MeshObject_set_mesh_expansion
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def enable_append_object_velocity(self):
libfunc = lib.MeshObject_is_append_object_velocity_enabled
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, [self()]))
@enable_append_object_velocity.setter
def enable_append_object_velocity(self, boolval):
if boolval:
libfunc = lib.MeshObject_enable_append_object_velocity
else:
libfunc = lib.MeshObject_disable_append_object_velocity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], None)
pb.execute_lib_func(libfunc, [self()])
@property
def object_velocity_influence(self):
libfunc = lib.MeshObject_get_object_velocity_influence
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_float)
return pb.execute_lib_func(libfunc, [self()])
@object_velocity_influence.setter
def object_velocity_influence(self, value):
libfunc = lib.MeshObject_set_object_velocity_influence
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), value])
@property
def priority(self):
libfunc = lib.MeshObject_get_priority
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@priority.setter
def priority(self, n):
libfunc = lib.MeshObject_set_priority
pb.init_lib_func(libfunc, [c_void_p, c_int, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), int(n)])
@property
def source_id(self):
libfunc = lib.MeshObject_get_source_id
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@source_id.setter
def source_id(self, n):
libfunc = lib.MeshObject_set_source_id
pb.init_lib_func(libfunc, [c_void_p, c_int, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), int(n)])
@property
def viscosity(self):
libfunc = lib.MeshObject_get_viscosity
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@viscosity.setter
def viscosity(self, v):
libfunc = lib.MeshObject_set_viscosity
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
@property
def lifetime(self):
libfunc = lib.MeshObject_get_lifetime
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@lifetime.setter
def lifetime(self, v):
libfunc = lib.MeshObject_set_lifetime
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
@property
def lifetime_variance(self):
libfunc = lib.MeshObject_get_lifetime_variance
pb.init_lib_func(libfunc, [c_void_p, c_void_p], c_int)
return pb.execute_lib_func(libfunc, [self()])
@lifetime_variance.setter
def lifetime_variance(self, v):
libfunc = lib.MeshObject_set_lifetime_variance
pb.init_lib_func(libfunc, [c_void_p, c_float, c_void_p], None)
pb.execute_lib_func(libfunc, [self(), float(v)])
def get_source_color(self):
libfunc = lib.MeshObject_get_source_color
pb.init_lib_func(libfunc, [c_void_p, c_void_p], Vector3_t)
cvect = pb.execute_lib_func(libfunc, [self()])
return Vector3.from_struct(cvect)
@decorators.xyz_or_vector
def set_source_color(self, r, g, b):
libfunc = lib.MeshObject_set_source_color
pb.init_lib_func(
libfunc,
[c_void_p, c_double, c_double, c_double, c_void_p], None
)
pb.execute_lib_func(libfunc, [self(), r, g, b])
@@ -0,0 +1,127 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import numbers
from .vector3 import Vector3
from .gridindex import GridIndex
def ijk_or_gridindex(func):
def ijk_or_gridindex_wrapper(self, *args):
try:
i, j, k = args
except:
i, j, k = args[0]
return func(self, i, j, k)
return ijk_or_gridindex_wrapper
def ijk_or_gridindex_and_value(func):
def ijk_or_gridindex_and_value_wrapper(self, *args):
try:
return func(self, *args)
except:
i, j, k = args[0]
return func(self, i, j, k, args[1])
return ijk_or_gridindex_and_value_wrapper
def xyz_or_vector(func):
def xyz_or_vector_wrapper(self, *args):
try:
return func(self, *args)
except:
return func(self, *args[0])
return xyz_or_vector_wrapper
def xyz_or_vector_and_radius(func):
def xyz_or_vector_wrapper(self, *args):
try:
return func(self, *args)
except:
x, y, z = args[0]
return func(self, x, y, z, args[1])
return xyz_or_vector_wrapper
def check_gt_zero(func):
def check_values(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg <= 0:
raise ValueError("Value must be greater than zero")
return func(self, *args)
return check_values
def check_ge_zero(func):
def check_values(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg < 0:
raise ValueError("Value must be greater than or equal to zero")
return func(self, *args)
return check_values
def check_gt(value):
def check_gt_decorator(func):
def check_gt_wrapper(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg <= value:
raise ValueError("Value must be greater than " + str(value))
return func(self, *args)
return check_gt_wrapper
return check_gt_decorator
def check_ge(value):
def check_ge_decorator(func):
def check_ge_wrapper(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg < value:
raise ValueError("Value must be greater than or equal to " + str(value))
return func(self, *args)
return check_ge_wrapper
return check_ge_decorator
def check_lt(value):
def check_lt_decorator(func):
def check_lt_wrapper(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg >= value:
raise ValueError("Value must be less than " + str(value))
return func(self, *args)
return check_lt_wrapper
return check_lt_decorator
def check_le(value):
def check_le_decorator(func):
def check_le_wrapper(self, *args):
for arg in args:
if isinstance(arg, numbers.Real) and arg > value:
raise ValueError("Value must be less than or equal to " + str(value))
return func(self, *args)
return check_le_wrapper
return check_le_decorator
def check_type(argtype):
def check_type_decorator(func):
def check_type_wrapper(self, *args):
for arg in args:
if not isinstance(arg, argtype):
raise TypeError("Argument must be of type " + str(argtype))
return func(self, *args)
return check_type_wrapper
return check_type_decorator
@@ -0,0 +1,58 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import ctypes
from ctypes import c_void_p, c_char_p, c_char, c_int, c_uint, c_float, c_double, byref
from .ffengine import ffengine as lib
from .vector3 import Vector3, Vector3_t
from . import pybindings as pb
class MixboxLutData_t(ctypes.Structure):
_fields_ = [("size", c_int),
("data", c_char_p)]
def initialize(lut_data, lut_data_size):
c_lut_data = (c_char * len(lut_data)).from_buffer_copy(lut_data)
mb_data = MixboxLutData_t()
mb_data.size = lut_data_size
mb_data.data = ctypes.cast(c_lut_data, c_char_p)
libfunc = lib.Mixbox_initialize
pb.init_lib_func(libfunc, [MixboxLutData_t, c_void_p], None)
pb.execute_lib_func(libfunc, [mb_data])
def is_initialized():
libfunc = lib.Mixbox_is_initialized
pb.init_lib_func(libfunc, [c_void_p], c_int)
return bool(pb.execute_lib_func(libfunc, []))
def lerp_srgb32f(r1, g1, b1, r2, g2, b2, t):
libfunc = lib.Mixbox_lerp_srgb32f
pb.init_lib_func(libfunc, [c_float, c_float, c_float, c_float, c_float, c_float, c_float, c_void_p], Vector3_t)
cvect = pb.execute_lib_func(libfunc, [r1, g1, b1, r2, g2, b2, t])
return cvect.x, cvect.y, cvect.z
@@ -0,0 +1,60 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from .ffengine import ffengine as lib
from ctypes import c_char_p, c_int, byref
def check_success(success, errprefix):
libfunc = lib.CBindings_get_error_message
init_lib_func(libfunc, [], c_char_p)
if not success:
raise RuntimeError(errprefix + str(libfunc().decode("utf-8")))
def init_lib_func(libfunc, argtypes, restype):
if libfunc.argtypes is None:
libfunc.argtypes = argtypes
libfunc.restype = restype
def execute_lib_func(libfunc, params):
args = []
for idx, arg in enumerate(params):
try:
cval = libfunc.argtypes[idx](arg)
except:
cval = arg
args.append(cval)
success = c_int();
args.append(byref(success))
result = None
if libfunc.restype:
funcresult = libfunc(*args)
check_success(success, libfunc.__name__ + " - ")
try:
return libfunc.restype(funcresult).value
except:
return funcresult
else:
libfunc(*args)
check_success(success, libfunc.__name__ + " - ")
return result
@@ -0,0 +1,113 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import ctypes
import array
import struct
class TriangleMesh_t(ctypes.Structure):
_fields_ = [("vertices", ctypes.c_void_p),
("triangles", ctypes.c_void_p),
("num_vertices", ctypes.c_int),
("num_triangles", ctypes.c_int)]
class TriangleMesh(object):
def __init__(self):
self.vertices = array.array('f', [])
self.triangles = array.array('i', [])
@classmethod
def from_bobj(cls, bobj_data):
int_data, bobj_data = bobj_data[:4], bobj_data[4:]
num_vertices = struct.unpack('i', int_data)[0]
num_floats = 3 * num_vertices
num_bytes = 4 * num_floats
vertex_data, bobj_data = bobj_data[:num_bytes], bobj_data[num_bytes:]
vertices = list(struct.unpack('{0}f'.format(num_floats), vertex_data))
int_data, bobj_data = bobj_data[:4], bobj_data[4:]
num_triangles = struct.unpack('i', int_data)[0]
num_ints = 3 * num_triangles
num_bytes = 4 * num_ints
triangle_data, bobj_data = bobj_data[:num_bytes], bobj_data[num_bytes:]
triangles = list(struct.unpack('{0}i'.format(num_ints), triangle_data))
self = cls()
self.vertices = array.array('f', vertices)
self.triangles = array.array('i', triangles)
return self
def to_bobj(self):
num_vertices = len(self.vertices) // 3
num_triangles = len(self.triangles) // 3
datastr = struct.pack('i', num_vertices)
datastr += self.vertices.tobytes()
datastr += struct.pack('i', num_triangles)
datastr += self.triangles.tobytes()
return datastr
def to_struct(self):
num_vertices = len(self.vertices) // 3
num_triangles = len(self.triangles) // 3
vertex_data = (ctypes.c_float * len(self.vertices))()
for i in range(len(self.vertices)):
vertex_data[i] = self.vertices[i]
triangle_data = (ctypes.c_int * len(self.triangles))()
for i in range(len(self.triangles)):
triangle_data[i] = self.triangles[i]
struct = TriangleMesh_t()
struct.vertices = ctypes.cast(vertex_data, ctypes.c_void_p)
struct.triangles = ctypes.cast(triangle_data, ctypes.c_void_p)
struct.num_vertices = num_vertices
struct.num_triangles = num_triangles
return struct
def apply_transform(self, matrix_world):
m = matrix_world
for i in range(0, len(self.vertices), 3):
v = [self.vertices[i + 0], self.vertices[i + 1], self.vertices[i + 2], 1]
self.vertices[i + 0] = v[0]*m[0] + v[1]*m[1] + v[2]*m[2] + v[3]*m[3]
self.vertices[i + 1] = v[0]*m[4] + v[1]*m[5] + v[2]*m[6] + v[3]*m[7]
self.vertices[i + 2] = v[0]*m[8] + v[1]*m[9] + v[2]*m[10] + v[3]*m[11]
def translate(self, tx, ty, tz):
for i in range(0, len(self.vertices), 3):
self.vertices[i + 0] += tx
self.vertices[i + 1] += ty
self.vertices[i + 2] += tz
def scale(self, scale):
for i in range(0, len(self.vertices), 3):
self.vertices[i + 0] *= scale
self.vertices[i + 1] *= scale
self.vertices[i + 2] *= scale
@@ -0,0 +1,217 @@
# MIT License
#
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
import array
import ctypes
import math
class Vector3_t(ctypes.Structure):
_fields_ = [("x", ctypes.c_float),
("y", ctypes.c_float),
("z", ctypes.c_float)]
class Vector3(object):
def __init__(self, x = 0.0, y = 0.0, z = 0.0):
if isinstance(x, Vector3):
self._values = array.array('f', [x.x, x.y, x.z])
else:
self._values = array.array('f', [x, y, z])
@classmethod
def from_struct(cls, cstruct):
return cls(cstruct.x, cstruct.y, cstruct.z)
def to_struct(self):
return Vector3_t(self.x, self.y, self.z)
def __str__(self):
return str(self.x) + " " + str(self.y) + " " + str(self.z)
def __getitem__(self, key):
if key < 0 or key > 2:
raise IndexError("Index must be in range [0, 2]")
if not isinstance(key, int):
raise TypeError("Index must be an integer")
return self._values[key]
def __setitem__(self, key, value):
if key < 0 or key > 2:
raise IndexError("Index must be in range [0, 2]")
if not isinstance(key, int):
raise TypeError("Index must be an integer")
self._values[key] = value
def __iter__(self):
yield self._values[0]
yield self._values[1]
yield self._values[2]
def __add__(self, other):
return Vector3(self.x + other.x,
self.y + other.y,
self.z + other.z)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
self.z += other.z
return self
def __sub__(self, other):
return Vector3(self.x - other.x,
self.y - other.y,
self.z - other.z)
def __isub__(self, other):
self.x -= other.x
self.y -= other.y
self.z -= other.z
return self
def __mul__(self, scale):
return Vector3(scale * self.x,
scale * self.y,
scale * self.z)
def __imul__(self, scale):
self.x *= scale
self.y *= scale
self.z *= scale
return self
def __rmul__(self, scale):
return Vector3(scale * self.x,
scale * self.y,
scale * self.z)
def __div__(self, denominator):
if denominator == 0.0:
raise ZeroDivisionError
inv = 1.0 / denominator
return Vector3(self.x * inv,
self.y * inv,
self.z * inv)
def __idiv__(self, denominator):
if denominator == 0.0:
raise ZeroDivisionError
inv = 1.0 / denominator
self.x *= inv
self.y *= inv
self.z *= inv
return self
def __neg__(self):
return Vector3(-self.x, -self.y, -self.z)
def __pos__(self):
return Vector3(self)
def __abs__(self):
return Vector3(abs(self.x), abs(self.y), abs(self.z))
def __invert__(self):
return Vector3(1.0 / self.x,
1.0 / self.y,
1.0 / self.z)
@property
def x(self):
return self._values[0]
@property
def y(self):
return self._values[1]
@property
def z(self):
return self._values[2]
@x.setter
def x(self, value):
self._values[0] = value
@y.setter
def y(self, value):
self._values[1] = value
@z.setter
def z(self, value):
self._values[2] = value
def add(self, vector):
self += vector
return self
def sub(self, vector):
self -= vector
return self
def mult(self, scale):
self *= scale
return self
def div(self, denominator):
self /= denominator
return self
def neg(self):
self.x = -self.x
self.y = -self.y
self.z = -self.z
return self
def invert(self):
self.x = 1.0 / self.x
self.y = 1.0 / self.y
self.z = 1.0 / self.z
return self
def dot(vector):
return self.x*vector.x + self.y*vector.y + self.z*vector.z
def cross(vector):
return Vector3(self.y*vector.z - self.z*vector.y,
self.z*vector.x - self.x*vector.z,
self.x*vector.y - self.y*vector.x)
def lengthsq(self):
return self.x*self.x + self.y*self.y + self.z*self.z
def length(self):
return math.sqrt(self.x*self.x + self.y*self.y + self.z*self.z)
def normalize(self):
length = self.length()
if length == 0.0:
raise ZeroDivisionError
inv = 1.0 / length
self *= inv
return self