tissue updated to v0.3.70 #105180
@ -1,21 +1,15 @@
|
||||
# Tissue
|
||||
![cover](http://www.co-de-it.com/wordpress/wp-content/uploads/2015/07/tissue_graphics.jpg)
|
||||
Tissue - Blender's add-on for computational design by Co-de-iT
|
||||
http://www.co-de-it.com/wordpress/code/blender-tissue
|
||||
https://www.co-de-it.com/code/blender-tissue
|
||||
|
||||
Tissue is already shipped with both Blender. However I recommend to update the default version downloading manually the most recent one, for more updated features and more stability.
|
||||
|
||||
### Blender 2.93
|
||||
### Blender 4.0.2
|
||||
|
||||
Tissue v0.3.52 for Blender 2.93 (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-52
|
||||
Download the latest release: https://github.com/alessandro-zomparelli/tissue/releases
|
||||
|
||||
Development branch (usually the most updated version): https://github.com/alessandro-zomparelli/tissue/tree/b290-dev
|
||||
|
||||
### Blender 2.79 (unsupported)
|
||||
|
||||
Tissue v0.3.4 for Blender 2.79b (latest stable release): https://github.com/alessandro-zomparelli/tissue/releases/tag/v0-3-4
|
||||
|
||||
Development branch (most updated version): https://github.com/alessandro-zomparelli/tissue/tree/dev1
|
||||
Current development branch (usually the most updated version): https://github.com/alessandro-zomparelli/tissue/tree/b401-dev
|
||||
|
||||
|
||||
### Installation:
|
||||
@ -27,21 +21,18 @@ Development branch (most updated version): https://github.com/alessandro-zompare
|
||||
|
||||
### Documentation
|
||||
|
||||
Tissue documentation for Blender 2.80: https://github.com/alessandro-zomparelli/tissue/wiki
|
||||
|
||||
Tissue documentation for Blender's latest version: https://docs.blender.org/manual/en/latest/addons/mesh/tissue.html
|
||||
|
||||
### Issues
|
||||
Please help me keeping Tissue stable and updated, report any issues or feedback here: https://github.com/alessandro-zomparelli/tissue/issues
|
||||
Please help me keep Tissue stable and updated, report any issues or feedback here: https://github.com/alessandro-zomparelli/tissue/issues
|
||||
|
||||
### Contribute
|
||||
Tissue is free and open-source. I really think that this is the power of Blender and I wanted to give my small contribution to it.
|
||||
Tissue is free and open-source. I think that this is the power of Blender and I wanted to give my small contribution to it.
|
||||
|
||||
If you like my work and you want to help me, please consider to support me on **Patreon**, where I share some tips about Blender, Tissue and scripting: https://www.patreon.com/alessandrozomparelli
|
||||
If you like my work and you want to help me, please consider supporting me on **Patreon**, where I share some tips about Blender, Tissue and scripting: https://www.patreon.com/alessandrozomparelli
|
||||
|
||||
[![Patreon](http://alessandrozomparelli.com/wp-content/uploads/2020/04/patreon-transparent-vector-small.png)](https://www.patreon.com/alessandrozomparelli)
|
||||
|
||||
A special thanks to all my patrons, in particular to my **Tissue Supporters**: *TomaLaboratory*, *Scott Shorter*, *Garrett Post*, *Kairomon*, *Art Evans*, *Justin Davis*, *John Wise*, *Avi Bryant*, *Ahmed Saber*, *SlimeSound Production*, *Steffen Meier*.
|
||||
|
||||
Many thanks,
|
||||
|
||||
Alessandro
|
||||
|
@ -1,6 +1,21 @@
|
||||
# SPDX-FileCopyrightText: 2017-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# --------------------------------- TISSUE ----------------------------------- #
|
||||
# ------------------------------- version 0.3 -------------------------------- #
|
||||
@ -12,15 +27,15 @@
|
||||
# (2017) #
|
||||
# #
|
||||
# http://www.co-de-it.com/ #
|
||||
# http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Mesh/Tissue #
|
||||
# https://docs.blender.org/manual/en/latest/addons/mesh/tissue.html #
|
||||
# #
|
||||
# ############################################################################ #
|
||||
|
||||
bl_info = {
|
||||
"name": "Tissue",
|
||||
"author": "Alessandro Zomparelli (Co-de-iT)",
|
||||
"version": (0, 3, 54),
|
||||
"blender": (2, 93, 0),
|
||||
"author": "Alessandro Zomparelli",
|
||||
"version": (0, 3, 70),
|
||||
"blender": (4, 0, 2),
|
||||
"location": "",
|
||||
"description": "Tools for Computational Design",
|
||||
"warning": "",
|
||||
@ -35,6 +50,7 @@ if "bpy" in locals():
|
||||
importlib.reload(tessellate_numpy)
|
||||
importlib.reload(tissue_properties)
|
||||
importlib.reload(weight_tools)
|
||||
importlib.reload(weight_reaction_diffusion)
|
||||
importlib.reload(dual_mesh)
|
||||
importlib.reload(lattice)
|
||||
importlib.reload(uv_to_mesh)
|
||||
@ -43,11 +59,14 @@ if "bpy" in locals():
|
||||
importlib.reload(material_tools)
|
||||
importlib.reload(curves_tools)
|
||||
importlib.reload(polyhedra)
|
||||
importlib.reload(texture_reaction_diffusion)
|
||||
importlib.reload(contour_curves)
|
||||
|
||||
else:
|
||||
from . import tessellate_numpy
|
||||
from . import tissue_properties
|
||||
from . import weight_tools
|
||||
from . import weight_reaction_diffusion
|
||||
from . import dual_mesh
|
||||
from . import lattice
|
||||
from . import uv_to_mesh
|
||||
@ -56,6 +75,8 @@ else:
|
||||
from . import material_tools
|
||||
from . import curves_tools
|
||||
from . import polyhedra
|
||||
from . import texture_reaction_diffusion
|
||||
from . import contour_curves
|
||||
|
||||
import bpy
|
||||
from bpy.props import PointerProperty, CollectionProperty, BoolProperty
|
||||
@ -86,6 +107,7 @@ classes = (
|
||||
tessellate_numpy.TISSUE_PT_tessellate_morphing,
|
||||
tessellate_numpy.TISSUE_PT_tessellate_iterations,
|
||||
tessellate_numpy.tissue_render_animation,
|
||||
tessellate_numpy.tissue_remove,
|
||||
|
||||
weight_tools.face_area_to_vertex_groups,
|
||||
weight_tools.vertex_colors_to_vertex_groups,
|
||||
@ -93,31 +115,36 @@ classes = (
|
||||
weight_tools.vertex_group_to_uv,
|
||||
weight_tools.TISSUE_PT_weight,
|
||||
weight_tools.TISSUE_PT_color,
|
||||
weight_tools.weight_contour_curves,
|
||||
weight_tools.tissue_weight_contour_curves_pattern,
|
||||
weight_tools.weight_contour_mask,
|
||||
weight_tools.weight_contour_displace,
|
||||
weight_tools.harmonic_weight,
|
||||
weight_tools.edges_deformation,
|
||||
weight_tools.edges_bending,
|
||||
weight_tools.weight_laplacian,
|
||||
weight_tools.reaction_diffusion,
|
||||
weight_tools.start_reaction_diffusion,
|
||||
weight_tools.TISSUE_PT_reaction_diffusion,
|
||||
weight_tools.TISSUE_PT_reaction_diffusion_weight,
|
||||
weight_tools.reset_reaction_diffusion_weight,
|
||||
weight_reaction_diffusion.start_reaction_diffusion,
|
||||
weight_reaction_diffusion.TISSUE_PT_reaction_diffusion,
|
||||
weight_reaction_diffusion.TISSUE_PT_reaction_diffusion_performance,
|
||||
weight_reaction_diffusion.TISSUE_PT_reaction_diffusion_vector_field,
|
||||
weight_reaction_diffusion.TISSUE_PT_reaction_diffusion_weight,
|
||||
weight_reaction_diffusion.TISSUE_PT_reaction_diffusion_cache,
|
||||
weight_reaction_diffusion.reset_reaction_diffusion_weight,
|
||||
weight_tools.formula_prop,
|
||||
weight_tools.reaction_diffusion_prop,
|
||||
weight_reaction_diffusion.reaction_diffusion_prop,
|
||||
weight_tools.weight_formula,
|
||||
weight_tools.update_weight_formula,
|
||||
weight_tools.curvature_to_vertex_groups,
|
||||
weight_tools.weight_formula_wiki,
|
||||
weight_tools.tissue_weight_distance,
|
||||
weight_tools.random_weight,
|
||||
weight_tools.bake_reaction_diffusion,
|
||||
weight_tools.reaction_diffusion_free_data,
|
||||
weight_reaction_diffusion.bake_reaction_diffusion,
|
||||
weight_reaction_diffusion.reaction_diffusion_free_data,
|
||||
weight_tools.tissue_weight_streamlines,
|
||||
|
||||
contour_curves.tissue_weight_contour_curves_pattern,
|
||||
contour_curves.tissue_update_contour_curves,
|
||||
contour_curves.tissue_contour_curves_prop,
|
||||
contour_curves.TISSUE_PT_contour_curves,
|
||||
|
||||
dual_mesh.dual_mesh,
|
||||
dual_mesh.dual_mesh_tessellated,
|
||||
|
||||
@ -128,12 +155,21 @@ classes = (
|
||||
|
||||
curves_tools.tissue_to_curve_prop,
|
||||
curves_tools.tissue_convert_to_curve,
|
||||
curves_tools.tissue_convert_to_curve_update,
|
||||
curves_tools.tissue_update_convert_to_curve,
|
||||
curves_tools.TISSUE_PT_convert_to_curve,
|
||||
|
||||
uv_to_mesh.uv_to_mesh,
|
||||
|
||||
polyhedra.polyhedra_wireframe
|
||||
polyhedra.polyhedral_wireframe,
|
||||
polyhedra.tissue_update_polyhedra,
|
||||
polyhedra.tissue_polyhedra_prop,
|
||||
polyhedra.TISSUE_PT_polyhedra_object,
|
||||
|
||||
texture_reaction_diffusion.tex_reaction_diffusion_prop,
|
||||
texture_reaction_diffusion.start_tex_reaction_diffusion,
|
||||
texture_reaction_diffusion.reset_tex_reaction_diffusion,
|
||||
texture_reaction_diffusion.TISSUE_PT_tex_reaction_diffusion,
|
||||
texture_reaction_diffusion.TISSUE_PT_tex_reaction_diffusion_images
|
||||
)
|
||||
|
||||
def register():
|
||||
@ -147,18 +183,27 @@ def register():
|
||||
bpy.types.Object.tissue_tessellate = PointerProperty(
|
||||
type=tissue_properties.tissue_tessellate_prop
|
||||
)
|
||||
bpy.types.Object.tissue_polyhedra = PointerProperty(
|
||||
type=polyhedra.tissue_polyhedra_prop
|
||||
)
|
||||
bpy.types.Object.tissue_to_curve = PointerProperty(
|
||||
type=curves_tools.tissue_to_curve_prop
|
||||
)
|
||||
bpy.types.Object.tissue_contour_curves = PointerProperty(
|
||||
type=contour_curves.tissue_contour_curves_prop
|
||||
)
|
||||
bpy.types.Object.formula_settings = CollectionProperty(
|
||||
type=weight_tools.formula_prop
|
||||
)
|
||||
bpy.types.Object.reaction_diffusion_settings = PointerProperty(
|
||||
type=weight_tools.reaction_diffusion_prop
|
||||
type=weight_reaction_diffusion.reaction_diffusion_prop
|
||||
)
|
||||
bpy.types.Object.tex_reaction_diffusion_settings = PointerProperty(
|
||||
type=texture_reaction_diffusion.tex_reaction_diffusion_prop
|
||||
)
|
||||
# weight_tools
|
||||
bpy.app.handlers.frame_change_post.append(weight_tools.reaction_diffusion_def)
|
||||
#bpy.app.handlers.frame_change_post.append(tessellate_numpy.anim_tessellate)
|
||||
bpy.app.handlers.frame_change_post.append(weight_reaction_diffusion.reaction_diffusion_def)
|
||||
bpy.app.handlers.frame_change_post.append(texture_reaction_diffusion.tex_reaction_diffusion_def)
|
||||
|
||||
def unregister():
|
||||
from bpy.utils import unregister_class
|
||||
|
@ -1,5 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
@ -15,7 +13,7 @@ class tissuePreferences(bpy.types.AddonPreferences):
|
||||
|
||||
print_stats : IntProperty(
|
||||
name="Print Stats",
|
||||
description="Print in the console all details about the computing time",
|
||||
description="Print in the console all details about the computing time.",
|
||||
default=1,
|
||||
min=0,
|
||||
max=4
|
||||
@ -37,9 +35,13 @@ class tissuePreferences(bpy.types.AddonPreferences):
|
||||
numba_spec = importlib.util.find_spec('numba')
|
||||
found = numba_spec is not None
|
||||
if found:
|
||||
layout.label(text='Numba module installed correctly!', icon='INFO')
|
||||
layout.prop(self, "use_numba_tess")
|
||||
else:
|
||||
try:
|
||||
import numba
|
||||
layout.label(text='Numba module installed correctly!', icon='INFO')
|
||||
layout.prop(self, "use_numba_tess")
|
||||
except:
|
||||
found = False
|
||||
if not found:
|
||||
layout.label(text='Numba module not installed!', icon='ERROR')
|
||||
layout.label(text='Installing Numba will make Tissue faster', icon='INFO')
|
||||
row = layout.row()
|
||||
@ -56,6 +58,8 @@ class tissue_install_numba(bpy.types.Operator):
|
||||
try:
|
||||
from .utils_pip import Pip
|
||||
#Pip.upgrade_pip()
|
||||
Pip.uninstall('llvmlite')
|
||||
Pip.uninstall('numba')
|
||||
Pip.install('llvmlite')
|
||||
Pip.install('numba')
|
||||
from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
|
1189
mesh_tissue/contour_curves.py
Normal file
1189
mesh_tissue/contour_curves.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
# (2017) #
|
||||
@ -37,7 +53,8 @@ from .utils import (
|
||||
convert_object_to_mesh,
|
||||
get_weight_numpy,
|
||||
loops_from_bmesh,
|
||||
get_mesh_before_subs
|
||||
get_mesh_before_subs,
|
||||
tissue_time
|
||||
)
|
||||
import time
|
||||
|
||||
@ -48,7 +65,7 @@ def anim_curve_active(self, context):
|
||||
try:
|
||||
props.object.name
|
||||
if not ob.tissue.bool_lock:
|
||||
bpy.ops.object.tissue_convert_to_curve_update()
|
||||
bpy.ops.object.tissue_update_convert_to_curve()
|
||||
except: pass
|
||||
|
||||
|
||||
@ -67,7 +84,7 @@ class tissue_to_curve_prop(PropertyGroup):
|
||||
)
|
||||
bool_lock : BoolProperty(
|
||||
name="Lock",
|
||||
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy",
|
||||
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
|
||||
default=False,
|
||||
update = anim_curve_active
|
||||
)
|
||||
@ -79,7 +96,7 @@ class tissue_to_curve_prop(PropertyGroup):
|
||||
)
|
||||
bool_run : BoolProperty(
|
||||
name="Animatable Curve",
|
||||
description="Automatically recompute the conversion when the frame is changed",
|
||||
description="Automatically recompute the conversion when the frame is changed.",
|
||||
default = False
|
||||
)
|
||||
use_modifiers : BoolProperty(
|
||||
@ -480,12 +497,12 @@ class tissue_convert_to_curve(Operator):
|
||||
|
||||
new_ob.tissue.bool_lock = False
|
||||
|
||||
bpy.ops.object.tissue_convert_to_curve_update()
|
||||
bpy.ops.object.tissue_update_convert_to_curve()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class tissue_convert_to_curve_update(Operator):
|
||||
bl_idname = "object.tissue_convert_to_curve_update"
|
||||
class tissue_update_convert_to_curve(Operator):
|
||||
bl_idname = "object.tissue_update_convert_to_curve"
|
||||
bl_label = "Tissue Update Curve"
|
||||
bl_description = "Update Curve object"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
@ -500,9 +517,10 @@ class tissue_convert_to_curve_update(Operator):
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.object
|
||||
tissue_time(None,'Tissue: Convert to Curve of "{}"...'.format(ob.name), levels=0)
|
||||
start_time = time.time()
|
||||
|
||||
ob = context.object
|
||||
props = ob.tissue_to_curve
|
||||
ob0 = props.object
|
||||
if props.mode == 'PARTICLES':
|
||||
@ -669,8 +687,7 @@ class tissue_convert_to_curve_update(Operator):
|
||||
ob.data.splines.update()
|
||||
if not props.bool_smooth: bpy.ops.object.shade_flat()
|
||||
|
||||
end_time = time.time()
|
||||
print('Tissue: object "{}" converted to Curve in {:.4f} sec'.format(ob.name, end_time-start_time))
|
||||
tissue_time(start_time,'Convert to Curve',levels=0)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@ -700,7 +717,7 @@ class TISSUE_PT_convert_to_curve(Panel):
|
||||
#layout.use_property_decorate = False
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
#col.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='Refresh')
|
||||
#col.operator("object.tissue_update_convert_to_curve", icon='FILE_REFRESH', text='Refresh')
|
||||
row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ####
|
||||
lock_icon = 'LOCKED' if ob.tissue.bool_lock else 'UNLOCKED'
|
||||
#lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
|
||||
@ -710,6 +727,8 @@ class TISSUE_PT_convert_to_curve(Panel):
|
||||
col2 = row.column(align=True)
|
||||
col2.prop(ob.tissue, "bool_run", text="",icon='TIME')
|
||||
col2.enabled = not ob.tissue.bool_lock
|
||||
col2 = row.column(align=True)
|
||||
col2.operator("mesh.tissue_remove", text="", icon='X')
|
||||
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
|
@ -1,7 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2017-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# --------------------------------- DUAL MESH -------------------------------- #
|
||||
# -------------------------------- version 0.3 ------------------------------- #
|
||||
# #
|
||||
@ -230,10 +246,14 @@ class dual_mesh(Operator):
|
||||
)
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
subsurf_modifier = context.object.modifiers.new("dual_mesh_subsurf", 'SUBSURF')
|
||||
context.object.modifiers.move(len(context.object.modifiers)-1, 0)
|
||||
bpy.ops.object.modifier_add(type='SUBSURF')
|
||||
ob.modifiers[-1].name = "dual_mesh_subsurf"
|
||||
while True:
|
||||
bpy.ops.object.modifier_move_up(modifier="dual_mesh_subsurf")
|
||||
if ob.modifiers[0].name == "dual_mesh_subsurf":
|
||||
break
|
||||
|
||||
bpy.ops.object.modifier_apply(modifier=subsurf_modifier.name)
|
||||
bpy.ops.object.modifier_apply(modifier='dual_mesh_subsurf')
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
@ -246,7 +266,7 @@ class dual_mesh(Operator):
|
||||
bpy.ops.mesh.select_more(use_face_step=False)
|
||||
|
||||
bpy.ops.mesh.select_similar(
|
||||
type='EDGE', compare='EQUAL', threshold=0.01)
|
||||
type='VERT_EDGES', compare='EQUAL', threshold=0.01)
|
||||
bpy.ops.mesh.select_all(action='INVERT')
|
||||
|
||||
bpy.ops.mesh.dissolve_verts()
|
||||
|
@ -1,7 +1,20 @@
|
||||
# SPDX-FileCopyrightText: 2017-2023 Blender Foundation
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
# --------------------------- LATTICE ALONG SURFACE -------------------------- #
|
||||
# -------------------------------- version 0.3 ------------------------------- #
|
||||
# #
|
||||
@ -327,6 +340,7 @@ class lattice_along_surface(Operator):
|
||||
grid_mesh = temp_grid_obj.data
|
||||
for v in grid_mesh.vertices:
|
||||
v.co = grid_obj.matrix_world @ v.co
|
||||
#grid_mesh.calc_normals()
|
||||
|
||||
if len(grid_mesh.polygons) > 64 * 64:
|
||||
bpy.data.objects.remove(temp_grid_obj)
|
||||
@ -372,13 +386,16 @@ class lattice_along_surface(Operator):
|
||||
lattice.scale.z = 1
|
||||
|
||||
context.view_layer.objects.active = obj
|
||||
lattice_modifier = context.object.modifiers.new("", 'LATTICE')
|
||||
lattice_modifier.object = lattice
|
||||
bpy.ops.object.modifier_add(type='LATTICE')
|
||||
obj.modifiers[-1].object = lattice
|
||||
|
||||
# set as parent
|
||||
if self.set_parent:
|
||||
override = {'active_object': obj, 'selected_objects' : [lattice,obj]}
|
||||
bpy.ops.object.parent_set(override, type='OBJECT', keep_transform=False)
|
||||
override = context.copy()
|
||||
override['active_object'] = obj
|
||||
override['selected_objects'] = [lattice,obj]
|
||||
with context.temp_override(**override):
|
||||
bpy.ops.object.parent_set(type='OBJECT', keep_transform=False)
|
||||
|
||||
# reading grid structure
|
||||
verts_grid, edges_grid, faces_grid = grid_from_mesh(
|
||||
@ -434,7 +451,7 @@ class lattice_along_surface(Operator):
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
context.view_layer.objects.active = obj
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.modifier_remove(modifier=lattice_modifier.name)
|
||||
bpy.ops.object.modifier_remove(modifier=obj.modifiers[-1].name)
|
||||
if nu > 64 or nv > 64:
|
||||
self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
|
||||
return {'CANCELLED'}
|
||||
|
@ -1,6 +1,20 @@
|
||||
# SPDX-FileCopyrightText: 2022 Blender Foundation
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
@ -210,8 +224,8 @@ class weight_to_materials(Operator):
|
||||
faces_weight.append(w)
|
||||
faces_weight = np.array(faces_weight)
|
||||
faces_weight = faces_weight * count
|
||||
faces_weight.astype('int')
|
||||
ob.data.polygons.foreach_set('material_index',list(faces_weight))
|
||||
faces_weight = list(faces_weight.astype('int'))
|
||||
ob.data.polygons.foreach_set('material_index', faces_weight)
|
||||
ob.data.update()
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
return {'FINISHED'}
|
||||
|
@ -1,6 +1,20 @@
|
||||
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import numpy as np
|
||||
import time
|
||||
@ -32,11 +46,87 @@ except:
|
||||
if bool_numba:
|
||||
#from numba import jit, njit, guvectorize, float64, int32, prange
|
||||
|
||||
|
||||
@njit(parallel=True)
|
||||
#@cuda.jit('void(float32[:,:], float32[:,:])')
|
||||
def tex_laplacian(lap, arr):
|
||||
arr2 = arr*2
|
||||
diag = sqrt(2)/2
|
||||
nx = arr.shape[0]
|
||||
ny = arr.shape[1]
|
||||
for i in prange(nx):
|
||||
for j in prange(ny):
|
||||
i0 = (i-1)%nx
|
||||
j0 = (j-1)%ny
|
||||
i1 = (i+1)%nx
|
||||
j1 = (j+1)%ny
|
||||
#lap[i+1,j+1] = arr[i, j+1] + arr[i+2, j+1] + arr[i+1, j] + arr[i+1, j+2] - 4*arr[i+1,j+1]
|
||||
|
||||
lap[i,j] = ((arr[i0, j] + arr[i1, j] - arr2[i,j]) + \
|
||||
(arr[i, j0] + arr[i, j1] - arr2[i,j]) + \
|
||||
(arr[i0, j0] + arr[i1, j1] - arr2[i,j])*diag + \
|
||||
(arr[i1, j0] + arr[i0, j1] - arr2[i,j])*diag)*0.75
|
||||
|
||||
@njit(parallel=True)
|
||||
def tex_laplacian_ani(lap, arr, VF):
|
||||
arr2 = arr*2
|
||||
nx = arr.shape[0]
|
||||
ny = arr.shape[1]
|
||||
i0 = np.arange(nx)-1
|
||||
i0[0] = 1
|
||||
i1 = np.arange(nx)+1
|
||||
i1[nx-1] = nx-2
|
||||
j0 = np.arange(ny)-1
|
||||
j0[0] = 1
|
||||
j1 = np.arange(ny)+1
|
||||
j1[ny-1] = ny-2
|
||||
for i in prange(nx):
|
||||
for j in prange(ny):
|
||||
lap[i,j] = (arr[i0[i], j] + arr[i1[i], j] - arr2[i,j])*VF[0,i,j] + \
|
||||
(arr[i, j0[j]] + arr[i, j1[j]] - arr2[i,j])*VF[1,i,j] + \
|
||||
(arr[i0[i], j0[j]] + arr[i1[i], j1[j]] - arr2[i,j])*VF[2,i,j] + \
|
||||
(arr[i1[i], j0[j]] + arr[i0[i], j1[j]] - arr2[i,j])*VF[3,i,j]
|
||||
#lap[0,:] = lap[1,:]
|
||||
#lap[:,0] = lap[:,1]
|
||||
#lap[-1,:] = lap[-2,:]
|
||||
#lap[:,-1] = lap[:,-2]
|
||||
|
||||
#@cuda.jit(parallel=True)
|
||||
@njit(parallel=True)
|
||||
def run_tex_rd(A, B, lap_A, lap_B, diff_A, diff_B, f, k, dt, steps, brush):
|
||||
for t in range(steps):
|
||||
tex_laplacian(lap_A, A)
|
||||
tex_laplacian(lap_B, B)
|
||||
nx = A.shape[0]
|
||||
ny = A.shape[1]
|
||||
for i in prange(nx):
|
||||
for j in prange(ny):
|
||||
B[i,j] += brush[i,j]
|
||||
ab2 = A[i,j]*B[i,j]**2
|
||||
A[i,j] += (lap_A[i,j]*diff_A - ab2 + f*(1-A[i,j]))*dt
|
||||
B[i,j] += (lap_B[i,j]*diff_B + ab2 - (k+f)*B[i,j])*dt
|
||||
|
||||
@njit(parallel=True)
|
||||
def run_tex_rd_ani(A, B, lap_A, lap_B, diff_A, diff_B, f, k, dt, steps, vf1, vf2, brush):
|
||||
for t in range(steps):
|
||||
tex_laplacian_ani(lap_A, A, vf2)
|
||||
#laplacian(lap_A, A)
|
||||
tex_laplacian_ani(lap_B, B, vf1)
|
||||
nx = A.shape[0]
|
||||
ny = A.shape[1]
|
||||
for i in prange(nx):
|
||||
for j in prange(ny):
|
||||
B[i,j] += brush[i,j]
|
||||
ab2 = A[i ,j]*B[i,j]**2
|
||||
A[i,j] += (lap_A[i,j]*diff_A[i,j] - ab2 + f[i,j]*(1-A[i,j]))*dt
|
||||
B[i,j] += (lap_B[i,j]*diff_B[i,j] + ab2 - (k[i,j]+f[i,j])*B[i,j])*dt
|
||||
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_reaction_diffusion(n_verts, n_edges, edge_verts, a, b, brush, diff_a, diff_b, f, k, dt, time_steps):
|
||||
arr = np.arange(n_edges)*2
|
||||
id0 = edge_verts[arr]
|
||||
id1 = edge_verts[arr+1]
|
||||
arr = np.arange(n_edges)
|
||||
id0 = edge_verts[arr*2]
|
||||
id1 = edge_verts[arr*2+1]
|
||||
for i in range(time_steps):
|
||||
lap_a, lap_b = rd_init_laplacian(n_verts)
|
||||
numba_rd_laplacian(id0, id1, a, b, lap_a, lap_b)
|
||||
@ -62,17 +152,14 @@ if bool_numba:
|
||||
return values
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_reaction_diffusion_anisotropic(n_verts, n_edges, edge_verts, a, b, brush, diff_a, diff_b, f, k, dt, time_steps, grad):
|
||||
arr = np.arange(n_edges)*2
|
||||
id0 = edge_verts[arr]
|
||||
id1 = edge_verts[arr+1]
|
||||
#grad = weight_grad[id0] - weight_grad[id1]
|
||||
#grad = np.abs(grad)
|
||||
#grad /= abs(np.max(grad))
|
||||
#grad = grad*0.98 + 0.02
|
||||
def numba_reaction_diffusion_anisotropic(n_verts, n_edges, edge_verts, a, b, brush, diff_a, diff_b, f, k, dt, time_steps, field_mult):
|
||||
arr = np.arange(n_edges)
|
||||
id0 = edge_verts[arr*2]
|
||||
id1 = edge_verts[arr*2+1]
|
||||
mult = field_mult[arr]
|
||||
for i in range(time_steps):
|
||||
lap_a, lap_b = rd_init_laplacian(n_verts)
|
||||
numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, grad)
|
||||
numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, mult)
|
||||
numba_rd_core(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt)
|
||||
numba_set_ab(a,b,brush)
|
||||
return a,b
|
||||
@ -112,28 +199,27 @@ if bool_numba:
|
||||
if a[i] < 0: a[i] = 0
|
||||
elif a[i] > 1: a[i] = 1
|
||||
|
||||
|
||||
#@guvectorize(['(float64[:] ,float64[:] ,float64[:] , float64[:], float64[:], float64[:])'],'(m),(m),(n),(n),(n),(n)',target='parallel')
|
||||
@njit(parallel=True)
|
||||
def numba_rd_laplacian(id0, id1, a, b, lap_a, lap_b):
|
||||
for i in prange(len(id0)):
|
||||
v0 = id0[i]
|
||||
v1 = id1[i]
|
||||
lap_a[v0] += a[v1] - a[v0]
|
||||
lap_a[v1] += a[v0] - a[v1]
|
||||
lap_b[v0] += b[v1] - b[v0]
|
||||
lap_b[v1] += b[v0] - b[v1]
|
||||
lap_a[v0] += (a[v1] - a[v0])
|
||||
lap_a[v1] += (a[v0] - a[v1])
|
||||
lap_b[v0] += (b[v1] - b[v0])
|
||||
lap_b[v1] += (b[v0] - b[v1])
|
||||
#return lap_a, lap_b
|
||||
|
||||
@njit(parallel=True)
|
||||
def numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, grad):
|
||||
def numba_rd_laplacian_anisotropic(id0, id1, a, b, lap_a, lap_b, mult):
|
||||
for i in prange(len(id0)):
|
||||
v0 = id0[i]
|
||||
v1 = id1[i]
|
||||
lap_a[v0] += (a[v1] - a[v0])
|
||||
lap_a[v1] += (a[v0] - a[v1])
|
||||
lap_b[v0] -= (b[v1] - b[v0])*grad[i]
|
||||
lap_b[v1] += (b[v0] - b[v1])*grad[i]
|
||||
multiplier = mult[i]
|
||||
lap_a[v0] += (a[v1] - a[v0])# * multiplier
|
||||
lap_a[v1] += (a[v0] - a[v1])# * multiplier
|
||||
lap_b[v0] += (b[v1] - b[v0]) * multiplier
|
||||
lap_b[v1] += (b[v0] - b[v1]) * multiplier
|
||||
#return lap_a, lap_b
|
||||
|
||||
@njit(parallel=True)
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
694
mesh_tissue/texture_reaction_diffusion.py
Normal file
694
mesh_tissue/texture_reaction_diffusion.py
Normal file
@ -0,0 +1,694 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
#-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
|
||||
# #
|
||||
# Vertex Color to Vertex Group allow you to convert colors channles to weight #
|
||||
# maps. #
|
||||
# The main purpose is to use vertex colors to store information when importing #
|
||||
# files from other softwares. The script works with the active vertex color #
|
||||
# slot. #
|
||||
# For use the command "Vertex Clors to Vertex Groups" use the search bar #
|
||||
# (space bar). #
|
||||
# #
|
||||
# (c) Alessandro Zomparelli #
|
||||
# (2017) #
|
||||
# #
|
||||
# http://www.co-de-it.com/ #
|
||||
# #
|
||||
################################################################################
|
||||
|
||||
import bpy, bmesh, os
|
||||
import numpy as np
|
||||
import math, timeit, time
|
||||
from math import pi
|
||||
from statistics import mean, stdev
|
||||
from mathutils import Vector
|
||||
from mathutils.kdtree import KDTree
|
||||
from numpy import *
|
||||
try: from .numba_functions import run_tex_rd, run_tex_rd_ani
|
||||
except: pass
|
||||
#from .numba_functions import integrate_field
|
||||
#from .numba_functions import numba_reaction_diffusion
|
||||
try: import numexpr as ne
|
||||
except: pass
|
||||
|
||||
# Reaction-Diffusion cache
|
||||
from pathlib import Path
|
||||
import random as rnd
|
||||
import string
|
||||
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
PropertyGroup,
|
||||
)
|
||||
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
FloatVectorProperty,
|
||||
IntVectorProperty
|
||||
)
|
||||
|
||||
from .utils import *
|
||||
|
||||
|
||||
def tex_reaction_diffusion_add_handler(self, context):
|
||||
# remove existing handlers
|
||||
tex_reaction_diffusion_remove_handler(self, context)
|
||||
# add new handler
|
||||
bpy.app.handlers.frame_change_post.append(tex_rd_scene)
|
||||
|
||||
|
||||
def tex_reaction_diffusion_remove_handler(self, context):
|
||||
# remove existing handlers
|
||||
old_handlers = []
|
||||
for h in bpy.app.handlers.frame_change_post:
|
||||
if "tex_rd" in str(h):
|
||||
old_handlers.append(h)
|
||||
for h in old_handlers: bpy.app.handlers.frame_change_post.remove(h)
|
||||
|
||||
|
||||
class tex_reaction_diffusion_prop(PropertyGroup):
|
||||
run : BoolProperty(default=False, update = tex_reaction_diffusion_add_handler,
|
||||
description='Compute a new iteration on frame changes. Currently is not working during Render Animation')
|
||||
|
||||
res_x : IntProperty(
|
||||
name="Resolution X", default=512, min=2, soft_max=1000,
|
||||
description="Resolution of the simulation")
|
||||
|
||||
res_y : IntProperty(
|
||||
name="Resolution Y", default=512, min=2, soft_max=1000,
|
||||
description="Resolution of the simulation")
|
||||
|
||||
time_steps : IntProperty(
|
||||
name="Steps", default=10, min=0, soft_max=50,
|
||||
description="Number of Steps")
|
||||
|
||||
dt : FloatProperty(
|
||||
name="dt", default=1, min=0, soft_max=0.2,
|
||||
description="Time Step")
|
||||
|
||||
diff_a : FloatProperty(
|
||||
name="Diff A", default=0.14, min=0, soft_max=2, precision=3,
|
||||
description="Diffusion A")
|
||||
|
||||
diff_b : FloatProperty(
|
||||
name="Diff B", default=0.07, min=0, soft_max=2, precision=3,
|
||||
description="Diffusion B")
|
||||
|
||||
f : FloatProperty(
|
||||
name="f", default=0.055, soft_min=0.01, soft_max=0.06, precision=4, step=0.05,
|
||||
description="Feed Rate")
|
||||
|
||||
k : FloatProperty(
|
||||
name="k", default=0.062, soft_min=0.035, soft_max=0.065, precision=4, step=0.05,
|
||||
description="Kill Rate")
|
||||
|
||||
diff_mult : FloatProperty(
|
||||
name="Scale", default=1, min=0, soft_max=1, max=10, precision=2,
|
||||
description="Multiplier for the diffusion of both substances")
|
||||
|
||||
anisotropy : FloatProperty(
|
||||
name="Anisotropy", default=0.5, min=0, max=1, precision=2,
|
||||
description="Influence of the Vector Field")
|
||||
|
||||
img_vector_field : StringProperty(
|
||||
name="Vector Field", default='',
|
||||
description="Image used for the Vector Field. RGB to XY")
|
||||
|
||||
img_a : StringProperty(
|
||||
name="A", default='',
|
||||
description="Image used for the chemical A")
|
||||
|
||||
img_b : StringProperty(
|
||||
name="B", default='',
|
||||
description="Image used for the chemical B")
|
||||
|
||||
img_diff_a : StringProperty(
|
||||
name="Diff A", default='',
|
||||
description="Image used for A diffusion")
|
||||
|
||||
img_diff_b : StringProperty(
|
||||
name="Diff B", default='',
|
||||
description="Image used for B diffusion")
|
||||
|
||||
img_scale : StringProperty(
|
||||
name="Scale", default='',
|
||||
description="Image used for Scale value")
|
||||
|
||||
img_f : StringProperty(
|
||||
name="f", default='',
|
||||
description="Image used for Feed value (f)")
|
||||
|
||||
img_k : StringProperty(
|
||||
name="k", default='',
|
||||
description="Image used for Kill value (k)")
|
||||
|
||||
img_brush : StringProperty(
|
||||
name="Brush", default='',
|
||||
description="Image used for adding/removing B")
|
||||
|
||||
invert_img_diff_a : BoolProperty(default=False,
|
||||
description='Invert the value of the Vertex Group Diff A')
|
||||
|
||||
invert_img_diff_b : BoolProperty(default=False,
|
||||
description='Invert the value of the Vertex Group Diff B')
|
||||
|
||||
invert_img_scale : BoolProperty(default=False,
|
||||
description='Invert the value of the Vertex Group Scale')
|
||||
|
||||
invert_img_f : BoolProperty(default=False,
|
||||
description='Invert the value of the Vertex Group f')
|
||||
|
||||
invert_img_k : BoolProperty(default=False,
|
||||
description='Invert the value of the Vertex Group k')
|
||||
|
||||
invert_img_vector_field : BoolProperty(default=False,
|
||||
description='Use the perpendicular direction')
|
||||
|
||||
min_diff_a : FloatProperty(
|
||||
name="Min Diff A", default=0.1, min=0, soft_max=2, precision=3,
|
||||
description="Min Diff A")
|
||||
|
||||
max_diff_a : FloatProperty(
|
||||
name="Max Diff A", default=0.1, min=0, soft_max=2, precision=3,
|
||||
description="Max Diff A")
|
||||
|
||||
min_diff_b : FloatProperty(
|
||||
name="Min Diff B", default=0.1, min=0, soft_max=2, precision=3,
|
||||
description="Min Diff B")
|
||||
|
||||
max_diff_b : FloatProperty(
|
||||
name="Max Diff B", default=0.1, min=0, soft_max=2, precision=3,
|
||||
description="Max Diff B")
|
||||
|
||||
min_scale : FloatProperty(
|
||||
name="Scale", default=0.35, min=0, soft_max=1, max=10, precision=2,
|
||||
description="Min Scale Value")
|
||||
|
||||
max_scale : FloatProperty(
|
||||
name="Scale", default=1, min=0, soft_max=1, max=10, precision=2,
|
||||
description="Max Scale value")
|
||||
|
||||
min_f : FloatProperty(
|
||||
name="Min f", default=0.02, min=0, soft_min=0.01, soft_max=0.06, max=0.2, precision=4, step=0.05,
|
||||
description="Min Feed Rate")
|
||||
|
||||
max_f : FloatProperty(
|
||||
name="Max f", default=0.055, min=0, soft_min=0.01, soft_max=0.06, max=0.2, precision=4, step=0.05,
|
||||
description="Max Feed Rate")
|
||||
|
||||
min_k : FloatProperty(
|
||||
name="Min k", default=0.035, min=0, soft_min=0.035, soft_max=0.065, max=0.2, precision=4, step=0.05,
|
||||
description="Min Kill Rate")
|
||||
|
||||
max_k : FloatProperty(
|
||||
name="Max k", default=0.062, min=0, soft_min=0.035, soft_max=0.065, max=0.2, precision=4, step=0.05,
|
||||
description="Max Kill Rate")
|
||||
|
||||
brush_mult : FloatProperty(
|
||||
name="Mult", default=0.5, min=-1, max=1, precision=3, step=0.05,
|
||||
description="Multiplier for brush value")
|
||||
|
||||
bool_cache : BoolProperty(
|
||||
name="Use Cache", default=False,
|
||||
description="Read modifiers affect the vertex groups")
|
||||
|
||||
cache_frame_start : IntProperty(
|
||||
name="Start", default=1,
|
||||
description="Frame on which the simulation starts")
|
||||
|
||||
cache_frame_end : IntProperty(
|
||||
name="End", default=250,
|
||||
description="Frame on which the simulation ends")
|
||||
|
||||
cache_dir : StringProperty(
|
||||
name="Cache directory", default="", subtype='FILE_PATH',
|
||||
description = 'Directory that contains Reaction-Diffusion cache files'
|
||||
)
|
||||
|
||||
normalize : BoolProperty(
|
||||
name="Normalize values", default=False,
|
||||
description="Normalize values from 0 to 1")
|
||||
|
||||
def tex_rd_scene(scene, bake=False):
|
||||
for ob in bpy.context.scene.objects:
|
||||
if ob.tex_reaction_diffusion_settings.run:
|
||||
tex_reaction_diffusion_def(ob)
|
||||
|
||||
def tex_reaction_diffusion_def(ob, bake=False):
|
||||
try:
|
||||
props = ob.tex_reaction_diffusion_settings
|
||||
except:
|
||||
return
|
||||
scene = bpy.context.scene
|
||||
print("Texture Reaction Diffusion: " + str(scene.frame_current))
|
||||
start_time = timeit.default_timer()
|
||||
img_a = bpy.data.images[props.img_a]
|
||||
img_b = bpy.data.images[props.img_b]
|
||||
diff_a = props.diff_a
|
||||
diff_b = props.diff_b
|
||||
diff_a_min = props.min_diff_a
|
||||
diff_a_max = props.max_diff_a
|
||||
diff_b_min = props.min_diff_b
|
||||
diff_b_max = props.max_diff_b
|
||||
f_min = props.min_f
|
||||
f_max = props.max_f
|
||||
k_min = props.min_k
|
||||
k_max = props.max_k
|
||||
ani = props.anisotropy
|
||||
dt = props.dt
|
||||
time_steps = props.time_steps
|
||||
res_x = props.res_x #int(img_b.size[0])
|
||||
res_y = props.res_y #int(img_b.size[1])
|
||||
|
||||
min_scale = props.min_scale
|
||||
max_scale = props.max_scale
|
||||
|
||||
images = bpy.data.images.keys()
|
||||
rd_images = [img_a, img_b]
|
||||
img_diff_a = None
|
||||
img_diff_b = None
|
||||
img_vector_field = None
|
||||
img_f = None
|
||||
img_k = None
|
||||
img_scale = None
|
||||
img_brush = None
|
||||
if props.img_vector_field in images:
|
||||
img_vector_field = bpy.data.images[props.img_vector_field]
|
||||
rd_images.append(img_vector_field)
|
||||
if props.img_diff_a in images:
|
||||
img_diff_a = bpy.data.images[props.img_diff_a]
|
||||
rd_images.append(img_diff_a)
|
||||
if props.img_diff_b in images:
|
||||
img_diff_b = bpy.data.images[props.img_diff_b]
|
||||
rd_images.append(img_diff_b)
|
||||
if props.img_f in images:
|
||||
img_f = bpy.data.images[props.img_f]
|
||||
rd_images.append(img_f)
|
||||
if props.img_k in images:
|
||||
img_k = bpy.data.images[props.img_k]
|
||||
rd_images.append(img_k)
|
||||
if props.img_scale in images:
|
||||
img_scale = bpy.data.images[props.img_scale]
|
||||
rd_images.append(img_scale)
|
||||
if props.img_brush in images:
|
||||
img_brush = bpy.data.images[props.img_brush]
|
||||
rd_images.append(img_brush)
|
||||
for im in rd_images:
|
||||
im.scale(res_x ,res_y)
|
||||
im.pixels.update()
|
||||
nx = res_y
|
||||
ny = res_x
|
||||
|
||||
a_px = np.float32(np.zeros(nx*ny*4))
|
||||
img_a.pixels.foreach_get(a_px)
|
||||
b_px = np.float32(np.zeros(nx*ny*4))
|
||||
img_b.pixels.foreach_get(b_px)
|
||||
if img_vector_field:
|
||||
vf_px = np.float32(np.zeros(nx*ny*4))
|
||||
img_vector_field.pixels.foreach_get(vf_px)
|
||||
vf_px = np.array(vf_px).reshape((-1,4))
|
||||
vf_x = vf_px[:,1]*2-1
|
||||
vf_x = vf_x.reshape((nx,ny))
|
||||
vf_y = vf_px[:,0]*2-1
|
||||
vf_y = vf_y.reshape((nx,ny))
|
||||
|
||||
# original field
|
||||
vf_x_ = sqrt(2)/2*vf_x
|
||||
vf_y_ = sqrt(2)/2*vf_y
|
||||
vf_xy1_ = abs(vf_x_ + vf_y_)
|
||||
vf_xy2_ = abs(vf_x_ - vf_y_)
|
||||
vf_xy1 = (vf_xy1_*ani + (1-ani))*sqrt(2)/2
|
||||
vf_xy2 = (vf_xy2_*ani + (1-ani))*sqrt(2)/2
|
||||
vf_x_ = abs(vf_x)*ani + (1-ani)
|
||||
vf_y_ = abs(vf_y)*ani + (1-ani)
|
||||
vf1 = np.concatenate((vf_x_[np.newaxis,:,:], vf_y_[np.newaxis,:,:], vf_xy1[np.newaxis,:,:], vf_xy2[np.newaxis,:,:]), axis=0)
|
||||
|
||||
# perpendicular field
|
||||
vf_x, vf_y = -vf_y, vf_x
|
||||
vf_x_ = sqrt(2)/2*vf_x
|
||||
vf_y_ = sqrt(2)/2*vf_y
|
||||
vf_xy1_ = abs(vf_x_ + vf_y_)
|
||||
vf_xy2_ = abs(vf_x_ - vf_y_)
|
||||
vf_xy1 = (vf_xy1_*ani + (1-ani))*sqrt(2)/2
|
||||
vf_xy2 = (vf_xy2_*ani + (1-ani))*sqrt(2)/2
|
||||
vf_x = abs(vf_x)*ani + (1-ani)
|
||||
vf_y = abs(vf_y)*ani + (1-ani)
|
||||
vf2 = np.concatenate((vf_x[np.newaxis,:,:], vf_y[np.newaxis,:,:], vf_xy1[np.newaxis,:,:], vf_xy2[np.newaxis,:,:]), axis=0)
|
||||
if props.invert_img_vector_field:
|
||||
vf1, vf2 = vf2, vf1
|
||||
else:
|
||||
vf = np.ones((1,nx,ny))
|
||||
vf_diag = np.ones((1,nx,ny))*sqrt(2)/2
|
||||
vf1 = np.concatenate((vf, vf, vf_diag, vf_diag), axis=0)
|
||||
vf2 = vf1
|
||||
|
||||
|
||||
if img_diff_a:
|
||||
diff_a = np_remap_image_values(img_diff_a, channel=0, min=diff_a_min, max=diff_a_max, invert=props.invert_img_diff_a)
|
||||
else:
|
||||
diff_a = np.ones((nx,ny))*props.diff_a
|
||||
|
||||
if img_diff_b:
|
||||
diff_b = np_remap_image_values(img_diff_b, channel=0, min=diff_b_min, max=diff_b_max, invert=props.invert_img_diff_b)
|
||||
else:
|
||||
diff_b = np.ones((nx,ny))*props.diff_b
|
||||
|
||||
if img_scale:
|
||||
scale = np_remap_image_values(img_scale, channel=0, min=min_scale, max=max_scale, invert=props.invert_img_scale)
|
||||
diff_a *= scale
|
||||
diff_b *= scale
|
||||
else:
|
||||
diff_a *= props.diff_mult
|
||||
diff_b *= props.diff_mult
|
||||
|
||||
if img_f:
|
||||
f = np_remap_image_values(img_f, channel=0, min=f_min, max=f_max, invert=props.invert_img_f)
|
||||
else:
|
||||
f = np.ones((nx,ny))*props.f
|
||||
|
||||
if img_k:
|
||||
k = np_remap_image_values(img_k, channel=0, min=k_min, max=k_max, invert=props.invert_img_k)
|
||||
else:
|
||||
k = np.ones((nx,ny))*props.k
|
||||
|
||||
if img_brush:
|
||||
brush = np_remap_image_values(img_brush)*props.brush_mult
|
||||
else:
|
||||
brush = np.zeros((nx,ny))
|
||||
|
||||
print("Load images: " + str(timeit.default_timer() - start_time) + " sec")
|
||||
|
||||
start_time = timeit.default_timer()
|
||||
|
||||
a_px = np.array(a_px).reshape((-1,4))
|
||||
a = a_px[:,0]
|
||||
a = a.reshape((nx,ny))
|
||||
lap_a = np.zeros((nx,ny))
|
||||
|
||||
b_px = np.array(b_px).reshape((-1,4))
|
||||
b = b_px[:,0]
|
||||
b = b.reshape((nx,ny))
|
||||
lap_b = np.zeros((nx,ny))
|
||||
|
||||
print("Reshape data time: " + str(timeit.default_timer() - start_time) + " sec")
|
||||
|
||||
start_time = timeit.default_timer()
|
||||
run_tex_rd_ani(a, b, lap_a, lap_b, diff_a, diff_b, f, k, dt, time_steps, vf1, vf2, brush)
|
||||
print("Simulation time: " + str(timeit.default_timer() - start_time) + " sec")
|
||||
|
||||
start_time = timeit.default_timer()
|
||||
np.clip(a,0,1,out=a)
|
||||
np.clip(b,0,1,out=b)
|
||||
a = a.flatten()
|
||||
b = b.flatten()
|
||||
a_px[:,0] = a
|
||||
a_px[:,1] = a
|
||||
a_px[:,2] = a
|
||||
b_px[:,0] = b
|
||||
b_px[:,1] = b
|
||||
b_px[:,2] = b
|
||||
img_a.pixels.foreach_set(np.float32(a_px.flatten()))
|
||||
img_b.pixels.foreach_set(np.float32(b_px.flatten()))
|
||||
img_a.pixels.update()
|
||||
img_b.pixels.update()
|
||||
img_a.update()
|
||||
img_b.update()
|
||||
print("Stored Images: " + str(timeit.default_timer() - start_time) + " sec")
|
||||
|
||||
class reset_tex_reaction_diffusion(Operator):
|
||||
bl_idname = "object.reset_tex_reaction_diffusion"
|
||||
bl_label = "Reset Texture Reaction Diffusion"
|
||||
bl_description = ("Run a Reaction-Diffusion based on images: A and B")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
run : BoolProperty(
|
||||
name="Run Reaction-Diffusion", default=True, description="Compute a new iteration on frame changes")
|
||||
|
||||
time_steps : IntProperty(
|
||||
name="Steps", default=10, min=0, soft_max=50,
|
||||
description="Number of Steps")
|
||||
|
||||
dt : FloatProperty(
|
||||
name="dt", default=1, min=0, soft_max=0.2,
|
||||
description="Time Step")
|
||||
|
||||
diff_a : FloatProperty(
|
||||
name="Diff A", default=0.14, min=0, soft_max=2,
|
||||
description="Diffusion A")
|
||||
|
||||
diff_b : FloatProperty(
|
||||
name="Diff B", default=0.07, min=0, soft_max=2,
|
||||
description="Diffusion B")
|
||||
|
||||
f : FloatProperty(
|
||||
name="f", default=0.055, min=0, soft_min=0.01, soft_max=0.06, max=0.1, precision=4,
|
||||
description="Feed Rate")
|
||||
|
||||
k : FloatProperty(
|
||||
name="k", default=0.062, min=0, soft_min=0.035, soft_max=0.065, max=0.1, precision=4,
|
||||
description="Kill Rate")
|
||||
|
||||
def execute(self, context):
|
||||
props = context.object.tex_reaction_diffusion_settings
|
||||
props.dt = self.dt
|
||||
props.time_steps = self.time_steps
|
||||
props.f = self.f
|
||||
props.k = self.k
|
||||
props.diff_a = self.diff_a
|
||||
props.diff_b = self.diff_b
|
||||
res_x = props.res_x
|
||||
res_y = props.res_y
|
||||
img_a = bpy.data.images[props.img_a]
|
||||
img_b = bpy.data.images[props.img_b]
|
||||
img_a.scale(width=res_x, height=res_y)
|
||||
img_b.scale(width=res_x, height=res_y)
|
||||
img_a.pixels.foreach_set([1]*res_x*res_y*4)
|
||||
img_b.pixels.foreach_set([0,0,0,1]*res_x*res_y)
|
||||
img_a.pixels.update()
|
||||
img_b.pixels.update()
|
||||
img_a.update()
|
||||
img_b.update()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class start_tex_reaction_diffusion(Operator):
|
||||
bl_idname = "object.start_tex_reaction_diffusion"
|
||||
bl_label = "Start Texture Reaction Diffusion"
|
||||
bl_description = ("Run a Reaction-Diffusion based on images: A and B")
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
#res_x : IntProperty(
|
||||
# name="Resolution X", default=512, min=2, soft_max=1000,
|
||||
# description="Resolution of the simulation")
|
||||
#res_y : IntProperty(
|
||||
# name="Resolution Y", default=512, min=2, soft_max=1000,
|
||||
# description="Resolution of the simulation")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return True
|
||||
|
||||
#def invoke(self, context, event):
|
||||
# return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
tex_reaction_diffusion_add_handler(self, context)
|
||||
set_animatable_fix_handler(self, context)
|
||||
|
||||
ob = context.object
|
||||
props = ob.tex_reaction_diffusion_settings
|
||||
if props.img_a in bpy.data.images.keys():
|
||||
img_a = bpy.data.images[props.img_a]
|
||||
img_a.scale(props.res_x, props.res_y)
|
||||
else:
|
||||
img_a = bpy.data.images.new(name="A", width=props.res_x, height=props.res_y)
|
||||
if props.img_b in bpy.data.images.keys():
|
||||
img_b = bpy.data.images[props.img_b]
|
||||
img_b.scale(props.res_x, props.res_y)
|
||||
else:
|
||||
img_b = bpy.data.images.new(name="B", width=props.res_x, height=props.res_y)
|
||||
props.run = True
|
||||
#props.res_x = self.res_x
|
||||
#props.res_y = self.res_y
|
||||
props.img_a = img_a.name
|
||||
props.img_b = img_b.name
|
||||
|
||||
#props.run = self.run
|
||||
#props.dt = self.dt
|
||||
#props.time_steps = self.time_steps
|
||||
#props.f = self.f
|
||||
#props.k = self.k
|
||||
#props.diff_a = self.diff_a
|
||||
#props.diff_b = self.diff_b
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class TISSUE_PT_tex_reaction_diffusion(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
bl_label = "Tissue Texture Reaction-Diffusion"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
#@classmethod
|
||||
#def poll(cls, context):
|
||||
# return True
|
||||
|
||||
def draw(self, context):
|
||||
tex_reaction_diffusion_add_handler(self, context)
|
||||
ob = bpy.context.object
|
||||
props = ob.tex_reaction_diffusion_settings
|
||||
img_a = props.img_a
|
||||
img_b = props.img_b
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
if not (img_a and img_b in bpy.data.images):
|
||||
row.operator("object.start_tex_reaction_diffusion",
|
||||
icon="EXPERIMENTAL")
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(props, 'res_x')
|
||||
row.prop(props, 'res_y')
|
||||
col.separator()
|
||||
col.prop_search(props, 'img_a', bpy.data, "images")
|
||||
col.prop_search(props, 'img_b', bpy.data, "images")
|
||||
else:
|
||||
row.operator("object.reset_tex_reaction_diffusion",
|
||||
icon="EXPERIMENTAL")
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "run", text="Run Reaction-Diffusion")
|
||||
col = layout.column(align=True)
|
||||
row = col.row(align=True)
|
||||
row.prop(props, 'res_x')
|
||||
row.prop(props, 'res_y')
|
||||
col.separator()
|
||||
col.prop_search(props, 'img_a', bpy.data, "images")
|
||||
col.prop_search(props, 'img_b', bpy.data, "images")
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "time_steps")
|
||||
row.prop(props, "dt")
|
||||
row.enabled = not props.bool_cache
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
col1 = row.column(align=True)
|
||||
col1.prop(props, "diff_a")
|
||||
col1.enabled = props.img_diff_a == '' and not props.bool_cache
|
||||
col1 = row.column(align=True)
|
||||
col1.prop(props, "diff_b")
|
||||
col1.enabled = props.img_diff_b == '' and not props.bool_cache
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "diff_mult")
|
||||
row.enabled = props.img_scale == '' and not props.bool_cache
|
||||
#col.separator()
|
||||
row = col.row(align=True)
|
||||
col1 = row.column(align=True)
|
||||
col1.prop(props, "f")
|
||||
col1.enabled = props.img_f == '' and not props.bool_cache
|
||||
col1 = row.column(align=True)
|
||||
col1.prop(props, "k")
|
||||
col1.enabled = props.img_k == '' and not props.bool_cache
|
||||
'''
|
||||
col.separator()
|
||||
col.label(text='Cache:')
|
||||
#col.prop(props, "bool_cache")
|
||||
col.prop(props, "cache_dir", text='')
|
||||
col.separator()
|
||||
row = col.row(align=True)
|
||||
row.prop(props, "cache_frame_start")
|
||||
row.prop(props, "cache_frame_end")
|
||||
col.separator()
|
||||
if props.bool_cache:
|
||||
col.operator("object.reaction_diffusion_free_data")
|
||||
else:
|
||||
row = col.row(align=True)
|
||||
row.operator("object.bake_reaction_diffusion")
|
||||
file = bpy.context.blend_data.filepath
|
||||
temp = bpy.context.preferences.filepaths.temporary_directory
|
||||
if file == temp == props.cache_dir == '':
|
||||
row.enabled = False
|
||||
col.label(text="Cannot use cache", icon='ERROR')
|
||||
col.label(text='please save the Blender or set a Cache directory')
|
||||
'''
|
||||
|
||||
class TISSUE_PT_tex_reaction_diffusion_images(Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
bl_parent_id = "TISSUE_PT_tex_reaction_diffusion"
|
||||
bl_label = "Image Maps"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
props = context.object.tex_reaction_diffusion_settings
|
||||
if props.img_a and props.img_b in bpy.data.images.keys():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def draw(self, context):
|
||||
ob = context.object
|
||||
props = ob.tex_reaction_diffusion_settings
|
||||
layout = self.layout
|
||||
#layout.use_property_split = True
|
||||
col = layout.column(align=True)
|
||||
insert_image_parameter(col, ob, 'brush', text='Brush:')
|
||||
insert_image_parameter(col, ob, 'diff_a', text='Diff A:')
|
||||
insert_image_parameter(col, ob, 'diff_b', text='Diff B:')
|
||||
insert_image_parameter(col, ob, 'scale', text='Scale:')
|
||||
insert_image_parameter(col, ob, 'f', text='f:')
|
||||
insert_image_parameter(col, ob, 'k', text='k:')
|
||||
insert_image_parameter(col, ob, 'vector_field', text='Vector Field:')
|
||||
col.enabled = not props.bool_cache
|
||||
|
||||
def insert_image_parameter(col, ob, name, text=''):
|
||||
props = ob.tex_reaction_diffusion_settings
|
||||
split = col.split(factor=0.25, align=True)
|
||||
col2 = split.column(align=True)
|
||||
col2.label(text=text)
|
||||
col2 = split.column(align=True)
|
||||
row2 = col2.row(align=True)
|
||||
row2.prop_search(props, 'img_' + name, bpy.data, "images", text='')
|
||||
if name not in ('brush'):
|
||||
if name == 'vector_field': icon = 'DRIVER_ROTATIONAL_DIFFERENCE'#'ORIENTATION_VIEW'
|
||||
else: icon = 'ARROW_LEFTRIGHT'
|
||||
row2.prop(props, "invert_img_" + name, text="", toggle=True, icon=icon)
|
||||
if 'img_' + name in props:
|
||||
if props['img_' + name] != '':
|
||||
if name == 'brush':
|
||||
col2.prop(props, "brush_mult")
|
||||
elif name == 'vector_field':
|
||||
col2.prop(props, "anisotropy")
|
||||
else:
|
||||
row2 = col2.row(align=True)
|
||||
row2.prop(props, "min_" + name, text="Min")
|
||||
row2 = col2.row(align=True)
|
||||
row2.prop(props, "max_" + name, text="Max")
|
||||
col.separator()
|
@ -1,7 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2022-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
|
||||
# ------------------------------- version 0.84 ------------------------------- #
|
||||
# #
|
||||
@ -29,7 +45,9 @@ from bpy.props import (
|
||||
StringProperty,
|
||||
PointerProperty
|
||||
)
|
||||
from .utils import tissue_time
|
||||
from . import config
|
||||
import time
|
||||
|
||||
|
||||
def update_dependencies(ob, objects):
|
||||
@ -50,6 +68,8 @@ def get_deps(ob):
|
||||
return [ob.tissue_tessellate.generator, ob.tissue_tessellate.component]
|
||||
elif type == 'TO_CURVE':
|
||||
return [ob.tissue_to_curve.object]
|
||||
elif type == 'POLYHEDRA':
|
||||
return [ob.tissue_polyhedra.object]
|
||||
else: return []
|
||||
|
||||
def anim_tessellate_active(self, context):
|
||||
@ -75,10 +95,9 @@ def anim_tessellate_object(ob):
|
||||
#from bpy.app.handlers import persistent
|
||||
|
||||
|
||||
def anim_tessellate(scene, depsgraph=None):
|
||||
print('Tissue: animating tessellations...')
|
||||
|
||||
#config.evaluatedDepsgraph = depsgraph
|
||||
def anim_tissue(scene, depsgraph=None):
|
||||
tissue_time(None,'Tissue: Animating Tissue objects at frame {}...'.format(scene.frame_current), levels=0)
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
active_object = bpy.context.object
|
||||
@ -109,10 +128,15 @@ def anim_tessellate(scene, depsgraph=None):
|
||||
override['mode'] = 'OBJECT'
|
||||
override['view_layer'] = scene.view_layers[0]
|
||||
break
|
||||
if ob.tissue.tissue_type == 'TESSELLATE':
|
||||
bpy.ops.object.tissue_update_tessellate(override)
|
||||
elif ob.tissue.tissue_type == 'TO_CURVE':
|
||||
bpy.ops.object.tissue_convert_to_curve_update(override)
|
||||
with bpy.context.temp_override(**override):
|
||||
if ob.tissue.tissue_type == 'TESSELLATE':
|
||||
bpy.ops.object.tissue_update_tessellate()
|
||||
elif ob.tissue.tissue_type == 'TO_CURVE':
|
||||
bpy.ops.object.tissue_update_convert_to_curve()
|
||||
elif ob.tissue.tissue_type == 'POLYHEDRA':
|
||||
bpy.ops.object.tissue_update_polyhedra()
|
||||
elif ob.tissue.tissue_type == 'CONTOUR_CURVES':
|
||||
bpy.ops.object.tissue_update_contour_curves()
|
||||
|
||||
if old_mode != None:
|
||||
objects = bpy.context.view_layer.objects
|
||||
@ -121,68 +145,40 @@ def anim_tessellate(scene, depsgraph=None):
|
||||
bpy.ops.object.mode_set(mode=old_mode)
|
||||
|
||||
config.evaluatedDepsgraph = None
|
||||
print('end')
|
||||
tissue_time(start_time,'Animated Tissue objects at frame {}'.format(scene.frame_current), levels=0)
|
||||
return
|
||||
'''
|
||||
def OLD_anim_tessellate(scene, depsgraph):
|
||||
print('Tissue: animating tessellations...')
|
||||
|
||||
#global evaluatedDepsgraph
|
||||
#print(evaluatedDepsgraph)
|
||||
print(config.evaluatedDepsgraph)
|
||||
config.evaluatedDepsgraph = depsgraph
|
||||
print(config.evaluatedDepsgraph)
|
||||
|
||||
try:
|
||||
active_object = bpy.context.object
|
||||
old_mode = bpy.context.object.mode
|
||||
selected_objects = bpy.context.selected_objects
|
||||
except: active_object = old_mode = selected_objects = None
|
||||
|
||||
if old_mode in ('OBJECT', 'PAINT_WEIGHT') or True:
|
||||
update_objects = []
|
||||
for ob in scene.objects:
|
||||
if ob.tissue.bool_run and not ob.tissue.bool_lock:
|
||||
if ob not in update_objects: update_objects.append(ob)
|
||||
update_objects = list(reversed(update_dependencies(ob, update_objects)))
|
||||
for ob in update_objects:
|
||||
for window in bpy.context.window_manager.windows:
|
||||
screen = window.screen
|
||||
for area in screen.areas:
|
||||
if area.type == 'VIEW_3D':
|
||||
override = bpy.context.copy()
|
||||
override['window'] = window
|
||||
override['screen'] = screen
|
||||
override['area'] = area
|
||||
override['selected_objects'] = [ob]
|
||||
override['object'] = ob
|
||||
override['active_object'] = ob
|
||||
override['selected_editable_objects'] = [ob]
|
||||
override['mode'] = 'OBJECT'
|
||||
override['view_layer'] = scene.view_layers[0]
|
||||
break
|
||||
bpy.ops.object.tissue_update_tessellate(override)
|
||||
|
||||
config.evaluatedDepsgraph = None
|
||||
print('end')
|
||||
print(config.evaluatedDepsgraph)
|
||||
return
|
||||
'''
|
||||
def remove_tessellate_handler():
|
||||
def remove_tissue_handler():
|
||||
tissue_handlers = []
|
||||
blender_handlers = bpy.app.handlers.frame_change_post
|
||||
for h in blender_handlers:
|
||||
if "anim_tessellate" in str(h):
|
||||
if "anim_tissue" in str(h):
|
||||
tissue_handlers.append(h)
|
||||
for h in tissue_handlers: blender_handlers.remove(h)
|
||||
|
||||
def set_tessellate_handler(self, context):
|
||||
|
||||
remove_tessellate_handler()
|
||||
def set_tissue_handler(self, context):
|
||||
remove_tissue_handler()
|
||||
for o in context.scene.objects:
|
||||
if o.tissue.bool_run:
|
||||
blender_handlers = bpy.app.handlers.frame_change_post
|
||||
blender_handlers.append(anim_tessellate)
|
||||
blender_handlers.append(anim_tissue)
|
||||
break
|
||||
return
|
||||
|
||||
def remove_polyhedra_handler():
|
||||
tissue_handlers = []
|
||||
blender_handlers = bpy.app.handlers.frame_change_post
|
||||
for h in blender_handlers:
|
||||
if "anim_polyhedra" in str(h):
|
||||
tissue_handlers.append(h)
|
||||
for h in tissue_handlers: blender_handlers.remove(h)
|
||||
|
||||
def set_polyhedra_handler(self, context):
|
||||
remove_polyhedra_handler()
|
||||
for o in context.scene.objects:
|
||||
if o.tissue.bool_run:
|
||||
blender_handlers = bpy.app.handlers.frame_change_post
|
||||
blender_handlers.append(anim_polyhedra)
|
||||
break
|
||||
return
|
||||
|
||||
@ -190,7 +186,7 @@ def set_tessellate_handler(self, context):
|
||||
class tissue_prop(PropertyGroup):
|
||||
bool_lock : BoolProperty(
|
||||
name="Lock",
|
||||
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy",
|
||||
description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.",
|
||||
default=False
|
||||
)
|
||||
bool_dependencies : BoolProperty(
|
||||
@ -202,17 +198,24 @@ class tissue_prop(PropertyGroup):
|
||||
name="Animatable",
|
||||
description="Automatically recompute the geometry when the frame is changed. Tessellations may not work using the default Render Animation",
|
||||
default = False,
|
||||
update = set_tessellate_handler
|
||||
update = set_tissue_handler
|
||||
)
|
||||
tissue_type : EnumProperty(
|
||||
items=(
|
||||
('NONE', "None", ""),
|
||||
('TESSELLATE', "Tessellate", ""),
|
||||
('TO_CURVE', "To Curve", "")
|
||||
('TO_CURVE', "To Curve", ""),
|
||||
('POLYHEDRA', "Polyhedra", ""),
|
||||
('CONTOUR_CURVES', "Contour Curves", "")
|
||||
),
|
||||
default='NONE',
|
||||
name=""
|
||||
)
|
||||
bool_hold : BoolProperty(
|
||||
name="Hold",
|
||||
description="Wait...",
|
||||
default=False
|
||||
)
|
||||
|
||||
class tissue_tessellate_prop(PropertyGroup):
|
||||
bool_hold : BoolProperty(
|
||||
@ -561,13 +564,13 @@ class tissue_tessellate_prop(PropertyGroup):
|
||||
boundary_mat_offset : IntProperty(
|
||||
name="Material Offset",
|
||||
default=0,
|
||||
description="Material Offset for boundaries (with Multi Components or Material ID)",
|
||||
description="Material Offset for boundaries (with components based on Materials)",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
fill_frame_mat : IntProperty(
|
||||
name="Material Offset",
|
||||
default=0,
|
||||
description="Material Offset for inner faces (with Multi Components or Material ID)",
|
||||
description="Material Offset for inner faces (with components based on Materials)",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
open_edges_crease : FloatProperty(
|
||||
@ -598,14 +601,23 @@ class tissue_tessellate_prop(PropertyGroup):
|
||||
name="Frame Thickness",
|
||||
default=0.2,
|
||||
min=0,
|
||||
soft_max=2,
|
||||
soft_max=1,
|
||||
description="Frame Thickness",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
frame_boundary_thickness : FloatProperty(
|
||||
name="Frame Boundary Thickness",
|
||||
default=0,
|
||||
min=0,
|
||||
soft_max=1,
|
||||
description="Frame Boundary Thickness (when zero it uses the Frame Thickness instead)",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
frame_mode : EnumProperty(
|
||||
items=(
|
||||
('CONSTANT', 'Constant', 'Even thickness'),
|
||||
('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
|
||||
('RELATIVE', 'Relative', 'Frame offset depends on face areas'),
|
||||
('CENTER', 'Center', 'Toward the center of the face (uses Incenter for Triangles)')),
|
||||
default='CONSTANT',
|
||||
name="Offset",
|
||||
update = anim_tessellate_active
|
||||
@ -641,7 +653,7 @@ class tissue_tessellate_prop(PropertyGroup):
|
||||
)
|
||||
use_origin_offset : BoolProperty(
|
||||
name="Align to Origins",
|
||||
default=False,
|
||||
default=True,
|
||||
description="Define offset according to components origin and local Z coordinate",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
@ -665,6 +677,31 @@ class tissue_tessellate_prop(PropertyGroup):
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
|
||||
vertex_group_frame_thickness : StringProperty(
|
||||
name="Frame Thickness weight", default='',
|
||||
description="Vertex Group used for frame thickness",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
invert_vertex_group_frame_thickness : BoolProperty(
|
||||
name="Invert", default=False,
|
||||
description="Invert the vertex group influence",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
vertex_group_frame_thickness_factor : FloatProperty(
|
||||
name="Factor",
|
||||
default=0,
|
||||
min=0,
|
||||
max=1,
|
||||
description="Frame thickness factor to use for zero vertex group influence",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
face_weight_frame : BoolProperty(
|
||||
name="Face Weight",
|
||||
default=True,
|
||||
description="Uniform weight for individual faces",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
|
||||
vertex_group_cap_owner : EnumProperty(
|
||||
items=(
|
||||
('BASE', 'Base', 'Use base vertex group'),
|
||||
@ -802,6 +839,12 @@ class tissue_tessellate_prop(PropertyGroup):
|
||||
description="Automatically rotate the boundary faces",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
preserve_quads : BoolProperty(
|
||||
name="Preserve Quads",
|
||||
default=False,
|
||||
description="Quad faces are tessellated using QUAD mode",
|
||||
update = anim_tessellate_active
|
||||
)
|
||||
|
||||
def store_parameters(operator, ob):
|
||||
ob.tissue_tessellate.bool_hold = True
|
||||
@ -852,6 +895,7 @@ def store_parameters(operator, ob):
|
||||
ob.tissue_tessellate.bridge_cuts = operator.bridge_cuts
|
||||
ob.tissue_tessellate.bridge_smoothness = operator.bridge_smoothness
|
||||
ob.tissue_tessellate.frame_thickness = operator.frame_thickness
|
||||
ob.tissue_tessellate.frame_boundary_thickness = operator.frame_boundary_thickness
|
||||
ob.tissue_tessellate.frame_mode = operator.frame_mode
|
||||
ob.tissue_tessellate.frame_boundary = operator.frame_boundary
|
||||
ob.tissue_tessellate.fill_frame = operator.fill_frame
|
||||
@ -863,6 +907,10 @@ def store_parameters(operator, ob):
|
||||
ob.tissue_tessellate.vertex_group_thickness = operator.vertex_group_thickness
|
||||
ob.tissue_tessellate.invert_vertex_group_thickness = operator.invert_vertex_group_thickness
|
||||
ob.tissue_tessellate.vertex_group_thickness_factor = operator.vertex_group_thickness_factor
|
||||
ob.tissue_tessellate.vertex_group_frame_thickness = operator.vertex_group_frame_thickness
|
||||
ob.tissue_tessellate.invert_vertex_group_frame_thickness = operator.invert_vertex_group_frame_thickness
|
||||
ob.tissue_tessellate.vertex_group_frame_thickness_factor = operator.vertex_group_frame_thickness_factor
|
||||
ob.tissue_tessellate.face_weight_frame = operator.face_weight_frame
|
||||
ob.tissue_tessellate.vertex_group_distribution = operator.vertex_group_distribution
|
||||
ob.tissue_tessellate.invert_vertex_group_distribution = operator.invert_vertex_group_distribution
|
||||
ob.tissue_tessellate.vertex_group_distribution_factor = operator.vertex_group_distribution_factor
|
||||
@ -888,6 +936,7 @@ def store_parameters(operator, ob):
|
||||
ob.tissue_tessellate.invert_vertex_group_scale_normals = operator.invert_vertex_group_scale_normals
|
||||
ob.tissue_tessellate.boundary_variable_offset = operator.boundary_variable_offset
|
||||
ob.tissue_tessellate.auto_rotate_boundary = operator.auto_rotate_boundary
|
||||
ob.tissue_tessellate.preserve_quads = operator.preserve_quads
|
||||
ob.tissue_tessellate.bool_hold = False
|
||||
return ob
|
||||
|
||||
@ -938,11 +987,16 @@ def load_parameters(operator, ob):
|
||||
operator.boundary_mat_offset = ob.tissue_tessellate.boundary_mat_offset
|
||||
operator.fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
|
||||
operator.frame_thickness = ob.tissue_tessellate.frame_thickness
|
||||
operator.frame_boundary_thickness = ob.tissue_tessellate.frame_boundary_thickness
|
||||
operator.frame_mode = ob.tissue_tessellate.frame_mode
|
||||
operator.use_origin_offset = ob.tissue_tessellate.use_origin_offset
|
||||
operator.vertex_group_thickness = ob.tissue_tessellate.vertex_group_thickness
|
||||
operator.invert_vertex_group_thickness = ob.tissue_tessellate.invert_vertex_group_thickness
|
||||
operator.vertex_group_thickness_factor = ob.tissue_tessellate.vertex_group_thickness_factor
|
||||
operator.vertex_group_frame_thickness = ob.tissue_tessellate.vertex_group_frame_thickness
|
||||
operator.invert_vertex_group_frame_thickness = ob.tissue_tessellate.invert_vertex_group_frame_thickness
|
||||
operator.vertex_group_frame_thickness_factor = ob.tissue_tessellate.vertex_group_frame_thickness_factor
|
||||
operator.face_weight_frame = ob.tissue_tessellate.face_weight_frame
|
||||
operator.vertex_group_distribution = ob.tissue_tessellate.vertex_group_distribution
|
||||
operator.invert_vertex_group_distribution = ob.tissue_tessellate.invert_vertex_group_distribution
|
||||
operator.vertex_group_distribution_factor = ob.tissue_tessellate.vertex_group_distribution_factor
|
||||
@ -968,6 +1022,7 @@ def load_parameters(operator, ob):
|
||||
operator.invert_vertex_group_scale_normals = ob.tissue_tessellate.invert_vertex_group_scale_normals
|
||||
operator.boundary_variable_offset = ob.tissue_tessellate.boundary_variable_offset
|
||||
operator.auto_rotate_boundary = ob.tissue_tessellate.auto_rotate_boundary
|
||||
operator.preserve_quads = ob.tissue_tessellate.preserve_quads
|
||||
return ob
|
||||
|
||||
def props_to_dict(ob):
|
||||
@ -1003,6 +1058,7 @@ def props_to_dict(ob):
|
||||
tessellate_dict['even_thickness'] = props.even_thickness
|
||||
tessellate_dict['even_thickness_iter'] = props.even_thickness_iter
|
||||
tessellate_dict['frame_thickness'] = props.frame_thickness
|
||||
tessellate_dict['frame_boundary_thickness'] = props.frame_boundary_thickness
|
||||
tessellate_dict['frame_mode'] = props.frame_mode
|
||||
tessellate_dict['frame_boundary'] = props.frame_boundary
|
||||
tessellate_dict['fill_frame'] = props.fill_frame
|
||||
@ -1011,6 +1067,10 @@ def props_to_dict(ob):
|
||||
tessellate_dict['vertex_group_thickness'] = props.vertex_group_thickness
|
||||
tessellate_dict['invert_vertex_group_thickness'] = props.invert_vertex_group_thickness
|
||||
tessellate_dict['vertex_group_thickness_factor'] = props.vertex_group_thickness_factor
|
||||
tessellate_dict['vertex_group_frame_thickness'] = props.vertex_group_frame_thickness
|
||||
tessellate_dict['invert_vertex_group_frame_thickness'] = props.invert_vertex_group_frame_thickness
|
||||
tessellate_dict['vertex_group_frame_thickness_factor'] = props.vertex_group_frame_thickness_factor
|
||||
tessellate_dict['face_weight_frame'] = props.face_weight_frame
|
||||
tessellate_dict['vertex_group_distribution'] = props.vertex_group_distribution
|
||||
tessellate_dict['invert_vertex_group_distribution'] = props.invert_vertex_group_distribution
|
||||
tessellate_dict['vertex_group_distribution_factor'] = props.vertex_group_distribution_factor
|
||||
@ -1036,6 +1096,10 @@ def props_to_dict(ob):
|
||||
tessellate_dict["invert_vertex_group_scale_normals"] = props.invert_vertex_group_scale_normals
|
||||
tessellate_dict["boundary_variable_offset"] = props.boundary_variable_offset
|
||||
tessellate_dict["auto_rotate_boundary"] = props.auto_rotate_boundary
|
||||
tessellate_dict["merge"] = props.merge
|
||||
tessellate_dict["merge_thres"] = props.merge_thres
|
||||
tessellate_dict["merge_open_edges_only"] = props.merge_open_edges_only
|
||||
tessellate_dict["preserve_quads"] = props.preserve_quads
|
||||
return tessellate_dict
|
||||
|
||||
def copy_tessellate_props(source_ob, target_ob):
|
||||
|
@ -1,5 +1,3 @@
|
||||
# SPDX-FileCopyrightText: 2019-2023 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy, bmesh
|
||||
@ -183,6 +181,25 @@ def vector_rotation(vec):
|
||||
if ang < 0: ang = 2*pi + ang
|
||||
return ang
|
||||
|
||||
def signed_angle_with_axis(va, vb, axis):
|
||||
return atan2(va.cross(vb).dot(axis.normalized()), va.dot(vb))
|
||||
|
||||
def round_angle_with_axis(va, vb, axis):
|
||||
angle = signed_angle_with_axis(va, vb, axis)
|
||||
return 2*pi + angle if angle < 0 else angle
|
||||
|
||||
def incenter(vecs):
|
||||
lengths = x = y = z = 0
|
||||
mid = len(vecs)//2+1
|
||||
for vi, vj, vk in zip(vecs, vecs[1:]+vecs[:1], vecs[mid:]+vecs[:mid]):
|
||||
length = (vj-vi).length
|
||||
lengths += length
|
||||
x += length*vk.x
|
||||
y += length*vk.y
|
||||
z += length*vk.z
|
||||
inc = Vector((x/lengths, y/lengths, z/lengths))
|
||||
return inc
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# SCENE
|
||||
# ------------------------------------------------------------------
|
||||
@ -215,7 +232,15 @@ def turn_off_animatable(scene):
|
||||
# OBJECTS
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
|
||||
def remove_temp_objects():
|
||||
# clean objects
|
||||
for o in bpy.data.objects:
|
||||
if "_tissue_tmp" in o.name:
|
||||
bpy.data.objects.remove(o)
|
||||
return
|
||||
|
||||
def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True, mirror_correction = True):
|
||||
#mirror_correction = False
|
||||
try: ob.name
|
||||
except: return None
|
||||
if ob.type != 'MESH':
|
||||
@ -226,7 +251,10 @@ def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
|
||||
#dg = bpy.context.evaluated_depsgraph_get()
|
||||
#ob_eval = ob.evaluated_get(dg)
|
||||
#me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
|
||||
me = simple_to_mesh(ob)
|
||||
if mirror_correction:
|
||||
me = simple_to_mesh_mirror(ob)
|
||||
else:
|
||||
me = simple_to_mesh(ob)
|
||||
new_ob = bpy.data.objects.new(ob.data.name, me)
|
||||
new_ob.location, new_ob.matrix_world = ob.location, ob.matrix_world
|
||||
if not apply_modifiers:
|
||||
@ -234,7 +262,10 @@ def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
|
||||
else:
|
||||
if apply_modifiers:
|
||||
new_ob = ob.copy()
|
||||
new_me = simple_to_mesh(ob)
|
||||
if mirror_correction:
|
||||
new_me = simple_to_mesh_mirror(ob)
|
||||
else:
|
||||
new_me = simple_to_mesh(ob)
|
||||
new_ob.modifiers.clear()
|
||||
new_ob.data = new_me
|
||||
else:
|
||||
@ -250,6 +281,76 @@ def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
|
||||
bpy.context.view_layer.objects.active = new_ob
|
||||
return new_ob
|
||||
|
||||
def simple_to_mesh_mirror(ob, depsgraph=None):
|
||||
'''
|
||||
Convert object to mesh applying Modifiers and Shape Keys.
|
||||
Automatically correct Faces rotation for Tessellations.
|
||||
'''
|
||||
if 'MIRROR' in [m.type for m in ob.modifiers]:
|
||||
|
||||
_ob = ob.copy()
|
||||
_ob.name = _ob.name + "_mirror"
|
||||
bpy.context.collection.objects.link(_ob)
|
||||
# Store modifiers
|
||||
mods = list(_ob.modifiers)
|
||||
# Store visibility setting
|
||||
mods_vis = [m.show_viewport for m in _ob.modifiers]
|
||||
# Turn modifiers off
|
||||
for m in _ob.modifiers:
|
||||
m.show_viewport = False
|
||||
while True:
|
||||
if len(mods) == 0: break
|
||||
remove_mods = []
|
||||
|
||||
for m, vis in zip(mods, mods_vis):
|
||||
m.show_viewport = vis
|
||||
remove_mods.append(m)
|
||||
if m.type == 'MIRROR' and vis:
|
||||
n_axis = m.use_axis[0] + m.use_axis[1] + m.use_axis[2]
|
||||
fraction = 2**n_axis
|
||||
me = simple_to_mesh(_ob, depsgraph)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
bm.faces.ensure_lookup_table()
|
||||
n_faces = len(bm.faces)
|
||||
if n_axis > 0:
|
||||
bm.faces.ensure_lookup_table()
|
||||
rotate_faces = bm.faces
|
||||
rot_index = []
|
||||
if n_axis == 1: fraction_val = [0,1]
|
||||
elif n_axis == 2: fraction_val = [0,1,1,0]
|
||||
elif n_axis == 3: fraction_val = [0,1,1,0,1,0,0,1]
|
||||
for i in fraction_val:
|
||||
for j in range(n_faces//fraction):
|
||||
rot_index.append(i)
|
||||
for face, shift in zip(rotate_faces, rot_index):
|
||||
if shift == 0: continue
|
||||
vs = face.verts[:]
|
||||
vs2 = vs[-shift:]+vs[:-shift]
|
||||
material_index = face.material_index
|
||||
bm.faces.remove(face)
|
||||
f2 = bm.faces.new(vs2)
|
||||
f2.select = True
|
||||
f2.material_index = material_index
|
||||
bm.normal_update()
|
||||
bm.to_mesh(me)
|
||||
bm.free()
|
||||
for rm in remove_mods:
|
||||
_ob.modifiers.remove(rm)
|
||||
_ob.data = me
|
||||
mods = mods[1:]
|
||||
mods_vis = mods_vis[1:]
|
||||
remove_mods = []
|
||||
break
|
||||
if m == mods[-1]:
|
||||
mods = []
|
||||
me = simple_to_mesh(_ob, depsgraph)
|
||||
_ob.data = me
|
||||
_ob.modifiers.clear()
|
||||
else:
|
||||
me = simple_to_mesh(ob, depsgraph)
|
||||
return me
|
||||
|
||||
def simple_to_mesh(ob, depsgraph=None):
|
||||
'''
|
||||
Convert object to mesh applying Modifiers and Shape Keys
|
||||
@ -263,6 +364,7 @@ def simple_to_mesh(ob, depsgraph=None):
|
||||
dg = depsgraph
|
||||
ob_eval = ob.evaluated_get(dg)
|
||||
me = bpy.data.meshes.new_from_object(ob_eval, preserve_all_data_layers=True, depsgraph=dg)
|
||||
#me.calc_normals()
|
||||
return me
|
||||
|
||||
def _join_objects(context, objects, link_to_scene=True, make_active=True):
|
||||
@ -320,11 +422,10 @@ def join_objects(context, objects):
|
||||
return new_ob
|
||||
|
||||
def join_objects(objects):
|
||||
override = bpy.context.copy()
|
||||
new_ob = objects[0]
|
||||
override['active_object'] = new_ob
|
||||
override['selected_editable_objects'] = objects
|
||||
bpy.ops.object.join(override)
|
||||
override = {'active_object': new_ob, 'selected_editable_objects': objects}
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.join()
|
||||
return new_ob
|
||||
|
||||
def repeat_mesh(me, n):
|
||||
@ -345,9 +446,8 @@ def array_mesh(ob, n):
|
||||
arr = ob.modifiers.new('Repeat','ARRAY')
|
||||
arr.relative_offset_displace[0] = 0
|
||||
arr.count = n
|
||||
# with bpy.context.temp_override(active_object=ob):
|
||||
# bpy.ops.object.modifier_apply(modifier='Repeat')
|
||||
# me = ob.data
|
||||
#bpy.ops.object.modifier_apply({'active_object':ob},modifier='Repeat')
|
||||
#me = ob.data
|
||||
ob.modifiers.update()
|
||||
|
||||
dg = bpy.context.evaluated_depsgraph_get()
|
||||
@ -366,7 +466,8 @@ def array_mesh_object(ob, n):
|
||||
override = bpy.context.copy()
|
||||
override['active_object'] = ob
|
||||
override = {'active_object': ob}
|
||||
bpy.ops.object.modifier_apply(override, modifier=arr.name)
|
||||
with bpy.context.temp_override(**override):
|
||||
bpy.ops.object.modifier_apply(modifier=arr.name)
|
||||
return ob
|
||||
|
||||
|
||||
@ -389,7 +490,7 @@ def get_mesh_before_subs(ob):
|
||||
hide_mods = []
|
||||
mods_visibility = []
|
||||
for m in hide_mods: m.show_viewport = False
|
||||
me = simple_to_mesh(ob)
|
||||
me = simple_to_mesh_mirror(ob)
|
||||
for m, vis in zip(hide_mods,mods_visibility): m.show_viewport = vis
|
||||
return me, subs
|
||||
|
||||
@ -542,9 +643,6 @@ def get_patches____(me_low, me_high, sides, subs, bool_selection, bool_material_
|
||||
# fill inners
|
||||
patches[:,1:-1,1:-1] = inners[None,:,:] + ips[:,None,None]
|
||||
|
||||
#end_time = time.time()
|
||||
#print('Tissue: Got Patches in {:.4f} sec'.format(end_time-start_time))
|
||||
|
||||
return patches, mask
|
||||
|
||||
def tessellate_prepare_component(ob1, props):
|
||||
@ -673,10 +771,11 @@ def tessellate_prepare_component(ob1, props):
|
||||
cut_edges = [g for g in bisect['geom_cut'] if type(g)==bmesh.types.BMEdge]
|
||||
cut_verts = [g for g in bisect['geom_cut'] if type(g)==bmesh.types.BMVert]
|
||||
|
||||
if bound!='CLIP':
|
||||
if True or bound!='CLIP':
|
||||
for e in cut_edges:
|
||||
seam = True
|
||||
# Prevent glitches
|
||||
'''
|
||||
for e1 in original_edges:
|
||||
match_00 = (e.verts[0].co-e1.verts[0].co).length < thres
|
||||
match_11 = (e.verts[1].co-e1.verts[1].co).length < thres
|
||||
@ -685,6 +784,7 @@ def tessellate_prepare_component(ob1, props):
|
||||
if (match_00 and match_11) or (match_01 and match_10):
|
||||
seam = False
|
||||
break
|
||||
'''
|
||||
e.seam = seam
|
||||
|
||||
if bound == 'CYCLIC':
|
||||
@ -912,6 +1012,26 @@ def get_edges_id_numpy(mesh):
|
||||
edges = np.concatenate((edges,indexes), axis=1)
|
||||
return edges
|
||||
|
||||
def get_edges_numpy_ex(mesh):
|
||||
'''
|
||||
Create a numpy array with the edges of a given mesh, or all the possible
|
||||
between the vertices of a same face
|
||||
'''
|
||||
edges_verts = get_edges_numpy(mesh)
|
||||
polygons_diag = []
|
||||
for f in mesh.polygons:
|
||||
sides = len(f.vertices)
|
||||
if sides < 4: continue
|
||||
for i in range(sides-2):
|
||||
v0 = f.vertices[i]
|
||||
for j in range(i+2, sides-1 if i == 0 else sides):
|
||||
v1 = f.vertices[j]
|
||||
polygons_diag.append((v0,v1))
|
||||
if len(polygons_diag) == 0:
|
||||
return edges_verts
|
||||
polygons_diag = np.array(polygons_diag,dtype=np.int32)
|
||||
return np.concatenate((edges_verts, polygons_diag), axis=0)
|
||||
|
||||
def get_polygons_select_numpy(mesh):
|
||||
n_polys = len(mesh.polygons)
|
||||
selections = [0]*n_polys*2
|
||||
@ -919,13 +1039,16 @@ def get_polygons_select_numpy(mesh):
|
||||
selections = np.array(selections)
|
||||
return selections
|
||||
|
||||
def get_attribute_numpy(elements_list, attribute='select', mult=1):
|
||||
def get_attribute_numpy(elements_list, attribute='select', mult=1, size=None):
|
||||
'''
|
||||
Generate a numpy array getting attribute from a list of element using
|
||||
the foreach_get() function.
|
||||
'''
|
||||
n_elements = len(elements_list)
|
||||
values = [0]*n_elements*mult
|
||||
if size:
|
||||
n_elements = size
|
||||
else:
|
||||
n_elements = len(elements_list)
|
||||
values = np.zeros(int(n_elements*mult))
|
||||
elements_list.foreach_get(attribute, values)
|
||||
values = np.array(values)
|
||||
if mult > 1: values = values.reshape((n_elements,mult))
|
||||
@ -1006,6 +1129,73 @@ def find_curves(edges, n_verts):
|
||||
curves.append(curve)
|
||||
return curves
|
||||
|
||||
def find_curves_attribute(edges, n_verts, attribute):
|
||||
# dictionary with a list for every point
|
||||
verts_dict = {key:[] for key in range(n_verts)}
|
||||
# get neighbors for every point
|
||||
for e in edges:
|
||||
verts_dict[e[0]].append(e[1])
|
||||
verts_dict[e[1]].append(e[0])
|
||||
curves = []
|
||||
ordered_attr = []
|
||||
while True:
|
||||
if len(verts_dict) == 0: break
|
||||
# next starting point
|
||||
v = list(verts_dict.keys())[0]
|
||||
# neighbors
|
||||
v01 = verts_dict[v]
|
||||
if len(v01) == 0:
|
||||
verts_dict.pop(v)
|
||||
continue
|
||||
curve = []
|
||||
attr = []
|
||||
if len(v01) > 1:
|
||||
curve.append(v01[1]) # add neighbors
|
||||
attr.append(attribute[v01[1]]) # add neighbors
|
||||
curve.append(v) # add starting point
|
||||
attr.append(attribute[v])
|
||||
curve.append(v01[0]) # add neighbors
|
||||
attr.append(attribute[v01[0]])
|
||||
verts_dict.pop(v)
|
||||
# start building curve
|
||||
while True:
|
||||
#last_point = curve[-1]
|
||||
#if last_point not in verts_dict: break
|
||||
|
||||
# try to change direction if needed
|
||||
if curve[-1] in verts_dict: pass
|
||||
elif curve[0] in verts_dict:
|
||||
curve.reverse()
|
||||
attr.reverse()
|
||||
else: break
|
||||
|
||||
# neighbors points
|
||||
last_point = curve[-1]
|
||||
v01 = verts_dict[last_point]
|
||||
|
||||
# curve end
|
||||
if len(v01) == 1:
|
||||
verts_dict.pop(last_point)
|
||||
if curve[0] in verts_dict: continue
|
||||
else: break
|
||||
|
||||
# chose next point
|
||||
new_point = None
|
||||
if v01[0] == curve[-2]: new_point = v01[1]
|
||||
elif v01[1] == curve[-2]: new_point = v01[0]
|
||||
#else: break
|
||||
|
||||
#if new_point != curve[1]:
|
||||
curve.append(new_point)
|
||||
ordered_attr.append(attr)
|
||||
verts_dict.pop(last_point)
|
||||
if curve[0] == curve[-1]:
|
||||
verts_dict.pop(new_point)
|
||||
break
|
||||
if(len(curve)>0):
|
||||
curves.append(curve)
|
||||
return curves, ordered_attr
|
||||
|
||||
def curve_from_points(points, name='Curve'):
|
||||
curve = bpy.data.curves.new(name,'CURVE')
|
||||
for c in points:
|
||||
@ -1015,8 +1205,54 @@ def curve_from_points(points, name='Curve'):
|
||||
ob_curve = bpy.data.objects.new(name,curve)
|
||||
return ob_curve
|
||||
|
||||
def curve_from_pydata(points, radii, indexes, name='Curve', skip_open=False, merge_distance=1, set_active=True, only_data=False):
|
||||
curve = bpy.data.curves.new(name,'CURVE')
|
||||
def curve_from_pydata(points, radii, indexes, name='Curve', skip_open=False, merge_distance=1, set_active=True, only_data=False, curve=None, spline_type='POLY'):
|
||||
if not curve:
|
||||
curve = bpy.data.curves.new(name,'CURVE')
|
||||
curve.dimensions = '3D'
|
||||
use_rad = True
|
||||
for c in indexes:
|
||||
bool_cyclic = c[0] == c[-1]
|
||||
if bool_cyclic: c.pop(-1)
|
||||
# cleanup
|
||||
pts = np.array([points[i] for i in c])
|
||||
try:
|
||||
rad = np.array([radii[i] for i in c])
|
||||
except:
|
||||
use_rad = False
|
||||
rad = 1
|
||||
if merge_distance > 0:
|
||||
pts1 = np.roll(pts,1,axis=0)
|
||||
dist = np.linalg.norm(np.array(pts1-pts, dtype=np.float64), axis=1)
|
||||
count = 0
|
||||
n = len(dist)
|
||||
mask = np.ones(n).astype('bool')
|
||||
for i in range(n):
|
||||
count += dist[i]
|
||||
if count > merge_distance: count = 0
|
||||
else: mask[i] = False
|
||||
pts = pts[mask]
|
||||
if use_rad: rad = rad[mask]
|
||||
|
||||
if skip_open and not bool_cyclic: continue
|
||||
s = curve.splines.new(spline_type)
|
||||
n_pts = len(pts)
|
||||
s.points.add(n_pts-1)
|
||||
w = np.ones(n_pts).reshape((n_pts,1))
|
||||
co = np.concatenate((pts,w),axis=1).reshape((n_pts*4))
|
||||
s.points.foreach_set('co',co)
|
||||
if use_rad: s.points.foreach_set('radius',rad)
|
||||
s.use_cyclic_u = bool_cyclic
|
||||
if only_data:
|
||||
return curve
|
||||
else:
|
||||
ob_curve = bpy.data.objects.new(name,curve)
|
||||
bpy.context.collection.objects.link(ob_curve)
|
||||
if set_active:
|
||||
bpy.context.view_layer.objects.active = ob_curve
|
||||
return ob_curve
|
||||
|
||||
def update_curve_from_pydata_simple(curve, points, radii, indexes, skip_open=False, merge_distance=1, set_active=True, only_data=False, spline_type='POLY'):
|
||||
curve.splines.clear()
|
||||
curve.dimensions = '3D'
|
||||
use_rad = True
|
||||
for c in indexes:
|
||||
@ -1043,7 +1279,7 @@ def curve_from_pydata(points, radii, indexes, name='Curve', skip_open=False, mer
|
||||
if use_rad: rad = rad[mask]
|
||||
|
||||
if skip_open and not bool_cyclic: continue
|
||||
s = curve.splines.new('POLY')
|
||||
s = curve.splines.new(spline_type)
|
||||
n_pts = len(pts)
|
||||
s.points.add(n_pts-1)
|
||||
w = np.ones(n_pts).reshape((n_pts,1))
|
||||
@ -1091,13 +1327,14 @@ def update_curve_from_pydata(curve, points, normals, radii, indexes, merge_dista
|
||||
#if skip_open and not bool_cyclic: continue
|
||||
n_pts = len(pts)
|
||||
series = np.arange(n_pts)
|
||||
patt1 = series + (series-series%pattern[1])/pattern[1]*pattern[0]+pattern[0]
|
||||
patt1 = patt1[patt1<n_pts].astype('int')
|
||||
patt0 = series + (series-series%pattern[0])/pattern[0]*pattern[1]
|
||||
patt0 = patt0[patt0<n_pts].astype('int')
|
||||
nor[patt0] *= 0.5*depth*(1 + offset)
|
||||
nor[patt1] *= 0.5*depth*(-1 + offset)
|
||||
if pattern[0]*pattern[1] != 0: pts += nor
|
||||
if pattern[0]*pattern[1] != 0:
|
||||
patt1 = series + (series-series%pattern[1])/pattern[1]*pattern[0]+pattern[0]
|
||||
patt1 = patt1[patt1<n_pts].astype('int')
|
||||
patt0 = series + (series-series%pattern[0])/pattern[0]*pattern[1]
|
||||
patt0 = patt0[patt0<n_pts].astype('int')
|
||||
nor[patt0] *= 0.5*depth*(1 + offset)
|
||||
nor[patt1] *= 0.5*depth*(-1 + offset)
|
||||
pts += nor
|
||||
s = curve.splines.new('POLY')
|
||||
s.points.add(n_pts-1)
|
||||
w = np.ones(n_pts).reshape((n_pts,1))
|
||||
@ -1253,7 +1490,7 @@ def get_weight(vertex_group, n_verts):
|
||||
:type vertex_group: :class:'bpy.types.VertexGroup'
|
||||
:arg n_verts: Number of Vertices (output list size).
|
||||
:type n_verts: int
|
||||
:return: Read weight values.
|
||||
:return: Readed weight values.
|
||||
:rtype: list
|
||||
"""
|
||||
weight = [0]*n_verts
|
||||
@ -1269,22 +1506,24 @@ def get_weight_numpy(vertex_group, n_verts):
|
||||
:type vertex_group: :class:'bpy.types.VertexGroup'
|
||||
:arg n_verts: Number of Vertices (output list size).
|
||||
:type n_verts: int
|
||||
:return: Read weight values as numpy array.
|
||||
:return: Readed weight values as numpy array.
|
||||
:rtype: :class:'numpy.ndarray'
|
||||
"""
|
||||
weight = [0]*n_verts
|
||||
weight = np.zeros(n_verts)
|
||||
for i in range(n_verts):
|
||||
try: weight[i] = vertex_group.weight(i)
|
||||
except: pass
|
||||
return np.array(weight)
|
||||
return weight
|
||||
|
||||
def bmesh_get_weight_numpy(group_index, layer, verts):
|
||||
def bmesh_get_weight_numpy(group_index, layer, verts, normalized=False):
|
||||
weight = np.zeros(len(verts))
|
||||
for i, v in enumerate(verts):
|
||||
dvert = v[layer]
|
||||
if group_index in dvert:
|
||||
weight[i] = dvert[group_index]
|
||||
#dvert[group_index] = 0.5
|
||||
if normalized:
|
||||
weight = (weight - np.min(weight))/np.ptp(weight)
|
||||
return weight
|
||||
|
||||
def bmesh_set_weight_numpy(group_index, layer, verts, weight):
|
||||
@ -1410,6 +1649,22 @@ def mesh_diffusion_vector(me, vectors, iter, diff, uv_dir=0):
|
||||
vectors[:,2] = z
|
||||
return vectors
|
||||
|
||||
def fill_neighbors_attribute(verts,weight,attribute):
|
||||
neigh = {}
|
||||
for v0 in verts:
|
||||
for f in v0.link_faces:
|
||||
for v1 in f.verts:
|
||||
if attribute == 'GEODESIC':
|
||||
dist = weight[v0.index] + (v0.co-v1.co).length
|
||||
elif attribute == 'TOPOLOGY':
|
||||
dist = weight[v0.index] + 1.0
|
||||
w1 = weight[v1.index]
|
||||
if w1 == None or w1 > dist:
|
||||
weight[v1.index] = dist
|
||||
neigh[v1] = 0
|
||||
if len(neigh) == 0: return weight
|
||||
else: return fill_neighbors_attribute(neigh.keys(), weight, attribute)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# MODIFIERS
|
||||
# ------------------------------------------------------------------
|
||||
@ -1434,7 +1689,7 @@ def mod_preserve_shape(mod):
|
||||
|
||||
def recurLayerCollection(layerColl, collName):
|
||||
'''
|
||||
Recursively transverse layer_collection for a particular name.
|
||||
Recursivly transverse layer_collection for a particular name.
|
||||
'''
|
||||
found = None
|
||||
if (layerColl.name == collName):
|
||||
@ -1456,3 +1711,15 @@ def auto_layer_collection():
|
||||
lc = recurLayerCollection(layer_collection, c.name)
|
||||
if not c.hide_viewport and not lc.hide_viewport:
|
||||
bpy.context.view_layer.active_layer_collection = lc
|
||||
|
||||
def np_remap_image_values(img, channel=0, min=0, max=1, invert=False):
|
||||
nx = img.size[1]
|
||||
ny = img.size[0]
|
||||
px = np.float32(np.zeros(nx*ny*4))
|
||||
img.pixels.foreach_get(px)
|
||||
px = np.array(px).reshape((-1,4))
|
||||
values = px[:,channel]
|
||||
values = values.reshape((nx,ny))
|
||||
if invert:
|
||||
values = 1-values
|
||||
return min + values*(max-min)
|
||||
|
@ -1,7 +1,25 @@
|
||||
# SPDX-FileCopyrightText: 2022 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Author: Stephen Leger (s-leger)
|
||||
#
|
||||
@ -26,7 +44,7 @@ class Pip:
|
||||
import sys
|
||||
site_package = site.getusersitepackages()
|
||||
if not os.path.exists(site_package):
|
||||
site_package = bpy.utils.user_resource('SCRIPTS', "site_package", create=True)
|
||||
site_package = bpy.utils.user_resource('SCRIPTS', path="site_package", create=True)
|
||||
site.addsitedir(site_package)
|
||||
if site_package not in sys.path:
|
||||
sys.path.append(site_package)
|
||||
|
@ -1,7 +1,23 @@
|
||||
# SPDX-FileCopyrightText: 2017-2022 Blender Foundation
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# 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 2
|
||||
# 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, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# --------------------------------- UV to MESH ------------------------------- #
|
||||
# -------------------------------- version 0.1.1 ----------------------------- #
|
||||
# #
|
||||
|
1180
mesh_tissue/weight_reaction_diffusion.py
Normal file
1180
mesh_tissue/weight_reaction_diffusion.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user