2025-12-01
This commit is contained in:
@@ -0,0 +1,250 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
bl_info = {
|
||||
"name" : "FLIP Fluids",
|
||||
"description": "A FLIP Fluid Simulation Tool for Blender (v1.8.4 Release 2025-07-17)",
|
||||
"author" : "Ryan Guy & Dennis Fassbaender <support[at]flipfluids.com>",
|
||||
"version" : (1, 8, 4),
|
||||
"blender" : (3, 6, 0),
|
||||
"location" : "Properties > Physics > FLIP Fluid",
|
||||
"warning" : "",
|
||||
"wiki_url" : "https://github.com/rlguy/Blender-FLIP-Fluids/wiki",
|
||||
"doc_url" : "https://github.com/rlguy/Blender-FLIP-Fluids/wiki",
|
||||
"category" : "Animation"
|
||||
}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
reloadable_modules = [
|
||||
'filesystem',
|
||||
'utils',
|
||||
'objects',
|
||||
'materials',
|
||||
'properties',
|
||||
'operators',
|
||||
'ui',
|
||||
'presets',
|
||||
'export',
|
||||
'bake',
|
||||
'render',
|
||||
'exit_handler'
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy, atexit, shutil, os
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from . import (
|
||||
filesystem,
|
||||
utils,
|
||||
objects,
|
||||
materials,
|
||||
properties,
|
||||
operators,
|
||||
ui,
|
||||
presets,
|
||||
export,
|
||||
bake,
|
||||
render,
|
||||
exit_handler
|
||||
)
|
||||
|
||||
from .utils import installation_utils
|
||||
from .utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def scene_update_post(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
installation_utils.scene_update_post(scene)
|
||||
if installation_utils.is_addon_active():
|
||||
if not render.is_rendering():
|
||||
# We don't want to update these while rendering to prevent
|
||||
# odd behaviour in the depsgraph
|
||||
properties.scene_update_post(scene)
|
||||
materials.scene_update_post(scene)
|
||||
render.scene_update_post(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_init(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_init(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_complete(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_complete(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_cancel(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_cancel(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def frame_change_pre(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def frame_change_post(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.frame_change_post(scene, depsgraph)
|
||||
render.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_pre(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_pre(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def load_pre(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.load_pre()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def load_post(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
if vcu.is_blender_28() and not vcu.is_blender_281():
|
||||
print("FLIP FLUIDS WARNING: Blender 2.80 contains bugs that can cause frequent crashes during render, Alembic export, and rigid/cloth simulation baking. Blender version 2.81 or higher is recommended.")
|
||||
|
||||
installation_utils.load_post()
|
||||
materials.load_post()
|
||||
properties.load_post()
|
||||
presets.load_post()
|
||||
exit_handler.load_post()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def save_pre(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.save_pre()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def save_post(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.save_post()
|
||||
exit_handler.save_post()
|
||||
|
||||
|
||||
def on_exit():
|
||||
exit_handler.on_exit()
|
||||
|
||||
|
||||
class FlipFluidCompleteInstallation(bpy.types.Operator):
|
||||
bl_idname = "flip_fluid_operators.complete_installation"
|
||||
bl_label = "Complete Installation"
|
||||
bl_description = ("Click to complete the installation of the FLIP Fluids addon. Alternatively, restarting Blender or re-loading the Blend file will also complete the installation process")
|
||||
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
load_post(None)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
objects.register()
|
||||
materials.register()
|
||||
properties.register()
|
||||
operators.register()
|
||||
ui.register()
|
||||
presets.register()
|
||||
|
||||
if vcu.is_blender_28():
|
||||
bpy.app.handlers.depsgraph_update_post.append(scene_update_post)
|
||||
else:
|
||||
bpy.app.handlers.scene_update_post.append(scene_update_post)
|
||||
|
||||
bpy.app.handlers.render_init.append(render_init)
|
||||
bpy.app.handlers.render_complete.append(render_complete)
|
||||
bpy.app.handlers.render_cancel.append(render_cancel)
|
||||
bpy.app.handlers.frame_change_pre.append(frame_change_pre)
|
||||
bpy.app.handlers.frame_change_post.append(frame_change_post)
|
||||
bpy.app.handlers.render_pre.append(render_pre)
|
||||
bpy.app.handlers.load_pre.append(load_pre)
|
||||
bpy.app.handlers.load_post.append(load_post)
|
||||
bpy.app.handlers.save_pre.append(save_pre)
|
||||
bpy.app.handlers.save_post.append(save_post)
|
||||
atexit.register(on_exit)
|
||||
|
||||
bpy.utils.register_class(FlipFluidCompleteInstallation)
|
||||
|
||||
|
||||
def unregister():
|
||||
objects.unregister()
|
||||
materials.unregister()
|
||||
properties.unregister()
|
||||
operators.unregister()
|
||||
ui.unregister()
|
||||
presets.unregister()
|
||||
|
||||
if vcu.is_blender_28():
|
||||
bpy.app.handlers.depsgraph_update_post.remove(scene_update_post)
|
||||
else:
|
||||
bpy.app.handlers.scene_update_post.remove(scene_update_post)
|
||||
|
||||
bpy.app.handlers.render_init.remove(render_init)
|
||||
bpy.app.handlers.render_complete.remove(render_complete)
|
||||
bpy.app.handlers.render_cancel.remove(render_cancel)
|
||||
bpy.app.handlers.frame_change_pre.remove(frame_change_pre)
|
||||
bpy.app.handlers.frame_change_post.remove(frame_change_post)
|
||||
bpy.app.handlers.render_pre.remove(render_pre)
|
||||
bpy.app.handlers.load_pre.remove(load_pre)
|
||||
bpy.app.handlers.load_post.remove(load_post)
|
||||
bpy.app.handlers.save_pre.remove(save_pre)
|
||||
bpy.app.handlers.save_post.remove(save_post)
|
||||
atexit.unregister(on_exit)
|
||||
|
||||
bpy.utils.unregister_class(FlipFluidCompleteInstallation)
|
||||
@@ -0,0 +1,250 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
bl_info = {
|
||||
"name" : "FLIP Fluids",
|
||||
"description": "A FLIP Fluid Simulation Tool for Blender (v@FLUIDENGINE_VERSION_LABEL@)",
|
||||
"author" : "Ryan Guy & Dennis Fassbaender <support[at]flipfluids.com>",
|
||||
"version" : (@FLUIDENGINE_VERSION_MAJOR@, @FLUIDENGINE_VERSION_MINOR@, @FLUIDENGINE_VERSION_REVISION@),
|
||||
"blender" : (3, 6, 0),
|
||||
"location" : "Properties > Physics > FLIP Fluid",
|
||||
"warning" : "",
|
||||
"wiki_url" : "https://github.com/rlguy/Blender-FLIP-Fluids/wiki",
|
||||
"doc_url" : "https://github.com/rlguy/Blender-FLIP-Fluids/wiki",
|
||||
"category" : "Animation"
|
||||
}
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
reloadable_modules = [
|
||||
'filesystem',
|
||||
'utils',
|
||||
'objects',
|
||||
'materials',
|
||||
'properties',
|
||||
'operators',
|
||||
'ui',
|
||||
'presets',
|
||||
'export',
|
||||
'bake',
|
||||
'render',
|
||||
'exit_handler'
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy, atexit, shutil, os
|
||||
from bpy.props import (
|
||||
PointerProperty,
|
||||
StringProperty
|
||||
)
|
||||
|
||||
from . import (
|
||||
filesystem,
|
||||
utils,
|
||||
objects,
|
||||
materials,
|
||||
properties,
|
||||
operators,
|
||||
ui,
|
||||
presets,
|
||||
export,
|
||||
bake,
|
||||
render,
|
||||
exit_handler
|
||||
)
|
||||
|
||||
from .utils import installation_utils
|
||||
from .utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def scene_update_post(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
installation_utils.scene_update_post(scene)
|
||||
if installation_utils.is_addon_active():
|
||||
if not render.is_rendering():
|
||||
# We don't want to update these while rendering to prevent
|
||||
# odd behaviour in the depsgraph
|
||||
properties.scene_update_post(scene)
|
||||
materials.scene_update_post(scene)
|
||||
render.scene_update_post(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_init(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_init(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_complete(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_complete(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_cancel(scene):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_cancel(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def frame_change_pre(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def frame_change_post(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.frame_change_post(scene, depsgraph)
|
||||
render.frame_change_post(scene, depsgraph)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def render_pre(scene, depsgraph=None):
|
||||
if scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
render.render_pre(scene)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def load_pre(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.load_pre()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def load_post(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
if vcu.is_blender_28() and not vcu.is_blender_281():
|
||||
print("FLIP FLUIDS WARNING: Blender 2.80 contains bugs that can cause frequent crashes during render, Alembic export, and rigid/cloth simulation baking. Blender version 2.81 or higher is recommended.")
|
||||
|
||||
installation_utils.load_post()
|
||||
materials.load_post()
|
||||
properties.load_post()
|
||||
presets.load_post()
|
||||
exit_handler.load_post()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def save_pre(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.save_pre()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def save_post(nonedata):
|
||||
if bpy.context.scene.flip_fluid.is_addon_disabled_in_blend_file():
|
||||
return
|
||||
|
||||
properties.save_post()
|
||||
exit_handler.save_post()
|
||||
|
||||
|
||||
def on_exit():
|
||||
exit_handler.on_exit()
|
||||
|
||||
|
||||
class FlipFluidCompleteInstallation(bpy.types.Operator):
|
||||
bl_idname = "flip_fluid_operators.complete_installation"
|
||||
bl_label = "Complete Installation"
|
||||
bl_description = ("Click to complete the installation of the FLIP Fluids addon. Alternatively, restarting Blender or re-loading the Blend file will also complete the installation process")
|
||||
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
load_post(None)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
objects.register()
|
||||
materials.register()
|
||||
properties.register()
|
||||
operators.register()
|
||||
ui.register()
|
||||
presets.register()
|
||||
|
||||
if vcu.is_blender_28():
|
||||
bpy.app.handlers.depsgraph_update_post.append(scene_update_post)
|
||||
else:
|
||||
bpy.app.handlers.scene_update_post.append(scene_update_post)
|
||||
|
||||
bpy.app.handlers.render_init.append(render_init)
|
||||
bpy.app.handlers.render_complete.append(render_complete)
|
||||
bpy.app.handlers.render_cancel.append(render_cancel)
|
||||
bpy.app.handlers.frame_change_pre.append(frame_change_pre)
|
||||
bpy.app.handlers.frame_change_post.append(frame_change_post)
|
||||
bpy.app.handlers.render_pre.append(render_pre)
|
||||
bpy.app.handlers.load_pre.append(load_pre)
|
||||
bpy.app.handlers.load_post.append(load_post)
|
||||
bpy.app.handlers.save_pre.append(save_pre)
|
||||
bpy.app.handlers.save_post.append(save_post)
|
||||
atexit.register(on_exit)
|
||||
|
||||
bpy.utils.register_class(FlipFluidCompleteInstallation)
|
||||
|
||||
|
||||
def unregister():
|
||||
objects.unregister()
|
||||
materials.unregister()
|
||||
properties.unregister()
|
||||
operators.unregister()
|
||||
ui.unregister()
|
||||
presets.unregister()
|
||||
|
||||
if vcu.is_blender_28():
|
||||
bpy.app.handlers.depsgraph_update_post.remove(scene_update_post)
|
||||
else:
|
||||
bpy.app.handlers.scene_update_post.remove(scene_update_post)
|
||||
|
||||
bpy.app.handlers.render_init.remove(render_init)
|
||||
bpy.app.handlers.render_complete.remove(render_complete)
|
||||
bpy.app.handlers.render_cancel.remove(render_cancel)
|
||||
bpy.app.handlers.frame_change_pre.remove(frame_change_pre)
|
||||
bpy.app.handlers.frame_change_post.remove(frame_change_post)
|
||||
bpy.app.handlers.render_pre.remove(render_pre)
|
||||
bpy.app.handlers.load_pre.remove(load_pre)
|
||||
bpy.app.handlers.load_post.remove(load_post)
|
||||
bpy.app.handlers.save_pre.remove(save_pre)
|
||||
bpy.app.handlers.save_post.remove(save_post)
|
||||
atexit.unregister(on_exit)
|
||||
|
||||
bpy.utils.unregister_class(FlipFluidCompleteInstallation)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "flip_fluids_addon"
|
||||
type = "add-on"
|
||||
version = "1.8.4"
|
||||
name = "FLIP Fluids"
|
||||
tagline = "A FLIP Fluid Simulation Tool for Blender (v1.8.4 Release 2025-07-17)"
|
||||
maintainer = "Ryan Guy & Dennis Fassbaender // support@flipfluids.com"
|
||||
website = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki"
|
||||
tags = ["Animation", "Physics"]
|
||||
blender_version_min = "4.2.0"
|
||||
license = ["SPDX:GPL-3.0-or-later"]
|
||||
platforms = ["windows-x64", "macos-x64", "macos-arm64", "linux-x64"]
|
||||
|
||||
[permissions]
|
||||
files = "Read and write simulation cache files to the filesystem"
|
||||
clipboard = "Copy and paste render and bake commands"
|
||||
@@ -0,0 +1,17 @@
|
||||
schema_version = "1.0.0"
|
||||
|
||||
id = "flip_fluids_addon"
|
||||
type = "add-on"
|
||||
version = "@FLUIDENGINE_VERSION_MAJOR@.@FLUIDENGINE_VERSION_MINOR@.@FLUIDENGINE_VERSION_REVISION@"
|
||||
name = "FLIP Fluids"
|
||||
tagline = "A FLIP Fluid Simulation Tool for Blender (v@FLUIDENGINE_VERSION_LABEL@)"
|
||||
maintainer = "Ryan Guy & Dennis Fassbaender // support@flipfluids.com"
|
||||
website = "https://github.com/rlguy/Blender-FLIP-Fluids/wiki"
|
||||
tags = ["Animation", "Physics"]
|
||||
blender_version_min = "4.2.0"
|
||||
license = ["SPDX:GPL-3.0-or-later"]
|
||||
platforms = ["windows-x64", "macos-x64", "macos-arm64", "linux-x64"]
|
||||
|
||||
[permissions]
|
||||
files = "Read and write simulation cache files to the filesystem"
|
||||
clipboard = "Copy and paste render and bake commands"
|
||||
@@ -0,0 +1,45 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import bpy, os, shutil
|
||||
|
||||
# These variables are used when running an exit handler where
|
||||
# access to Blender data may no longer be available
|
||||
IS_BLEND_FILE_SAVED = False
|
||||
CACHE_DIRECTORY = ""
|
||||
|
||||
def on_exit():
|
||||
# nothing currently to do on exit
|
||||
pass
|
||||
|
||||
|
||||
def save_post():
|
||||
global IS_BLEND_FILE_SAVED
|
||||
IS_BLEND_FILE_SAVED = True
|
||||
|
||||
|
||||
def load_post():
|
||||
global IS_BLEND_FILE_SAVED
|
||||
base = os.path.basename(bpy.data.filepath)
|
||||
save_file = os.path.splitext(base)[0]
|
||||
is_unsaved = not base or not save_file
|
||||
IS_BLEND_FILE_SAVED = not is_unsaved
|
||||
|
||||
|
||||
def set_cache_directory(dirpath):
|
||||
global CACHE_DIRECTORY
|
||||
CACHE_DIRECTORY = dirpath
|
||||
@@ -0,0 +1,480 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy, math, array, json, os, zipfile, shutil
|
||||
from mathutils import Vector
|
||||
|
||||
from .objects import flip_fluid_map
|
||||
from .objects.flip_fluid_geometry_exporter import GeometryExportObject, MotionExportType, GeometryExportType
|
||||
from .utils import export_utils as utils
|
||||
from .objects.flip_fluid_aabb import AABB
|
||||
from .ffengine import TriangleMesh
|
||||
from .utils import version_compatibility_utils as vcu
|
||||
from .utils import cache_utils, export_utils, installation_utils
|
||||
|
||||
|
||||
def __get_domain_object():
|
||||
return bpy.context.scene.flip_fluid.get_domain_object()
|
||||
|
||||
|
||||
def __get_domain_properties():
|
||||
return bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
|
||||
|
||||
def __export_simulation_data_to_file(context, simobjects, filename):
|
||||
success, data = __get_simulation_data_dict(context, simobjects)
|
||||
if not success:
|
||||
return False
|
||||
|
||||
jsonstr = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': '))
|
||||
|
||||
os.makedirs(os.path.dirname(filename), exist_ok=True)
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write(jsonstr)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def __get_simulation_data_dict(context, simobjects):
|
||||
data = {}
|
||||
success, data['domain_data'] = __get_domain_data_dict(context, simobjects.domain)
|
||||
if not success:
|
||||
return False, None
|
||||
|
||||
data['fluid_data'] = __get_fluid_data(context, simobjects.fluid_objects)
|
||||
data['obstacle_data'] = __get_obstacle_data(context, simobjects.obstacle_objects)
|
||||
data['inflow_data'] = __get_inflow_data(context, simobjects.inflow_objects)
|
||||
data['outflow_data'] = __get_outflow_data(context, simobjects.outflow_objects)
|
||||
data['force_field_data'] = __get_force_field_data(context, simobjects.force_field_objects)
|
||||
return True, data
|
||||
|
||||
|
||||
def __get_domain_data_dict(context, dobj):
|
||||
dprops = dobj.flip_fluid.domain
|
||||
d = utils.flip_fluid_object_to_dict(dobj, dprops)
|
||||
|
||||
# A KeyError in this dict at this point may indicate that the FLIP Fluids addon installation
|
||||
# was not completed by restarting Blender, or that the addon version may not be compatible
|
||||
# with a newer version of Blender.
|
||||
if not 'advanced' in d:
|
||||
errmsg = "This error may indicate that either (1) A Blender restart is required "
|
||||
errmsg += "to complete installation of the FLIP Fluids addon. Save, restart Blender, and "
|
||||
errmsg += "try again. Or (2) This version of the FLIP Fluids addon is not compatible "
|
||||
errmsg += "with the Blender version. Update to the latest version of the FLIP Fluids "
|
||||
errmsg += "addon and try again. Contact the developers at support@flipfluids.com for "
|
||||
errmsg += "assistance."
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message="Installation or Compatibility Error",
|
||||
error_description=errmsg,
|
||||
popup_width=600
|
||||
)
|
||||
return False, None
|
||||
|
||||
initialize_properties = {}
|
||||
initialize_properties['name'] = dobj.name
|
||||
|
||||
bbox = AABB.from_blender_object(dobj)
|
||||
isize, jsize, ksize, viewport_dx = dprops.simulation.get_viewport_grid_dimensions()
|
||||
_, _, _, simulation_dx = dprops.simulation.get_simulation_grid_dimensions()
|
||||
simulation_preview_dx = dprops.simulation.get_simulation_preview_dx()
|
||||
|
||||
initialize_properties['isize'] = isize
|
||||
initialize_properties['jsize'] = jsize
|
||||
initialize_properties['ksize'] = ksize
|
||||
|
||||
dwidth = initialize_properties['isize'] * viewport_dx
|
||||
dheight = initialize_properties['jsize'] * viewport_dx
|
||||
ddepth = initialize_properties['ksize'] * viewport_dx
|
||||
initialize_properties['bbox'] = AABB(bbox.x, bbox.y, bbox.z,
|
||||
dwidth, dheight, ddepth).to_dict()
|
||||
initialize_properties['scale'] = dprops.world.get_world_scale()
|
||||
initialize_properties['dx'] = simulation_dx
|
||||
initialize_properties['preview_dx'] = simulation_preview_dx
|
||||
|
||||
initialize_properties['upscale_simulation'] = dprops.simulation.is_current_grid_upscaled()
|
||||
if initialize_properties['upscale_simulation']:
|
||||
initialize_properties['savestate_isize'] = dprops.simulation.savestate_isize
|
||||
initialize_properties['savestate_jsize'] = dprops.simulation.savestate_jsize
|
||||
initialize_properties['savestate_ksize'] = dprops.simulation.savestate_ksize
|
||||
initialize_properties['savestate_dx'] = dprops.simulation.savestate_dx
|
||||
|
||||
initialize_properties['logfile_name'] = dprops.cache.logfile_name
|
||||
initialize_properties['frame_start'] = dprops.simulation.frame_start
|
||||
initialize_properties['frame_end'] = dprops.simulation.frame_end
|
||||
|
||||
initialize_properties['enable_savestates'] = dprops.simulation.enable_savestates
|
||||
initialize_properties['savestate_interval'] = dprops.simulation.savestate_interval
|
||||
initialize_properties['delete_outdated_savestates'] = dprops.simulation.delete_outdated_savestates
|
||||
initialize_properties['delete_outdated_meshes'] = dprops.simulation.delete_outdated_meshes
|
||||
|
||||
initialize_properties['geometry_database_filepath'] = dprops.cache.get_geometry_database_abspath()
|
||||
|
||||
d['initialize'] = initialize_properties
|
||||
|
||||
dprops.advanced.initialize_num_threads_auto_detect()
|
||||
d['advanced']['num_threads_auto_detect'] = dprops.advanced.num_threads_auto_detect
|
||||
d['simulation']['frames_per_second'] = dprops.simulation.get_frame_rate_data_dict()
|
||||
d['simulation']['time_scale'] = dprops.simulation.get_time_scale_data_dict()
|
||||
d['world']['gravity'] = dprops.world.get_gravity_data_dict()
|
||||
d['world']['scene_use_gravity'] = dprops.world.get_scene_use_gravity_data_dict()
|
||||
d['world']['native_surface_tension_scale'] = dprops.world.native_surface_tension_scale
|
||||
d['world']['minimum_surface_tension_cfl'] = dprops.world.minimum_surface_tension_cfl
|
||||
d['world']['maximum_surface_tension_cfl'] = dprops.world.maximum_surface_tension_cfl
|
||||
d['surface']['native_particle_scale'] = dprops.surface.native_particle_scale
|
||||
d['surface']['compute_chunks_auto'] = dprops.surface.compute_chunks_auto
|
||||
|
||||
meshing_volume_object_name = ""
|
||||
meshing_volume_object = dprops.surface.get_meshing_volume_object()
|
||||
if meshing_volume_object is not None:
|
||||
meshing_volume_object_name = meshing_volume_object.name
|
||||
d['surface']['meshing_volume_object'] = meshing_volume_object_name
|
||||
|
||||
installation_utils.update_mixbox_installation_status()
|
||||
is_mixbox_supported = installation_utils.is_mixbox_supported()
|
||||
is_mixbox_installed = installation_utils.is_mixbox_installation_complete()
|
||||
color_mixing_mode = dprops.surface.color_attribute_mixing_mode
|
||||
if not is_mixbox_supported or not is_mixbox_installed:
|
||||
color_mixing_mode = 'COLOR_MIXING_MODE_RGB'
|
||||
d['surface']['color_attribute_mixing_mode'] = color_mixing_mode
|
||||
|
||||
return True, d
|
||||
|
||||
|
||||
def __get_fluid_data(context, objects):
|
||||
d = []
|
||||
for idx, obj in enumerate(objects):
|
||||
fprops = obj.flip_fluid.fluid
|
||||
data = utils.flip_fluid_object_to_dict(obj, obj.flip_fluid.fluid)
|
||||
data['name'] = obj.name
|
||||
|
||||
target_object = fprops.get_target_object()
|
||||
target_object_name = ""
|
||||
if target_object:
|
||||
target_object_name = target_object.name
|
||||
data['target_object'] = target_object_name
|
||||
|
||||
d.append(data)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def __get_obstacle_data(context, objects):
|
||||
d = []
|
||||
for idx, obj in enumerate(objects):
|
||||
data = utils.flip_fluid_object_to_dict(obj, obj.flip_fluid.obstacle)
|
||||
data['name'] = obj.name
|
||||
d.append(data)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def __get_inflow_data(context, objects):
|
||||
d = []
|
||||
for idx, obj in enumerate(objects):
|
||||
props = obj.flip_fluid.inflow
|
||||
data = utils.flip_fluid_object_to_dict(obj, obj.flip_fluid.inflow)
|
||||
data['name'] = obj.name
|
||||
|
||||
target_object = props.get_target_object()
|
||||
target_object_name = ""
|
||||
if target_object:
|
||||
target_object_name = target_object.name
|
||||
data['target_object'] = target_object_name
|
||||
|
||||
d.append(data)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def __get_outflow_data(context, objects):
|
||||
d = []
|
||||
for idx, obj in enumerate(objects):
|
||||
data = utils.flip_fluid_object_to_dict(obj, obj.flip_fluid.outflow)
|
||||
data['name'] = obj.name
|
||||
d.append(data)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def __get_force_field_data(context, objects):
|
||||
d = []
|
||||
for idx, obj in enumerate(objects):
|
||||
data = utils.flip_fluid_object_to_dict(obj, obj.flip_fluid.force_field)
|
||||
data['name'] = obj.name
|
||||
d.append(data)
|
||||
|
||||
return d
|
||||
|
||||
|
||||
def __format_bytes(num):
|
||||
# Method adapted from: http://stackoverflow.com/a/10171475
|
||||
unit_list = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB']
|
||||
decimal_list = [0, 0, 1, 2, 2, 2]
|
||||
|
||||
if num > 1:
|
||||
exponent = min(int(math.log(num, 1024)), len(unit_list) - 1)
|
||||
quotient = float(num) / 1024**exponent
|
||||
unit, num_decimals = unit_list[exponent], decimal_list[exponent]
|
||||
format_string = '{:.%sf} {}' % (num_decimals)
|
||||
return format_string.format(quotient, unit)
|
||||
if num == 0:
|
||||
return '0 bytes'
|
||||
if num == 1:
|
||||
return '1 byte'
|
||||
|
||||
|
||||
def __export_static_mesh_data(object_data, mesh_directory):
|
||||
mesh_data = object_data['data']['mesh_data']
|
||||
bobj_data = mesh_data.to_bobj()
|
||||
filepath = os.path.join(mesh_directory, "mesh.bobj")
|
||||
|
||||
with open(filepath, 'wb') as mesh_file:
|
||||
mesh_file.write(bobj_data)
|
||||
|
||||
info = {'mesh_type': 'STATIC'}
|
||||
info_json = json.dumps(info, sort_keys=True)
|
||||
info_filepath = os.path.join(mesh_directory, "mesh.info")
|
||||
with open(info_filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(info_json)
|
||||
|
||||
dprops = __get_domain_properties()
|
||||
if dprops.debug.display_console_output:
|
||||
export_str = "Exporting static mesh: <" + object_data['name'] + ">, "
|
||||
export_str += "verts: " + str(len(mesh_data.vertices) // 3)
|
||||
export_str += ", tris: " + str(len(mesh_data.triangles) // 3)
|
||||
export_str += ", filesize: " + __format_bytes(len(bobj_data))
|
||||
print(export_str)
|
||||
|
||||
object_data['data']['mesh_data'] = None
|
||||
|
||||
|
||||
def __export_keyframed_mesh_data(object_data, mesh_directory):
|
||||
mesh_data = object_data['data']['mesh_data']
|
||||
bobj_data = mesh_data.to_bobj()
|
||||
mesh_filepath = os.path.join(mesh_directory, "mesh.bobj")
|
||||
with open(mesh_filepath, 'wb') as mesh_file:
|
||||
mesh_file.write(bobj_data)
|
||||
|
||||
matrix_data = object_data['data']['matrix_data']
|
||||
matrix_json = json.dumps(matrix_data)
|
||||
matrix_filepath = os.path.join(mesh_directory, "transforms.data")
|
||||
with open(matrix_filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(matrix_json)
|
||||
|
||||
info = {
|
||||
'mesh_type': 'KEYFRAMED',
|
||||
'frame_start': min(matrix_data.keys()),
|
||||
'frame_end': max(matrix_data.keys())
|
||||
}
|
||||
info_json = json.dumps(info, sort_keys=True)
|
||||
info_filepath = os.path.join(mesh_directory, "mesh.info")
|
||||
with open(info_filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(info_json)
|
||||
|
||||
matrix_filesize = os.stat(matrix_filepath).st_size
|
||||
filesize = len(bobj_data) + matrix_filesize
|
||||
|
||||
dprops = __get_domain_properties()
|
||||
if dprops.debug.display_console_output:
|
||||
export_str = "Exporting keyframed mesh: <" + object_data['name'] + ">, "
|
||||
export_str += "numframes: " + str(len(matrix_data))
|
||||
export_str += ", verts: " + str(len(mesh_data.vertices) // 3)
|
||||
export_str += ", tris: " + str(len(mesh_data.triangles) // 3)
|
||||
export_str += ", filesize: " + __format_bytes(filesize)
|
||||
print(export_str)
|
||||
|
||||
object_data['data']['mesh_data'] = None
|
||||
object_data['data']['matrix_data'] = None
|
||||
|
||||
|
||||
def __export_animated_mesh_data(object_data, mesh_directory):
|
||||
mesh_data = object_data['data']['mesh_data']
|
||||
frame_data = object_data['data']['frame_data']
|
||||
|
||||
dprops = __get_domain_properties()
|
||||
for i, mesh in enumerate(mesh_data):
|
||||
bobj_data = mesh.to_bobj()
|
||||
frameno = frame_data[i]
|
||||
mesh_name = "mesh" + str(frameno).zfill(6) + ".bobj"
|
||||
filepath = os.path.join(mesh_directory, mesh_name)
|
||||
|
||||
with open(filepath, 'wb') as mesh_file:
|
||||
mesh_file.write(bobj_data)
|
||||
|
||||
if dprops.debug.display_console_output:
|
||||
export_str = "Exporting animated mesh: <" + object_data['name'] + ">, "
|
||||
export_str += "frame: " + str(frameno)
|
||||
export_str += ", verts: " + str(len(mesh.vertices) // 3)
|
||||
export_str += ", tris: " + str(len(mesh.triangles) // 3)
|
||||
export_str += ", filesize: " + __format_bytes(len(bobj_data))
|
||||
print(export_str)
|
||||
|
||||
files = os.listdir(mesh_directory)
|
||||
files = [f.split('.')[0][-6:] for f in files]
|
||||
file_numbers = [int(f) for f in files if f.isdigit()]
|
||||
|
||||
info = {
|
||||
'mesh_type': 'ANIMATED',
|
||||
'frame_start': min(file_numbers),
|
||||
'frame_end': max(file_numbers)
|
||||
}
|
||||
info_json = json.dumps(info, sort_keys=True)
|
||||
info_filepath = os.path.join(mesh_directory, "mesh.info")
|
||||
with open(info_filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(info_json)
|
||||
|
||||
object_data['data']['mesh_data'] = []
|
||||
frame_data = object_data['data']['frame_data'] = []
|
||||
|
||||
|
||||
def __initialize_export_object_geometry_types(export_object):
|
||||
bl_object = export_object.get_blender_object()
|
||||
if bl_object.type == 'MESH':
|
||||
export_object.add_geometry_export_type(GeometryExportType.MESH)
|
||||
elif bl_object.type == 'CURVE':
|
||||
export_object.add_geometry_export_type(GeometryExportType.CURVE)
|
||||
elif bl_object.type == 'EMPTY':
|
||||
export_object.add_geometry_export_type(GeometryExportType.CENTROID)
|
||||
|
||||
fprops = bl_object.flip_fluid
|
||||
if fprops.is_fluid() or fprops.is_inflow() or fprops.is_force_field():
|
||||
export_object.add_geometry_export_type(GeometryExportType.AXIS)
|
||||
|
||||
|
||||
|
||||
def __generate_export_object(bl_object):
|
||||
export_object = GeometryExportObject(bl_object.name)
|
||||
__initialize_export_object_geometry_types(export_object)
|
||||
|
||||
return export_object
|
||||
|
||||
|
||||
def __get_target_object_export_type(bl_target, bl_target_parent):
|
||||
parent_props = bl_target_parent.flip_fluid.get_property_group()
|
||||
if hasattr(parent_props, 'export_animated_target') and parent_props.export_animated_target:
|
||||
return MotionExportType.ANIMATED
|
||||
if export_utils.is_object_keyframe_animated(bl_target):
|
||||
return MotionExportType.KEYFRAMED
|
||||
return MotionExportType.STATIC
|
||||
|
||||
|
||||
def __generate_target_export_object(bl_target_object, bl_target_parent_object):
|
||||
export_object = GeometryExportObject(bl_target_object.name)
|
||||
|
||||
export_type = __get_target_object_export_type(bl_target_object, bl_target_parent_object)
|
||||
export_object.set_motion_export_type(export_type)
|
||||
export_object.add_geometry_export_type(GeometryExportType.CENTROID)
|
||||
|
||||
return export_object
|
||||
|
||||
|
||||
def __get_meshing_volume_object_export_type(bl_meshing_volume, bl_domain):
|
||||
dprops = bl_domain.flip_fluid.get_property_group()
|
||||
if dprops.surface.export_animated_meshing_volume_object:
|
||||
return MotionExportType.ANIMATED
|
||||
if export_utils.is_object_keyframe_animated(bl_meshing_volume):
|
||||
return MotionExportType.KEYFRAMED
|
||||
return MotionExportType.STATIC
|
||||
|
||||
|
||||
def __generate_meshing_volume_export_object(bl_meshing_object, bl_domain_object):
|
||||
export_object = GeometryExportObject(bl_meshing_object.name)
|
||||
|
||||
export_type = __get_meshing_volume_object_export_type(bl_meshing_object, bl_domain_object)
|
||||
export_object.set_motion_export_type(export_type)
|
||||
export_object.add_geometry_export_type(GeometryExportType.MESH)
|
||||
|
||||
return export_object
|
||||
|
||||
|
||||
def add_objects_to_geometry_exporter(geometry_exporter):
|
||||
domain = bpy.context.scene.flip_fluid.get_domain_object()
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
|
||||
# Objects disabled in the viewport shouldn't be exported for the simulation, so skip these
|
||||
objects = bpy.context.scene.flip_fluid.get_simulation_objects(skip_hide_viewport=True)
|
||||
disable_topology_warning = dprops.advanced.disable_changing_topology_warning
|
||||
|
||||
# Add regular FLIP Fluid objects
|
||||
for obj in objects:
|
||||
props = obj.flip_fluid.get_property_group()
|
||||
export_object = __generate_export_object(obj)
|
||||
|
||||
is_dynamic_topology_exception = False
|
||||
if obj.flip_fluid.is_force_field():
|
||||
is_dynamic_topology_exception = True
|
||||
elif obj.flip_fluid.is_inflow():
|
||||
if not obj.flip_fluid.inflow.append_object_velocity:
|
||||
is_dynamic_topology_exception = True
|
||||
elif obj.flip_fluid.is_fluid():
|
||||
if not obj.flip_fluid.fluid.append_object_velocity:
|
||||
is_dynamic_topology_exception = True
|
||||
elif obj.flip_fluid.is_outflow():
|
||||
is_dynamic_topology_exception = True
|
||||
|
||||
skip_reexport = hasattr(props, "skip_reexport") and props.skip_reexport
|
||||
force_reexport = hasattr(props, "force_reexport_on_next_bake") and props.force_reexport_on_next_bake
|
||||
skip_reexport = skip_reexport and not force_reexport
|
||||
export_object.skip_reexport = skip_reexport and not force_reexport
|
||||
export_object.disable_changing_topology_warning = disable_topology_warning or is_dynamic_topology_exception
|
||||
geometry_exporter.add_geometry_export_object(export_object)
|
||||
|
||||
# Add Fluid/Inflow target objects
|
||||
for obj in objects:
|
||||
if not obj.flip_fluid.is_fluid() and not obj.flip_fluid.is_inflow():
|
||||
continue
|
||||
props = obj.flip_fluid.get_property_group()
|
||||
if not props.is_target_valid():
|
||||
continue
|
||||
|
||||
target_object = props.get_target_object()
|
||||
export_object = __generate_target_export_object(target_object, obj)
|
||||
export_object.disable_changing_topology_warning = True
|
||||
geometry_exporter.add_geometry_export_object(export_object)
|
||||
|
||||
# Add Meshing Volume object
|
||||
if dprops.surface.is_meshing_volume_object_valid():
|
||||
meshing_volume_object = dprops.surface.get_meshing_volume_object()
|
||||
if meshing_volume_object is not None:
|
||||
export_object = __generate_meshing_volume_export_object(meshing_volume_object, domain)
|
||||
export_object.disable_changing_topology_warning = True
|
||||
geometry_exporter.add_geometry_export_object(export_object)
|
||||
|
||||
geometry_exporter.initialize()
|
||||
|
||||
|
||||
def export_simulation_data(context, data_filepath):
|
||||
domain_object = __get_domain_object()
|
||||
dprops = __get_domain_properties()
|
||||
if domain_object is None:
|
||||
return False
|
||||
|
||||
simprops = bpy.context.scene.flip_fluid
|
||||
|
||||
simulation_objects = flip_fluid_map.Map({})
|
||||
simulation_objects.domain = domain_object
|
||||
|
||||
# Objects disabled in the viewport shouldn't be exported for the simulation, so skip these
|
||||
simulation_objects.fluid_objects = simprops.get_fluid_objects(skip_hide_viewport=True)
|
||||
simulation_objects.obstacle_objects = simprops.get_obstacle_objects(skip_hide_viewport=True)
|
||||
simulation_objects.inflow_objects = simprops.get_inflow_objects(skip_hide_viewport=True)
|
||||
simulation_objects.outflow_objects = simprops.get_outflow_objects(skip_hide_viewport=True)
|
||||
simulation_objects.force_field_objects = simprops.get_force_field_objects(skip_hide_viewport=True)
|
||||
|
||||
success = __export_simulation_data_to_file(context, simulation_objects, data_filepath)
|
||||
|
||||
return success
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
@@ -0,0 +1,30 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
reloadable_modules = [
|
||||
'filesystem_protection_layer',
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy
|
||||
|
||||
from . import (
|
||||
filesystem_protection_layer,
|
||||
)
|
||||
@@ -0,0 +1,261 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy, os, pathlib
|
||||
|
||||
from ..utils import installation_utils
|
||||
|
||||
|
||||
__EXTENSION_WHITELIST = [
|
||||
".backup",
|
||||
".bat",
|
||||
".sh",
|
||||
".bbox",
|
||||
".bin",
|
||||
".blend",
|
||||
".bobj",
|
||||
".cpp",
|
||||
".data",
|
||||
".ffd",
|
||||
".ffp3",
|
||||
".fpd",
|
||||
".h",
|
||||
".info",
|
||||
".json",
|
||||
".md",
|
||||
".png",
|
||||
".preset",
|
||||
".sim",
|
||||
".sqlite3",
|
||||
".state",
|
||||
".txt",
|
||||
".txt~",
|
||||
".wwi",
|
||||
".wwf",
|
||||
".wwp"
|
||||
]
|
||||
|
||||
# These extensions are not allowed to be mass-deleted within a directory
|
||||
__DELETE_DIRECTORY_EXTENSION_BLACKLIST = [
|
||||
".blend"
|
||||
]
|
||||
|
||||
|
||||
class FilesystemProtectionError(Exception):
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
def __str__(self):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
def get_extension_whitelist():
|
||||
global __EXTENSION_WHITELIST
|
||||
return __EXTENSION_WHITELIST
|
||||
|
||||
|
||||
def get_delete_directory_extension_blacklist():
|
||||
global __DELETE_DIRECTORY_EXTENSION_BLACKLIST
|
||||
return __DELETE_DIRECTORY_EXTENSION_BLACKLIST
|
||||
|
||||
|
||||
def get_directory_whitelist():
|
||||
whitelist = []
|
||||
dprops = bpy.context.scene.flip_fluid.get_domain_properties()
|
||||
if dprops is not None:
|
||||
whitelist.append(dprops.cache.get_cache_abspath())
|
||||
|
||||
this_filepath = os.path.realpath(__file__)
|
||||
addon_filepath = os.path.dirname(os.path.dirname(this_filepath))
|
||||
whitelist.append(addon_filepath)
|
||||
|
||||
preset_library_installations = installation_utils.get_preset_library_installations()
|
||||
for install in preset_library_installations:
|
||||
whitelist.append(install["path"])
|
||||
|
||||
if bpy.data.filepath:
|
||||
compositing_tools_filepath = os.path.dirname(bpy.data.filepath)
|
||||
whitelist.append(compositing_tools_filepath)
|
||||
|
||||
return whitelist
|
||||
|
||||
|
||||
def path_is_parent(parent_path, child_path):
|
||||
parent_path = os.path.abspath(parent_path)
|
||||
child_path = os.path.abspath(child_path)
|
||||
try:
|
||||
parent_child_commonpath = os.path.commonpath([parent_path, child_path])
|
||||
except ValueError:
|
||||
# paths not on same drive
|
||||
return False
|
||||
return os.path.commonpath([parent_path]) == parent_child_commonpath
|
||||
|
||||
|
||||
def check_extensions_valid(extensions):
|
||||
extension_whitelist = get_extension_whitelist()
|
||||
bad_extensions = []
|
||||
for ext in extensions:
|
||||
if ext not in extension_whitelist:
|
||||
bad_extensions.append(ext)
|
||||
|
||||
if bad_extensions:
|
||||
error_msg = "Extension not in whitelist: "
|
||||
for ext in bad_extensions:
|
||||
error_msg += "<" + ext + "> "
|
||||
error_msg += "***Please contact the developers with this error message***"
|
||||
raise FilesystemProtectionError(error_msg)
|
||||
|
||||
|
||||
def check_directory_valid(base_directory):
|
||||
directory_whitelist = get_directory_whitelist()
|
||||
is_safe_sub_directory = False
|
||||
for d in directory_whitelist:
|
||||
if path_is_parent(d, base_directory):
|
||||
is_safe_sub_directory = True
|
||||
break
|
||||
|
||||
if not is_safe_sub_directory:
|
||||
error_msg = "Directory is not in whitelist: <" + base_directory + "> Whitelist: "
|
||||
for d in directory_whitelist:
|
||||
error_msg += "<" + d + "> "
|
||||
error_msg += "***Please contact the developers with this error message***"
|
||||
raise FilesystemProtectionError(error_msg)
|
||||
|
||||
|
||||
def delete_files_in_directory(base_directory, extensions, remove_directory=False, display_popup_on_error=True):
|
||||
extension_blacklist = get_delete_directory_extension_blacklist()
|
||||
for ext in extension_blacklist:
|
||||
if ext in extensions:
|
||||
error_msg = "Extension in directory deletion blacklist: "
|
||||
error_msg += "<" + ext + "> "
|
||||
error_msg += "***Please contact the developers with this error message***"
|
||||
raise FilesystemProtectionError(error_msg)
|
||||
|
||||
check_extensions_valid(extensions)
|
||||
check_directory_valid(base_directory)
|
||||
|
||||
if not os.path.isdir(base_directory):
|
||||
return
|
||||
|
||||
file_list = [f for f in os.listdir(base_directory) if os.path.isfile(os.path.join(base_directory, f))]
|
||||
valid_filepaths = []
|
||||
for f in file_list:
|
||||
if pathlib.Path(f).suffix in extensions:
|
||||
valid_filepaths.append(os.path.join(base_directory, f))
|
||||
|
||||
remove_error_count = 0
|
||||
first_error = None
|
||||
for f in valid_filepaths:
|
||||
try:
|
||||
os.remove(f)
|
||||
except OSError as e:
|
||||
remove_error_count += 1
|
||||
if first_error is None:
|
||||
first_error = str(e)
|
||||
|
||||
if remove_directory and not os.listdir(base_directory):
|
||||
try:
|
||||
os.rmdir(base_directory)
|
||||
except OSError as e:
|
||||
remove_error_count += 1
|
||||
if first_error is None:
|
||||
first_error = str(e)
|
||||
|
||||
if remove_error_count > 0:
|
||||
errmsg = "Error encountered attempting to remove " + str(remove_error_count) + " file(s) in <" + base_directory + ">. Reason: <" + first_error + ">. "
|
||||
errmsg += "Try closing all applications accessing the directory, restarting Blender/System, or deleting directory manually."
|
||||
if display_popup_on_error:
|
||||
# Method may be run from bake.py script where there is not window from this context
|
||||
# in this case, 'display_popup_on_error' should be set so this operator does not run.
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message="Error Removing Files",
|
||||
error_description=errmsg,
|
||||
popup_width=700
|
||||
)
|
||||
else:
|
||||
print(errmsg)
|
||||
raise PermissionError(errmsg)
|
||||
|
||||
|
||||
def delete_file(filepath, error_ok=False, display_popup_on_error=True):
|
||||
if not os.path.isfile(filepath):
|
||||
return
|
||||
|
||||
extension = pathlib.Path(filepath).suffix
|
||||
check_extensions_valid([extension])
|
||||
check_directory_valid(filepath)
|
||||
|
||||
try:
|
||||
os.remove(filepath)
|
||||
except Exception as e:
|
||||
if error_ok:
|
||||
pass
|
||||
else:
|
||||
errmsg = "Error encountered attempting to remove file. Reason: <" + str(e) + ">."
|
||||
if display_popup_on_error:
|
||||
# Method may be run from bake.py script where there is not window from this context
|
||||
# in this case, 'display_popup_on_error' should be set so this operator does not run.
|
||||
bpy.ops.flip_fluid_operators.display_error(
|
||||
'INVOKE_DEFAULT',
|
||||
error_message="Error Removing Files",
|
||||
error_description=errmsg,
|
||||
popup_width=700
|
||||
)
|
||||
else:
|
||||
print(errmsg)
|
||||
raise PermissionError(errmsg)
|
||||
|
||||
|
||||
def clear_cache_directory(cache_directory, clear_export=False, clear_logs=False, remove_directory=False):
|
||||
stats_filepath = os.path.join(cache_directory, "flipstats.data")
|
||||
delete_file(stats_filepath)
|
||||
|
||||
bakefiles_dir = os.path.join(cache_directory, "bakefiles")
|
||||
extensions = [".bbox", ".bobj", ".data", ".wwp", ".wwf", ".wwi", ".fpd", ".ffd", ".ffp3", ".txt"]
|
||||
delete_files_in_directory(bakefiles_dir, extensions, remove_directory=True)
|
||||
|
||||
temp_dir = os.path.join(cache_directory, "temp")
|
||||
extensions = [".data"]
|
||||
delete_files_in_directory(temp_dir, extensions, remove_directory=True)
|
||||
|
||||
scripts_dir = os.path.join(cache_directory, "scripts")
|
||||
extensions = [".bat", ".sh", ".txt"]
|
||||
delete_files_in_directory(scripts_dir, extensions, remove_directory=True)
|
||||
|
||||
savestates_dir = os.path.join(cache_directory, "savestates")
|
||||
if os.path.isdir(savestates_dir):
|
||||
extensions = [".data", ".state", ".backup"]
|
||||
savestate_subdirs = [d for d in os.listdir(savestates_dir) if os.path.isdir(os.path.join(savestates_dir, d))]
|
||||
for subd in savestate_subdirs:
|
||||
if subd.startswith("autosave"):
|
||||
dirpath = os.path.join(savestates_dir, subd)
|
||||
delete_files_in_directory(dirpath, extensions, remove_directory=True)
|
||||
delete_files_in_directory(savestates_dir, [], remove_directory=True)
|
||||
|
||||
if clear_export:
|
||||
export_dir = os.path.join(cache_directory, "export")
|
||||
if os.path.isdir(export_dir):
|
||||
extensions = [".sqlite3", ".sim"]
|
||||
delete_files_in_directory(export_dir, extensions, remove_directory=True)
|
||||
|
||||
if clear_logs:
|
||||
logs_dir = os.path.join(cache_directory, "logs")
|
||||
if os.path.isdir(logs_dir):
|
||||
extensions = [".txt"]
|
||||
delete_files_in_directory(logs_dir, extensions, remove_directory=True)
|
||||
|
||||
if remove_directory and not os.listdir(cache_directory):
|
||||
os.rmdir(cache_directory)
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
The FLIP Fluids Material Library Blend files and containing materials in this directory
|
||||
and subdirectories are licensed under the MIT License:
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
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.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,46 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
reloadable_modules = [
|
||||
'material_library'
|
||||
]
|
||||
for module_name in reloadable_modules:
|
||||
if module_name in locals():
|
||||
importlib.reload(locals()[module_name])
|
||||
|
||||
import bpy
|
||||
|
||||
from . import (
|
||||
material_library
|
||||
)
|
||||
|
||||
|
||||
def load_post():
|
||||
material_library.load_post()
|
||||
|
||||
|
||||
def scene_update_post(scene):
|
||||
material_library.scene_update_post(scene)
|
||||
|
||||
|
||||
def register():
|
||||
material_library.register()
|
||||
|
||||
|
||||
def unregister():
|
||||
material_library.unregister()
|
||||
@@ -0,0 +1,140 @@
|
||||
# Blender FLIP Fluids Add-on
|
||||
# Copyright (C) 2025 Ryan L. Guy & Dennis Fassbaender
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import bpy, os, hashlib
|
||||
from bpy.props import (
|
||||
PointerProperty
|
||||
)
|
||||
|
||||
from ..objects import flip_fluid_material_library
|
||||
from ..utils import version_compatibility_utils as vcu
|
||||
|
||||
|
||||
def get_surface_material_enums_ui(scene=None, context=None):
|
||||
bpy.context.scene.flip_fluid_material_library.check_icons_initialized()
|
||||
enums = []
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_SURFACE')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_ALL')
|
||||
enums += __get_non_material_library_enums_by_type()
|
||||
enums += [__get_none_material_enum()]
|
||||
enums.reverse()
|
||||
return enums
|
||||
|
||||
|
||||
def get_fluid_particles_material_enums_ui(scene=None, context=None):
|
||||
bpy.context.scene.flip_fluid_material_library.check_icons_initialized()
|
||||
enums = []
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_SURFACE')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_WHITEWATER')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_ALL')
|
||||
enums += __get_non_material_library_enums_by_type()
|
||||
enums += [__get_none_material_enum()]
|
||||
enums.reverse()
|
||||
return enums
|
||||
|
||||
|
||||
def get_whitewater_material_enums_ui(scene=None, context=None):
|
||||
bpy.context.scene.flip_fluid_material_library.check_icons_initialized()
|
||||
enums = []
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_WHITEWATER')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_ALL')
|
||||
enums += __get_non_material_library_enums_by_type()
|
||||
enums += [__get_none_material_enum()]
|
||||
enums.reverse()
|
||||
return enums
|
||||
|
||||
|
||||
def get_material_import_enums_ui(scene = None, context = None):
|
||||
bpy.context.scene.flip_fluid_material_library.check_icons_initialized()
|
||||
enums = []
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_SURFACE')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_WHITEWATER')
|
||||
enums += __get_material_library_enums_by_type('MATERIAL_TYPE_ALL')
|
||||
enums += [__get_all_materials_enum()]
|
||||
enums.reverse()
|
||||
return enums
|
||||
|
||||
|
||||
def import_material(material_name):
|
||||
return bpy.context.scene.flip_fluid_material_library.import_material(material_name)
|
||||
|
||||
|
||||
def import_material_copy(material_name):
|
||||
return bpy.context.scene.flip_fluid_material_library.import_material_copy(material_name)
|
||||
|
||||
|
||||
def is_material_imported(material_name):
|
||||
return bpy.context.scene.flip_fluid_material_library.is_material_imported(material_name)
|
||||
|
||||
|
||||
def is_material_library_available():
|
||||
library = bpy.context.scene.flip_fluid_material_library
|
||||
return len(library.material_list) > 0
|
||||
|
||||
|
||||
def __get_material_library_enums_by_type(material_type):
|
||||
library = bpy.context.scene.flip_fluid_material_library
|
||||
enums = []
|
||||
for mdata in library.material_list:
|
||||
if mdata.type == material_type:
|
||||
enums.append(mdata.get_ui_enum())
|
||||
return enums
|
||||
|
||||
|
||||
def __get_non_material_library_enums_by_type():
|
||||
enums = []
|
||||
for m in bpy.data.materials:
|
||||
if not m.flip_fluid_material_library.is_library_material:
|
||||
if vcu.is_blender_30():
|
||||
# material preview can be None in Blender 3.0. Use the preview_ensure function
|
||||
# to make sure the preview is loaded
|
||||
m.preview_ensure()
|
||||
e = (m.name, m.name, "", m.preview.icon_id, __get_non_material_library_material_hash(m))
|
||||
enums.append(e)
|
||||
return enums
|
||||
|
||||
|
||||
def __get_non_material_library_material_hash(material):
|
||||
return int(hashlib.sha1(material.name.encode('utf-8')).hexdigest(), 16) % int(1e6)
|
||||
|
||||
|
||||
def __get_none_material_enum():
|
||||
return ("MATERIAL_NONE", "None", "", 0, 0)
|
||||
|
||||
|
||||
def __get_all_materials_enum():
|
||||
return ("ALL_MATERIALS", "All Materials", "Import all materials", 0, 0)
|
||||
|
||||
def load_post():
|
||||
library_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "material_library")
|
||||
bpy.context.scene.flip_fluid_material_library.load_post()
|
||||
bpy.context.scene.flip_fluid_material_library.initialize(library_path)
|
||||
|
||||
|
||||
def scene_update_post(scene):
|
||||
bpy.context.scene.flip_fluid_material_library.scene_update_post(scene)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.types.Scene.flip_fluid_material_library = PointerProperty(
|
||||
name="Flip Fluid Material Library",
|
||||
description="",
|
||||
type=flip_fluid_material_library.FLIPFluidMaterialLibrary,
|
||||
)
|
||||
|
||||
|
||||
def unregister():
|
||||
del bpy.types.Scene.flip_fluid_material_library
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
extensions/user_default/flip_fluids_addon/materials/material_library/icons/FF Chocolate_imported.png
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
8de393985416f34d933299f6d8828ee9
|
||||
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
LFS
BIN
Binary file not shown.
BIN
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user