Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2019-07-01 14:06:16 +0300
committermeta-androcto <meta.androcto1@gmail.com>2019-07-01 14:06:16 +0300
commit498d912a9d5854c0cfc1f54ed8b4216d442b89f1 (patch)
tree66e565094b4eec07c2e05c9106724ee5e4649454 /mesh_tissue
parentfffaf5d2759d38d4166f608eab8871fcd59a7e11 (diff)
mesh_tissue: initial update 2.80
Diffstat (limited to 'mesh_tissue')
-rw-r--r--mesh_tissue/README.md30
-rw-r--r--mesh_tissue/__init__.py78
-rw-r--r--mesh_tissue/colors_groups_exchanger.py2663
-rw-r--r--mesh_tissue/dual_mesh.py166
-rw-r--r--mesh_tissue/lattice.py181
-rw-r--r--mesh_tissue/numba_functions.py40
-rw-r--r--mesh_tissue/tessellate_numpy.py3589
-rw-r--r--mesh_tissue/utils.py110
-rw-r--r--mesh_tissue/uv_to_mesh.py36
9 files changed, 5268 insertions, 1625 deletions
diff --git a/mesh_tissue/README.md b/mesh_tissue/README.md
index 9998d1cf..e5511cef 100644
--- a/mesh_tissue/README.md
+++ b/mesh_tissue/README.md
@@ -1,14 +1,38 @@
# 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
-Latest version (master): https://github.com/alessandro-zomparelli/tissue/archive/master.zip
+### Blender 2.79
-Releases: https://github.com/alessandro-zomparelli/tissue/releases
+Official version (master): https://github.com/alessandro-zomparelli/tissue/archive/master.zip
-Installation:
+Latest development version (dev1): https://github.com/alessandro-zomparelli/tissue/tree/dev1
+(Includes animatable Tessellation)
+
+### Blender 2.80
+
+Latest development version (b280-dev): https://github.com/alessandro-zomparelli/tissue/tree/b280-dev
+(Includes animatable Tessellation and Patch method)
+
+
+
+### Installation:
1. Start Blender. Open User Preferences, the addons tab
2. Click "install from file" and point Blender at the downloaded zip
3. Activate Tissue add-on from user preferences
3. Save user preferences if you want to have it on at startup.
+
+
+### Contribute
+Please help me keeping Tissue stable and updated, report any issue here: https://github.com/alessandro-zomparelli/tissue/issues
+
+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.
+If you like my work and you want to help to continue the development of Tissue, please consider to make a small donation. Any small contribution is really appreciated, thanks! :-D
+
+Alessandro
+
+
+[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=ARSDJWXVFZ346)
+
diff --git a/mesh_tissue/__init__.py b/mesh_tissue/__init__.py
index d2fbb989..26fbd199 100644
--- a/mesh_tissue/__init__.py
+++ b/mesh_tissue/__init__.py
@@ -33,14 +33,13 @@
bl_info = {
"name": "Tissue",
"author": "Alessandro Zomparelli (Co-de-iT)",
- "version": (0, 3, 4),
- "blender": (2, 79, 0),
+ "version": (0, 3, 25),
+ "blender": (2, 80, 0),
"location": "",
"description": "Tools for Computational Design",
"warning": "",
- "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/"
- "Py/Scripts/Mesh/Tissue",
- "tracker_url": "https://plus.google.com/u/0/+AlessandroZomparelli/",
+ "wiki_url": "https://github.com/alessandro-zomparelli/tissue/wiki",
+ "tracker_url": "https://github.com/alessandro-zomparelli/tissue/issues",
"category": "Mesh"}
@@ -51,6 +50,7 @@ if "bpy" in locals():
importlib.reload(dual_mesh)
importlib.reload(lattice)
importlib.reload(uv_to_mesh)
+ importlib.reload(utils)
else:
from . import tessellate_numpy
@@ -58,24 +58,76 @@ else:
from . import dual_mesh
from . import lattice
from . import uv_to_mesh
+ from . import utils
import bpy
-from bpy.props import PointerProperty
+from bpy.props import PointerProperty, CollectionProperty, BoolProperty
+classes = (
+ tessellate_numpy.tissue_tessellate_prop,
+ tessellate_numpy.tessellate,
+ tessellate_numpy.update_tessellate,
+ tessellate_numpy.TISSUE_PT_tessellate,
+ tessellate_numpy.rotate_face,
+ tessellate_numpy.TISSUE_PT_tessellate_object,
+
+ colors_groups_exchanger.face_area_to_vertex_groups,
+ colors_groups_exchanger.vertex_colors_to_vertex_groups,
+ colors_groups_exchanger.vertex_group_to_vertex_colors,
+ colors_groups_exchanger.TISSUE_PT_weight,
+ colors_groups_exchanger.TISSUE_PT_color,
+ colors_groups_exchanger.weight_contour_curves,
+ colors_groups_exchanger.weight_contour_mask,
+ colors_groups_exchanger.weight_contour_displace,
+ colors_groups_exchanger.harmonic_weight,
+ colors_groups_exchanger.edges_deformation,
+ colors_groups_exchanger.edges_bending,
+ colors_groups_exchanger.weight_laplacian,
+ colors_groups_exchanger.reaction_diffusion,
+ colors_groups_exchanger.start_reaction_diffusion,
+ colors_groups_exchanger.TISSUE_PT_reaction_diffusion,
+ colors_groups_exchanger.reset_reaction_diffusion_weight,
+ colors_groups_exchanger.formula_prop,
+ colors_groups_exchanger.reaction_diffusion_prop,
+ colors_groups_exchanger.weight_formula,
+ colors_groups_exchanger.curvature_to_vertex_groups,
+ colors_groups_exchanger.weight_formula_wiki,
+
+ dual_mesh.dual_mesh,
+ dual_mesh.dual_mesh_tessellated,
+
+ lattice.lattice_along_surface,
+
+ uv_to_mesh.uv_to_mesh
+)
def register():
- bpy.utils.register_module(__name__)
+ from bpy.utils import register_class
+ for cls in classes:
+ bpy.utils.register_class(cls)
+ #bpy.utils.register_module(__name__)
bpy.types.Object.tissue_tessellate = PointerProperty(
type=tessellate_numpy.tissue_tessellate_prop
)
-
+ bpy.types.Object.formula_settings = CollectionProperty(
+ type=colors_groups_exchanger.formula_prop
+ )
+ bpy.types.Object.reaction_diffusion_settings = PointerProperty(
+ type=colors_groups_exchanger.reaction_diffusion_prop
+ )
+ # colors_groups_exchanger
+ bpy.app.handlers.frame_change_post.append(colors_groups_exchanger.reaction_diffusion_def)
+ #bpy.app.handlers.frame_change_post.append(tessellate_numpy.anim_tessellate)
def unregister():
- tessellate_numpy.unregister()
- colors_groups_exchanger.unregister()
- dual_mesh.unregister()
- lattice.unregister()
- uv_to_mesh.unregister()
+ from bpy.utils import unregister_class
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+ #tessellate_numpy.unregister()
+ #colors_groups_exchanger.unregister()
+ #dual_mesh.unregister()
+ #lattice.unregister()
+ #uv_to_mesh.unregister()
del bpy.types.Object.tissue_tessellate
diff --git a/mesh_tissue/colors_groups_exchanger.py b/mesh_tissue/colors_groups_exchanger.py
index 77c26d7e..0da15e85 100644
--- a/mesh_tissue/colors_groups_exchanger.py
+++ b/mesh_tissue/colors_groups_exchanger.py
@@ -16,12 +16,12 @@
#
# ##### END GPL LICENSE BLOCK #####
-# ------------------------- COLORS / GROUPS EXCHANGER ------------------------ #
+#-------------------------- COLORS / GROUPS EXCHANGER -------------------------#
# #
-# Vertex Color to Vertex Group allow you to convert colors channels to weight #
+# 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 software. The script works with the active vertex color #
+# 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). #
@@ -31,181 +31,1807 @@
# #
# http://www.co-de-it.com/ #
# #
-# ############################################################################ #
+################################################################################
+
+import bpy, bmesh
+import numpy as np
+import math, timeit
+from math import *#pi, sin
+from statistics import mean, stdev
+from mathutils import Vector
+from numpy import *
+try: from .numba_functions import numba_reaction_diffusion
+except: pass
+
+from bpy.types import (
+ Operator,
+ Panel,
+ PropertyGroup,
+ )
+
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+ FloatVectorProperty,
+ IntVectorProperty
+)
+
+from .utils import *
bl_info = {
"name": "Colors/Groups Exchanger",
"author": "Alessandro Zomparelli (Co-de-iT)",
- "version": (0, 3, 2),
- "blender": (2, 78, 0),
+ "version": (0, 3),
+ "blender": (2, 7, 9),
"location": "",
"description": ("Convert vertex colors channels to vertex groups and vertex"
" groups to colors"),
"warning": "",
"wiki_url": "",
+ "tracker_url": "",
"category": "Mesh"}
+def reaction_diffusion_add_handler(self, context):
+ # remove existing handlers
+ old_handlers = []
+ for h in bpy.app.handlers.frame_change_post:
+ if "reaction_diffusion" in str(h):
+ old_handlers.append(h)
+ for h in old_handlers: bpy.app.handlers.frame_change_post.remove(h)
+ # add new handler
+ bpy.app.handlers.frame_change_post.append(reaction_diffusion_def)
+
+class formula_prop(PropertyGroup):
+ name : StringProperty()
+ formula : StringProperty()
+ float_var : FloatVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
+ int_var : IntVectorProperty(name="", description="", default=(0, 0, 0, 0, 0), size=5)
+
+class reaction_diffusion_prop(PropertyGroup):
+ run : BoolProperty(default=False, update = reaction_diffusion_add_handler)
+
+ time_steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=1, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=0.1, min=0, soft_max=2, precision=3,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.05, min=0, soft_max=2, precision=3,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5, precision=3,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5, precision=3,
+ description="Kill Rate")
+
+ diff_mult : bpy.props.FloatProperty(
+ name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
+ description="Multiplier for the diffusion of both substances")
+
+def compute_formula(ob=None, formula="rx", float_var=(0,0,0,0,0), int_var=(0,0,0,0,0)):
+ verts = ob.data.vertices
+ n_verts = len(verts)
+
+ f1,f2,f3,f4,f5 = float_var
+ i1,i2,i3,i4,i5 = int_var
+
+ do_groups = "w[" in formula
+ do_local = "lx" in formula or "ly" in formula or "lz" in formula
+ do_global = "gx" in formula or "gy" in formula or "gz" in formula
+ do_relative = "rx" in formula or "ry" in formula or "rz" in formula
+ do_normal = "nx" in formula or "ny" in formula or "nz" in formula
+ mat = ob.matrix_world
+
+ for i in range(1000):
+ if "w["+str(i)+"]" in formula and i > len(ob.vertex_groups)-1:
+ return "w["+str(i)+"] not found"
+
+ w = []
+ for i in range(len(ob.vertex_groups)):
+ w.append([])
+ if "w["+str(i)+"]" in formula:
+ vg = ob.vertex_groups[i]
+ for v in verts:
+ try:
+ w[i].append(vg.weight(v.index))
+ except:
+ w[i].append(0)
+ w[i] = array(w[i])
+
+ start_time = timeit.default_timer()
+ # compute vertex coordinates
+ if do_local or do_relative or do_global:
+ co = [0]*n_verts*3
+ verts.foreach_get('co', co)
+ np_co = array(co).reshape((n_verts, 3))
+ lx, ly, lz = array(np_co).transpose()
+ if do_relative:
+ rx = np.interp(lx, (lx.min(), lx.max()), (0, +1))
+ ry = np.interp(ly, (ly.min(), ly.max()), (0, +1))
+ rz = np.interp(lz, (lz.min(), lz.max()), (0, +1))
+ if do_global:
+ co = [v.co for v in verts]
+ global_co = []
+ for v in co:
+ global_co.append(mat * v)
+ global_co = array(global_co).reshape((n_verts, 3))
+ gx, gy, gz = array(global_co).transpose()
+ # compute vertex normals
+ if do_normal:
+ normal = [0]*n_verts*3
+ verts.foreach_get('normal', normal)
+ normal = array(normal).reshape((n_verts, 3))
+ nx, ny, nz = array(normal).transpose()
+
+ try:
+ weight = eval(formula)
+ return weight
+ except:
+ return "There is something wrong"
+ print("Weight Formula: " + str(timeit.default_timer() - start_time))
+
+class weight_formula_wiki(bpy.types.Operator):
+ bl_idname = "scene.weight_formula_wiki"
+ bl_label = "Online Documentation"
+ bl_options = {'REGISTER', 'UNDO'}
-import bpy
-from bpy.props import (
- BoolProperty,
- EnumProperty,
- FloatProperty,
- IntProperty,
- )
-from bpy.types import Operator
-from math import (
- pi, sin,
- )
+ def execute(self, context):
+ bpy.ops.wm.url_open(url="https://github.com/alessandro-zomparelli/tissue/wiki/Weight-Tools#weight-formula")
+ return {'FINISHED'}
+
+class weight_formula(bpy.types.Operator):
+ bl_idname = "object.weight_formula"
+ bl_label = "Weight Formula"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ ex = [
+ #'cos(arctan(nx/ny)*6 + sin(rz*30)*0.5)/2 + cos(arctan(nx/ny)*6 - sin(rz*30)*0.5 + pi/2)/2 + 0.5',
+ 'cos(arctan(nx/ny)*i1*2 + sin(rz*i3))/i2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i3))/i2 + 0.5',
+ 'cos(arctan(nx/ny)*i1*2 + sin(rz*i2))/2 + cos(arctan(nx/ny)*i1*2 - sin(rz*i2))/2',
+ '(sin(arctan(nx/ny)*i1)*sin(nz*i1)+1)/2',
+ 'cos(arctan(nx/ny)*f1)',
+ 'cos(arctan(lx/ly)*f1 + sin(rz*f2)*f3)',
+ 'sin(nx*15)<sin(ny*15)',
+ 'cos(ny*rz**2*i1)',
+ 'sin(rx*30) > 0',
+ 'sin(nz*i1)',
+ 'w[0]**2',
+ 'sqrt((rx-0.5)**2 + (ry-0.5)**2)*2',
+ 'abs(0.5-rz)*2',
+ 'rx'
+ ]
+ ex_items = list((s,s,"") for s in ex)
+ ex_items.append(('CUSTOM', "User Formula", ""))
+
+ examples : bpy.props.EnumProperty(
+ items = ex_items, default='CUSTOM', name="Examples")
+
+ old_ex = ""
+
+ formula : bpy.props.StringProperty(
+ name="Formula", default="", description="Formula to Evaluate")
+ bl_description = ("Generate a Vertex Group based on the given formula")
+
+ slider_f01 : bpy.props.FloatProperty(
+ name="f1", default=1, description="Slider")
+ bl_description = ("Slider Float 1")
+ slider_f02 : bpy.props.FloatProperty(
+ name="f2", default=1, description="Slider")
+ bl_description = ("Slider Float 2")
+ slider_f03 : bpy.props.FloatProperty(
+ name="f3", default=1, description="Slider")
+ bl_description = ("Slider Float 3")
+ slider_f04 : bpy.props.FloatProperty(
+ name="f4", default=1, description="Slider")
+ bl_description = ("Slider Float 4")
+ slider_f05 : bpy.props.FloatProperty(
+ name="f5", default=1, description="Slider")
+ bl_description = ("Slider Float 5")
+ slider_i01 : bpy.props.IntProperty(
+ name="i1", default=1, description="Slider")
+ bl_description = ("Slider Integer 1")
+ slider_i02 : bpy.props.IntProperty(
+ name="i2", default=1, description="Slider")
+ bl_description = ("Slider Integer 2")
+ slider_i03 : bpy.props.IntProperty(
+ name="i3", default=1, description="Slider")
+ bl_description = ("Slider Integer 3")
+ slider_i04 : bpy.props.IntProperty(
+ name="i4", default=1, description="Slider")
+ bl_description = ("Slider Integer 4")
+ slider_i05 : bpy.props.IntProperty(
+ name="i5", default=1, description="Slider")
+ bl_description = ("Slider Integer 5")
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def draw(self, context):
+ layout = self.layout
+ #layout.label(text="Examples")
+ layout.prop(self, "examples", text="Examples")
+ #if self.examples == 'CUSTOM':
+ layout.label(text="Formula")
+ layout.prop(self, "formula", text="")
+ #try: self.examples = self.formula
+ #except: pass
+
+ if self.examples != self.old_ex and self.examples != 'CUSTOM':
+ self.formula = self.examples
+ self.old_ex = self.examples
+ elif self.formula != self.examples:
+ self.examples = 'CUSTOM'
+ formula = self.formula
+
+ layout.separator()
+ if "f1" in formula: layout.prop(self, "slider_f01")
+ if "f2" in formula: layout.prop(self, "slider_f02")
+ if "f3" in formula: layout.prop(self, "slider_f03")
+ if "f4" in formula: layout.prop(self, "slider_f04")
+ if "f5" in formula: layout.prop(self, "slider_f05")
+ if "i1" in formula: layout.prop(self, "slider_i01")
+ if "i2" in formula: layout.prop(self, "slider_i02")
+ if "i3" in formula: layout.prop(self, "slider_i03")
+ if "i4" in formula: layout.prop(self, "slider_i04")
+ if "i5" in formula: layout.prop(self, "slider_i05")
+
+ layout.label(text="Variables (for each vertex):")
+ layout.label(text="lx, ly, lz: Local Coordinates", icon='ORIENTATION_LOCAL')
+ layout.label(text="gx, gy, gz: Global Coordinates", icon='WORLD')
+ layout.label(text="rx, ry, rz: Local Coordinates (0 to 1)", icon='NORMALIZE_FCURVES')
+ layout.label(text="nx, ny, nz: Normal Coordinates", icon='SNAP_NORMAL')
+ layout.label(text="w[0], w[1], w[2], ... : Vertex Groups", icon="GROUP_VERTEX")
+ layout.separator()
+ layout.label(text="f1, f2, f3, f4, f5: Float Sliders", icon='MOD_HUE_SATURATION')#PROPERTIES
+ layout.label(text="i1, i2, i3, i4, i5: Integer Sliders", icon='MOD_HUE_SATURATION')
+ layout.separator()
+ #layout.label(text="All mathematical functions are based on Numpy", icon='INFO')
+ #layout.label(text="https://docs.scipy.org/doc/numpy-1.13.0/reference/routines.math.html", icon='INFO')
+ layout.operator("scene.weight_formula_wiki", icon="HELP")
+ #layout.label(text="(where 'i' is the index of the Vertex Group)")
+
+ def execute(self, context):
+ ob = bpy.context.active_object
+ n_verts = len(ob.data.vertices)
+ #if self.examples == 'CUSTOM':
+ # formula = self.formula
+ #else:
+ #self.formula = self.examples
+ # formula = self.examples
+
+ #f1, f2, f3, f4, f5 = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
+ #i1, i2, i3, i4, i5 = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
+ f_sliders = self.slider_f01, self.slider_f02, self.slider_f03, self.slider_f04, self.slider_f05
+ i_sliders = self.slider_i01, self.slider_i02, self.slider_i03, self.slider_i04, self.slider_i05
+
+ if self.examples != self.old_ex and self.examples != 'CUSTOM':
+ self.formula = self.examples
+ self.old_ex = self.examples
+ elif self.formula != self.examples:
+ self.examples = 'CUSTOM'
+ formula = self.formula
+
+ if formula == "": return {'FINISHED'}
+ vertex_group_name = "Formula " + formula
+ ob.vertex_groups.new(name=vertex_group_name)
+
+ weight = compute_formula(ob, formula=formula, float_var=f_sliders, int_var=i_sliders)
+ if type(weight) == str:
+ self.report({'ERROR'}, weight)
+ return {'CANCELLED'}
+
+ #print("time: " + str(timeit.default_timer() - start_time))
+
+ #start_time = timeit.default_timer()
+ weight = nan_to_num(weight)
+ if type(weight) == int or type(weight) == float:
+ for i in range(n_verts):
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ elif type(weight) == ndarray:
+ for i in range(n_verts):
+ ob.vertex_groups[-1].add([i], weight[i], 'REPLACE')
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+
+ # Store formula settings
+ new_formula = ob.formula_settings.add()
+ new_formula.name = ob.vertex_groups[-1].name
+ new_formula.formula = formula
+ new_formula.int_var = i_sliders
+ new_formula.float_var = f_sliders
+
+ #for f in ob.formula_settings:
+ # print(f.name, f.formula, f.int_var, f.float_var)
+ return {'FINISHED'}
+
+class _weight_laplacian(bpy.types.Operator):
+ bl_idname = "object._weight_laplacian"
+ bl_label = "Weight Laplacian"
+ bl_description = ("Compute the Vertex Group Laplacian")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('POSITIVE', "Positive Only", ""),
+ ('NEGATIVE', "Negative Only", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ mode : bpy.props.EnumProperty(
+ items=(('LENGTH', "Length Weight", ""),
+ ('SIMPLE', "Simple", "")),
+ default='SIMPLE', name="Evaluation Mode")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=0, soft_min=-1, soft_max=0,
+ description="Laplacian value with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=0.5, soft_min=0, soft_max=5,
+ description="Laplacian value with 1 weight")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="Evaluation Mode")
+ col.prop(self, "mode", text="")
+ col.label(text="Bounds")
+ col.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ col.label(text="Strain Rate \u03B5:")
+ col.prop(self, "min_def")
+ col.prop(self, "max_def")
+ col.label(text="\u03B5" + ": from " + self.bounds_string)
+
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ group_id = ob.vertex_groups.active_index
+ input_group = ob.vertex_groups[group_id].name
+ print(input_group)
+
+ group_name = "Laplacian"
+ ob.vertex_groups.new(name=group_name)
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ for v in me.vertices:
+ try:
+ weight.append(ob.vertex_groups[input_group].weight(v.index))
+ except:
+ weight.append(0)
+
+ n_verts = len(bm.verts)
+ lap = [0]*n_verts
+ #print(len(lap))
+ #print(len(weight))
+ for e in bm.edges:
+ if self.mode == 'LENGTH':
+ length = e.calc_length()
+ if length == 0: continue
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap[id0] += weight[id1]/length - weight[id0]/length
+ lap[id1] += weight[id0]/length - weight[id1]/length
+ else:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap[id0] += weight[id1] - weight[id0]
+ lap[id1] += weight[id0] - weight[id1]
+
+ mean_lap = mean(lap)
+ stdev_lap = stdev(lap)
+ filter_lap = [i for i in lap if mean_lap-2*stdev_lap < i < mean_lap+2*stdev_lap]
+ if self.bounds == 'MANUAL':
+ min_def = self.min_def
+ max_def = self.max_def
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(filter_lap)
+ max_def = max(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'NEGATIVE':
+ min_def = 0
+ max_def = min(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'POSITIVE':
+ min_def = 0
+ max_def = max(filter_lap)
+ self.min_def = min_def
+ self.max_def = max_def
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0: delta_def = 0.0001
+
+ for i in range(len(lap)):
+ val = (lap[i]-min_def)/delta_def
+ if val > 0.7: print(str(val) + " " + str(lap[i]))
+ #val = weight[i] + 0.2*lap[i]
+ ob.vertex_groups[-1].add([i], val, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+class weight_laplacian(bpy.types.Operator):
+ bl_idname = "object.weight_laplacian"
+ bl_label = "Weight Laplacian"
+ bl_description = ("Compute the Vertex Group Laplacian")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=0.2, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=1, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.5, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5,
+ description="Kill Rate")
+
+ diff_mult : bpy.props.FloatProperty(
+ name="Scale", default=1, min=0, soft_max=1, max=2, precision=2,
+ description="Multiplier for the diffusion of both substances")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ f = self.f
+ k = self.k
+ diff_a = self.diff_a * self.diff_mult
+ diff_b = self.diff_b * self.diff_mult
+ dt = self.dt
+
+ # initialize
+ n_verts = len(bm.verts)
+ # find max number of edges for vertex
+ max_edges = 0
+ n_neighbors = []
+ id_neighbors = []
+ for v in bm.verts:
+ n_edges = len(v.link_edges)
+ max_edges = max(max_edges, n_edges)
+ n_neighbors.append(n_edges)
+ neighbors = []
+ for e in link_edges:
+ for v1 in e.verts:
+ if v != v1: neighbors.append(v1.index)
+ id_neighbors.append(neighbors)
+ n_neighbors = array(n_neighbors)
+
+
+ a = [[] for i in range(n_verts)]
+ lap_map = []
+
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_map[id0].append(id1)
+ lap_map[id1].append(id0)
+
+ e1 = array(e1)
+ e2 = array(e2)
+ lap_a = a[e1]
+
+ for i in range(self.steps):
+
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ #print(len(lap))
+ #print(len(weight))
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_a[id0] += a[id1] - a[id0]
+ lap_a[id1] += a[id0] - a[id1]
+ lap_b[id0] += b[id1] - b[id0]
+ lap_b[id1] += b[id0] - b[id1]
+ ab2 = a*b**2
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class reaction_diffusion(bpy.types.Operator):
+ bl_idname = "object.reaction_diffusion"
+ bl_label = "Reaction Diffusion"
+ bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=0.2, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=1, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.5, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5,
+ description="Kill Rate")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+
+ def execute(self, context):
+ #bpy.app.handlers.frame_change_post.remove(reaction_diffusion_def)
+ reaction_diffusion_add_handler(self, context)
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ f = self.f
+ k = self.k
+ diff_a = self.diff_a
+ diff_b = self.diff_b
+ dt = self.dt
+ n_verts = len(bm.verts)
+
+ for i in range(self.steps):
+
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ #print(len(lap))
+ #print(len(weight))
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_a[id0] += a[id1] - a[id0]
+ lap_a[id1] += a[id0] - a[id1]
+ lap_b[id0] += b[id1] - b[id0]
+ lap_b[id1] += b[id0] - b[id1]
+ ab2 = a*b**2
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+
+ bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
+
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
+
+
+class edges_deformation(bpy.types.Operator):
+ bl_idname = "object.edges_deformation"
+ bl_label = "Edges Deformation"
+ bl_description = ("Compute Weight based on the deformation of edges"+
+ "according to visible modifiers.")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('COMPRESSION', "Compressed Only", ""),
+ ('TENSION', "Extended Only", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ mode : bpy.props.EnumProperty(
+ items=(('MAX', "Max Deformation", ""),
+ ('MEAN', "Average Deformation", "")),
+ default='MEAN', name="Evaluation Mode")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=0, soft_min=-1, soft_max=0,
+ description="Deformations with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=0.5, soft_min=0, soft_max=5,
+ description="Deformations with 1 weight")
+
+ bounds_string = ""
+
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.modifiers) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.label(text="Evaluation Mode")
+ col.prop(self, "mode", text="")
+ col.label(text="Bounds")
+ col.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ col.label(text="Strain Rate \u03B5:")
+ col.prop(self, "min_def")
+ col.prop(self, "max_def")
+ col.label(text="\u03B5" + ": from " + self.bounds_string)
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ # check if the object is Cloth or Softbody
+ physics = False
+ for m in ob.modifiers:
+ if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
+ physics = True
+ if context.scene.frame_current == 1 and self.frame != None:
+ context.scene.frame_current = self.frame
+ break
+ if not physics: self.frame = None
+
+ if self.mode == 'MEAN': group_name = "Average Deformation"
+ elif self.mode == 'MAX': group_name = "Max Deformation"
+ ob.vertex_groups.new(name=group_name)
+ me0 = ob.data
+
+ me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
+ self.report({'ERROR'}, "The topology of the object should be" +
+ "unaltered")
+ return {'CANCELLED'}
+
+ bm0 = bmesh.new()
+ bm0.from_mesh(me0)
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ deformations = []
+ for e0, e in zip(bm0.edges, bm.edges):
+ try:
+ l0 = e0.calc_length()
+ l1 = e.calc_length()
+ epsilon = (l1 - l0)/l0
+ deformations.append(epsilon)
+ except: deformations.append(1)
+ v_deformations = []
+ for v in bm.verts:
+ vdef = []
+ for e in v.link_edges:
+ vdef.append(deformations[e.index])
+ if self.mode == 'MEAN': v_deformations.append(mean(vdef))
+ elif self.mode == 'MAX': v_deformations.append(max(vdef, key=abs))
+ #elif self.mode == 'MIN': v_deformations.append(min(vdef, key=abs))
+
+ if self.bounds == 'MANUAL':
+ min_def = self.min_def
+ max_def = self.max_def
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(v_deformations)
+ max_def = max(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'COMPRESSION':
+ min_def = 0
+ max_def = min(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ elif self.bounds == 'TENSION':
+ min_def = 0
+ max_def = max(v_deformations)
+ self.min_def = min_def
+ self.max_def = max_def
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0:
+ if self.bounds == 'MANUAL':
+ delta_def = 0.0001
+ else:
+ message = "The object doesn't have deformations."
+ if physics:
+ message = message + ("\nIf you are using Physics try to " +
+ "save it in the cache before.")
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ else:
+ if physics:
+ self.frame = context.scene.frame_current
+
+ for i in range(len(v_deformations)):
+ weight = (v_deformations[i] - min_def)/delta_def
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.data.meshes.remove(me)
+ return {'FINISHED'}
+
+class edges_bending(bpy.types.Operator):
+ bl_idname = "object.edges_bending"
+ bl_label = "Edges Bending"
+ bl_description = ("Compute Weight based on the bending of edges"+
+ "according to visible modifiers.")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('POSITIVE', "Positive Only", ""),
+ ('NEGATIVE', "Negative Only", ""),
+ ('UNSIGNED', "Absolute Bending", ""),
+ ('AUTOMATIC', "Signed Bending", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ min_def : bpy.props.FloatProperty(
+ name="Min", default=-10, soft_min=-45, soft_max=45,
+ description="Deformations with 0 weight")
+
+ max_def : bpy.props.FloatProperty(
+ name="Max", default=10, soft_min=-45, soft_max=45,
+ description="Deformations with 1 weight")
+
+ bounds_string = ""
+ frame = None
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.modifiers) > 0
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="Bounds")
+ layout.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ layout.prop(self, "min_def")
+ layout.prop(self, "max_def")
+
+ def execute(self, context):
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+
+ group_name = "Edges Bending"
+ ob.vertex_groups.new(name=group_name)
+
+ # check if the object is Cloth or Softbody
+ physics = False
+ for m in ob.modifiers:
+ if m.type == 'CLOTH' or m.type == 'SOFT_BODY':
+ physics = True
+ if context.scene.frame_current == 1 and self.frame != None:
+ context.scene.frame_current = self.frame
+ break
+ if not physics: self.frame = None
+
+ #ob.data.update()
+ #context.scene.update()
+ me0 = ob.data
+ me = simple_to_mesh(ob) #ob.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ if len(me.vertices) != len(me0.vertices) or len(me.edges) != len(me0.edges):
+ self.report({'ERROR'}, "The topology of the object should be" +
+ "unaltered")
+ bm0 = bmesh.new()
+ bm0.from_mesh(me0)
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ deformations = []
+ for e0, e in zip(bm0.edges, bm.edges):
+ try:
+ ang = e.calc_face_angle_signed()
+ ang0 = e0.calc_face_angle_signed()
+ if self.bounds == 'UNSIGNED':
+ deformations.append(abs(ang-ang0))
+ else:
+ deformations.append(ang-ang0)
+ except: deformations.append(0)
+ v_deformations = []
+ for v in bm.verts:
+ vdef = []
+ for e in v.link_edges:
+ vdef.append(deformations[e.index])
+ v_deformations.append(mean(vdef))
+ print(v_deformations)
+ if self.bounds == 'MANUAL':
+ min_def = radians(self.min_def)
+ max_def = radians(self.max_def)
+ elif self.bounds == 'AUTOMATIC':
+ min_def = min(v_deformations)
+ max_def = max(v_deformations)
+ elif self.bounds == 'POSITIVE':
+ min_def = 0
+ max_def = min(v_deformations)
+ elif self.bounds == 'NEGATIVE':
+ min_def = 0
+ max_def = max(v_deformations)
+ elif self.bounds == 'UNSIGNED':
+ min_def = 0
+ max_def = max(v_deformations)
+ delta_def = max_def - min_def
+
+ # check undeformed errors
+ if delta_def == 0:
+ if self.bounds == 'MANUAL':
+ delta_def = 0.0001
+ else:
+ message = "The object doesn't have deformations."
+ if physics:
+ message = message + ("\nIf you are using Physics try to " +
+ "save it in the cache before.")
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ else:
+ if physics:
+ self.frame = context.scene.frame_current
+
+ for i in range(len(v_deformations)):
+ weight = (v_deformations[i] - min_def)/delta_def
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ self.bounds_string = str(round(min_def,2)) + " to " + str(round(max_def,2))
+ ob.vertex_groups[-1].name = group_name + " " + self.bounds_string
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.data.meshes.remove(me)
+ return {'FINISHED'}
+
+class weight_contour_displace(bpy.types.Operator):
+ bl_idname = "object.weight_contour_displace"
+ bl_label = "Contour Displace"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+ min_iso : bpy.props.FloatProperty(
+ name="Min Iso Value", default=0.49, min=0, max=1,
+ description="Threshold value")
+ max_iso : bpy.props.FloatProperty(
+ name="Max Iso Value", default=0.51, min=0, max=1,
+ description="Threshold value")
+ n_cuts : bpy.props.IntProperty(
+ name="Cuts", default=2, min=1, soft_max=10,
+ description="Number of cuts in the selected range of values")
+ bool_displace : bpy.props.BoolProperty(
+ name="Add Displace", default=True, description="Add Displace Modifier")
+ bool_flip : bpy.props.BoolProperty(
+ name="Flip", default=False, description="Flip Output Weight")
+
+ weight_mode : bpy.props.EnumProperty(
+ items=[('Remapped', 'Remapped', 'Remap values'),
+ ('Alternate', 'Alternate', 'Alternate 0 and 1'),
+ ('Original', 'Original', 'Keep original Vertex Group')],
+ name="Weight", description="Choose how to convert vertex group",
+ default="Remapped", options={'LIBRARY_EDITABLE'})
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+
+ ob0 = bpy.context.object
+
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ #me0 = ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ me0 = simple_to_mesh(ob0)
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ for v in me0.vertices:
+ try:
+ weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
+ except:
+ weight.append(0)
+
+ # define iso values
+ iso_values = []
+ for i_cut in range(self.n_cuts):
+ delta_iso = abs(self.max_iso - self.min_iso)
+ min_iso = min(self.min_iso, self.max_iso)
+ max_iso = max(self.min_iso, self.max_iso)
+ if delta_iso == 0: iso_val = min_iso
+ elif self.n_cuts > 1: iso_val = i_cut/(self.n_cuts-1)*delta_iso + min_iso
+ else: iso_val = (self.max_iso + self.min_iso)/2
+ iso_values.append(iso_val)
+
+ # Start Cuts Iterations
+ filtered_edges = bm.edges
+ for iso_val in iso_values:
+ delete_edges = []
+
+ faces_mask = []
+ for f in bm.faces:
+ w_min = 2
+ w_max = 2
+ for v in f.verts:
+ w = weight[v.index]
+ if w_min == 2:
+ w_max = w_min = w
+ if w > w_max: w_max = w
+ if w < w_min: w_min = w
+ if w_min < iso_val and w_max > iso_val:
+ faces_mask.append(f)
+ break
+ #print("selected faces:" + str(len(faces_mask)))
+
+ #link_faces = [[f for f in e.link_faces] for e in bm.edges]
+
+ #faces_todo = [f.select for f in bm.faces]
+ #faces_todo = [True for f in bm.faces]
+ verts = []
+ edges = []
+ edges_id = {}
+ _filtered_edges = []
+ n_verts = len(bm.verts)
+ count = n_verts
+ for e in filtered_edges:
+ #id0 = e.vertices[0]
+ #id1 = e.vertices[1]
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ _filtered_edges.append(e)
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val:
+ _filtered_edges.append(e)
+ continue
+ else:
+ v0 = bm.verts[id0].co
+ v1 = bm.verts[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ if e not in delete_edges:
+ delete_edges.append(e)
+ verts.append(v)
+ edges_id[str(id0)+"_"+str(id1)] = count
+ edges_id[str(id1)+"_"+str(id0)] = count
+ count += 1
+ _filtered_edges.append(e)
+ filtered_edges = _filtered_edges
+ #print("creating faces")
+ splitted_faces = []
+
+ switch = False
+ # splitting faces
+ for f in faces_mask:
+ # create sub-faces slots. Once a new vertex is reached it will
+ # change slot, storing the next vertices for a new face.
+ build_faces = [[],[]]
+ #switch = False
+ verts0 = [v.index for v in f.verts]
+ verts1 = list(verts0)
+ verts1.append(verts1.pop(0)) # shift list
+ for id0, id1 in zip(verts0, verts1):
+
+ # add first vertex to active slot
+ build_faces[switch].append(id0)
+
+ # try to split edge
+ try:
+ # check if the edge must be splitted
+ new_vert = edges_id[str(id0)+"_"+str(id1)]
+ # add new vertex
+ build_faces[switch].append(new_vert)
+ # if there is an open face on the other slot
+ if len(build_faces[not switch]) > 0:
+ # store actual face
+ splitted_faces.append(build_faces[switch])
+ # reset actual faces and switch
+ build_faces[switch] = []
+ # change face slot
+ switch = not switch
+ # continue previous face
+ build_faces[switch].append(new_vert)
+ except: pass
+ if len(build_faces[not switch]) == 2:
+ build_faces[not switch].append(id0)
+ if len(build_faces[not switch]) > 2:
+ splitted_faces.append(build_faces[not switch])
+ # add last face
+ splitted_faces.append(build_faces[switch])
+ #del_faces.append(f.index)
+
+ #print("generate new bmesh")
+ # adding new vertices
+ for v in verts: new_vert = bm.verts.new(v)
+ bm.verts.index_update()
+ bm.verts.ensure_lookup_table()
+ # adding new faces
+ missed_faces = []
+ added_faces = []
+ for f in splitted_faces:
+ try:
+ face_verts = [bm.verts[i] for i in f]
+ new_face = bm.faces.new(face_verts)
+ for e in new_face.edges:
+ filtered_edges.append(e)
+ except:
+ missed_faces.append(f)
+
+ #print("missed " + str(len(missed_faces)) + " faces")
+ bm.faces.ensure_lookup_table()
+ # updating weight values
+ weight = weight + [iso_val]*len(verts)
+
+ # deleting old edges/faces
+ bm.edges.ensure_lookup_table()
+ for e in delete_edges:
+ bm.edges.remove(e)
+ _filtered_edges = []
+ for e in filtered_edges:
+ if e not in delete_edges: _filtered_edges.append(e)
+ filtered_edges = _filtered_edges
+
+ #print("creating curve")
+ name = ob0.name + '_ContourDisp'
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ # generate new vertex group
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ #ob.vertex_groups.new(name=vertex_group_name)
+
+ #print("doing weight")
+ all_weight = weight + [iso_val]*len(verts)
+ #mult = 1/(1-iso_val)
+ for id in range(len(all_weight)):
+ #if False: w = (all_weight[id]-iso_val)*mult
+ w = all_weight[id]
+ if self.weight_mode == 'Alternate':
+ direction = self.bool_flip
+ for i in range(len(iso_values)-1):
+ val0, val1 = iso_values[i], iso_values[i+1]
+ if val0 < w <= val1:
+ if direction: w1 = (w-val0)/(val1-val0)
+ else: w1 = (val1-w)/(val1-val0)
+ direction = not direction
+ if w < iso_values[0]: w1 = not self.bool_flip
+ if w > iso_values[-1]: w1 = not direction
+ elif self.weight_mode == 'Remapped':
+ if w < min_iso: w1 = 0
+ elif w > max_iso: w1 = 1
+ else: w1 = (w - min_iso)/delta_iso
+ else:
+ if self.bool_flip: w1 = 1-w
+ else: w1 = w
+ ob.vertex_groups[vertex_group_name].add([id], w1, 'REPLACE')
+ #print("weight done")
+ #for id in range(len(weight), len(ob.data.vertices)):
+ # ob.vertex_groups[vertex_group_name].add([id], iso_val*0, 'ADD')
+
+
+ ob.vertex_groups.active_index = group_id
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+
+ # Displace Modifier
+ if self.bool_displace:
+ ob.modifiers.new(type='DISPLACE', name='Displace')
+ ob.modifiers["Displace"].mid_level = 0
+ ob.modifiers["Displace"].strength = 0.1
+ ob.modifiers['Displace'].vertex_group = vertex_group_name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ print("Contour Displace time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+
+ return {'FINISHED'}
+
+class weight_contour_mask(bpy.types.Operator):
+ bl_idname = "object.weight_contour_mask"
+ bl_label = "Contour Mask"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+ iso : bpy.props.FloatProperty(
+ name="Iso Value", default=0.5, soft_min=0, soft_max=1,
+ description="Threshold value")
+ bool_solidify : bpy.props.BoolProperty(
+ name="Solidify", default=True, description="Add Solidify Modifier")
+ normalize_weight : bpy.props.BoolProperty(
+ name="Normalize Weight", default=True,
+ description="Normalize weight of remaining vertices")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+
+ ob0 = bpy.context.object
+
+ iso_val = self.iso
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ me0 = simple_to_mesh(ob0)#ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ for v in me0.vertices:
+ try:
+ #weight.append(v.groups[vertex_group_name].weight)
+ weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
+ except:
+ weight.append(0)
+
+ faces_mask = []
+ for f in bm.faces:
+ w_min = 2
+ w_max = 2
+ for v in f.verts:
+ w = weight[v.index]
+ if w_min == 2:
+ w_max = w_min = w
+ if w > w_max: w_max = w
+ if w < w_min: w_min = w
+ if w_min < iso_val and w_max > iso_val:
+ faces_mask.append(f)
+ break
+ #print("selected faces:" + str(len(faces_mask)))
+
+ #link_faces = [[f for f in e.link_faces] for e in bm.edges]
+
+ filtered_edges = bm.edges# me0.edges
+ faces_todo = [f.select for f in bm.faces]
+ verts = []
+ edges = []
+ delete_edges = []
+ edges_id = {}
+ _filtered_edges = []
+ n_verts = len(bm.verts)
+ count = n_verts
+ for e in filtered_edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val: continue
+ else:
+ v0 = me0.vertices[id0].co
+ v1 = me0.vertices[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ delete_edges.append(e)
+ verts.append(v)
+ edges_id[str(id0)+"_"+str(id1)] = count
+ edges_id[str(id1)+"_"+str(id0)] = count
+ count += 1
+
+ #print("creating faces")
+ splitted_faces = []
+
+ switch = False
+ # splitting faces
+ for f in faces_mask:
+ # create sub-faces slots. Once a new vertex is reached it will
+ # change slot, storing the next vertices for a new face.
+ build_faces = [[],[]]
+ #switch = False
+ verts0 = list(me0.polygons[f.index].vertices)
+ verts1 = list(verts0)
+ verts1.append(verts1.pop(0)) # shift list
+ for id0, id1 in zip(verts0, verts1):
+
+ # add first vertex to active slot
+ build_faces[switch].append(id0)
+
+ # try to split edge
+ try:
+ # check if the edge must be splitted
+ new_vert = edges_id[str(id0)+"_"+str(id1)]
+ # add new vertex
+ build_faces[switch].append(new_vert)
+ # if there is an open face on the other slot
+ if len(build_faces[not switch]) > 0:
+ # store actual face
+ splitted_faces.append(build_faces[switch])
+ # reset actual faces and switch
+ build_faces[switch] = []
+ # change face slot
+ switch = not switch
+ # continue previous face
+ build_faces[switch].append(new_vert)
+ except: pass
+ if len(build_faces[not switch]) == 2:
+ build_faces[not switch].append(id0)
+ if len(build_faces[not switch]) > 2:
+ splitted_faces.append(build_faces[not switch])
+ # add last face
+ splitted_faces.append(build_faces[switch])
+
+ #print("generate new bmesh")
+ # adding new vertices
+ for v in verts: bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+
+ # deleting old edges/faces
+ bm.edges.ensure_lookup_table()
+ remove_edges = []
+ for e in delete_edges: bm.edges.remove(e)
+
+ bm.verts.ensure_lookup_table()
+ # adding new faces
+ missed_faces = []
+ for f in splitted_faces:
+ try:
+ face_verts = [bm.verts[i] for i in f]
+ bm.faces.new(face_verts)
+ except:
+ missed_faces.append(f)
+ #print("missed " + str(len(missed_faces)) + " faces")
+
+ # Mask geometry
+ if(True):
+ all_weight = weight + [iso_val+0.0001]*len(verts)
+ weight = []
+ for w, v in zip(all_weight, bm.verts):
+ if w < iso_val: bm.verts.remove(v)
+ else: weight.append(w)
+
+ # Create mesh and object
+ #print("creating curve")
+ name = ob0.name + '_ContourMask_{:.3f}'.format(iso_val)
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ # generate new vertex group
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+
+ if iso_val != 1: mult = 1/(1-iso_val)
+ else: mult = 1
+ for id in range(len(weight)):
+ if self.normalize_weight: w = (weight[id]-iso_val)*mult
+ else: w = weight[id]
+ ob.vertex_groups[vertex_group_name].add([id], w, 'REPLACE')
+ ob.vertex_groups.active_index = group_id
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+
+ # Add Solidify
+ if self.bool_solidify and True:
+ ob.modifiers.new(type='SOLIDIFY', name='Solidify')
+ ob.modifiers['Solidify'].thickness = 0.05
+ ob.modifiers['Solidify'].offset = 0
+ ob.modifiers['Solidify'].vertex_group = vertex_group_name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ print("Contour Mask time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+
+ return {'FINISHED'}
+
+class weight_contour_curves(bpy.types.Operator):
+ bl_idname = "object.weight_contour_curves"
+ bl_label = "Contour Curves"
+ bl_description = ("")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_modifiers : bpy.props.BoolProperty(
+ name="Use Modifiers", default=True,
+ description="Apply all the modifiers")
+
+ min_iso : bpy.props.FloatProperty(
+ name="Min Value", default=0., soft_min=0, soft_max=1,
+ description="Minimum weight value")
+ max_iso : bpy.props.FloatProperty(
+ name="Max Value", default=1, soft_min=0, soft_max=1,
+ description="Maximum weight value")
+ n_curves : bpy.props.IntProperty(
+ name="Curves", default=3, soft_min=1, soft_max=10,
+ description="Number of Contour Curves")
+
+ min_rad : bpy.props.FloatProperty(
+ name="Min Radius", default=0.25, soft_min=0, soft_max=1,
+ description="Minimum Curve Radius")
+ max_rad : bpy.props.FloatProperty(
+ name="Max Radius", default=0.75, soft_min=0, soft_max=1,
+ description="Maximum Curve Radius")
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.object
+ return len(ob.vertex_groups) > 0 or ob.type == 'CURVE'
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=350)
+
+ def execute(self, context):
+ start_time = timeit.default_timer()
+ try:
+ check = bpy.context.object.vertex_groups[0]
+ except:
+ self.report({'ERROR'}, "The object doesn't have Vertex Groups")
+ return {'CANCELLED'}
+ ob0 = bpy.context.object
+
+ group_id = ob0.vertex_groups.active_index
+ vertex_group_name = ob0.vertex_groups[group_id].name
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if self.use_modifiers:
+ me0 = simple_to_mesh(ob0) #ob0.to_mesh(preserve_all_data_layers=True, depsgraph=bpy.context.evaluated_depsgraph_get()).copy()
+ else:
+ me0 = ob0.data.copy()
+
+ # generate new bmesh
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ bm.edges.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ # store weight values
+ weight = []
+ ob = bpy.data.objects.new("temp", me0)
+ for g in ob0.vertex_groups:
+ ob.vertex_groups.new(name=g.name)
+ for v in me0.vertices:
+ try:
+ #weight.append(v.groups[vertex_group_name].weight)
+ weight.append(ob.vertex_groups[vertex_group_name].weight(v.index))
+ except:
+ weight.append(0)
+
+ filtered_edges = bm.edges
+ total_verts = []
+ total_segments = []
+ radius = []
+ # start iterate contours levels
+ for c in range(self.n_curves):
+ min_iso = min(self.min_iso, self.max_iso)
+ max_iso = max(self.min_iso, self.max_iso)
+ try:
+ iso_val = c*(max_iso-min_iso)/(self.n_curves-1)+min_iso
+ if iso_val < 0: iso_val = (min_iso + max_iso)/2
+ except:
+ iso_val = (min_iso + max_iso)/2
+ #print(iso_val)
+ faces_mask = []
+ for f in bm.faces:
+ w_min = 2
+ w_max = 2
+ for v in f.verts:
+ w = weight[v.index]
+ if w_min == 2:
+ w_max = w_min = w
+ if w > w_max: w_max = w
+ if w < w_min: w_min = w
+ if w_min < iso_val and w_max > iso_val:
+ faces_mask.append(f)
+ break
+
+ faces_todo = [f.select for f in bm.faces]
+ verts = []
+
+ edges_id = {}
+ _filtered_edges = []
+ n_verts = len(bm.verts)
+ count = len(total_verts)
+ for e in filtered_edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ w0 = weight[id0]
+ w1 = weight[id1]
+
+ if w0 == w1: continue
+ elif w0 > iso_val and w1 > iso_val:
+ _filtered_edges.append(e)
+ continue
+ elif w0 < iso_val and w1 < iso_val: continue
+ elif w0 == iso_val or w1 == iso_val:
+ _filtered_edges.append(e)
+ continue
+ else:
+ #v0 = me0.vertices[id0].select = True
+ #v1 = me0.vertices[id1].select = True
+ v0 = me0.vertices[id0].co
+ v1 = me0.vertices[id1].co
+ v = v0.lerp(v1, (iso_val-w0)/(w1-w0))
+ verts.append(v)
+ edges_id[e.index] = count
+ count += 1
+ _filtered_edges.append(e)
+ filtered_edges = _filtered_edges
+
+ if len(verts) == 0: continue
+
+ # finding segments
+ segments = []
+ for f in faces_mask:
+ seg = []
+ for e in f.edges:
+ try:
+ seg.append(edges_id[e.index])
+ if len(seg) == 2:
+ segments.append(seg)
+ seg = []
+ except: pass
+
+ total_segments = total_segments + segments
+ total_verts = total_verts + verts
+
+ # Radius
+
+ try:
+ iso_rad = c*(self.max_rad-self.min_rad)/(self.n_curves-1)+self.min_rad
+ if iso_rad < 0: iso_rad = (self.min_rad + self.max_rad)/2
+ except:
+ iso_rad = (self.min_rad + self.max_rad)/2
+ radius = radius + [iso_rad]*len(verts)
-class vertex_colors_to_vertex_groups(Operator):
+ #print("generate new bmesh")
+ bm = bmesh.new()
+ # adding new vertices
+ for v in total_verts: bm.verts.new(v)
+ bm.verts.ensure_lookup_table()
+
+ # adding new edges
+ for s in total_segments:
+ try:
+ pts = [bm.verts[i] for i in s]
+ bm.edges.new(pts)
+ except: pass
+
+ try:
+ #print("creating curve")
+ name = ob0.name + '_ContourCurves'
+ me = bpy.data.meshes.new(name)
+ bm.to_mesh(me)
+ ob = bpy.data.objects.new(name, me)
+
+ # Link object to scene and make active
+ scn = bpy.context.scene
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ ob0.select_set(False)
+
+ bpy.ops.object.convert(target='CURVE')
+ ob = context.object
+ count = 0
+ for s in ob.data.splines:
+ for p in s.points:
+ p.radius = radius[count]
+ count += 1
+ ob.data.bevel_depth = 0.01
+ ob.data.fill_mode = 'FULL'
+ ob.data.bevel_resolution = 3
+ except:
+ self.report({'ERROR'}, "There are no values in the chosen range")
+ return {'CANCELLED'}
+
+ # align new object
+ ob.matrix_world = ob0.matrix_world
+ print("Contour Curves time: " + str(timeit.default_timer() - start_time) + " sec")
+
+ bpy.data.meshes.remove(me0)
+ bpy.data.meshes.remove(me)
+
+ return {'FINISHED'}
+
+class vertex_colors_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.vertex_colors_to_vertex_groups"
bl_label = "Vertex Color"
- bl_description = ("Convert the active Vertex Color into a Vertex Group")
bl_options = {'REGISTER', 'UNDO'}
-
- red: BoolProperty(
- name="Red Channel",
- default=False,
- description="Convert Red Channel"
- )
- green: BoolProperty(
- name="Green Channel",
- default=False,
- description="Convert Green Channel"
- )
- blue: BoolProperty(
- name="Blue Channel",
- default=False,
- description="Convert Blue Channel"
- )
- value: BoolProperty(
- name="Value Channel",
- default=True,
- description="Convert Value Channel"
- )
- invert: BoolProperty(
- name="Invert",
- default=False,
- description="Invert all Color Channels"
- )
+ bl_description = ("Convert the active Vertex Color into a Vertex Group.")
+
+ red : bpy.props.BoolProperty(
+ name="red channel", default=False, description="convert red channel")
+ green : bpy.props.BoolProperty(
+ name="green channel", default=False,
+ description="convert green channel")
+ blue : bpy.props.BoolProperty(
+ name="blue channel", default=False, description="convert blue channel")
+ value : bpy.props.BoolProperty(
+ name="value channel", default=True, description="convert value channel")
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert all color channels")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.data.vertex_colors) > 0
def execute(self, context):
obj = bpy.context.active_object
- ids = len(obj.vertex_groups)
- id_red = ids
- id_green = ids
- id_blue = ids
- id_value = ids
-
- # Fix T53350: Access of h, s, v values has changed since 2.79 release
- # use rgb_to_hsv function to get the v component
- blender_version = bool(bpy.app.version >= (2, 79, 1))
- from colorsys import rgb_to_hsv
+ id = len(obj.vertex_groups)
+ id_red = id
+ id_green = id
+ id_blue = id
+ id_value = id
boolCol = len(obj.data.vertex_colors)
- if boolCol:
- col_name = obj.data.vertex_colors.active.name
+ if(boolCol): col_name = obj.data.vertex_colors.active.name
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='SELECT')
- if self.red and boolCol:
+ if(self.red and boolCol):
bpy.ops.object.vertex_group_add()
bpy.ops.object.vertex_group_assign()
- id_red = ids
+ id_red = id
obj.vertex_groups[id_red].name = col_name + '_red'
- ids += 1
- if self.green and boolCol:
+ id+=1
+ if(self.green and boolCol):
bpy.ops.object.vertex_group_add()
bpy.ops.object.vertex_group_assign()
- id_green = ids
+ id_green = id
obj.vertex_groups[id_green].name = col_name + '_green'
- ids += 1
- if self.blue and boolCol:
+ id+=1
+ if(self.blue and boolCol):
bpy.ops.object.vertex_group_add()
bpy.ops.object.vertex_group_assign()
- id_blue = ids
+ id_blue = id
obj.vertex_groups[id_blue].name = col_name + '_blue'
- ids += 1
- if self.value and boolCol:
+ id+=1
+ if(self.value and boolCol):
bpy.ops.object.vertex_group_add()
bpy.ops.object.vertex_group_assign()
- id_value = ids
+ id_value = id
obj.vertex_groups[id_value].name = col_name + '_value'
- ids += 1
-
- mult = -1 if self.invert else 1
+ id+=1
+ mult = 1
+ if(self.invert): mult = -1
bpy.ops.object.mode_set(mode='OBJECT')
-
sub_red = 1 + self.value + self.blue + self.green
sub_green = 1 + self.value + self.blue
sub_blue = 1 + self.value
sub_value = 1
- ids = len(obj.vertex_groups)
- if (id_red <= ids and id_green <= ids and id_blue <= ids and id_value <=
- ids and boolCol):
+ id = len(obj.vertex_groups)
+ if(id_red <= id and id_green <= id and id_blue <= id and id_value <= \
+ id and boolCol):
v_colors = obj.data.vertex_colors.active.data
i = 0
for f in obj.data.polygons:
for v in f.vertices:
gr = obj.data.vertices[v].groups
- if self.red:
- gr[min(len(gr) - sub_red, id_red)].weight = \
- self.invert + mult * v_colors[i].color[0]
-
- if self.green:
- gr[min(len(gr) - sub_green, id_green)].weight = \
- self.invert + mult * v_colors[i].color[1]
-
- if self.blue:
- gr[min(len(gr) - sub_blue, id_blue)].weight = \
- self.invert + mult * v_colors[i].color[2]
-
- if self.value:
- col_value = v_colors[i].color.v if not blender_version else \
- rgb_to_hsv(v_colors[i].color[0],
- v_colors[i].color[0],
- v_colors[i].color[0])[2]
-
- gr[min(len(gr) - sub_value, id_value)].weight = \
- self.invert + mult * col_value
- i += 1
+ if(self.red): gr[min(len(gr)-sub_red, id_red)].weight = \
+ self.invert + mult * v_colors[i].color[0]
+ if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\
+ = self.invert + mult * v_colors[i].color[1]
+ if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \
+ self.invert + mult * v_colors[i].color[2]
+ if(self.value):
+ r = v_colors[i].color[0]
+ g = v_colors[i].color[1]
+ b = v_colors[i].color[2]
+ gr[min(len(gr)-sub_value, id_value)].weight\
+ = self.invert + mult * (0.2126*r + 0.7152*g + 0.0722*b)
+ i+=1
bpy.ops.paint.weight_paint_toggle()
-
return {'FINISHED'}
-
-class vertex_group_to_vertex_colors(Operator):
+class vertex_group_to_vertex_colors(bpy.types.Operator):
bl_idname = "object.vertex_group_to_vertex_colors"
bl_label = "Vertex Group"
- bl_description = ("Convert the active Vertex Group into a Vertex Color")
bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Convert the active Vertex Group into a Vertex Color.")
+
+ channel : bpy.props.EnumProperty(
+ items=[('Blue', 'Blue Channel', 'Convert to Blue Channel'),
+ ('Green', 'Green Channel', 'Convert to Green Channel'),
+ ('Red', 'Red Channel', 'Convert to Red Channel'),
+ ('Value', 'Value Channel', 'Convert to Grayscale'),
+ ('False Colors', 'False Colors', 'Convert to False Colors')],
+ name="Convert to", description="Choose how to convert vertex group",
+ default="Value", options={'LIBRARY_EDITABLE'})
+
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert color channel")
- channel: EnumProperty(
- items=[('Blue', 'Blue Channel', 'Convert to Blue Channel'),
- ('Green', 'Green Channel', 'Convert to Green Channel'),
- ('Red', 'Red Channel', 'Convert to Red Channel'),
- ('Value', 'Value Channel', 'Convert to Grayscale'),
- ('False Colors', 'False Colors', 'Convert to False Colors')],
- name="Convert to",
- description="Choose how to convert vertex group",
- default="Value",
- options={'LIBRARY_EDITABLE'}
- )
-
- invert: BoolProperty(
- name="Invert",
- default=False,
- description="Invert Color Channel"
- )
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
def execute(self, context):
obj = bpy.context.active_object
group_id = obj.vertex_groups.active_index
-
if (group_id == -1):
return {'FINISHED'}
@@ -215,314 +1841,665 @@ class vertex_group_to_vertex_colors(Operator):
colors_id = obj.data.vertex_colors.active_index
colors_name = group_name
- if(self.channel == 'False Colors'):
- colors_name += "_false_colors"
- elif(self.channel == 'Value'):
- colors_name += "_value"
- elif(self.channel == 'Red'):
- colors_name += "_red"
- elif(self.channel == 'Green'):
- colors_name += "_green"
- elif(self.channel == 'Blue'):
- colors_name += "_blue"
+ if(self.channel == 'False Colors'): colors_name += "_false_colors"
+ elif(self.channel == 'Value'): colors_name += "_value"
+ elif(self.channel == 'Red'): colors_name += "_red"
+ elif(self.channel == 'Green'): colors_name += "_green"
+ elif(self.channel == 'Blue'): colors_name += "_blue"
bpy.context.object.data.vertex_colors[colors_id].name = colors_name
v_colors = obj.data.vertex_colors.active.data
mult = 1
- if self.invert:
- mult = -1
+ if(self.invert): mult = -1
i = 0
for f in obj.data.polygons:
for v in f.vertices:
gr = obj.data.vertices[v].groups
- if(self.channel == 'False Colors'):
- v_colors[i].color = (0, 0, 1)
- else:
- v_colors[i].color = (0, 0, 0)
+ if(self.channel == 'False Colors'): v_colors[i].color = (0,0,0.5,1)
+ else: v_colors[i].color = (0,0,0,1)
for g in gr:
if g.group == group_id:
+ w = g.weight
if(self.channel == 'False Colors'):
- if g.weight < 0.25:
- v_colors[i].color = (0, g.weight * 4, 1)
- elif g.weight < 0.5:
- v_colors[i].color = (0, 1, 1 - (g.weight - 0.25) * 4)
- elif g.weight < 0.75:
- v_colors[i].color = ((g.weight - 0.5) * 4, 1, 0)
+ mult = 0.6+0.4*w
+ if w < 0.25:
+ v_colors[i].color = (0, w*4*mult, 1*mult,1)
+ elif w < 0.5:
+ v_colors[i].color = (0, 1*mult, (1-(w-0.25)*4)*mult,1)
+ elif w < 0.75:
+ v_colors[i].color = ((w-0.5)*4*mult,1*mult,0,1)
else:
- v_colors[i].color = (1, 1 - (g.weight - 0.75) * 4, 0)
+ v_colors[i].color = (1*mult,(1-(w-0.75)*4)*mult,0,1)
elif(self.channel == 'Value'):
v_colors[i].color = (
- self.invert + mult * g.weight,
- self.invert + mult * g.weight,
- self.invert + mult * g.weight)
+ self.invert + mult * w,
+ self.invert + mult * w,
+ self.invert + mult * w,
+ 1)
elif(self.channel == 'Red'):
v_colors[i].color = (
- self.invert + mult * g.weight, 0, 0)
+ self.invert + mult * w,0,0,1)
elif(self.channel == 'Green'):
v_colors[i].color = (
- 0, self.invert + mult * g.weight, 0)
+ 0, self.invert + mult * w,0,1)
elif(self.channel == 'Blue'):
v_colors[i].color = (
- 0, 0, self.invert + mult * g.weight)
- i += 1
+ 0,0, self.invert + mult * w,1)
+ i+=1
bpy.ops.paint.vertex_paint_toggle()
bpy.context.object.data.vertex_colors[colors_id].active_render = True
-
return {'FINISHED'}
-
-class curvature_to_vertex_groups(Operator):
+class curvature_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.curvature_to_vertex_groups"
bl_label = "Curvature"
- bl_description = ("Generate a Vertex Group based on the curvature of the"
- "mesh. Is based on Dirty Vertex Color")
bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Generate a Vertex Group based on the curvature of the"
+ "mesh. Is based on Dirty Vertex Color.")
- invert: BoolProperty(
- name="Invert",
- default=False,
- description="Invert Values"
- )
- blur_strength: FloatProperty(
- name="Blur Strength",
- default=1,
- min=0.001,
- max=1,
- description="Blur strength per iteration"
- )
- blur_iterations: IntProperty(
- name="Blur Iterations",
- default=1,
- min=0,
- max=40,
- description="Number of times to blur the values"
- )
- min_angle: FloatProperty(
- name="Min Angle",
- default=0,
- min=0,
- max=pi / 2,
- subtype='ANGLE',
- description="Minimum angle"
- )
- max_angle: FloatProperty(
- name="Max Angle",
- default=pi,
- min=pi / 2,
- max=pi,
- subtype='ANGLE',
- description="Maximum angle"
- )
- invert: BoolProperty(
- name="Invert",
- default=False,
- description="Invert the curvature map"
- )
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert values")
- def execute(self, context):
- # Fix T53350: Access of r, g, b values has changed since 2.79.1
- blender_version = bool(bpy.app.version >= (2, 79, 1))
+ blur_strength : bpy.props.FloatProperty(
+ name="Blur Strength", default=1, min=0.001,
+ max=1, description="Blur strength per iteration")
+
+ blur_iterations : bpy.props.IntProperty(
+ name="Blur Iterations", default=1, min=0,
+ max=40, description="Number of times to blur the values")
+
+ min_angle : bpy.props.FloatProperty(
+ name="Min Angle", default=0, min=0,
+ max=pi/2, subtype='ANGLE', description="Minimum angle")
+
+ max_angle : bpy.props.FloatProperty(
+ name="Max Angle", default=pi, min=pi/2,
+ max=pi, subtype='ANGLE', description="Maximum angle")
+ invert : bpy.props.BoolProperty(
+ name="Invert", default=False,
+ description="Invert the curvature map")
+
+ def execute(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.mesh.vertex_color_add()
vertex_colors = bpy.context.active_object.data.vertex_colors
vertex_colors[-1].active = True
vertex_colors[-1].active_render = True
vertex_colors[-1].name = "Curvature"
-
- for c in vertex_colors[-1].data:
- if blender_version:
- # white color, alpha 1
- c.color[0], c.color[1], c.color[2], c.color[3] = 1, 1, 1, 1
- else:
- c.color.r, c.color.g, c.color.b = 1, 1, 1
-
+ for c in vertex_colors[-1].data: c.color = (1,1,1,1)
bpy.ops.object.mode_set(mode='VERTEX_PAINT')
-
bpy.ops.paint.vertex_color_dirt(
- blur_strength=self.blur_strength,
- blur_iterations=self.blur_iterations,
- clean_angle=self.max_angle,
- dirt_angle=self.min_angle
- )
+ blur_strength=self.blur_strength,
+ blur_iterations=self.blur_iterations, clean_angle=self.max_angle,
+ dirt_angle=self.min_angle)
bpy.ops.object.vertex_colors_to_vertex_groups(invert=self.invert)
bpy.ops.mesh.vertex_color_remove()
-
return {'FINISHED'}
-class face_area_to_vertex_groups(Operator):
+class face_area_to_vertex_groups(bpy.types.Operator):
bl_idname = "object.face_area_to_vertex_groups"
bl_label = "Area"
- bl_description = ("Generate a Vertex Group based on the area of individual"
- "faces")
bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Generate a Vertex Group based on the area of individual"
+ "faces.")
+
+ invert : bpy.props.BoolProperty(
+ name="invert", default=False, description="invert values")
+ bounds : bpy.props.EnumProperty(
+ items=(('MANUAL', "Manual Bounds", ""),
+ ('AUTOMATIC', "Automatic Bounds", "")),
+ default='AUTOMATIC', name="Bounds")
+
+ min_area : bpy.props.FloatProperty(
+ name="Min", default=0.01, soft_min=0, soft_max=1,
+ description="Faces with 0 weight")
- invert: BoolProperty(
- name="Invert",
- default=False,
- description="Invert Values"
- )
+ max_area : bpy.props.FloatProperty(
+ name="Max", default=0.1, soft_min=0, soft_max=1,
+ description="Faces with 1 weight")
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="Bounds")
+ layout.prop(self, "bounds", text="")
+ if self.bounds == 'MANUAL':
+ layout.prop(self, "min_area")
+ layout.prop(self, "max_area")
def execute(self, context):
- obj = bpy.context.active_object
- id_value = len(obj.vertex_groups)
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_all(action='SELECT')
- bpy.ops.object.vertex_group_add()
- bpy.ops.object.vertex_group_assign()
- obj.vertex_groups[id_value].name = 'faces_area'
- mult = 1
+ try: ob = context.object
+ except:
+ self.report({'ERROR'}, "Please select an Object")
+ return {'CANCELLED'}
+ ob.vertex_groups.new(name="Faces Area")
- if self.invert:
- mult = -1
+ areas = [[] for v in ob.data.vertices]
- bpy.ops.object.mode_set(mode='OBJECT')
- min_area = False
- max_area = False
- n_values = [0] * len(obj.data.vertices)
- values = [0] * len(obj.data.vertices)
- for p in obj.data.polygons:
+ for p in ob.data.polygons:
for v in p.vertices:
- n_values[v] += 1
- values[v] += p.area
+ areas[v].append(p.area)
+
+ for i in range(len(areas)):
+ areas[i] = mean(areas[i])
+ print(areas)
+ if self.bounds == 'MANUAL':
+ min_area = self.min_area
+ max_area = self.max_area
+ elif self.bounds == 'AUTOMATIC':
+ min_area = min(areas)
+ max_area = max(areas)
+ elif self.bounds == 'COMPRESSION':
+ min_area = 1
+ max_area = min(areas)
+ elif self.bounds == 'TENSION':
+ min_area = 1
+ max_area = max(areas)
+ print(min_area)
+ print(max_area)
+ delta_area = max_area - min_area
+ if delta_area == 0:
+ delta_area = 0.0001
+ if self.bounds == 'MANUAL':
+ delta_area = 0.0001
+ else:
+ self.report({'ERROR'}, "The faces have the same areas")
+ #return {'CANCELLED'}
+ for i in range(len(areas)):
+ weight = (areas[i] - min_area)/delta_area
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ return {'FINISHED'}
- if min_area:
- if min_area > p.area:
- min_area = p.area
- else:
- min_area = p.area
- if max_area:
- if max_area < p.area:
- max_area = p.area
- else:
- max_area = p.area
+class harmonic_weight(bpy.types.Operator):
+ bl_idname = "object.harmonic_weight"
+ bl_label = "Harmonic"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = ("Create an harmonic variation of the active Vertex Group")
- for v in obj.data.vertices:
- gr = v.groups
- index = v.index
- try:
- gr[min(len(gr) - 1, id_value)].weight = \
- self.invert + mult * \
- ((values[index] / n_values[index] - min_area) /
- (max_area - min_area))
- except:
- gr[min(len(gr) - 1, id_value)].weight = 0.5
+ freq : bpy.props.FloatProperty(
+ name="Frequency", default=20, soft_min=0,
+ soft_max=100, description="Wave frequency")
+
+ amp : bpy.props.FloatProperty(
+ name="Amplitude", default=1, soft_min=0,
+ soft_max=10, description="Wave amplitude")
- bpy.ops.paint.weight_paint_toggle()
+ midlevel : bpy.props.FloatProperty(
+ name="Midlevel", default=0, min=-1,
+ max=1, description="Midlevel")
+ add : bpy.props.FloatProperty(
+ name="Add", default=0, min=-1,
+ max=1, description="Add to the Weight")
+
+ mult : bpy.props.FloatProperty(
+ name="Multiply", default=0, min=0,
+ max=1, description="Multiply for he Weight")
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.object.vertex_groups) > 0
+
+ def execute(self, context):
+ ob = bpy.context.active_object
+ if len(ob.vertex_groups) > 0:
+ group_id = ob.vertex_groups.active_index
+ ob.vertex_groups.new(name="Harmonic")
+ for i in range(len(ob.data.vertices)):
+ try: val = ob.vertex_groups[group_id].weight(i)
+ except: val = 0
+ weight = self.amp*(sin(val*self.freq) - self.midlevel)/2 + 0.5 + self.add*val*(1-(1-val)*self.mult)
+ ob.vertex_groups[-1].add([i], weight, 'REPLACE')
+ ob.data.update()
+ else:
+ self.report({'ERROR'}, "Active object doesn't have vertex groups")
+ return {'CANCELLED'}
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
return {'FINISHED'}
-class harmonic_weight(Operator):
- bl_idname = "object.harmonic_weight"
- bl_label = "Harmonic"
- bl_description = "Create an harmonic variation of the active Vertex Group"
+
+class TISSUE_PT_color(bpy.types.Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "vertexpaint"
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.operator("object.vertex_colors_to_vertex_groups",
+ icon="GROUP_VERTEX", text="Convert to Weight")
+
+class TISSUE_PT_weight(bpy.types.Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "weightpaint"
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ #if context.object.type == 'MESH' and context.mode == 'OBJECT':
+ #col.label(text="Transform:")
+ #col.separator()
+ #elif bpy.context.mode == 'PAINT_WEIGHT':
+ col.label(text="Weight Generate:")
+ #col.operator(
+ # "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
+ col.operator("object.face_area_to_vertex_groups", icon="FACESEL")
+ col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
+ try: col.operator("object.weight_formula", icon="CON_TRANSFORM")
+ except: col.operator("object.weight_formula")#, icon="CON_TRANSFORM")
+ #col.label(text="Weight Processing:")
+ col.separator()
+
+ # TO BE FIXED
+ #col.operator("object.weight_laplacian", icon="SMOOTHCURVE")
+
+ col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
+ col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL",
+ text="Convert to Colors")
+ col.separator()
+ col.label(text="Deformation Analysis:")
+ col.operator("object.edges_deformation", icon="DRIVER_DISTANCE")#FULLSCREEN_ENTER")
+ col.operator("object.edges_bending", icon="DRIVER_ROTATIONAL_DIFFERENCE")#"MOD_SIMPLEDEFORM")
+ col.separator()
+ col.label(text="Weight Contour:")
+ col.operator("object.weight_contour_curves", icon="MOD_CURVE")
+ col.operator("object.weight_contour_displace", icon="MOD_DISPLACE")
+ col.operator("object.weight_contour_mask", icon="MOD_MASK")
+ col.separator()
+ col.label(text="Simulations:")
+ #col.operator("object.reaction_diffusion", icon="MOD_OCEAN")
+ col.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reaction-Diffusion")
+
+ #col.prop(context.object, "reaction_diffusion_run", icon="PLAY", text="Run Simulation")
+ ####col.prop(context.object, "reaction_diffusion_run")
+ #col.separator()
+ #col.label(text="Vertex Color from:")
+ #col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
+
+
+
+
+class start_reaction_diffusion(bpy.types.Operator):
+ bl_idname = "object.start_reaction_diffusion"
+ bl_label = "Start Reaction Diffusion"
+ bl_description = ("Run a Reaction-Diffusion based on existing Vertex Groups: A and B")
bl_options = {'REGISTER', 'UNDO'}
- freq: FloatProperty(
- name="Frequency",
- default=20,
- soft_min=0,
- soft_max=100,
- description="Wave frequency"
- )
- amp: FloatProperty(
- name="Amplitude",
- default=1,
- soft_min=0,
- soft_max=10,
- description="Wave amplitude"
- )
- midlevel: FloatProperty(
- name="Midlevel",
- default=0,
- min=-1,
- max=1,
- description="Midlevel"
- )
- add: FloatProperty(
- name="Add",
- default=0,
- min=-1,
- max=1,
- description="Add to the Weight"
- )
- mult: FloatProperty(
- name="Multiply",
- default=0,
- min=0,
- max=1,
- description="Multiply for he Weight"
- )
+ time_steps : bpy.props.IntProperty(
+ name="Steps", default=10, min=0, soft_max=50,
+ description="Number of Steps")
+
+ dt : bpy.props.FloatProperty(
+ name="dt", default=1, min=0, soft_max=0.2,
+ description="Time Step")
+
+ diff_a : bpy.props.FloatProperty(
+ name="Diff A", default=0.18, min=0, soft_max=2,
+ description="Diffusion A")
+
+ diff_b : bpy.props.FloatProperty(
+ name="Diff B", default=0.09, min=0, soft_max=2,
+ description="Diffusion B")
+
+ f : bpy.props.FloatProperty(
+ name="f", default=0.055, min=0, soft_max=0.5, precision=4,
+ description="Feed Rate")
+
+ k : bpy.props.FloatProperty(
+ name="k", default=0.062, min=0, soft_max=0.5, precision=4,
+ description="Kill Rate")
+
+ @classmethod
+ def poll(cls, context):
+ return context.object.type == 'MESH'
def execute(self, context):
- obj = bpy.context.active_object
+ reaction_diffusion_add_handler(self, context)
+
+ ob = context.object
+ '''
+ ob.reaction_diffusion_settings.dt = self.dt
+ ob.reaction_diffusion_settings.time_steps = self.time_steps
+ ob.reaction_diffusion_settings.f = self.f
+ ob.reaction_diffusion_settings.k = self.k
+ ob.reaction_diffusion_settings.diff_a = self.diff_a
+ ob.reaction_diffusion_settings.diff_b = self.diff_b
+ '''
+
+ # check vertex group A
try:
- group_id = obj.vertex_groups.active_index
- for v in obj.data.vertices:
- val = v.groups[group_id].weight
- v.groups[group_id].weight = (self.amp * (sin(val * self.freq) -
- self.midlevel) / 2 + 0.5 + self.add * val) * \
- (1 - (1 - val) * self.mult)
+ vg = ob.vertex_groups['A']
except:
- self.report({'ERROR'}, "Active object doesn't have vertex groups")
+ ob.vertex_groups.new(name='A')
+ # check vertex group B
+ try:
+ vg = ob.vertex_groups['B']
+ except:
+ ob.vertex_groups.new(name='B')
- return {'CANCELLED'}
+ for v in ob.data.vertices:
+ ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
+ ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
return {'FINISHED'}
+class reset_reaction_diffusion_weight(bpy.types.Operator):
+ bl_idname = "object.reset_reaction_diffusion_weight"
+ bl_label = "Reset Reaction Diffusion Weight"
+ bl_description = ("Set A and B weight to default values")
+ bl_options = {'REGISTER', 'UNDO'}
-class colors_groups_exchanger_panel(bpy.types.Panel):
- bl_label = "Tissue Tools"
- bl_category = "Tools"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_options = {'DEFAULT_CLOSED'}
+ @classmethod
+ def poll(cls, context):
+ return context.object.type == 'MESH'
- def draw(self, context):
+ def execute(self, context):
+ reaction_diffusion_add_handler(self, context)
+
+ ob = context.object
+
+ # check vertex group A
+ try:
+ vg = ob.vertex_groups['A']
+ except:
+ ob.vertex_groups.new(name='A')
+ # check vertex group B
try:
- if bpy.context.active_object.type == 'MESH':
- layout = self.layout
- col = layout.column(align=True)
- col.label(text="Transform:")
- col.operator("object.dual_mesh")
- col.separator()
- col.label(text="Weight from:")
- col.operator(
- "object.vertex_colors_to_vertex_groups", icon="GROUP_VCOL")
- col.operator("object.face_area_to_vertex_groups", icon="SNAP_FACE")
- col.operator("object.curvature_to_vertex_groups", icon="SMOOTHCURVE")
- col.operator("object.harmonic_weight", icon="IPO_ELASTIC")
- col.separator()
- col.label(text="Vertex Color from:")
- col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VERTEX")
+ vg = ob.vertex_groups['B']
except:
- pass
+ ob.vertex_groups.new(name='B')
+ for v in ob.data.vertices:
+ ob.vertex_groups['A'].add([v.index], 1, 'REPLACE')
+ ob.vertex_groups['B'].add([v.index], 0, 'REPLACE')
-def register():
- bpy.utils.register_class(vertex_colors_to_vertex_groups)
- bpy.utils.register_class(vertex_group_to_vertex_colors)
- bpy.utils.register_class(face_area_to_vertex_groups)
- bpy.utils.register_class(colors_groups_exchanger_panel)
- bpy.utils.register_class(harmonic_weight)
+ ob.vertex_groups.update()
+ ob.data.update()
+ bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+
+ return {'FINISHED'}
+
+from bpy.app.handlers import persistent
+
+@persistent
+def reaction_diffusion_def_blur(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+ #try:
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ n_verts = len(bm.verts)
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #ob.data.use_paint_mask_vertex = True
+
+ for i in range(time_steps):
+ ab2 = a*b**2
+ ob.vertex_groups.active = ob.vertex_groups['A']
+ bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_a)
+ ob.vertex_groups.active = ob.vertex_groups['B']
+ bpy.ops.object.vertex_group_smooth(group_select_mode='ACTIVE', factor=diff_b)
+
+ a = []
+ b = []
+ for v in me.vertices:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ a = array(a)
+ b = array(b)
+
+ a += - (ab2 + f*(1-a))*dt
+ b += (ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ #bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT
+ #bpy.ops.paint.weight_paint_toggle()
+ #bpy.ops.paint.weight_paint_toggle()
+
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #except:
+ # pass
+
+def reaction_diffusion_def_(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+ #try:
+ me = ob.data
+ bm = bmesh.new()
+ bm.from_mesh(me)
+ bm.edges.ensure_lookup_table()
+
+ # store weight values
+ a = []
+ b = []
+ for v in me.vertices:
+ try:
+ a.append(ob.vertex_groups["A"].weight(v.index))
+ except:
+ a.append(0)
+ try:
+ b.append(ob.vertex_groups["B"].weight(v.index))
+ except:
+ b.append(0)
+
+ a = array(a)
+ b = array(b)
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ n_verts = len(bm.verts)
+ for i in range(time_steps):
+ lap_a = zeros((n_verts))#[0]*n_verts
+ lap_b = zeros((n_verts))#[0]*n_verts
+ #print(len(lap))
+ #print(len(weight))
+ if i == 0:
+ lap_map = [[] for i in range(n_verts)]
+ lap_mult = []
+ for e in bm.edges:
+ id0 = e.verts[0].index
+ id1 = e.verts[1].index
+ lap_map[id0].append(id1)
+ lap_map[id1].append(id0)
+ for id in range(n_verts):
+ lap_mult.append(len(lap_map[id]))
+ lap_mult = array(lap_mult)
+ lap_map = array(lap_map)
+ for id in range(n_verts):
+ map = lap_map[id]
+ lap_a[id] = a[lap_map[id]].sum()
+ lap_b[id] = b[lap_map[id]].sum()
+ lap_a -= a*lap_mult
+ lap_b -= b*lap_mult
+ ab2 = a*b**2
+
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+ ob.vertex_groups.update()
+ ob.data.update()
+ #bpy.ops.object.mode_set(mode='EDIT')
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ bpy.ops.paint.weight_paint_toggle()
+ bpy.ops.paint.weight_paint_toggle()
+ #bpy.ops.object.mode_set(mode='WEIGHT_PAINT')
+ #except:
+ # pass
+
+def reaction_diffusion_def(scene):
+ for ob in scene.objects:
+ if ob.reaction_diffusion_settings.run:
+ me = ob.data
+ n_edges = len(me.edges)
+ n_verts = len(me.vertices)
+
+ # store weight values
+ a = np.zeros(n_verts)
+ b = np.zeros(n_verts)
+ for i in range(n_verts):
+ try: a[i] = ob.vertex_groups["A"].weight(i)
+ except: pass
+ try: b[i] = ob.vertex_groups["B"].weight(i)
+ except: pass
+
+ props = ob.reaction_diffusion_settings
+ dt = props.dt
+ time_steps = props.time_steps
+ f = props.f
+ k = props.k
+ diff_a = props.diff_a * props.diff_mult
+ diff_b = props.diff_b * props.diff_mult
+
+ edge_verts = [0]*n_edges*2
+ me.edges.foreach_get("vertices", edge_verts)
+
+ edge_verts = np.array(edge_verts)
+ arr = np.arange(n_edges)*2
+ id0 = edge_verts[arr] # first vertex indices for each edge
+ id1 = edge_verts[arr+1] # second vertex indices for each edge
-def unregister():
- bpy.utils.unregister_class(vertex_colors_to_vertex_groups)
- bpy.utils.unregister_class(vertex_group_to_vertex_colors)
- bpy.utils.unregister_class(face_area_to_vertex_groups)
- bpy.utils.unregister_class(colors_groups_exchanger_panel)
- bpy.utils.unregister_class(harmonic_weight)
+ try:
+ a, b = numba_reaction_diffusion(n_verts, a, b, diff_a, diff_b, f, k, dt, id0, id1, time_steps)
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+ except:
+ for i in range(time_steps):
+ lap_a = np.zeros(n_verts)
+ lap_b = np.zeros(n_verts)
+ lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
+ lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
+
+ for i, j, la0, lb0 in np.nditer([id0,id1,lap_a0,lap_b0]):
+ lap_a[i] += la0
+ lap_b[i] += lb0
+ lap_a[j] -= la0
+ lap_b[j] -= lb0
+ ab2 = a*b**2
+ a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
+ b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
+ #a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ #b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+
+ a = nan_to_num(a)
+ b = nan_to_num(b)
+
+ for i in range(n_verts):
+ ob.vertex_groups['A'].add([i], a[i], 'REPLACE')
+ ob.vertex_groups['B'].add([i], b[i], 'REPLACE')
+
+ for ps in ob.particle_systems:
+ if ps.vertex_group_density == 'B' or ps.vertex_group_density == 'A':
+ ps.invert_vertex_group_density = not ps.invert_vertex_group_density
+ ps.invert_vertex_group_density = not ps.invert_vertex_group_density
+
+class TISSUE_PT_reaction_diffusion(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_label = "Tissue - Reaction-Diffusion"
+ bl_options = {'DEFAULT_CLOSED'}
+ @classmethod
+ def poll(cls, context):
+ return 'A' and 'B' in context.object.vertex_groups
-if __name__ == "__main__":
- register()
+ def draw(self, context):
+ reaction_diffusion_add_handler(self, context)
+
+ ob = context.object
+ props = ob.reaction_diffusion_settings
+ layout = self.layout
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ if not ("A" and "B" in ob.vertex_groups):
+ row.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reaction-Diffusion")
+ else:
+ row.operator("object.start_reaction_diffusion",
+ icon="EXPERIMENTAL",
+ text="Reset Reaction-Diffusion")
+ 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, "time_steps")
+ row.prop(props, "dt")
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "diff_a")
+ row.prop(props, "diff_b")
+ row = col.row(align=True)
+ row.prop(props, "diff_mult")
+ #col.separator()
+ row = col.row(align=True)
+ row.prop(props, "f")
+ row.prop(props, "k")
diff --git a/mesh_tissue/dual_mesh.py b/mesh_tissue/dual_mesh.py
index 96f3b59b..eefd4c07 100644
--- a/mesh_tissue/dual_mesh.py
+++ b/mesh_tissue/dual_mesh.py
@@ -32,8 +32,8 @@
bl_info = {
"name": "Dual Mesh",
"author": "Alessandro Zomparelli (Co-de-iT)",
- "version": (0, 3),
- "blender": (2, 78, 0),
+ "version": (0, 4),
+ "blender": (2, 8, 0),
"location": "",
"description": "Convert a generic mesh to its dual",
"warning": "",
@@ -47,15 +47,105 @@ from bpy.props import (
EnumProperty,
)
import bmesh
+from .utils import *
+class dual_mesh_tessellated(Operator):
+ bl_idname = "object.dual_mesh_tessellated"
+ bl_label = "Dual Mesh"
+ bl_description = ("Generate a polygonal mesh using Tessellate. (Non-destructive)")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ apply_modifiers : BoolProperty(
+ name="Apply Modifiers",
+ default=True,
+ description="Apply object's modifiers"
+ )
+
+ source_faces : EnumProperty(
+ items=[
+ ('QUAD', 'Quad Faces', ''),
+ ('TRI', 'Triangles', '')],
+ name="Source Faces",
+ description="Source polygons",
+ default="QUAD",
+ options={'LIBRARY_EDITABLE'}
+ )
+
+ def execute(self, context):
+ auto_layer_collection()
+ ob0 = context.object
+ name1 = "DualMesh_{}_Component".format(self.source_faces)
+ # Generate component
+ if self.source_faces == 'QUAD':
+ verts = [(0.0, 0.0, 0.0), (0.0, 0.5, 0.0),
+ (0.0, 1.0, 0.0), (0.5, 1.0, 0.0),
+ (1.0, 1.0, 0.0), (1.0, 0.5, 0.0),
+ (1.0, 0.0, 0.0), (0.5, 0.0, 0.0),
+ (1/3, 1/3, 0.0), (2/3, 2/3, 0.0)]
+ edges = [(0,1), (1,2), (2,3), (3,4), (4,5), (5,6), (6,7),
+ (7,0), (1,8), (8,7), (3,9), (9,5), (8,9)]
+ faces = [(7,8,1,0), (8,9,3,2,1), (9,5,4,3), (9,8,7,6,5)]
+ else:
+ verts = [(0.0,0.0,0.0), (0.5,0.0,0.0), (1.0,0.0,0.0), (0.0,1.0,0.0), (0.5,1.0,0.0), (1.0,1.0,0.0)]
+ edges = [(0,1), (1,2), (2,5), (5,4), (4,3), (3,0), (1,4)]
+ faces = [(0,1,4,3), (1,2,5,4)]
+
+ # check pre-existing component
+ try:
+ _verts = [0]*len(verts)*3
+ __verts = [c for co in verts for c in co]
+ ob1 = bpy.data.objects[name1]
+ ob1.data.vertices.foreach_get("co",_verts)
+ for a, b in zip(_verts, __verts):
+ if abs(a-b) > 0.0001:
+ raise ValueError
+ except:
+ me = bpy.data.meshes.new("Dual-Mesh") # add a new mesh
+ me.from_pydata(verts, edges, faces)
+ me.update(calc_edges=True, calc_edges_loose=True, calc_loop_triangles=True)
+ if self.source_faces == 'QUAD': n_seams = 8
+ else: n_seams = 6
+ for i in range(n_seams): me.edges[i].use_seam = True
+ ob1 = bpy.data.objects.new(name1, me)
+ bpy.context.collection.objects.link(ob1)
+ # fix visualization issue
+ bpy.context.view_layer.objects.active = ob1
+ ob1.select_set(True)
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.object.editmode_toggle()
+ ob1.select_set(False)
+ # hide component
+ ob1.hide_select = True
+ ob1.hide_render = True
+ ob1.hide_viewport = True
+ ob = convert_object_to_mesh(ob0,False,False)
+ ob.name = 'DualMesh'
+ #ob = bpy.data.objects.new("DualMesh", convert_object_to_mesh(ob0,False,False))
+ #bpy.context.collection.objects.link(ob)
+ #bpy.context.view_layer.objects.active = ob
+ #ob.select_set(True)
+ ob.tissue_tessellate.component = ob1
+ ob.tissue_tessellate.generator = ob0
+ ob.tissue_tessellate.gen_modifiers = self.apply_modifiers
+ ob.tissue_tessellate.merge = True
+ ob.tissue_tessellate.bool_dissolve_seams = True
+ if self.source_faces == 'TRI': ob.tissue_tessellate.fill_mode = 'FAN'
+ print(ob1)
+ print(ob0)
+ print(bpy.context.object)
+ bpy.ops.object.update_tessellate()
+ ob.location = ob0.location
+ ob.matrix_world = ob0.matrix_world
+ return {'FINISHED'}
+
class dual_mesh(Operator):
bl_idname = "object.dual_mesh"
- bl_label = "Dual Mesh"
- bl_description = ("Convert a generic mesh into a polygonal mesh")
+ bl_label = "Convert to Dual Mesh"
+ bl_description = ("Convert a generic mesh into a polygonal mesh. (Destructive)")
bl_options = {'REGISTER', 'UNDO'}
- quad_method: EnumProperty(
+ quad_method : EnumProperty(
items=[('BEAUTY', 'Beauty',
'Split the quads in nice triangles, slower method'),
('FIXED', 'Fixed',
@@ -70,7 +160,7 @@ class dual_mesh(Operator):
default="FIXED",
options={'LIBRARY_EDITABLE'}
)
- polygon_method: EnumProperty(
+ polygon_method : EnumProperty(
items=[
('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'),
('CLIP', 'Clip',
@@ -80,12 +170,12 @@ class dual_mesh(Operator):
default="BEAUTY",
options={'LIBRARY_EDITABLE'}
)
- preserve_borders: BoolProperty(
+ preserve_borders : BoolProperty(
name="Preserve Borders",
default=True,
description="Preserve original borders"
)
- apply_modifiers: BoolProperty(
+ apply_modifiers : BoolProperty(
name="Apply Modifiers",
default=True,
description="Apply object's modifiers"
@@ -103,16 +193,6 @@ class dual_mesh(Operator):
sel = bpy.context.selected_objects
doneMeshes = []
- '''
- if self.new_object:
- bpy.ops.object.duplicate_move()
- for i in range(len(sel)):
- bpy.context.selected_objects[i].name = sel[i].name + "_dual"
- if sel[i] == act:
- bpy.context.scene.objects.active = bpy.context.selected_objects[i]
- sel = bpy.context.selected_objects
- '''
-
for ob0 in sel:
if ob0.type != 'MESH':
continue
@@ -153,16 +233,7 @@ class dual_mesh(Operator):
)
bpy.ops.mesh.extrude_region_move(
MESH_OT_extrude_region={"mirror": False},
- TRANSFORM_OT_translate={"value": (0, 0, 0),
- "constraint_axis": (False, False, False),
- "orient_type": 'GLOBAL', "mirror": False,
- "proportional": 'DISABLED',
- "proportional_edit_falloff": 'SMOOTH', "proportional_size": 1,
- "snap": False, "snap_target": 'CLOSEST',
- "snap_point": (0, 0, 0), "snap_align": False,
- "snap_normal": (0, 0, 0), "gpencil_strokes": False,
- "texture_space": False, "remove_on_cancel": False,
- "release_confirm": False}
+ TRANSFORM_OT_translate={"value": (0, 0, 0)}
)
bpy.ops.mesh.select_mode(
@@ -185,19 +256,21 @@ class dual_mesh(Operator):
bpy.ops.object.modifier_apply(
apply_as='DATA', modifier='dual_mesh_subsurf'
)
+
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
verts = ob.data.vertices
bpy.ops.object.mode_set(mode='OBJECT')
- verts[0].select = True
+ verts[-1].select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_more(use_face_step=False)
bpy.ops.mesh.select_similar(
type='EDGE', compare='EQUAL', threshold=0.01)
bpy.ops.mesh.select_all(action='INVERT')
+
bpy.ops.mesh.dissolve_verts()
bpy.ops.mesh.select_all(action='DESELECT')
@@ -244,7 +317,7 @@ class dual_mesh(Operator):
bm = bmesh.from_edit_mesh(ob.data)
for v in bm.verts:
if len(v.link_edges) == 2 and len(v.link_faces) < 3:
- v.select_set(True)
+ v.select = True
# dissolve
bpy.ops.mesh.dissolve_verts()
@@ -280,36 +353,3 @@ class dual_mesh(Operator):
bpy.ops.object.mode_set(mode=mode)
return {'FINISHED'}
-
-
-"""
-class dual_mesh_panel(bpy.types.Panel):
- bl_label = "Dual Mesh"
- bl_category = "Tools"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_context = "objectmode"
-
- def draw(self, context):
- layout = self.layout
- col = layout.column(align=True)
- try:
- if bpy.context.active_object.type == 'MESH':
- col.operator("object.dual_mesh")
- except:
- pass
-"""
-
-
-def register():
- bpy.utils.register_class(dual_mesh)
- # bpy.utils.register_class(dual_mesh_panel)
-
-
-def unregister():
- bpy.utils.unregister_class(dual_mesh)
- # bpy.utils.unregister_class(dual_mesh_panel)
-
-
-if __name__ == "__main__":
- register()
diff --git a/mesh_tissue/lattice.py b/mesh_tissue/lattice.py
index 2850c77a..fade7837 100644
--- a/mesh_tissue/lattice.py
+++ b/mesh_tissue/lattice.py
@@ -31,7 +31,7 @@ bl_info = {
"name": "Lattice",
"author": "Alessandro Zomparelli (Co-de-iT)",
"version": (0, 3),
- "blender": (2, 78, 0),
+ "blender": (2, 7, 8),
"location": "",
"description": "Generate a Lattice based on a grid mesh",
"warning": "",
@@ -42,12 +42,11 @@ bl_info = {
import bpy
import bmesh
from bpy.types import Operator
-from bpy.props import (
- BoolProperty,
- FloatProperty,
- )
+from bpy.props import (BoolProperty, StringProperty, FloatProperty)
from mathutils import Vector
+from .utils import *
+
def not_in(element, grid):
output = True
@@ -173,86 +172,93 @@ class lattice_along_surface(Operator):
"Lattice's topology")
bl_options = {'REGISTER', 'UNDO'}
- set_parent: BoolProperty(
+ set_parent : BoolProperty(
name="Set Parent",
default=True,
description="Automatically set the Lattice as parent"
)
- flipNormals: BoolProperty(
+ flipNormals : BoolProperty(
name="Flip Normals",
default=False,
description="Flip normals direction"
)
- swapUV: BoolProperty(
+ swapUV : BoolProperty(
name="Swap UV",
default=False,
description="Flip grid's U and V"
)
- flipU: BoolProperty(
+ flipU : BoolProperty(
name="Flip U",
default=False,
description="Flip grid's U")
- flipV: BoolProperty(
+ flipV : BoolProperty(
name="Flip V",
default=False,
description="Flip grid's V"
)
- flipW: BoolProperty(
+ flipW : BoolProperty(
name="Flip W",
default=False,
description="Flip grid's W"
)
- use_groups: BoolProperty(
+ use_groups : BoolProperty(
name="Vertex Group",
default=False,
description="Use active Vertex Group for lattice's thickness"
)
- high_quality_lattice: BoolProperty(
+ high_quality_lattice : BoolProperty(
name="High quality",
default=True,
description="Increase the the subdivisions in normal direction for a "
"more correct result"
)
- hide_lattice: BoolProperty(
+ hide_lattice : BoolProperty(
name="Hide Lattice",
default=True,
description="Automatically hide the Lattice object"
)
- scale_x: FloatProperty(
+ scale_x : FloatProperty(
name="Scale X",
default=1,
min=0.001,
max=1,
description="Object scale"
)
- scale_y: FloatProperty(
+ scale_y : FloatProperty(
name="Scale Y", default=1,
min=0.001,
max=1,
description="Object scale"
)
- scale_z: FloatProperty(
+ scale_z : FloatProperty(
name="Scale Z",
default=1,
min=0.001,
max=1,
description="Object scale"
)
- thickness: FloatProperty(
+ thickness : FloatProperty(
name="Thickness",
default=1,
soft_min=0,
soft_max=5,
description="Lattice thickness"
)
- displace: FloatProperty(
+ displace : FloatProperty(
name="Displace",
default=0,
soft_min=-1,
soft_max=1,
description="Lattice displace"
)
+ grid_object = ""
+ source_object = ""
+
+ @classmethod
+ def poll(cls, context):
+ try: return bpy.context.object.mode == 'OBJECT'
+ except: return False
def draw(self, context):
layout = self.layout
@@ -282,12 +288,6 @@ class lattice_along_surface(Operator):
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1
)
- """
- col.prop(
- self, "scale_z", text="W", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
- """
col.separator()
col.label(text="Flip:")
row = col.row()
@@ -303,39 +303,48 @@ class lattice_along_surface(Operator):
col.prop(self, "set_parent")
def execute(self, context):
- if len(bpy.context.selected_objects) != 2:
- self.report({'ERROR'}, "Please, select two objects")
- return {'CANCELLED'}
- depsgraph = context.evaluated_depsgraph_get()
- grid_obj = bpy.context.active_object
- if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
- self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
- "Curve and Surface objects are allowed.")
- return {'CANCELLED'}
- obj = None
- for o in bpy.context.selected_objects:
- if o.name != grid_obj.name and o.type in \
- ('MESH', 'CURVE', 'SURFACE', 'FONT'):
- obj = o
- o.select_set(False)
- break
- obj_eval = obj.evaluated_get(depsgraph)
- try:
- obj_dim = obj.dimensions
- obj_me = obj_eval.to_mesh()
- except:
- self.report({'ERROR'}, "The object to deform is not valid. Only "
- "Mesh, Curve, Surface and Font objects are allowed.")
- return {'CANCELLED'}
+ if self.source_object == self.grid_object == "" or True:
+ if len(bpy.context.selected_objects) != 2:
+ self.report({'ERROR'}, "Please, select two objects")
+ return {'CANCELLED'}
+ grid_obj = bpy.context.object
+ if grid_obj.type not in ('MESH', 'CURVE', 'SURFACE'):
+ self.report({'ERROR'}, "The surface object is not valid. Only Mesh,"
+ "Curve and Surface objects are allowed.")
+ return {'CANCELLED'}
+ obj = None
+ for o in bpy.context.selected_objects:
+ if o.name != grid_obj.name and o.type in \
+ ('MESH', 'CURVE', 'SURFACE', 'FONT'):
+ obj = o
+ o.select_set(False)
+ break
+ try:
+ obj_dim = obj.dimensions
+ obj_me = simple_to_mesh(obj)#obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
+ except:
+ self.report({'ERROR'}, "The object to deform is not valid. Only "
+ "Mesh, Curve, Surface and Font objects are allowed.")
+ return {'CANCELLED'}
+ self.grid_object = grid_obj.name
+ self.source_object = obj.name
+ else:
+ grid_obj = bpy.data.objects[self.grid_object]
+ obj = bpy.data.objects[self.source_object]
+ obj_me = simple_to_mesh(obj)# obj.to_mesh(bpy.context.depsgraph, apply_modifiers=True)
+ for o in bpy.context.selected_objects: o.select_set(False)
+ grid_obj.select_set(True)
+ bpy.context.view_layer.objects.active = grid_obj
- bpy.ops.object.duplicate_move()
- grid_obj = bpy.context.active_object
- bpy.ops.object.convert(target='MESH')
- bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
- grid_mesh = grid_obj.evaluated_get(depsgraph).to_mesh()
+ temp_grid_obj = grid_obj.copy()
+ temp_grid_obj.data = simple_to_mesh(grid_obj)
+ 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.ops.object.delete(use_global=False)
+ bpy.data.objects.remove(temp_grid_obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
self.report({'ERROR'}, "Maximum resolution allowed for Lattice is 64")
@@ -346,7 +355,8 @@ class lattice_along_surface(Operator):
max = Vector((0, 0, 0))
first = True
for v in obj_me.vertices:
- vert = obj.matrix_world * v.co
+ v0 = v.co.copy()
+ vert = obj.matrix_world @ v0
if vert[0] < min[0] or first:
min[0] = vert[0]
if vert[1] < min[1] or first:
@@ -361,12 +371,10 @@ class lattice_along_surface(Operator):
max[2] = vert[2]
first = False
- obj_eval.to_mesh_clear()
-
bb = max - min
+ print(bb)
lattice_loc = (max + min) / 2
- bpy.ops.object.add(type='LATTICE', align='WORLD',
- enter_editmode=False)
+ bpy.ops.object.add(type='LATTICE')
lattice = bpy.context.active_object
lattice.location = lattice_loc
lattice.scale = Vector((bb.x / self.scale_x, bb.y / self.scale_y,
@@ -409,10 +417,10 @@ class lattice_along_surface(Operator):
for w in range(nw):
if self.use_groups:
try:
- displace = grid_obj.vertex_groups.active.weight(
+ displace = temp_grid_obj.vertex_groups.active.weight(
verts_grid[i][j]) * scale_normal * bb.z
except:
- displace = scale_normal * bb.z
+ displace = 0#scale_normal * bb.z
else:
displace = scale_normal * bb.z
target_point = (grid_mesh.vertices[verts_grid[i][j]].co +
@@ -424,15 +432,17 @@ class lattice_along_surface(Operator):
i = nu - i - 1
if self.flipV:
j = nv - j - 1
+
lattice.data.points[i + j * nu + w * nu * nv].co_deform.x = \
target_point.x / bpy.data.objects[lattice.name].scale.x
lattice.data.points[i + j * nu + w * nu * nv].co_deform.y = \
target_point.y / bpy.data.objects[lattice.name].scale.y
lattice.data.points[i + j * nu + w * nu * nv].co_deform.z = \
target_point.z / bpy.data.objects[lattice.name].scale.z
+
except:
bpy.ops.object.mode_set(mode='OBJECT')
- grid_obj.select_set(True)
+ temp_grid_obj.select_set(True)
lattice.select_set(True)
obj.select_set(False)
bpy.ops.object.delete(use_global=False)
@@ -446,15 +456,11 @@ class lattice_along_surface(Operator):
self.report({'ERROR'}, "The grid mesh is not correct")
return {'CANCELLED'}
- # grid_obj.data = old_grid_data
- # print(old_grid_matrix)
- # grid_obj.matrix_world = old_grid_matrix
-
bpy.ops.object.mode_set(mode='OBJECT')
- grid_obj.select_set(True)
- lattice.select_set(False)
+ #grid_obj.select_set(True)
+ #lattice.select_set(False)
obj.select_set(False)
- bpy.ops.object.delete(use_global=False)
+ #bpy.ops.object.delete(use_global=False)
bpy.context.view_layer.objects.active = lattice
lattice.select_set(True)
@@ -478,38 +484,7 @@ class lattice_along_surface(Operator):
bpy.ops.object.mode_set(mode='OBJECT')
except:
pass
+ bpy.data.meshes.remove(grid_mesh)
+ bpy.data.meshes.remove(obj_me)
return {'FINISHED'}
-
-
-"""
-class lattice_along_surface_panel(bpy.types.Panel):
- bl_label = "Modifiers Tools"
- bl_category = "Tools"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_context = "objectmode"
-
- def draw(self, context):
- layout = self.layout
- col = layout.column(align=True)
-
- try:
- col.operator("object.lattice_along_surface", icon="MOD_LATTICE")
- except:
- pass
-"""
-
-
-def register():
- bpy.utils.register_class(lattice_along_surface)
- # bpy.utils.register_class(lattice_along_surface_panel)
-
-
-def unregister():
- bpy.utils.unregister_class(lattice_along_surface)
- # bpy.utils.unregister_class(lattice_along_surface_panel)
-
-
-if __name__ == "__main__":
- register()
diff --git a/mesh_tissue/numba_functions.py b/mesh_tissue/numba_functions.py
new file mode 100644
index 00000000..1cb8e6b8
--- /dev/null
+++ b/mesh_tissue/numba_functions.py
@@ -0,0 +1,40 @@
+# ##### 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 #####
+
+from numba import jit
+import numpy as np
+
+@jit
+def numba_reaction_diffusion(n_verts, a, b, diff_a, diff_b, f, k, dt, id0, id1, time_steps):
+ for i in range(time_steps):
+ lap_a = np.zeros(n_verts)
+ lap_b = np.zeros(n_verts)
+ lap_a0 = a[id1] - a[id0] # laplacian increment for first vertex of each edge
+ lap_b0 = b[id1] - b[id0] # laplacian increment for first vertex of each edge
+
+ for i, j, la0, lb0 in zip(id0,id1,lap_a0,lap_b0):
+ lap_a[i] += la0
+ lap_b[i] += lb0
+ lap_a[j] -= la0
+ lap_b[j] -= lb0
+ ab2 = a*b**2
+ #a += eval("(diff_a*lap_a - ab2 + f*(1-a))*dt")
+ #b += eval("(diff_b*lap_b + ab2 - (k+f)*b)*dt")
+ a += (diff_a*lap_a - ab2 + f*(1-a))*dt
+ b += (diff_b*lap_b + ab2 - (k+f)*b)*dt
+ return a, b
diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py
index e5f6db5c..9526d115 100644
--- a/mesh_tissue/tessellate_numpy.py
+++ b/mesh_tissue/tessellate_numpy.py
@@ -42,78 +42,548 @@ from bpy.props import (
FloatProperty,
IntProperty,
StringProperty,
+ PointerProperty
)
from mathutils import Vector
import numpy as np
from math import sqrt
-import random
-
-
-def lerp(a, b, t):
- return a + (b - a) * t
-
-
-def lerp2(v1, v2, v3, v4, v):
- v12 = v1 + (v2 - v1) * v.x
- v43 = v4 + (v3 - v4) * v.x
- return v12 + (v43 - v12) * v.y
-
+import random, time
+import bmesh
+from .utils import *
+
+def anim_tessellate_active(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ if not props.bool_hold:
+ try:
+ props.generator.name
+ props.component.name
+ bpy.ops.object.update_tessellate()
+ except: pass
+
+def anim_tessellate_object(ob):
+ try:
+ #bpy.context.view_layer.objects.active = ob
+ bpy.ops.object.update_tessellate()
+ except:
+ return None
+
+#from bpy.app.handlers import persistent
+
+#@persistent
+def anim_tessellate(scene):
+ # store selected objects
+ #scene = context.scene
+ try: active_object = bpy.context.object
+ except: active_object = None
+ try: selected_objects = bpy.context.selected_objects
+ except: selected_objects = []
+ if bpy.context.mode in ('OBJECT', 'PAINT_WEIGHT'):
+ old_mode = bpy.context.mode
+ if old_mode == 'PAINT_WEIGHT': old_mode = 'WEIGHT_PAINT'
+ for ob in scene.objects:
+ if ob.tissue_tessellate.bool_run:
+ hidden = ob.hide_viewport
+ ob.hide_viewport = False
+ for o in scene.objects:
+ if not o.hide_viewport: ob.select_set(False)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ try:
+ bpy.ops.object.update_tessellate()
+ except: pass
+ ob.hide_viewport = hidden
+ # restore selected objects
+ for o in scene.objects:
+ if not o.hide_viewport: o.select_set(False)
+ for o in selected_objects:
+ if not o.hide_viewport: o.select_set(True)
+ bpy.context.view_layer.objects.active = active_object
+ try: bpy.ops.object.mode_set(mode=old_mode)
+ except: pass
+ return
+
+def set_tessellate_handler(self, context):
+ old_handlers = []
+ blender_handlers = bpy.app.handlers.frame_change_post
+ for h in blender_handlers:
+ if "anim_tessellate" in str(h):
+ old_handlers.append(h)
+ for h in old_handlers: blender_handlers.remove(h)
+ for o in context.scene.objects:
+ if o.tissue_tessellate.bool_run:
+ blender_handlers.append(anim_tessellate)
+ break
+ return
-def lerp3(v1, v2, v3, v4, v):
- loc = lerp2(v1.co, v2.co, v3.co, v4.co, v)
- nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v)
- nor.normalize()
- return loc + nor * v.z
+class tissue_tessellate_prop(PropertyGroup):
+ bool_hold : BoolProperty(
+ name="Hold Update",
+ description="Prevent automatic update while other properties are changed",
+ default=False
+ )
+ bool_run : BoolProperty(
+ name="Animatable Tessellation",
+ description="Automatically recompute the tessellation when the frame is changed",
+ default = False,
+ update = set_tessellate_handler
+ )
+ zscale : FloatProperty(
+ name="Scale", default=1, soft_min=0, soft_max=10,
+ description="Scale factor for the component thickness",
+ update = anim_tessellate_active
+ )
+ scale_mode : EnumProperty(
+ items=(
+ ('CONSTANT', "Constant", "Uniform thinkness"),
+ ('ADAPTIVE', "Proportional", "Preserve component's proportions")
+ ),
+ default='ADAPTIVE',
+ name="Z-Scale according to faces size",
+ update = anim_tessellate_active
+ )
+ offset : FloatProperty(
+ name="Surface Offset",
+ default=1,
+ min=-1,
+ max=1,
+ soft_min=-1,
+ soft_max=1,
+ description="Surface offset",
+ update = anim_tessellate_active
+ )
+ mode : EnumProperty(
+ items=(
+ ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
+ ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
+ ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
+ default='BOUNDS',
+ name="Component Mode",
+ update = anim_tessellate_active
+ )
+ rotation_mode : EnumProperty(
+ items=(('RANDOM', "Random", "Random faces rotation"),
+ ('UV', "Active UV", "Rotate according to UV coordinates"),
+ ('DEFAULT', "Default", "Default rotation")),
+ default='DEFAULT',
+ name="Component Rotation",
+ update = anim_tessellate_active
+ )
+ fill_mode : EnumProperty(
+ items=(
+ ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
+ ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
+ ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
+ 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
+ 'patches.\nAfter the last Subsurf (or Multires) only ' +
+ 'deformation\nmodifiers can be used')),
+ default='QUAD',
+ name="Fill Mode",
+ update = anim_tessellate_active
+ )
+ combine_mode : EnumProperty(
+ items=(
+ ('LAST', 'Last', 'Show only the last iteration'),
+ ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
+ ('ALL', 'All', 'Combine the result of all iterations')),
+ default='LAST',
+ name="Combine Mode",
+ update = anim_tessellate_active
+ )
+ gen_modifiers : BoolProperty(
+ name="Generator Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the base object",
+ update = anim_tessellate_active
+ )
+ com_modifiers : BoolProperty(
+ name="Component Modifiers",
+ default=False,
+ description="Apply Modifiers and Shape Keys to the component object",
+ update = anim_tessellate_active
+ )
+ merge : BoolProperty(
+ name="Merge",
+ default=False,
+ description="Merge vertices in adjacent duplicates",
+ update = anim_tessellate_active
+ )
+ merge_thres : FloatProperty(
+ name="Distance",
+ default=0.001,
+ soft_min=0,
+ soft_max=10,
+ description="Limit below which to merge vertices",
+ update = anim_tessellate_active
+ )
+ generator : PointerProperty(
+ type=bpy.types.Object,
+ name="",
+ description="Base object for the tessellation",
+ update = anim_tessellate_active
+ )
+ component : PointerProperty(
+ type=bpy.types.Object,
+ name="",
+ description="Component object for the tessellation",
+ #default="",
+ update = anim_tessellate_active
+ )
+ bool_random : BoolProperty(
+ name="Randomize",
+ default=False,
+ description="Randomize component rotation",
+ update = anim_tessellate_active
+ )
+ random_seed : IntProperty(
+ name="Seed",
+ default=0,
+ soft_min=0,
+ soft_max=10,
+ description="Random seed",
+ update = anim_tessellate_active
+ )
+ bool_vertex_group : BoolProperty(
+ name="Map Vertex Group",
+ default=False,
+ description="Transfer all Vertex Groups from Base object",
+ update = anim_tessellate_active
+ )
+ bool_selection : BoolProperty(
+ name="On selected Faces",
+ default=False,
+ description="Create Tessellation only on selected faces",
+ update = anim_tessellate_active
+ )
+ bool_shapekeys : BoolProperty(
+ name="Use Shape Keys",
+ default=False,
+ description="Transfer Component's Shape Keys. If the name of Vertex "
+ "Groups and Shape Keys are the same, they will be "
+ "automatically combined",
+ update = anim_tessellate_active
+ )
+ bool_smooth : BoolProperty(
+ name="Smooth Shading",
+ default=False,
+ description="Output faces with smooth shading rather than flat shaded",
+ update = anim_tessellate_active
+ )
+ bool_materials : BoolProperty(
+ name="Transfer Materials",
+ default=False,
+ description="Preserve component's materials",
+ update = anim_tessellate_active
+ )
+ bool_material_id : BoolProperty(
+ name="Tessellation on Material ID",
+ default=False,
+ description="Apply the component only on the selected Material",
+ update = anim_tessellate_active
+ )
+ material_id : IntProperty(
+ name="Material ID",
+ default=0,
+ min=0,
+ description="Material ID",
+ update = anim_tessellate_active
+ )
+ bool_dissolve_seams : BoolProperty(
+ name="Dissolve Seams",
+ default=False,
+ description="Dissolve all seam edges",
+ update = anim_tessellate_active
+ )
+ iterations : IntProperty(
+ name="Iterations",
+ default=1,
+ min=1,
+ soft_max=5,
+ description="Automatically repeat the Tessellation using the "
+ + "generated geometry as new base object.\nUsefull for "
+ + "for branching systems. Dangerous!",
+ update = anim_tessellate_active
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces",
+ update = anim_tessellate_active
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Individual Faces', 'Based on individual faces normal')),
+ default='VERTS',
+ name="Direction",
+ update = anim_tessellate_active
+ )
+ bool_multi_components : BoolProperty(
+ name="Multi Components",
+ default=False,
+ description="Combine different components according to materials name",
+ update = anim_tessellate_active
+ )
+ error_message : StringProperty(
+ name="Error Message",
+ default=""
+ )
+ warning_message : StringProperty(
+ name="Warning Message",
+ default=""
+ )
+ bounds_x : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default X coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
+ default='EXTEND',
+ name="Bounds X",
+ update = anim_tessellate_active
+ )
+ bounds_y : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default Y coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
+ default='EXTEND',
+ name="Bounds Y",
+ update = anim_tessellate_active
+ )
+ cap_faces : BoolProperty(
+ name="Cap Holes",
+ default=False,
+ description="Cap open edges loops",
+ update = anim_tessellate_active
+ )
+ open_edges_crease : FloatProperty(
+ name="Open Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for open edges",
+ update = anim_tessellate_active
+ )
+def store_parameters(operator, ob):
+ ob.tissue_tessellate.bool_hold = True
+ ob.tissue_tessellate.generator = bpy.data.objects[operator.generator]
+ ob.tissue_tessellate.component = bpy.data.objects[operator.component]
+ ob.tissue_tessellate.zscale = operator.zscale
+ ob.tissue_tessellate.offset = operator.offset
+ ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers
+ ob.tissue_tessellate.com_modifiers = operator.com_modifiers
+ ob.tissue_tessellate.mode = operator.mode
+ ob.tissue_tessellate.rotation_mode = operator.rotation_mode
+ ob.tissue_tessellate.merge = operator.merge
+ ob.tissue_tessellate.merge_thres = operator.merge_thres
+ ob.tissue_tessellate.scale_mode = operator.scale_mode
+ ob.tissue_tessellate.bool_random = operator.bool_random
+ ob.tissue_tessellate.random_seed = operator.random_seed
+ ob.tissue_tessellate.fill_mode = operator.fill_mode
+ ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group
+ ob.tissue_tessellate.bool_selection = operator.bool_selection
+ ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys
+ ob.tissue_tessellate.bool_smooth = operator.bool_smooth
+ ob.tissue_tessellate.bool_materials = operator.bool_materials
+ ob.tissue_tessellate.bool_material_id = operator.bool_material_id
+ ob.tissue_tessellate.material_id = operator.material_id
+ ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams
+ ob.tissue_tessellate.iterations = operator.iterations
+ ob.tissue_tessellate.bool_advanced = operator.bool_advanced
+ ob.tissue_tessellate.normals_mode = operator.normals_mode
+ ob.tissue_tessellate.bool_combine = operator.bool_combine
+ ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components
+ ob.tissue_tessellate.combine_mode = operator.combine_mode
+ ob.tissue_tessellate.bounds_x = operator.bounds_x
+ ob.tissue_tessellate.bounds_y = operator.bounds_y
+ ob.tissue_tessellate.cap_faces = operator.cap_faces
+ ob.tissue_tessellate.bool_hold = False
+ return ob
-def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
- scale_mode, rotation_mode, rand_seed, fill_mode,
- bool_vertex_group, bool_selection, bool_shapekeys):
+def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode,
+ scale_mode, rotation_mode, rand_seed, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id, material_id,
+ bounds_x, bounds_y):
random.seed(rand_seed)
- old_me0 = ob0.data # Store generator mesh
-
- if gen_modifiers or com_modifiers:
- depsgraph = bpy.context.evaluated_depsgraph_get()
- else:
- depsgraph = None
- me0_owner = None
- if gen_modifiers: # Apply generator modifiers
- me0_owner = ob0.evaluated_get(depsgraph)
- me0 = me0_owner.to_mesh()
- else:
- me0 = ob0.data
- ob0.data = me0
- base_polygons = []
+ ob0 = convert_object_to_mesh(_ob0)
+ me0 = _ob0.data
# Check if zero faces are selected
- if bool_selection:
- for p in ob0.data.polygons:
- if p.select:
- base_polygons.append(p)
- else:
- base_polygons = ob0.data.polygons
- if len(base_polygons) == 0:
- return 0
+ if _ob0.type == 'MESH':
+ bool_cancel = True
+ for p in me0.polygons:
+ check_sel = check_mat = False
+ if not bool_selection or p.select: check_sel = True
+ if not bool_material_id or p.material_index == material_id: check_mat = True
+ if check_sel and check_mat:
+ bool_cancel = False
+ break
+ if bool_cancel:
+ return 0
+
+ levels = 0
+ sculpt_levels = 0
+ render_levels = 0
+ bool_multires = False
+ multires_name = ""
+ not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
+ 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH',
+ 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN',
+ 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE']
+ modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers]
+ show_modifiers = [m.show_viewport for m in _ob0.modifiers]
+ show_modifiers.reverse()
+ modifiers0.reverse()
+ for m in modifiers0:
+ visible = m.show_viewport
+ #m.show_viewport = False
+ if m.type in ('SUBSURF', 'MULTIRES') and visible:
+ levels = m.levels
+ multires_name = m.name
+ if m.type == 'MULTIRES':
+ bool_multires = True
+ multires_name = m.name
+ sculpt_levels = m.sculpt_levels
+ render_levels = m.render_levels
+ else: bool_multires = False
+ break
+ elif m.type in not_allowed:
+ #ob0.data = old_me0
+ #bpy.data.meshes.remove(me0)
+ return "modifiers_error"
+
+ before = _ob0.copy()
+ #if ob0.type == 'MESH': before.data = me0
+ before_mod = list(before.modifiers)
+ before_mod.reverse()
+ for m in before_mod:
+ if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport:
+ before.modifiers.remove(m)
+ break
+ else: before.modifiers.remove(m)
+
+ before_subsurf = simple_to_mesh(before)
+
+ before_bm = bmesh.new()
+ before_bm.from_mesh(before_subsurf)
+ before_bm.faces.ensure_lookup_table()
+ for f in before_bm.faces:
+ if len(f.loops) != 4:
+ return "topology_error"
+ before_bm.edges.ensure_lookup_table()
+ for e in before_bm.edges:
+ if len(e.link_faces) == 0:
+ return "wires_error"
+ before_bm.verts.ensure_lookup_table()
+ for v in before_bm.verts:
+ if len(v.link_faces) == 0:
+ return "verts_error"
+
+ me0 = ob0.data
+ verts0 = me0.vertices # Collect generator vertices
- # Apply component modifiers
- me1_owner = ob1.evaluated_get(depsgraph)
- if com_modifiers:
- me1 = me1_owner.to_mesh()
- else:
- me1 = ob1.data
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
- verts0 = me0.vertices # Collect generator vertices
+ # set Shape Keys to zero
+ if bool_shapekeys:
+ try:
+ original_key_values = []
+ for sk in _ob1.data.shape_keys.key_blocks:
+ original_key_values.append(sk.value)
+ sk.value = 0
+ except:
+ bool_shapekeys = False
+
+ if not com_modifiers and not bool_shapekeys:
+ mod_visibility = []
+ for m in _ob1.modifiers:
+ mod_visibility.append(m.show_viewport)
+ m.show_viewport = False
+ com_modifiers = True
+
+ ob1 = convert_object_to_mesh(_ob1, com_modifiers, False)
+ me1 = ob1.data
+
+ if mode != 'BOUNDS':
+ bpy.context.object.active_shape_key_index = 0
+ # Bound X
+ if bounds_x != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (1,0,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.x > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.x < 0]:
+ f.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_x == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_x == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # Bound Y
+ if bounds_y != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (0,1,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.y > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.y < 0]:
+ f.select = True
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_y == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_y == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
# Component statistics
n_verts = len(me1.vertices)
- # n_edges = len(me1.edges)
- # n_faces = len(me1.polygons)
-
- # Component transformations
- # loc = ob1.location
- # dim = ob1.dimensions
- # scale = ob1.scale
# Create empty lists
new_verts = []
@@ -145,112 +615,763 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
# adaptive XY
verts1 = []
for v in me1.vertices:
- if mode == "ADAPTIVE":
+ if mode == 'BOUNDS':
vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c
vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5)
vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5)
vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- else:
+ elif mode == 'LOCAL':
vert = v.co.xyz
- vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- verts1.append(vert)
+ vert[2] *= zscale
+ #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ elif mode == 'GLOBAL':
+ vert = ob1.matrix_world @ v.co
+ vert[2] *= zscale
+ try:
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
+ except: pass
+ #verts1.append(vert)
+ v.co = vert
+
+ # Bounds X, Y
+ if mode != 'BOUNDS':
+ if bounds_x == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x += 1
+ except: pass
+ if bounds_y == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y += 1
+ except: pass
+ verts1 = [v.co for v in me1.vertices]
+
+ patch_faces = 4**levels
+ sides = int(sqrt(patch_faces))
+ sides0 = sides-2
+ patch_faces0 = int((sides-2)**2)
+ n_patches = int(len(me0.polygons)/patch_faces)
+ if len(me0.polygons)%patch_faces != 0:
+ #ob0.data = old_me0
+ return "topology_error"
+
+ new_verts = []
+ new_edges = []
+ new_faces = []
+
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_patch = None
+
+ # All vertex group
+ if bool_vertex_group:
+ try:
+ weight = []
+ for vg in ob0.vertex_groups:
+ _weight = []
+ for v in me0.vertices:
+ try:
+ _weight.append(vg.weight(v.index))
+ except:
+ _weight.append(0)
+ weight.append(_weight)
+ except:
+ bool_vertex_group = False
+
+ # Adaptive Z
+ if scale_mode == 'ADAPTIVE':
+ if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
+ else: com_area = 1
+ mult = 1/com_area*patch_faces
+ verts_area = []
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ for v in bm.verts:
+ area = 0
+ faces = v.link_faces
+ for f in faces:
+ area += f.calc_area()
+ area/=len(faces)
+ area*=mult
+ verts_area.append(sqrt(area))
+
+ random.seed(rand_seed)
+ bool_correct = False
+
+ _faces = [[[0] for ii in range(sides)] for jj in range(sides)]
+ _verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)]
+
+ for i in range(n_patches):
+ poly = me0.polygons[i*patch_faces]
+ if bool_selection and not poly.select: continue
+ if bool_material_id and not poly.material_index == material_id: continue
+
+ bool_correct = True
+ new_patch = bpy.data.objects.new("patch", me1.copy())
+ bpy.context.collection.objects.link(new_patch)
+
+ new_patch.select_set(True)
+ bpy.context.view_layer.objects.active = new_patch
+
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ try: new_patch.local_view_set(space, True)
+ except: pass
+
+ # Vertex Group
+ if bool_vertex_group:
+ for vg in ob0.vertex_groups:
+ new_patch.vertex_groups.new(name=vg.name)
+
+ # find patch faces
+ faces = _faces.copy()
+ verts = _verts.copy()
+ shift1 = sides
+ shift2 = sides*2-1
+ shift3 = sides*3-2
+ for j in range(patch_faces):
+ if j < patch_faces0:
+ if levels == 0:
+ u = j%sides0
+ v = j//sides0
+ else:
+ u = j%sides0+1
+ v = j//sides0+1
+ elif j < patch_faces0 + shift1:
+ u = j-patch_faces0
+ v = 0
+ elif j < patch_faces0 + shift2:
+ u = sides-1
+ v = j-(patch_faces0 + sides)+1
+ elif j < patch_faces0 + shift3:
+ jj = j-(patch_faces0 + shift2)
+ u = sides-jj-2
+ v = sides-1
+ else:
+ jj = j-(patch_faces0 + shift3)
+ u = 0
+ v = sides-jj-2
+ face = me0.polygons[j+i*patch_faces]
+ faces[u][v] = face
+ verts[u][v] = verts0[face.vertices[0]]
+ if u == sides-1:
+ verts[sides][v] = verts0[face.vertices[1]]
+ if v == sides-1:
+ verts[u][sides] = verts0[face.vertices[3]]
+ if u == v == sides-1:
+ verts[sides][sides] = verts0[face.vertices[2]]
+
+ # Random rotation
+ if rotation_mode == 'RANDOM':
+ rand = random.randint(0, 3)
+ if rand == 1:
+ verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
+ elif rand == 2:
+ verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
+ elif rand == 3:
+ verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
+
+ # UV rotation
+ elif rotation_mode == 'UV' and ob0.type == 'MESH':
+ if len(ob0.data.uv_layers) > 0:
+ uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv
+ uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv
+ uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv
+ uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv
+ v01 = (uv0 + uv1)
+ v32 = (uv3 + uv2)
+ v0132 = v32 - v01
+ v0132.normalize()
+ v12 = (uv1 + uv2)
+ v03 = (uv0 + uv3)
+ v1203 = v03 - v12
+ v1203.normalize()
+
+ vertUV = []
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)):
+ if (dot0132 > 0):
+ pass
+ else:
+ verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)]
+ else:
+ if(dot1203 < 0):
+ verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)]
+ else:
+ verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)]
+
+ step = 1/sides
+ for vert, patch_vert in zip(verts1, new_patch.data.vertices):
+ # grid coordinates
+ u = int(vert[0]//step)
+ v = int(vert[1]//step)
+ u1 = min(u+1, sides)
+ v1 = min(v+1, sides)
+ if mode != 'BOUNDS':
+ if u > sides-1:
+ u = sides-1
+ u1 = sides
+ if u < 0:
+ u = 0
+ u1 = 1
+ if v > sides-1:
+ v = sides-1
+ v1 = sides
+ if v < 0:
+ v = 0
+ v1 = 1
+ v00 = verts[u][v]
+ v10 = verts[u1][v]
+ v01 = verts[u][v1]
+ v11 = verts[u1][v1]
+ # factor coordinates
+ fu = (vert[0]-u*step)/step
+ fv = (vert[1]-v*step)/step
+ fw = vert.z
+ # interpolate Z scaling factor
+ fvec2d = Vector((fu,fv,0))
+ if scale_mode == 'ADAPTIVE':
+ a00 = verts_area[v00.index]
+ a10 = verts_area[v10.index]
+ a01 = verts_area[v01.index]
+ a11 = verts_area[v11.index]
+ fw*=lerp2(a00,a10,a01,a11,fvec2d)
+ # build factor vector
+ fvec = Vector((fu,fv,fw))
+ # interpolate vertex on patch
+ patch_vert.co = lerp3(v00, v10, v01, v11, fvec)
+
+ # Vertex Group
+ if bool_vertex_group:
+ for _weight, vg in zip(weight, new_patch.vertex_groups):
+ w00 = _weight[v00.index]
+ w10 = _weight[v10.index]
+ w01 = _weight[v01.index]
+ w11 = _weight[v11.index]
+ wuv = lerp2(w00,w10,w01,w11, fvec2d)
+ vg.add([patch_vert.index], wuv, "ADD")
+
+ if bool_shapekeys:
+ for sk in ob1.data.shape_keys.key_blocks:
+ source = sk.data
+ for sk_v, _v in zip(source, me1.vertices):
+ if mode == 'BOUNDS':
+ sk_vert = sk_v.co - min_c # (ob1.matrix_world * v.co) - min_c
+ sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5)
+ sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5)
+ sk_vert[2] = (sk_vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ elif mode == 'LOCAL':
+ sk_vert = sk_v.co#.xyzco
+ #sk_vert[2] *= zscale
+ #sk_vert[2] = (sk_vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ elif mode == 'GLOBAL':
+ #sk_vert = ob1.matrix_world @ sk_v.co
+ sk_vert = sk_v.co
+ #sk_vert[2] *= zscale
+
+ # grid coordinates
+ u = int(sk_vert[0]//step)
+ v = int(sk_vert[1]//step)
+ u1 = min(u+1, sides)
+ v1 = min(v+1, sides)
+ if mode != 'BOUNDS':
+ if u > sides-1:
+ u = sides-1
+ u1 = sides
+ if u < 0:
+ u = 0
+ u1 = 1
+ if v > sides-1:
+ v = sides-1
+ v1 = sides
+ if v < 0:
+ v = 0
+ v1 = 1
+ v00 = verts[u][v]
+ v10 = verts[u1][v]
+ v01 = verts[u][v1]
+ v11 = verts[u1][v1]
+ # factor coordinates
+ fu = (sk_vert[0]-u*step)/step
+ fv = (sk_vert[1]-v*step)/step
+ fw = sk_vert.z
+
+ if scale_mode == 'ADAPTIVE':
+ a00 = verts_area[v00.index]
+ a10 = verts_area[v10.index]
+ a01 = verts_area[v01.index]
+ a11 = verts_area[v11.index]
+ fw*=lerp2(a00,a10,a01,a11,Vector((fu,fv,0)))
+
+ fvec = Vector((fu,fv,fw))
+ sk_co = lerp3(v00, v10, v01, v11, fvec)
+
+ new_patch.data.shape_keys.key_blocks[sk.name].data[_v.index].co = sk_co
+
+ #if ob0.type == 'MESH': ob0.data = old_me0
+ if not bool_correct: return 0
+
+ bpy.ops.object.join()
+
+ if bool_shapekeys:
+ # set original values and combine Shape Keys and Vertex Groups
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ new_patch.data.shape_keys.key_blocks[sk.name].value = val
+ if bool_vertex_group:
+ for sk in new_patch.data.shape_keys.key_blocks:
+ for vg in new_patch.vertex_groups:
+ if sk.name == vg.name:
+ sk.vertex_group = vg.name
- # component vertices
- vs1 = np.array([v for v in verts1]).reshape(len(verts1), 3, 1)
- vx = vs1[:, 0]
- vy = vs1[:, 1]
- vz = vs1[:, 2]
+ new_name = ob0.name + "_" + ob1.name
+ new_patch.name = "tessellate_temp"
+
+ if bool_multires:
+ for m in ob0.modifiers:
+ if m.type == 'MULTIRES' and m.name == multires_name:
+ m.levels = levels
+ m.sculpt_levels = sculpt_levels
+ m.render_levels = render_levels
+ # restore original modifiers visibility for component object
+ try:
+ for m, vis in zip(_ob1.modifiers, mod_visibility):
+ m.show_viewport = vis
+ except: pass
+
+ bpy.data.objects.remove(before)
+ bpy.data.objects.remove(ob0)
+ bpy.data.objects.remove(ob1)
+ return new_patch
+
+def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
+ scale_mode, rotation_mode, rand_seed, fill_mode,
+ bool_vertex_group, bool_selection, bool_shapekeys,
+ bool_material_id, material_id, normals_mode, bounds_x, bounds_y):
+
+ if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
+ random.seed(rand_seed)
+
+ if bool_shapekeys:
+ try:
+ original_key_values = []
+ for sk in _ob1.data.shape_keys.key_blocks:
+ original_key_values.append(sk.value)
+ sk.value = 0
+ except:
+ bool_shapekeys = False
+
+ ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True)
+ me0 = ob0.data
+ ob1 = convert_object_to_mesh(_ob1, com_modifiers, True)
+ me1 = ob1.data
+
+ base_polygons = []
+ base_face_normals = []
+
+ n_faces0 = len(me0.polygons)
+
+ # Check if zero faces are selected
+ if (bool_selection and ob0.type == 'MESH') or bool_material_id:
+ for p in me0.polygons:
+ if (bool_selection and ob0.type == 'MESH'):
+ is_sel = p.select
+ else: is_sel = True
+ if bool_material_id:
+ is_mat = p.material_index == material_id
+ else: is_mat = True
+ if is_sel and is_mat:
+ base_polygons.append(p)
+ base_face_normals.append(p.normal)
+ else:
+ base_polygons = me0.polygons
+ base_face_normals = [p.normal for p in me0.polygons]
+
+ # numpy test: slower
+ #base_face_normals = np.zeros(n_faces0*3)
+ #me0.polygons.foreach_get("normal", base_face_normals)
+ #base_face_normals = base_face_normals.reshape((n_faces0,3))
+
+ if len(base_polygons) == 0:
+ return 0
+
+ if mode != 'BOUNDS':
+
+ bpy.ops.object.select_all(action='DESELECT')
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ bpy.context.view_layer.objects.active = ob1
+ ob1.select_set(True)
+ bpy.context.object.active_shape_key_index = 0
+ # Bound X
+ if bounds_x != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (1,0,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.x > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.x < 0]:
+ f.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_x == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_x == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # Bound Y
+ if bounds_y != 'EXTEND':
+ if mode == 'GLOBAL':
+ planes_co = ((0,0,0),(1,1,1))
+ plane_no = (0,1,0)
+ if mode == 'LOCAL':
+ planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0)))
+ plane_no = planes_co[0]-planes_co[1]
+ bpy.ops.object.mode_set(mode='EDIT')
+ for co in planes_co:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no)
+ bpy.ops.mesh.mark_seam()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ _faces = ob1.data.polygons
+ if mode == 'GLOBAL':
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]:
+ f.select = True
+ for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]:
+ f.select = True
+ else:
+ for f in [f for f in _faces if f.center.y > 1]:
+ f.select = True
+ for f in [f for f in _faces if f.center.y < 0]:
+ f.select = True
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='FACE')
+ if bounds_y == 'CLIP':
+ bpy.ops.mesh.delete(type='FACE')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bounds_y == 'CYCLIC':
+ bpy.ops.mesh.split()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ #ob1 = new_ob1
+
+ me1 = ob1.data
+
+ verts0 = me0.vertices # Collect generator vertices
+
+ # Component statistics
+ n_verts1 = len(me1.vertices)
+ n_edges1 = len(me1.edges)
+ n_faces1 = len(me1.polygons)
+
+ # Create empty lists
+ new_verts = []
+ new_edges = []
+ new_faces = []
+ new_verts_np = np.array(())
+
+ # Component Coordinates
+ co1 = [0]*n_verts1*3
+
+ if mode == 'GLOBAL':
+ for v in me1.vertices:
+ v.co = ob1.matrix_world @ v.co
+ try:
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co
+ except: pass
+ if mode != 'BOUNDS':
+ if bounds_x == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x -= 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).x < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.x += 1
+ try:
+ _ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.x += 1
+ except: pass
+ if bounds_y == 'CYCLIC':
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y > 1]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y -= 1
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y -= 1
+ except: pass
+ move_verts = []
+ for f in [f for f in me1.polygons if (f.center).y < 0]:
+ for v in f.vertices:
+ if v not in move_verts: move_verts.append(v)
+ for v in move_verts:
+ me1.vertices[v].co.y += 1
+ try:
+ #new_ob1.active_shape_key_index = 0
+ for sk in me1.shape_keys.key_blocks:
+ sk.data[v].co.y += 1
+ except: pass
+
+
+ me1.vertices.foreach_get("co", co1)
+ co1 = np.array(co1)
+ vx = co1[0::3].reshape((n_verts1,1))
+ vy = co1[1::3].reshape((n_verts1,1))
+ vz = co1[2::3].reshape((n_verts1,1))
+ min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner
+ max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner
+ bb = max_c - min_c # Bounding Box
+
+ # Component Coordinates
+ if mode == 'BOUNDS':
+ vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5
+ vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5
+ vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale
+ else:
+ vz *= zscale
# Component polygons
fs1 = [[i for i in p.vertices] for p in me1.polygons]
new_faces = fs1[:]
# Component edges
- es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
+ es1 = np.array([[i for i in e.vertices] for e in me1.edges])
+ #es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose]
new_edges = es1[:]
# SHAPE KEYS
- shapekeys = []
- do_shapekeys = False
- if me1.shape_keys is not None and bool_shapekeys:
- if len(me1.shape_keys.key_blocks) > 1:
+ if bool_shapekeys:
+ basis = True #com_modifiers
+ vx_key = []
+ vy_key = []
+ vz_key = []
+ sk_np = []
+ for sk in ob1.data.shape_keys.key_blocks:
do_shapekeys = True
+ # set all keys to 0
+ for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0
+ sk.value = 1
- # Read active key
- active_key = ob1.active_shape_key_index
- if active_key == 0:
- active_key = 1
+ if basis:
+ basis = False
+ continue
+
+ # Apply component modifiers
+ if com_modifiers:
+ sk_ob = convert_object_to_mesh(_ob1)
+ sk_data = sk_ob.data
+ source = sk_data.vertices
+ else:
+ source = sk.data
- for v in me1.shape_keys.key_blocks[active_key].data:
- if mode == "ADAPTIVE":
+ shapekeys = []
+ for v in source:
+ if mode == 'BOUNDS':
vert = v.co - min_c
vert[0] = vert[0] / bb[0]
vert[1] = vert[1] / bb[1]
vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale
- else:
+ elif mode == 'LOCAL':
+ vert = v.co.xyz
+ vert[2] *= zscale
+ #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
+ # zscale
+ elif mode == 'GLOBAL':
vert = v.co.xyz
- vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \
- zscale
+ #vert = ob1.matrix_world @ v.co
+ vert[2] *= zscale
shapekeys.append(vert)
# Component vertices
key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1)
- vx_key = key1[:, 0]
- vy_key = key1[:, 1]
- vz_key = key1[:, 2]
+ vx_key.append(key1[:, 0])
+ vy_key.append(key1[:, 1])
+ vz_key.append(key1[:, 2])
+ #sk_np.append([])
- # Active vertex group
+ # All vertex group
if bool_vertex_group:
try:
weight = []
- group_index = ob0.vertex_groups.active_index
- active_vertex_group = ob0.vertex_groups[group_index]
- for v in me0.vertices:
- try:
- weight.append(active_vertex_group.weight(v.index))
- except:
- weight.append(0)
+ vertex_groups = ob0.vertex_groups
+ for vg in vertex_groups:
+ _weight = []
+ for v in me0.vertices:
+ try:
+ _weight.append(vg.weight(v.index))
+ #print(vg.weight(v.index))
+ #_weight.append(v.groups[0])
+ except:
+ _weight.append(0)
+ weight.append(_weight)
except:
bool_vertex_group = False
+ # Adaptive Z
+ if scale_mode == 'ADAPTIVE':
+ if mode == 'BOUNDS': com_area = (bb[0]*bb[1])
+ else: com_area = 1
+ if com_area == 0: mult = 1
+ else: mult = 1/com_area
+ verts_area = []
+ bm = bmesh.new()
+ bm.from_mesh(me0)
+ bm.verts.ensure_lookup_table()
+ for v in bm.verts:
+ area = 0
+ faces = v.link_faces
+ for f in faces:
+ area += f.calc_area()
+ try:
+ area/=len(faces)
+ area*=mult
+ verts_area.append(sqrt(area))
+ except:
+ verts_area.append(1)
+
# FAN tessellation mode
if fill_mode == 'FAN':
fan_verts = [v.co.to_tuple() for v in me0.vertices]
fan_polygons = []
+ fan_select = []
+ fan_material = []
+ fan_normals = []
# selected_faces = []
for p in base_polygons:
- # if bool_selection and not p.select: continue
fan_center = Vector((0, 0, 0))
+ center_area = 0
for v in p.vertices:
fan_center += me0.vertices[v].co
+ if scale_mode == 'ADAPTIVE':
+ center_area += verts_area[v]
fan_center /= len(p.vertices)
+ center_area /= len(p.vertices)
+
last_vert = len(fan_verts)
fan_verts.append(fan_center.to_tuple())
+ #fan_verts.append(fan_center)
+ if scale_mode == 'ADAPTIVE':
+ verts_area.append(center_area)
# Vertex Group
if bool_vertex_group:
- center_weight = sum([weight[i] for i in p.vertices]) / len(p.vertices)
- weight.append(center_weight)
+ for w in weight:
+ center_weight = sum([w[i] for i in p.vertices]) / len(p.vertices)
+ w.append(center_weight)
for i in range(len(p.vertices)):
fan_polygons.append((p.vertices[i],
p.vertices[(i + 1) % len(p.vertices)],
last_vert, last_vert))
- # if bool_selection: selected_faces.append(p.select)
+
+ if bool_material_id: fan_material.append(p.material_index)
+ if bool_selection: fan_select.append(p.select)
+ if normals_mode == 'FACES':
+ fan_normals.append(p.normal)
+
fan_me = bpy.data.meshes.new('Fan.Mesh')
fan_me.from_pydata(tuple(fan_verts), [], tuple(fan_polygons))
- me0 = fan_me
+ me0 = fan_me.copy()
+ bpy.data.meshes.remove(fan_me)
verts0 = me0.vertices
base_polygons = me0.polygons
- """
- for i in range(len(selected_faces)):
- fan_me.polygons[i].select = selected_faces[i]
- """
+ if normals_mode == 'FACES': base_face_normals = fan_normals
+
count = 0 # necessary for UV calculation
# TESSELLATION
j = 0
+ jj = -1
+ bool_correct = False
+
+ # optimization test
+ n_faces = len(base_polygons)
+ _vs0 = [0]*n_faces
+ _nvs0 = [0]*n_faces
+ _sz = [0]*n_faces
+ _w0 = [[0]*n_faces]*len(ob0.vertex_groups)
+ np_faces = [np.array(p) for p in fs1]
+ new_faces = [0]*n_faces*n_faces1
+ face1_count = 0
+
for p in base_polygons:
+
+ bool_correct = True
+ if rotation_mode == 'UV' and ob0.type != 'MESH':
+ rotation_mode = 'DEFAULT'
+
# Random rotation
if rotation_mode == 'RANDOM':
shifted_vertices = []
@@ -258,211 +1379,333 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode,
rand = random.randint(0, n_poly_verts)
for i in range(n_poly_verts):
shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts])
+ if scale_mode == 'ADAPTIVE':
+ verts_area0 = np.array([verts_area[i] for i in shifted_vertices])
vs0 = np.array([verts0[i].co for i in shifted_vertices])
nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
+ if normals_mode == 'VERTS':
+ nvs0 = np.array([verts0[i].normal for i in shifted_vertices])
# vertex weight
if bool_vertex_group:
ws0 = []
- for i in shifted_vertices:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
-
- ws0 = np.array(ws0)
+ for w in weight:
+ _ws0 = []
+ for i in shifted_vertices:
+ try:
+ _ws0.append(w[i])
+ except:
+ _ws0.append(0)
+ ws0.append(np.array(_ws0))
# UV rotation
- elif rotation_mode == 'UV' and len(ob0.data.uv_layers) > 0 and \
- fill_mode != 'FAN':
- i = p.index
- v01 = (me0.uv_layers.active.data[count].uv +
- me0.uv_layers.active.data[count + 1].uv)
- if len(p.vertices) > 3:
- v32 = (me0.uv_layers.active.data[count + 3].uv +
- me0.uv_layers.active.data[count + 2].uv)
- else:
- v32 = (me0.uv_layers.active.data[count].uv +
+ elif rotation_mode == 'UV':
+ if len(ob0.data.uv_layers) > 0 and fill_mode != 'FAN':
+ i = p.index
+ if bool_material_id:
+ count = sum([len(p.vertices) for p in me0.polygons[:i]])
+ #if i == 0: count = 0
+ v01 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 1].uv)
+ if len(p.vertices) > 3:
+ v32 = (me0.uv_layers.active.data[count + 3].uv +
+ me0.uv_layers.active.data[count + 2].uv)
+ else:
+ v32 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 2].uv)
+ v0132 = v32 - v01
+ v0132.normalize()
+
+ v12 = (me0.uv_layers.active.data[count + 1].uv +
me0.uv_layers.active.data[count + 2].uv)
- v0132 = v32 - v01
- v0132.normalize()
-
- v12 = (me0.uv_layers.active.data[count + 1].uv +
- me0.uv_layers.active.data[count + 2].uv)
- if len(p.vertices) > 3:
- v03 = (me0.uv_layers.active.data[count].uv +
- me0.uv_layers.active.data[count + 3].uv)
- else:
- v03 = (me0.uv_layers.active.data[count].uv +
- me0.uv_layers.active.data[count].uv)
- v1203 = v03 - v12
- v1203.normalize()
-
- vertUV = []
- dot1203 = v1203.x
- dot0132 = v0132.x
- if(abs(dot1203) < abs(dot0132)):
- if (dot0132 > 0):
- vertUV = p.vertices[1:] + p.vertices[:1]
+ if len(p.vertices) > 3:
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count + 3].uv)
else:
- vertUV = p.vertices[3:] + p.vertices[:3]
- else:
- if(dot1203 < 0):
- vertUV = p.vertices[:]
+ v03 = (me0.uv_layers.active.data[count].uv +
+ me0.uv_layers.active.data[count].uv)
+ v1203 = v03 - v12
+ v1203.normalize()
+
+ vertUV = []
+ dot1203 = v1203.x
+ dot0132 = v0132.x
+ if(abs(dot1203) < abs(dot0132)):
+ if (dot0132 > 0):
+ vertUV = p.vertices[1:] + p.vertices[:1]
+ else:
+ vertUV = p.vertices[3:] + p.vertices[:3]
else:
- vertUV = p.vertices[2:] + p.vertices[:2]
- vs0 = np.array([verts0[i].co for i in vertUV])
- nvs0 = np.array([verts0[i].normal for i in vertUV])
-
- # Vertex weight
- if bool_vertex_group:
- ws0 = []
- for i in vertUV:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
- ws0 = np.array(ws0)
-
- count += len(p.vertices)
+ if(dot1203 < 0):
+ vertUV = p.vertices[:]
+ else:
+ vertUV = p.vertices[2:] + p.vertices[:2]
+ vs0 = np.array([verts0[i].co for i in vertUV])
+ nvs0 = np.array([verts0[i].normal for i in vertUV])
+
+ # Vertex weight
+ if bool_vertex_group:
+ ws0 = []
+ for w in weight:
+ _ws0 = []
+ for i in vertUV:
+ try:
+ _ws0.append(w[i])
+ except:
+ _ws0.append(0)
+ ws0.append(np.array(_ws0))
+
+ count += len(p.vertices)
+ else: rotation_mode = 'DEFAULT'
# Default rotation
- else:
+ if rotation_mode == 'DEFAULT':
vs0 = np.array([verts0[i].co for i in p.vertices])
nvs0 = np.array([verts0[i].normal for i in p.vertices])
# Vertex weight
if bool_vertex_group:
ws0 = []
- for i in p.vertices:
- try:
- ws0.append(weight[i])
- except:
- ws0.append(0)
- ws0 = np.array(ws0)
+ for w in weight:
+ _ws0 = []
+ for i in p.vertices:
+ try:
+ _ws0.append(w[i])
+ except:
+ _ws0.append(0)
+ ws0.append(np.array(_ws0))
+
+ # optimization test
+ _vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1])
+ if normals_mode == 'VERTS':
+ _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1])
+ #else:
+ # _nvs0[j] = base_face_normals[j]
- # considering only 4 vertices
- vs0 = np.array((vs0[0], vs0[1], vs0[2], vs0[-1]))
- nvs0 = np.array((nvs0[0], nvs0[1], nvs0[2], nvs0[-1]))
-
- # remapped vertex coordinates
- v0 = vs0[0] + (vs0[1] - vs0[0]) * vx
- v1 = vs0[3] + (vs0[2] - vs0[3]) * vx
- v2 = v0 + (v1 - v0) * vy
-
- # remapped vertex normal
- nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx
- nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx
- nv2 = nv0 + (nv1 - nv0) * vy
# vertex z to normal
- v3 = v2 + nv2 * vz * (sqrt(p.area) if scale_mode == "ADAPTIVE" else 1)
+ if scale_mode == 'ADAPTIVE':
+ poly_faces = (p.vertices[0], p.vertices[1], p.vertices[2], p.vertices[-1])
+ if rotation_mode == 'RANDOM': sz = verts_area0
+ else: sz = np.array([verts_area[i] for i in poly_faces])
+
+ _sz[j] = sz
if bool_vertex_group:
- ws0 = np.array((ws0[0], ws0[1], ws0[2], ws0[-1]))
- # Interpolate vertex weight
- w0 = ws0[0] + (ws0[1] - ws0[0]) * vx
- w1 = ws0[3] + (ws0[2] - ws0[3]) * vx
- w2 = w0 + (w1 - w0) * vy
-
- # Shapekeys
- if do_shapekeys:
- # remapped vertex coordinates
- v0 = vs0[0] + (vs0[1] - vs0[0]) * vx_key
- v1 = vs0[3] + (vs0[2] - vs0[3]) * vx_key
- v2 = v0 + (v1 - v0) * vy_key
- # remapped vertex normal
- nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx_key
- nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx_key
- nv2 = nv0 + (nv1 - nv0) * vy_key
- # vertex z to normal
- v3_key = v2 + nv2 * vz_key * (sqrt(p.area) if
- scale_mode == "ADAPTIVE" else 1)
- v3 = v3 + (v3_key - v3) * w2
-
- if j == 0:
- new_verts_np = v3
- if bool_vertex_group:
- new_vertex_group_np = w2
- else:
- # Appending vertices
- new_verts_np = np.concatenate((new_verts_np, v3), axis=0)
- # Appending vertex group
- if bool_vertex_group:
- new_vertex_group_np = np.concatenate((new_vertex_group_np, w2),
- axis=0)
- # Appending faces
- for p in fs1:
- new_faces.append([i + n_verts * j for i in p])
- # Appending edges
- for e in es1:
- new_edges.append([i + n_verts * j for i in e])
+ vg_count = 0
+ for _ws0 in ws0:
+ _w0[vg_count][j] = (_ws0[0], _ws0[1], _ws0[2], _ws0[-1])
+ vg_count += 1
+
+ for p in fs1:
+ new_faces[face1_count] = [i + n_verts1 * j for i in p]
+ face1_count += 1
j += 1
+ # build edges list
+ n_edges1 = new_edges.shape[0]
+ new_edges = new_edges.reshape((1, n_edges1, 2))
+ new_edges = new_edges.repeat(n_faces,axis=0)
+ new_edges = new_edges.reshape((n_edges1*n_faces, 2))
+ increment = np.arange(n_faces)*n_verts1
+ increment = increment.repeat(n_edges1, axis=0)
+ increment = increment.reshape((n_faces*n_edges1,1))
+ new_edges = new_edges + increment
+
+ # optimization test
+ _vs0 = np.array(_vs0)
+ _sz = np.array(_sz)
+
+ _vs0_0 = _vs0[:,0].reshape((n_faces,1,3))
+ _vs0_1 = _vs0[:,1].reshape((n_faces,1,3))
+ _vs0_2 = _vs0[:,2].reshape((n_faces,1,3))
+ _vs0_3 = _vs0[:,3].reshape((n_faces,1,3))
+
+ # remapped vertex coordinates
+ v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
+ v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
+ v2 = v0 + (v1 - v0) * vy
+
+ # remapped vertex normal
+ if normals_mode == 'VERTS':
+ _nvs0 = np.array(_nvs0)
+ _nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3))
+ _nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3))
+ _nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3))
+ _nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3))
+ nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
+ nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
+ nv2 = nv0 + (nv1 - nv0) * vy
+ else:
+ nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
+
+ if bool_vertex_group:
+ n_vg = len(_w0)
+ w = np.array(_w0)
+ #for w in _w0:
+ #w = np.array(w)
+ w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1))
+ w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1))
+ w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1))
+ w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1))
+ # remapped weight
+ w0 = w_0 + (w_1 - w_0) * vx
+ w1 = w_3 + (w_2 - w_3) * vx
+ w = w0 + (w1 - w0) * vy
+ w = w.reshape((n_vg, n_faces*n_verts1))
+ #w = w2.tolist()
+
+ if scale_mode == 'ADAPTIVE':
+ _sz_0 = _sz[:,0].reshape((n_faces,1,1))
+ _sz_1 = _sz[:,1].reshape((n_faces,1,1))
+ _sz_2 = _sz[:,2].reshape((n_faces,1,1))
+ _sz_3 = _sz[:,3].reshape((n_faces,1,1))
+ # remapped z scale
+ sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
+ sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
+ sz2 = sz0 + (sz1 - sz0) * vy
+ v3 = v2 + nv2 * vz * sz2
+ else:
+ v3 = v2 + nv2 * vz
+
+ new_verts_np = v3.reshape((n_faces*n_verts1,3))
+
+ if bool_shapekeys:
+ n_sk = len(vx_key)
+ sk_np = [0]*n_sk
+ for i in range(n_sk):
+ vx = np.array(vx_key)
+ vy = np.array(vy_key)
+ vz = np.array(vz_key)
+
+ # remapped vertex coordinates
+ v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx
+ v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx
+ v2 = v0 + (v1 - v0) * vy
+
+ # remapped vertex normal
+ if normals_mode == 'VERTS':
+ nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx
+ nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx
+ nv2 = nv0 + (nv1 - nv0) * vy
+ else:
+ nv2 = np.array(base_face_normals).reshape((n_faces,1,3))
+
+ if scale_mode == 'ADAPTIVE':
+ # remapped z scale
+ sz0 = _sz_0 + (_sz_1 - _sz_0) * vx
+ sz1 = _sz_3 + (_sz_2 - _sz_3) * vx
+ sz2 = sz0 + (sz1 - sz0) * vy
+ v3 = v2 + nv2 * vz * sz2
+ else:
+ v3 = v2 + nv2 * vz
+
+ sk_np[i] = v3.reshape((n_faces*n_verts1,3))
+
+ #if ob0.type == 'MESH': ob0.data = old_me0
+
+ if not bool_correct: return 0
+
new_verts = new_verts_np.tolist()
new_name = ob0.name + "_" + ob1.name
new_me = bpy.data.meshes.new(new_name)
- new_me.from_pydata(new_verts, new_edges, new_faces)
+ new_me.from_pydata(new_verts, new_edges.tolist(), new_faces)
new_me.update(calc_edges=True)
new_ob = bpy.data.objects.new("tessellate_temp", new_me)
# vertex group
+ if bool_vertex_group and False:
+ for vg in ob0.vertex_groups:
+ new_ob.vertex_groups.new(name=vg.name)
+ for i in range(len(vg_np[vg.index])):
+ new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD")
+ # vertex group
if bool_vertex_group:
- new_ob.vertex_groups.new(name="generator_group")
- for i in range(len(new_vertex_group_np)):
- new_ob.vertex_groups["generator_group"].add([i],
- new_vertex_group_np[i],
- "ADD")
- ob0.data = old_me0
+ for vg in ob0.vertex_groups:
+ new_ob.vertex_groups.new(name=vg.name)
+ for i in range(len(w[vg.index])):
+ new_ob.vertex_groups[vg.name].add([i], w[vg.index,i],"ADD")
+
+ if bool_shapekeys:
+ basis = com_modifiers
+ sk_count = 0
+ for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
+ sk.value = val
+ new_ob.shape_key_add(name=sk.name)
+ new_ob.data.shape_keys.key_blocks[sk.name].value = val
+ # set shape keys vertices
+ sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data
+ if sk_count == 0:
+ sk_count += 1
+ continue
+ for id in range(len(sk_data)):
+ sk_data[id].co = sk_np[sk_count-1][id]
+ sk_count += 1
+ if bool_vertex_group:
+ for sk in new_ob.data.shape_keys.key_blocks:
+ for vg in new_ob.vertex_groups:
+ if sk.name == vg.name:
+ sk.vertex_group = vg.name
+
+ # EDGES SEAMS
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("use_seam",edge_data)
+ if any(edge_data):
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("use_seam",edge_data)
+
+ # EDGES SHARP
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("use_edge_sharp",edge_data)
+ if any(edge_data):
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("use_edge_sharp",edge_data)
+
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.context.collection.objects.link(new_ob)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+
+ # EDGES BEVEL
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("bevel_weight",edge_data)
+ if any(edge_data):
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.transform.edge_bevelweight(value=1)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set("bevel_weight",edge_data)
- if me0_owner:
- me0_owner.to_mesh_clear()
+ # EDGE CREASES
+ edge_data = [0]*n_edges1
+ me1.edges.foreach_get("crease",edge_data)
+ if any(edge_data):
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.transform.edge_crease(value=1)
+ bpy.ops.object.mode_set(mode='OBJECT')
+ edge_data = edge_data*n_faces
+ new_ob.data.edges.foreach_set('crease', edge_data)
- if me1_owner:
- me1_owner.to_mesh_clear()
+ # MATERIALS
+ for slot in ob1.material_slots: new_ob.data.materials.append(slot.material)
- return new_ob
+ polygon_materials = [0]*n_faces1
+ me1.polygons.foreach_get("material_index", polygon_materials)
+ polygon_materials *= n_faces
+ new_ob.data.polygons.foreach_set("material_index", polygon_materials)
+ new_ob.data.update() ###
-def store_parameters(operator, ob):
- ob.tissue_tessellate.generator = operator.generator
- ob.tissue_tessellate.component = operator.component
- ob.tissue_tessellate.zscale = operator.zscale
- ob.tissue_tessellate.offset = operator.offset
- ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers
- ob.tissue_tessellate.com_modifiers = operator.com_modifiers
- ob.tissue_tessellate.mode = operator.mode
- ob.tissue_tessellate.rotation_mode = operator.rotation_mode
- ob.tissue_tessellate.merge = operator.merge
- ob.tissue_tessellate.merge_thres = operator.merge_thres
- ob.tissue_tessellate.scale_mode = operator.scale_mode
- ob.tissue_tessellate.bool_random = operator.bool_random
- ob.tissue_tessellate.random_seed = operator.random_seed
- ob.tissue_tessellate.fill_mode = operator.fill_mode
- ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group
- ob.tissue_tessellate.bool_selection = operator.bool_selection
- ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys
- return ob
-
+ try:
+ bpy.data.objects.remove(new_ob1)
+ except: pass
-class tissue_tessellate_prop(PropertyGroup):
- generator: StringProperty()
- component: StringProperty()
- offset: FloatProperty()
- zscale: FloatProperty(default=1)
- merge: BoolProperty()
- merge_thres: FloatProperty()
- gen_modifiers: BoolProperty()
- com_modifiers: BoolProperty()
- mode: StringProperty()
- rotation_mode: StringProperty()
- scale_mode: StringProperty()
- fill_mode: StringProperty()
- bool_random: BoolProperty()
- random_seed: IntProperty()
- vertexgroup: StringProperty()
- bool_vertex_group: BoolProperty()
- bool_selection: BoolProperty()
- bool_shapekeys: BoolProperty()
+ bpy.data.objects.remove(ob0)
+ bpy.data.meshes.remove(me0)
+ bpy.data.objects.remove(ob1)
+ bpy.data.meshes.remove(me1)
+ return new_ob
class tessellate(Operator):
@@ -472,179 +1715,277 @@ class tessellate(Operator):
"faces, adapting the shape to the different faces")
bl_options = {'REGISTER', 'UNDO'}
- object_name: StringProperty(
+
+ object_name : StringProperty(
name="",
description="Name of the generated object"
)
- zscale: FloatProperty(
+ zscale : FloatProperty(
name="Scale",
default=1,
soft_min=0,
soft_max=10,
description="Scale factor for the component thickness"
)
- scale_mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")),
- default='CONSTANT',
+ scale_mode : EnumProperty(
+ items=(
+ ('CONSTANT', "Constant", "Uniform thickness"),
+ ('ADAPTIVE', "Proportional", "Preserve component's proportions")
+ ),
+ default='ADAPTIVE',
name="Z-Scale according to faces size"
)
- offset: FloatProperty(
+ offset : FloatProperty(
name="Surface Offset",
- default=0,
+ default=1,
min=-1, max=1,
soft_min=-1,
soft_max=1,
description="Surface offset"
)
- mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")),
- default='ADAPTIVE',
+ mode : EnumProperty(
+ items=(
+ ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
+ ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
+ ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
+ default='BOUNDS',
name="Component Mode"
)
- rotation_mode: EnumProperty(
- items=(('RANDOM', "Random", ""),
- ('UV', "Active UV", ""),
- ('DEFAULT', "Default", "")),
+ rotation_mode : EnumProperty(
+ items=(('RANDOM', "Random", "Random faces rotation"),
+ ('UV', "Active UV", "Face rotation is based on UV coordinates"),
+ ('DEFAULT', "Default", "Default rotation")),
default='DEFAULT',
name="Component Rotation"
)
- fill_mode: EnumProperty(
- items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")),
+ fill_mode : EnumProperty(
+ items=(
+ ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
+ ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
+ ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
+ 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
+ 'patches.\nAfter the last Subsurf (or Multires) only ' +
+ 'deformation\nmodifiers can be used')),
default='QUAD',
name="Fill Mode"
)
- gen_modifiers: BoolProperty(
+ combine_mode : EnumProperty(
+ items=(
+ ('LAST', 'Last', 'Show only the last iteration'),
+ ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
+ ('ALL', 'All', 'Combine the result of all iterations')),
+ default='LAST',
+ name="Combine Mode",
+ )
+ gen_modifiers : BoolProperty(
name="Generator Modifiers",
default=False,
- description="Apply modifiers to base object"
+ description="Apply Modifiers and Shape Keys to the base object"
)
- com_modifiers: BoolProperty(
+ com_modifiers : BoolProperty(
name="Component Modifiers",
default=False,
- description="Apply modifiers to component object"
+ description="Apply Modifiers and Shape Keys to the component object"
)
- merge: BoolProperty(
+ merge : BoolProperty(
name="Merge",
default=False,
description="Merge vertices in adjacent duplicates"
)
- merge_thres: FloatProperty(
+ merge_thres : FloatProperty(
name="Distance",
default=0.001,
soft_min=0,
soft_max=10,
description="Limit below which to merge vertices"
)
- generator: StringProperty(
- name="",
- description="Base object for the tessellation"
- )
- component: StringProperty(
- name="",
- description="Component object for the tessellation"
- )
- bool_random: BoolProperty(
+ bool_random : BoolProperty(
name="Randomize",
default=False,
description="Randomize component rotation"
)
- random_seed: IntProperty(
+ random_seed : IntProperty(
name="Seed",
default=0,
soft_min=0,
soft_max=10,
description="Random seed"
)
- bool_vertex_group: BoolProperty(
- name="Map Vertex Group",
+ bool_vertex_group : BoolProperty(
+ name="Map Vertex Groups",
default=False,
- description="Map the active "
- "Vertex Group from the Base object to generated geometry"
+ description="Transfer all Vertex Groups from Base object"
)
- bool_selection: BoolProperty(
+ bool_selection : BoolProperty(
name="On selected Faces",
default=False,
description="Create Tessellation only on selected faces"
)
- bool_shapekeys: BoolProperty(
+ bool_shapekeys : BoolProperty(
name="Use Shape Keys",
default=False,
- description="Use component's active Shape Key according to "
- "active Vertex Group of the base object"
+ description="Transfer Component's Shape Keys. If the name of Vertex "
+ "Groups and Shape Keys are the same, they will be "
+ "automatically combined"
)
- working_on = ""
-
- @staticmethod
- def check_gen_comp(checking):
- # note pass the stored name key in here to check it out
- return checking in bpy.data.objects.keys()
+ bool_smooth : BoolProperty(
+ name="Smooth Shading",
+ default=False,
+ description="Output faces with smooth shading rather than flat shaded"
+ )
+ bool_materials : BoolProperty(
+ name="Transfer Materials",
+ default=True,
+ description="Preserve component's materials"
+ )
+ generator : StringProperty(
+ name="",
+ description="Base object for the tessellation",
+ default = ""
+ )
+ component : StringProperty(
+ name="",
+ description="Component object for the tessellation",
+ default = ""
+ )
+ bool_material_id : BoolProperty(
+ name="Tessellation on Material ID",
+ default=False,
+ description="Apply the component only on the selected Material"
+ )
+ bool_dissolve_seams : BoolProperty(
+ name="Dissolve Seams",
+ default=False,
+ description="Dissolve all seam edges"
+ )
+ material_id : IntProperty(
+ name="Material ID",
+ default=0,
+ min=0,
+ description="Material ID"
+ )
+ iterations : IntProperty(
+ name="Iterations",
+ default=1,
+ min=1,
+ soft_max=5,
+ description="Automatically repeat the Tessellation using the "
+ + "generated geometry as new base object.\nUsefull for "
+ + "for branching systems. Dangerous!"
+ )
+ bool_combine : BoolProperty(
+ name="Combine unused",
+ default=False,
+ description="Combine the generated geometry with unused faces"
+ )
+ bool_advanced : BoolProperty(
+ name="Advanced Settings",
+ default=False,
+ description="Show more settings"
+ )
+ normals_mode : EnumProperty(
+ items=(
+ ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'),
+ ('FACES', 'Individual Faces', 'Based on individual faces normal')),
+ default='VERTS',
+ name="Direction"
+ )
+ bool_multi_components : BoolProperty(
+ name="Multi Components",
+ default=False,
+ description="Combine different components according to materials name"
+ )
+ bounds_x : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default X coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
+ default='EXTEND',
+ name="Bounds X",
+ )
+ bounds_y : EnumProperty(
+ items=(
+ ('EXTEND', 'Extend', 'Default Y coordinates'),
+ ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
+ ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
+ default='EXTEND',
+ name="Bounds Y",
+ )
+ cap_faces : BoolProperty(
+ name="Cap Holes",
+ default=False,
+ description="Cap open edges loops"
+ )
+ open_edges_crease : FloatProperty(
+ name="Open Edges Crease",
+ default=0,
+ min=0,
+ max=1,
+ description="Automatically set crease for open edges"
+ )
+ working_on : ""
def draw(self, context):
+ allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
+ '''
try:
bool_working = self.working_on == self.object_name and \
self.working_on != ""
except:
bool_working = False
+ '''
+
+ bool_working = False
+ bool_allowed = False
+ ob0 = None
+ ob1 = None
sel = bpy.context.selected_objects
+ if len(sel) == 1:
+ try:
+ ob0 = sel[0].tissue_tessellate.generator
+ ob1 = sel[0].tissue_tessellate.component
+ self.generator = ob0.name
+ self.component = ob1.name
+ bool_working = True
+ bool_allowed = True
+ except:
+ pass
- bool_meshes = False
if len(sel) == 2:
- bool_meshes = True
+ bool_allowed = True
for o in sel:
- if o.type != 'MESH':
- bool_meshes = False
+ if o.type not in allowed_obj:
+ bool_allowed = False
if len(sel) != 2 and not bool_working:
layout = self.layout
layout.label(icon='INFO')
layout.label(text="Please, select two different objects")
layout.label(text="Select first the Component object, then select")
- layout.label(text="the Base mesh.")
- elif not bool_meshes and not bool_working:
+ layout.label(text="the Base object.")
+ elif not bool_allowed and not bool_working:
layout = self.layout
layout.label(icon='INFO')
- layout.label(text="Please, select two Mesh objects")
+ layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed")
else:
- try:
- ob0 = bpy.data.Objects[self.generator]
- except:
+ if ob0 == ob1 == None:
ob0 = bpy.context.active_object
self.generator = ob0.name
-
- for o in sel:
- if (o.name == ob0.name or o.type != 'MESH'):
- continue
- else:
- ob1 = o
- self.component = o.name
- self.no_component = False
- break
-
- # Checks for Tool Shelf panel, it lost the original Selection
- if bpy.context.active_object.name == self.object_name:
- # checks if the objects were deleted
- if self.check_gen_comp(
- bpy.context.active_object.tissue_tessellate.component):
- ob1 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.component
- ]
- self.component = ob1.name
-
- if self.check_gen_comp(
- bpy.context.active_object.tissue_tessellate.generator):
- ob0 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.generator
- ]
- self.generator = ob0.name
- self.no_component = False
+ for o in sel:
+ if o != ob0:
+ ob1 = o
+ self.component = o.name
+ self.no_component = False
+ break
# new object name
if self.object_name == "":
if self.generator == "":
self.object_name = "Tessellation"
else:
- self.object_name = self.generator + "_Tessellation"
+ #self.object_name = self.generator + "_Tessellation"
+ self.object_name = "Tessellation"
layout = self.layout
# Base and Component
@@ -652,100 +1993,54 @@ class tessellate(Operator):
row = col.row(align=True)
row.label(text="BASE : " + self.generator)
row.label(text="COMPONENT : " + self.component)
+
+ # Base Modifiers
row = col.row(align=True)
col2 = row.column(align=True)
- col2.prop(self, "gen_modifiers", text="Use Modifiers")
-
- if not self.check_gen_comp(self.generator) or \
- len(bpy.data.objects[self.generator].modifiers) == 0:
+ col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
+ base = bpy.data.objects[self.generator]
+ try:
+ if not (base.modifiers or base.data.shape_keys):
+ col2.enabled = False
+ self.gen_modifiers = False
+ except:
col2.enabled = False
self.gen_modifiers = False
- col2 = row.column(align=True)
- col2.prop(self, "com_modifiers", text="Use Modifiers")
-
- if not self.check_gen_comp(self.component) or \
- len(bpy.data.objects[self.component].modifiers) == 0:
- col2.enabled = False
- self.com_modifiers = False
- # On selected faces
- row = col.row(align=True)
- row.prop(self, "bool_selection", text="On selected Faces")
- col.separator()
-
- # General
- col = layout.column(align=True)
- col.label(text="New Object Name:")
- col.prop(self, "object_name")
-
- # Count number of faces
- if self.gen_modifiers or self.com_modifiers:
- depsgraph = context.evaluated_depsgraph_get()
- else:
- depsgraph = None
+ # Component Modifiers
+ row.separator()
+ col3 = row.column(align=True)
+ col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ component = bpy.data.objects[self.component]
try:
- polygons = 0
- me_temp_owner = None
- if self.gen_modifiers:
- me_temp_owner = ob0.evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = ob0.data
-
- for p in me_temp.polygons:
- if not self.bool_selection or p.select:
- if self.fill_mode == "FAN":
- polygons += len(p.vertices)
- else:
- polygons += 1
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- if self.com_modifiers:
- me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.component].data
- polygons *= len(me_temp.polygons)
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- str_polygons = '{:0,.0f}'.format(polygons)
- if polygons > 200000:
- col.label(text=str_polygons + " polygons will be created!",
- icon='ERROR')
- else:
- col.label(text=str_polygons + " faces will be created!",
- icon='INFO')
+ if not (component.modifiers or component.data.shape_keys):
+ col3.enabled = False
+ self.com_modifiers = False
except:
- pass
+ col3.enabled = False
+ self.com_modifiers = False
col.separator()
-
# Fill and Rotation
row = col.row(align=True)
row.label(text="Fill Mode:")
- row.separator()
row.label(text="Rotation:")
row = col.row(align=True)
-
- # Fill
+ #col2 = row.column(align=True)
row.prop(
self, "fill_mode", text="", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
- row.separator()
# Rotation
- row.prop(
+ row.separator()
+ col2 = row.column(align=True)
+ col2.prop(
self, "rotation_mode", text="", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
if self.rotation_mode == 'RANDOM':
- row = col.row(align=True)
- row.prop(self, "random_seed")
+ col2.prop(self, "random_seed")
+
if self.rotation_mode == 'UV':
uv_error = False
if self.fill_mode == 'FAN':
@@ -754,14 +2049,19 @@ class tessellate(Operator):
icon='ERROR')
uv_error = True
- if not self.check_gen_comp(self.generator) or \
- len(bpy.data.objects[self.generator].data.uv_layers) == 0:
+ if ob0.type != 'MESH':
row = col.row(align=True)
- check_name = bpy.data.objects[self.generator].name if \
- self.check_gen_comp(self.generator) else "None"
- row.label(text="'" + check_name +
- "' doesn't have UV Maps", icon='ERROR')
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
uv_error = True
+ else:
+ if len(ob0.data.uv_layers) == 0:
+ row = col.row(align=True)
+ check_name = self.generator
+ row.label(text="'" + check_name +
+ "' doesn't have UV Maps", icon='ERROR')
+ uv_error = True
if uv_error:
row = col.row(align=True)
row.label(text="Default rotation will be used instead",
@@ -769,15 +2069,31 @@ class tessellate(Operator):
# Component XY
row = col.row(align=True)
- row.label(text="Component XY:")
+ row.label(text="Component Coordinates:")
row = col.row(align=True)
row.prop(
self, "mode", text="Component XY", icon='NONE', expand=True,
slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
+ if self.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ self, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ row = col.row(align=True)
+ row.label(text="Y:")
+ row.prop(
+ self, "bounds_y", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
# Component Z
- col.label(text="Component Z:")
+ col.label(text="Thickness:")
row = col.row(align=True)
row.prop(
self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
@@ -787,166 +2103,184 @@ class tessellate(Operator):
self, "zscale", text="Scale", icon='NONE', expand=False,
slider=True, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
- col.prop(
- self, "offset", text="Offset", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
+ if self.mode == 'BOUNDS':
+ col.prop(
+ self, "offset", text="Offset", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ # Direction
+ row = col.row(align=True)
+ row.label(text="Direction:")
+ row = col.row(align=True)
+ row.prop(
+ self, "normals_mode", text="Direction", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
full_event=False, emboss=True, index=-1)
+ row.enabled = self.fill_mode != 'PATCH'
# Merge
col = layout.column(align=True)
row = col.row(align=True)
row.prop(self, "merge")
-
if self.merge:
row.prop(self, "merge_thres")
row = col.row(align=True)
- # ADVANCED
- col = layout.column(align=True)
- col.label(text="Advanced Settings:")
- # vertex group + shape keys
row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "bool_vertex_group")
-
- if self.check_gen_comp(self.generator) and \
- len(bpy.data.objects[self.generator].vertex_groups) == 0:
- col2.enabled = False
- self.bool_vertex_group = False
-
- col2 = row.column(align=True)
-
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
- return
+ row.prop(self, "bool_smooth")
+ if self.merge:
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_dissolve_seams")
+ #if ob1.type != 'MESH': col2.enabled = False
- col2.prop(self, "bool_shapekeys", text="Use Shape Keys")
+ row = col.row(align=True)
+ row.prop(self, "cap_faces")
+ if self.cap_faces:
+ col2 = row.column(align=True)
+ col2.prop(self, "open_edges_crease", text="Crease")
- if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is None:
- col2.enabled = False
- self.bool_shapekeys = False
- elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is not None:
- if len(bpy.data.objects[
- self.component].data.shape_keys.key_blocks) < 2:
+ # Advanced Settings
+ col = layout.column(align=True)
+ col.separator()
+ col.separator()
+ row = col.row(align=True)
+ row.prop(self, "bool_advanced", icon='SETTINGS')
+ if self.bool_advanced:
+ allow_multi = False
+ allow_shapekeys = not self.com_modifiers
+ for m in ob0.data.materials:
+ try:
+ o = bpy.data.objects[m.name]
+ allow_multi = True
+ try:
+ if o.data.shape_keys is None: continue
+ elif len(o.data.shape_keys.key_blocks) < 2: continue
+ else: allow_shapekeys = not self.com_modifiers
+ except: pass
+ except: pass
+ # DATA #
+ col = layout.column(align=True)
+ col.label(text="Morphing:")
+ # vertex group + shape keys
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX')
+ #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
+ try:
+ if len(ob0.vertex_groups) == 0:
+ col2.enabled = False
+ except:
col2.enabled = False
- self.bool_shapekeys = False
+ row.separator()
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
+ row2.enabled = allow_shapekeys
+
+ # LIMITED TESSELLATION
+ col = layout.column(align=True)
+ col.label(text="Limited Tessellation:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_multi_components", icon='MOD_TINT')
+ if not allow_multi:
+ col2.enabled = False
+ self.bool_multi_components = False
+ col.separator()
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
+ #if self.bool_material_id or self.bool_selection or self.bool_multi_components:
+ #col2 = row.column(align=True)
+ # col2.prop(self, "bool_combine")
+ row.separator()
+ if ob0.type != 'MESH':
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
+ if self.bool_material_id and not self.bool_multi_components:
+ #col2 = row.column(align=True)
+ col2.prop(self, "material_id")
+ col2.enabled = not self.bool_multi_components
+
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
+ row.prop(self, 'iterations', text='Repeat', icon='SETTINGS')
+
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Combine Iterations:')
+ row = col.row(align=True)
+ row.prop(
+ self, "combine_mode", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
def execute(self, context):
+ allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT')
try:
- ob0 = bpy.context.active_object
- self.generator = ob0.name
+ ob0 = bpy.data.objects[self.generator]
+ ob1 = bpy.data.objects[self.component]
except:
- self.report({'ERROR'}, "A Generator mesh object must be selected")
-
- # component object
- sel = bpy.context.selected_objects
- no_component = True
- for o in sel:
- if (o.name == ob0.name or o.type != 'MESH'):
- continue
- else:
- ob1 = o
- self.component = o.name
- no_component = False
- break
-
- # Checks for Tool Shelf panel, it lost the original Selection
- if bpy.context.active_object == self.object_name:
- ob1 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.component]
- self.component = ob1.name
- ob0 = bpy.data.objects[
- bpy.context.active_object.tissue_tessellate.generator]
- self.generator = ob0.name
- no_component = False
-
- if no_component:
- # self.report({'ERROR'}, "A component mesh object must be selected")
return {'CANCELLED'}
- # new object name
- if self.object_name == "":
- if self.generator == "":
- self.object_name = "Tessellation"
- else:
- self.object_name = self.generator + "_Tessellation"
+ self.object_name = "Tessellation"
+ # Check if existing object with same name
+ names = [o.name for o in bpy.data.objects]
+ if self.object_name in names:
+ count_name = 1
+ while True:
+ test_name = self.object_name + '.{:03d}'.format(count_name)
+ if not (test_name in names):
+ self.object_name = test_name
+ break
+ count_name += 1
- if bpy.data.objects[self.component].type != 'MESH':
- message = "Component must be Mesh Objects!"
+ if ob1.type not in allowed_obj:
+ message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
- self.component = ""
+ self.component = None
- if bpy.data.objects[self.generator].type != 'MESH':
- message = "Generator must be Mesh Objects!"
+ if ob0.type not in allowed_obj:
+ message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
self.report({'ERROR'}, message)
self.generator = ""
- if self.component != "" and self.generator != "":
+ if True:#self.component not in ("",None) and self.generator not in ("",None):
if bpy.ops.object.select_all.poll():
bpy.ops.object.select_all(action='TOGGLE')
+ bpy.ops.object.mode_set(mode='OBJECT')
- new_ob = tassellate(
- ob0, ob1, self.offset, self.zscale, self.gen_modifiers,
- self.com_modifiers, self.mode, self.scale_mode,
- self.rotation_mode, self.random_seed, self.fill_mode,
- self.bool_vertex_group, self.bool_selection,
- self.bool_shapekeys
- )
-
- if new_ob == 0:
- message = "Zero faces selected in the Base mesh!"
- self.report({'ERROR'}, message)
- return {'CANCELLED'}
-
- new_ob.name = self.object_name
- # new_ob = bpy.data.objects.new(self.object_name, new_me)
-
- new_ob.location = ob0.location
- new_ob.matrix_world = ob0.matrix_world
-
- bpy.context.collection.objects.link(new_ob)
- new_ob.select_set(True)
- bpy.context.view_layer.objects.active = new_ob
- if self.merge:
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_mode(
- use_extend=False, use_expand=False, type='VERT')
- bpy.ops.mesh.select_non_manifold(
- extend=False, use_wire=False, use_boundary=True,
- use_multi_face=False, use_non_contiguous=False,
- use_verts=False)
- bpy.ops.mesh.remove_doubles(
- threshold=self.merge_thres, use_unselected=False)
- bpy.ops.object.mode_set(mode='OBJECT')
+ #data0 = ob0.to_mesh(False)
+ #data0 = ob0.data.copy()
+ bool_update = False
+ if bpy.context.object == ob0:
+ auto_layer_collection()
+ #new_ob = bpy.data.objects.new(self.object_name, data0)
+ new_ob = convert_object_to_mesh(ob0,False,False)
+ new_ob.data.name = self.object_name
+ #bpy.context.collection.objects.link(new_ob)
+ #bpy.context.view_layer.objects.active = new_ob
+ new_ob.name = self.object_name
+ #new_ob.select_set(True)
+ else:
+ new_ob = bpy.context.object
+ bool_update = True
new_ob = store_parameters(self, new_ob)
- self.object_name = new_ob.name
- self.working_on = self.object_name
-
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.object.mode_set(mode='OBJECT')
-
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * int(
- len(new_ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(new_ob.data.polygons)):
- new_ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ try: bpy.ops.object.update_tessellate()
+ except RuntimeError as e:
+ bpy.data.objects.remove(new_ob)
+ self.report({'ERROR'}, str(e))
+ return {'CANCELLED'}
+ if not bool_update:
+ self.object_name = new_ob.name
+ #self.working_on = self.object_name
+ new_ob.location = ob0.location
+ new_ob.matrix_world = ob0.matrix_world
- return {'FINISHED'}
-
- def check(self, context):
- return True
+ return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
@@ -963,9 +2297,10 @@ class update_tessellate(Operator):
@classmethod
def poll(cls, context):
- try:
- return context.active_object.tissue_tessellate.generator != "" and \
- context.active_object.tissue_tessellate.component != ""
+ #try:
+ try: #context.object == None: return False
+ return context.object.tissue_tessellate.generator != None and \
+ context.object.tissue_tessellate.component != None
except:
return False
@@ -975,7 +2310,9 @@ class update_tessellate(Operator):
return checking in bpy.data.objects.keys()
def execute(self, context):
- ob = bpy.context.active_object
+ start_time = time.time()
+
+ ob = bpy.context.object
if not self.go:
generator = ob.tissue_tessellate.generator
component = ob.tissue_tessellate.component
@@ -994,35 +2331,287 @@ class update_tessellate(Operator):
bool_selection = ob.tissue_tessellate.bool_selection
bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
mode = ob.tissue_tessellate.mode
+ bool_smooth = ob.tissue_tessellate.bool_smooth
+ bool_materials = ob.tissue_tessellate.bool_materials
+ bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
+ bool_material_id = ob.tissue_tessellate.bool_material_id
+ material_id = ob.tissue_tessellate.material_id
+ iterations = ob.tissue_tessellate.iterations
+ bool_combine = ob.tissue_tessellate.bool_combine
+ normals_mode = ob.tissue_tessellate.normals_mode
+ bool_advanced = ob.tissue_tessellate.bool_advanced
+ bool_multi_components = ob.tissue_tessellate.bool_multi_components
+ combine_mode = ob.tissue_tessellate.combine_mode
+ bounds_x = ob.tissue_tessellate.bounds_x
+ bounds_y = ob.tissue_tessellate.bounds_y
+ cap_faces = ob.tissue_tessellate.cap_faces
+ open_edges_crease = ob.tissue_tessellate.open_edges_crease
- if not self.check_gen_comp(generator) or \
- not self.check_gen_comp(component):
- self.report({'ERROR'},
- "Base or Component Objects are missing from the data "
- "(Most likely deleted or renamed)")
- return {'CANCELLED'}
-
- if (generator == "" or component == ""):
+ try:
+ generator.name
+ component.name
+ except:
self.report({'ERROR'},
"Active object must be Tessellate before Update")
return {'CANCELLED'}
- ob0 = bpy.data.objects[generator]
- ob1 = bpy.data.objects[component]
+ # Solve Local View issues
+ local_spaces = []
+ local_ob0 = []
+ local_ob1 = []
+ for area in bpy.context.screen.areas:
+ for space in area.spaces:
+ print(space)
+ try:
+ if ob.local_view_get(space):
+ local_spaces.append(space)
+ local_ob0 = ob0.local_view_get(space)
+ ob0.local_view_set(space, True)
+ local_ob1 = ob1.local_view_get(space)
+ ob1.local_view_set(space, True)
+ except:
+ pass
- temp_ob = tassellate(
- ob0, ob1, offset, zscale, gen_modifiers, com_modifiers,
- mode, scale_mode, rotation_mode, random_seed, fill_mode,
- bool_vertex_group, bool_selection, bool_shapekeys
- )
+ starting_mode = bpy.context.object.mode
+ #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
+ bpy.ops.object.mode_set(mode='OBJECT')
- if temp_ob == 0:
+ ob0 = generator
+ ob1 = component
+ auto_layer_collection()
+
+ ob0_hide = ob0.hide_get()
+ ob0_hidev = ob0.hide_viewport
+ ob0_hider = ob0.hide_render
+ ob1_hide = ob1.hide_get()
+ ob1_hidev = ob1.hide_viewport
+ ob1_hider = ob1.hide_render
+ ob0.hide_set(False)
+ ob0.hide_viewport = False
+ ob0.hide_render = False
+ ob1.hide_set(False)
+ ob1.hide_viewport = False
+ ob1.hide_render = False
+
+ if ob0.type == 'META':
+ base_ob = convert_object_to_mesh(ob0, False, True)
+ else:
+ base_ob = ob0.copy()
+ base_ob.data = ob0.data.copy()
+ bpy.context.collection.objects.link(base_ob)
+
+ # In Blender 2.80 cache of copied objects is lost, must be re-baked
+ bool_update_cloth = False
+ for m in base_ob.modifiers:
+ if m.type == 'CLOTH':
+ m.point_cache.frame_end = bpy.context.scene.frame_current
+ bool_update_cloth = True
+ if bool_update_cloth:
+ bpy.ops.ptcache.free_bake_all()
+ bpy.ops.ptcache.bake_all()
+
+ #new_ob.location = ob.location
+ #new_ob.matrix_world = ob.matrix_world
+ base_ob.modifiers.update()
+ bpy.ops.object.select_all(action='DESELECT')
+ iter_objects = [base_ob]
+ #base_ob = new_ob#.copy()
+
+ for iter in range(iterations):
+ same_iteration = []
+ matched_materials = []
+ if bool_multi_components: mat_iter = len(base_ob.material_slots)
+ else: mat_iter = 1
+ for m_id in range(mat_iter):
+ if bool_multi_components:
+ try:
+ mat = base_ob.material_slots[m_id].material
+ ob1 = bpy.data.objects[mat.name]
+ material_id = m_id
+ matched_materials.append(m_id)
+ bool_material_id = True
+ except:
+ continue
+ if com_modifiers:
+ data1 = simple_to_mesh(ob1)
+ else: data1 = ob1.data.copy()
+ n_edges1 = len(data1.edges)
+
+ if iter != 0: gen_modifiers = True
+ if fill_mode == 'PATCH':
+ new_ob = tessellate_patch(
+ base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode,
+ rotation_mode, random_seed, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id, material_id,
+ bounds_x, bounds_y
+ )
+ else:
+ new_ob = tessellate_original(
+ base_ob, ob1, offset, zscale, gen_modifiers,
+ com_modifiers, mode, scale_mode, rotation_mode,
+ random_seed, fill_mode, bool_vertex_group,
+ bool_selection, bool_shapekeys, bool_material_id,
+ material_id, normals_mode, bounds_x, bounds_y
+ )
+ if type(new_ob) is bpy.types.Object:
+ bpy.context.view_layer.objects.active = new_ob
+ else:
+ continue
+ n_components = int(len(new_ob.data.edges) / n_edges1)
+ # SELECTION
+ if bool_selection:
+ try:
+ # create selection list
+ polygon_selection = [p.select for p in ob1.data.polygons] * int(
+ len(new_ob.data.polygons) / len(ob1.data.polygons))
+ new_ob.data.polygons.foreach_set("select", polygon_selection)
+ except:
+ pass
+
+ if type(new_ob) == str: break
+
+ if bool_multi_components and type(new_ob) not in (int,str):
+ same_iteration.append(new_ob)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+
+ if type(new_ob) == str: break
+
+ #bpy.data.objects.remove(base_ob)
+ if bool_multi_components:
+ bpy.context.view_layer.update()
+ bpy.context.view_layer.objects.active.select_set(True)
+ for o in bpy.data.objects:
+ if o in same_iteration:
+ o.select_set(True)
+ o.location = ob.location
+ else:
+ try:
+ o.select_set(False)
+ except: pass
+ bpy.ops.object.join()
+ new_ob = bpy.context.view_layer.objects.active
+ new_ob.select_set(True)
+ new_ob.data.update()
+
+ #try:
+ # combine object
+ if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
+ # remove faces from last mesh
+ bm = bmesh.new()
+
+ last_mesh = iter_objects[-1].data.copy()
+
+ bm.from_mesh(last_mesh)
+ bm.faces.ensure_lookup_table()
+ if bool_multi_components:
+ remove_materials = matched_materials
+ elif bool_material_id:
+ remove_materials = [material_id]
+ else: remove_materials = []
+ if bool_selection:
+ remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
+ else:
+ remove_faces = [f for f in bm.faces if f.material_index in remove_materials]
+ bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
+ bm.to_mesh(last_mesh)
+ last_mesh.update()
+
+ if len(last_mesh.vertices) > 0:
+ iter_objects[-1].data = last_mesh.copy()
+ iter_objects[-1].data.update()
+ else:
+ bpy.data.objects.remove(iter_objects[-1])
+ iter_objects = iter_objects[:-1]
+
+ base_ob = convert_object_to_mesh(new_ob,True,True)
+ #bpy.context.collection.objects.unlink(base_ob)
+ if iter < iterations-1: new_ob.data = base_ob.data
+
+ iter_objects.append(new_ob)
+ new_ob.location = ob.location
+ new_ob.matrix_world = ob.matrix_world
+ try:
+ bpy.data.objects.remove(bpy.data.objects['_Tessellation_Base'])
+ except: pass
+ base_ob.name = "_Tessellation_Base"
+ elif combine_mode == 'ALL':
+ base_ob = new_ob.copy()
+ iter_objects.append(new_ob)
+ new_ob.location = ob.location
+ new_ob.matrix_world = ob.matrix_world
+ else:
+ if base_ob != new_ob:
+ bpy.data.objects.remove(base_ob)
+ base_ob = new_ob
+ iter_objects = [new_ob]
+
+ if new_ob == 0:
+ #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
message = "Zero faces selected in the Base mesh!"
+ bpy.ops.object.mode_set(mode=starting_mode)
+ self.report({'ERROR'}, message)
+ return {'CANCELLED'}
+ errors = {}
+ errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
+ "after the last Subsurf (or Multires) are not allowed."
+ errors["topology_error"] = "Make sure that the topology of the mesh before \n" \
+ "the last Subsurf (or Multires) is quads only."
+ errors["wires_error"] = "Please remove all wire edges in the base object."
+ errors["verts_error"] = "Please remove all floating vertices in the base object"
+ if new_ob in errors:
+ for o in iter_objects: bpy.data.objects.remove(o)
+ bpy.context.view_layer.objects.active = ob
+ ob.select_set(True)
+ message = errors[new_ob]
+ ob.tissue_tessellate.error_message = message
+ bpy.ops.object.mode_set(mode=starting_mode)
self.report({'ERROR'}, message)
return {'CANCELLED'}
- ob.data = temp_ob.data
- bpy.data.objects.remove(temp_ob)
+ new_ob.location = ob.location
+ new_ob.matrix_world = ob.matrix_world
+
+ ### REPEAT
+ if combine_mode != 'LAST' and len(iter_objects)>0:
+ if base_ob not in iter_objects: bpy.data.objects.remove(base_ob)
+ for o in iter_objects:
+ o.location = ob.location
+ o.select_set(True)
+ bpy.ops.object.join()
+ new_ob.data.update()
+
+ # update data and preserve name
+ if ob.type != 'MESH':
+ loc, matr = ob.location, ob.matrix_world
+ ob = convert_object_to_mesh(ob,False,True)
+ ob.location, ob.matrix_world = loc, matr
+ data_name = ob.data.name
+ old_data = ob.data
+ ob.data = new_ob.data
+ bpy.data.meshes.remove(old_data)
+ ob.data.name = data_name
+
+ # copy vertex group
+ if bool_vertex_group:
+ for vg in new_ob.vertex_groups:
+ if not vg.name in ob.vertex_groups.keys():
+ ob.vertex_groups.new(name=vg.name)
+ new_vg = ob.vertex_groups[vg.name]
+ for i in range(len(ob.data.vertices)):
+ try:
+ weight = vg.weight(i)
+ except:
+ weight = 0
+ new_vg.add([i], weight, 'REPLACE')
+
+ selected_objects = [o for o in bpy.context.selected_objects]
+ for o in selected_objects: o.select_set(False)
+
+ ob.select_set(True)
+ bpy.context.view_layer.objects.active = ob
+ bpy.data.objects.remove(new_ob)
+
if merge:
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_mode(
@@ -1030,516 +2619,378 @@ class update_tessellate(Operator):
bpy.ops.mesh.select_non_manifold(
extend=False, use_wire=False, use_boundary=True,
use_multi_face=False, use_non_contiguous=False, use_verts=False)
+
bpy.ops.mesh.remove_doubles(
threshold=merge_thres, use_unselected=False)
+
bpy.ops.object.mode_set(mode='OBJECT')
+ if bool_dissolve_seams:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(type='EDGE')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for e in ob.data.edges:
+ e.select = e.use_seam
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.dissolve_edges()
+ if cap_faces:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_mode(
+ use_extend=False, use_expand=False, type='EDGE')
+ bpy.ops.mesh.select_non_manifold(
+ extend=False, use_wire=False, use_boundary=True,
+ use_multi_face=False, use_non_contiguous=False, use_verts=False)
+ bpy.ops.mesh.edge_face_add()
+ if open_edges_crease != 0:
+ bpy.ops.transform.edge_crease(value=open_edges_crease)
+
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode='OBJECT')
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * int(
- len(ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(ob.data.polygons)):
- ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ if bool_smooth: bpy.ops.object.shade_smooth()
+ ####values = [True] * len(ob.data.polygons)
+ ####ob.data.polygons.foreach_set("use_smooth", values)
+
+ #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis
+
+ end_time = time.time()
+ print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time))
+
+ for mesh in bpy.data.meshes:
+ if not mesh.users: bpy.data.meshes.remove(mesh)
+
+ for o in selected_objects:
+ try: o.select_set(True)
+ except: pass
+
+ bpy.ops.object.mode_set(mode=starting_mode)
+
+ # clean objects
+ for o in bpy.data.objects:
+ if o.name not in context.view_layer.objects and "temp" in o.name:
+ bpy.data.objects.remove(o)
+
+ ob.tissue_tessellate.error_message = ""
+
+ # Restore Base visibility
+ ob0.hide_set(ob0_hide)
+ ob0.hide_viewport = ob0_hidev
+ ob0.hide_render = ob0_hider
+ # Restore Component visibility
+ ob1.hide_set(ob1_hide)
+ ob1.hide_viewport = ob1_hidev
+ ob1.hide_render = ob1_hider
+ # Restore Local visibility
+ for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
+ ob0.local_view_set(space, local0)
+ ob1.local_view_set(space, local1)
+ print(local_ob0)
+ print(local_ob1)
return {'FINISHED'}
def check(self, context):
return True
-
-class settings_tessellate(Operator):
- bl_idname = "object.settings_tessellate"
- bl_label = "Settings"
- bl_description = ("Update the tessellated mesh according to base and component changes\n"
- "Allow also to change tessellation's parameters")
- bl_options = {'REGISTER', 'UNDO'}
-
- object_name: StringProperty(
- name="",
- description="Name of the generated object"
- )
- zscale: FloatProperty(
- name="Scale",
- default=1,
- soft_min=0,
- soft_max=10,
- description="Scale factor for the component thickness"
- )
- scale_mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")),
- default='ADAPTIVE',
- name="Scale variation"
- )
- offset: FloatProperty(
- name="Surface Offset",
- default=0,
- min=-1, max=1,
- soft_min=-1,
- soft_max=1,
- description="Surface offset"
- )
- mode: EnumProperty(
- items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")),
- default='ADAPTIVE',
- name="Component Mode"
- )
- rotation_mode: EnumProperty(
- items=(('RANDOM', "Random", ""), ('UV', "Active UV", ""),
- ('DEFAULT', "Default", "")),
- default='DEFAULT',
- name="Component Rotation"
- )
- fill_mode: EnumProperty(
- items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")),
- default='QUAD',
- name="Fill Mode"
- )
- gen_modifiers: BoolProperty(
- name="Generator Modifiers",
- default=False,
- description="Apply modifiers to base object"
- )
- com_modifiers: BoolProperty(
- name="Component Modifiers",
- default=False,
- description="Apply modifiers to component object"
- )
- merge: BoolProperty(
- name="Merge",
- default=False,
- description="Merge vertices in adjacent duplicates"
- )
- merge_thres: FloatProperty(
- name="Distance",
- default=0.001,
- soft_min=0,
- soft_max=10,
- description="Limit below which to merge vertices"
- )
- generator: StringProperty(
- name="",
- description="Base object for the tessellation"
- )
- component: StringProperty(
- name="",
- description="Component object for the tessellation"
- )
- bool_random: BoolProperty(
- name="Randomize",
- default=False,
- description="Randomize component rotation"
- )
- random_seed: IntProperty(
- name="Seed",
- default=0,
- soft_min=0,
- soft_max=10,
- description="Random seed"
- )
- bool_vertex_group: BoolProperty(
- name="Map Vertex Group",
- default=False,
- description="Map on generated "
- "geometry the active Vertex Group from the base object"
- )
- bool_selection: BoolProperty(
- name="On selected Faces",
- default=False,
- description="Create Tessellation only on select faces"
- )
- bool_shapekeys: BoolProperty(
- name="Use Shape Keys",
- default=False,
- description="Use component's active Shape Key according to active "
- "Vertex Group of the base object"
- )
- go = False
+class TISSUE_PT_tessellate(Panel):
+ bl_label = "Tissue Tools"
+ bl_category = "Tissue"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+ #bl_options = {'DEFAULT_OPEN'}
@classmethod
def poll(cls, context):
- try:
- return context.active_object.tissue_tessellate.generator != "" and \
- context.active_object.tissue_tessellate.component != ""
- except:
- return False
-
- @staticmethod
- def check_gen_comp(checking):
- # note pass the stored name key in here to check it out
- return checking in bpy.data.objects.keys()
+ return context.mode in {'OBJECT', 'EDIT_MESH'}
def draw(self, context):
layout = self.layout
- ob0 = bpy.context.active_object
-
- if not self.go:
- self.generator = ob0.tissue_tessellate.generator
- self.component = ob0.tissue_tessellate.component
- self.zscale = ob0.tissue_tessellate.zscale
- self.scale_mode = ob0.tissue_tessellate.scale_mode
- self.rotation_mode = ob0.tissue_tessellate.rotation_mode
- self.offset = ob0.tissue_tessellate.offset
- self.merge = ob0.tissue_tessellate.merge
- self.merge_thres = ob0.tissue_tessellate.merge_thres
- self.gen_modifiers = ob0.tissue_tessellate.gen_modifiers
- self.com_modifiers = ob0.tissue_tessellate.com_modifiers
- self.bool_random = ob0.tissue_tessellate.bool_random
- self.random_seed = ob0.tissue_tessellate.random_seed
- self.fill_mode = ob0.tissue_tessellate.fill_mode
- self.bool_vertex_group = ob0.tissue_tessellate.bool_vertex_group
- self.bool_selection = ob0.tissue_tessellate.bool_selection
- self.bool_shapekeys = ob0.tissue_tessellate.bool_shapekeys
- self.mode = ob0.tissue_tessellate.mode
-
- # start drawing
- layout = self.layout
- # check for keys in data - as the objects can be deleted or renamed
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
-
- layout.label(text="Base or Component Objects are missing from the data",
- icon="INFO")
- layout.label(text="(Most likely deleted or renamed)",
- icon="BLANK1")
- layout.label(text="Settings could not be altered anymore",
- icon="BLANK1")
- layout.label(text="Please re-run Tessellate with two new selected objects",
- icon="BLANK1")
- return
-
- # ob0 = bpy.context.active_object
- # Base and Component
col = layout.column(align=True)
- row = col.row(align=True)
- row.label(text="BASE :")
- row.label(text="COMPONENT :")
- row = col.row(align=True)
-
- col2 = row.column(align=True)
- col2.prop_search(self, "generator", bpy.data, "objects")
- row.separator()
- col2 = row.column(align=True)
- col2.prop_search(self, "component", bpy.data, "objects")
-
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "gen_modifiers", text="Use Modifiers")
-
- if len(bpy.data.objects[self.generator].modifiers) == 0:
- col2.enabled = False
- self.gen_modifiers = False
-
- col2 = row.column(align=True)
- col2.prop(self, "com_modifiers", text="Use Modifiers")
-
- if len(bpy.data.objects[self.component].modifiers) == 0:
- col2.enabled = False
- self.com_modifiers = False
-
- # On selected faces
- row = col.row(align=True)
- row.prop(self, "bool_selection", text="On selected Faces")
+ col.label(text="Tessellate:")
+ col.operator("object.tessellate")
+ col.operator("object.dual_mesh_tessellated")
col.separator()
+ #col = layout.column(align=True)
+ #col.label(text="Tessellate Edit:")
+ #col.operator("object.settings_tessellate")
+ col.operator("object.update_tessellate", icon='FILE_REFRESH')
- if self.gen_modifiers or self.com_modifiers:
- depsgraph = context.evaluated_depsgraph_get()
-
- # Count number of faces
- try:
- polygons = 0
- me_temp_owner = None
- if self.gen_modifiers:
- me_temp_owner = bpy.data.objects[self.generator].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.generator].data
-
- for p in me_temp.polygons:
- if not self.bool_selection or p.select:
- if self.fill_mode == "FAN":
- polygons += len(p.vertices)
- else:
- polygons += 1
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
-
- if self.com_modifiers:
- me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph)
- me_temp = me_temp_owner.to_mesh()
- else:
- me_temp_owner = None
- me_temp = bpy.data.objects[self.component].data
- polygons *= len(me_temp.polygons)
-
- if me_temp_owner:
- me_temp_owner.to_mesh_clear()
+ #col = layout.column(align=True)
+ col.operator("mesh.rotate_face", icon='NDOF_TURN')
- str_polygons = '{:0,.0f}'.format(polygons)
- if polygons > 200000:
- col.label(text=str_polygons + " polygons will be created!",
- icon='ERROR')
- else:
- col.label(text=str_polygons + " faces will be created!",
- icon='INFO')
- except:
- pass
col.separator()
+ col.label(text="Other:")
+ col.operator("object.dual_mesh")
+ col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
- # Fill and Rotation
- row = col.row(align=True)
- row.label(text="Fill Mode:")
- row.separator()
- row.label(text="Rotation:")
- row = col.row(align=True)
-
- # fill
- row.prop(self, "fill_mode", text="", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
- row.separator()
-
- # rotation
- row.prop(self, "rotation_mode", text="", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
-
- if self.rotation_mode == 'RANDOM':
- row = col.row(align=True)
- row.prop(self, "random_seed")
-
- if self.rotation_mode == 'UV':
- uv_error = False
- if self.fill_mode == 'FAN':
- row = col.row(align=True)
- row.label(text="UV rotation doesn't work in FAN mode",
- icon='ERROR')
- uv_error = True
-
- if len(bpy.data.objects[self.generator].data.uv_layers) == 0:
- row = col.row(align=True)
- row.label(text="'" + bpy.data.objects[self.generator].name +
- " doesn't have UV Maps", icon='ERROR')
- uv_error = True
- if uv_error:
- row = col.row(align=True)
- row.label(text="Default rotation will be used instead",
- icon='INFO')
-
- # component XY
- row = col.row(align=True)
- row.label(text="Component XY:")
- row = col.row(align=True)
- row.prop(self, "mode", text="Component XY", icon='NONE', expand=True,
- slider=False, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
-
- # component Z
- col.label(text="Component Z:")
- row = col.row(align=True)
- row.prop(self, "scale_mode", text="Scale Mode", icon='NONE',
- expand=True, slider=False, toggle=False, icon_only=False,
- event=False, full_event=False, emboss=True, index=-1)
- col.prop(self, "zscale", text="Scale", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
- col.prop(self, "offset", text="Offset", icon='NONE', expand=False,
- slider=True, toggle=False, icon_only=False, event=False,
- full_event=False, emboss=True, index=-1)
-
- # merge
- col = layout.column(align=True)
- row = col.row(align=True)
- row.prop(self, "merge")
- if self.merge:
- row.prop(self, "merge_thres")
- row = col.row(align=True)
-
- # ADVANCED #
- col = layout.column(align=True)
- tessellate.rotation_mode
-
- col.label(text="Advanced Settings:")
- # vertex group + shape keys
- row = col.row(align=True)
- col2 = row.column(align=True)
- col2.prop(self, "bool_vertex_group")
-
- if len(bpy.data.objects[self.generator].vertex_groups) == 0:
- col2.enabled = False
- self.bool_vertex_group = False
- col2 = row.column(align=True)
- col2.prop(self, "bool_shapekeys", text="Use Shape Keys")
-
- if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is None:
- col2.enabled = False
- self.bool_shapekeys = False
- elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \
- bpy.data.objects[self.component].data.shape_keys is not None:
- if len(bpy.data.objects[self.component].data.shape_keys.key_blocks) < 2:
- col2.enabled = False
- self.bool_shapekeys = False
- self.go = True
-
- def execute(self, context):
- self.ob = bpy.context.active_object
-
- if not self.go:
- self.generator = self.ob.tissue_tessellate.generator
- self.component = self.ob.tissue_tessellate.component
- self.zscale = self.ob.tissue_tessellate.zscale
- self.scale_mode = self.ob.tissue_tessellate.scale_mode
- self.rotation_mode = self.ob.tissue_tessellate.rotation_mode
- self.offset = self.ob.tissue_tessellate.offset
- self.merge = self.ob.tissue_tessellate.merge
- self.merge_thres = self.ob.tissue_tessellate.merge_thres
- self.gen_modifiers = self.ob.tissue_tessellate.gen_modifiers
- self.com_modifiers = self.ob.tissue_tessellate.com_modifiers
- self.bool_random = self.ob.tissue_tessellate.bool_random
- self.random_seed = self.ob.tissue_tessellate.random_seed
- self.fill_mode = self.ob.tissue_tessellate.fill_mode
- self.bool_vertex_group = self.ob.tissue_tessellate.bool_vertex_group
- self.bool_selection = self.ob.tissue_tessellate.bool_selection
- self.bool_shapekeys = self.ob.tissue_tessellate.bool_shapekeys
-
- if not self.check_gen_comp(self.generator) or \
- not self.check_gen_comp(self.component):
- # do nothing as the Warning was already done in it UI
- return {'CANCELLED'}
-
- if (self.generator == "" or self.component == ""):
- self.report({'ERROR'},
- "Active object must be Tessellated before Update")
- return {'CANCELLED'}
-
- if (bpy.data.objects[self.generator].type != 'MESH'):
- self.report({'ERROR'}, "Base object must be a Mesh")
- return {'CANCELLED'}
-
- if (bpy.data.objects[self.component].type != 'MESH'):
- self.report({'ERROR'}, "Component object must be a Mesh")
- return {'CANCELLED'}
-
- ob0 = bpy.data.objects[self.generator]
- ob1 = bpy.data.objects[self.component]
+ act = context.active_object
+ if act and act.type == 'MESH':
+ col.operator("object.uv_to_mesh", icon="UV")
- temp_ob = tassellate(
- ob0, ob1, self.offset, self.zscale, self.gen_modifiers,
- self.com_modifiers, self.mode, self.scale_mode, self.rotation_mode,
- self.random_seed, self.fill_mode, self.bool_vertex_group,
- self.bool_selection, self.bool_shapekeys
- )
- if temp_ob == 0:
- message = "Zero faces selected in the Base mesh"
- self.report({'ERROR'}, message)
-
- return {'CANCELLED'}
+class TISSUE_PT_tessellate_object(Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ bl_label = "Tissue - Tessellate"
+ bl_options = {'DEFAULT_CLOSED'}
- # Transfer mesh data
- self.ob.data = temp_ob.data
+ @classmethod
+ def poll(cls, context):
+ try: return context.object.type == 'MESH'
+ except: return False
- # Create object in order to transfer vertex group
- bpy.context.collection.objects.link(temp_ob)
- temp_ob.select_set(True)
- bpy.context.view_layer.objects.active = temp_ob
+ def draw(self, context):
+ ob = context.object
+ props = ob.tissue_tessellate
+ allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META')
try:
- bpy.ops.object.vertex_group_copy_to_linked()
- except:
- pass
-
- bpy.context.scene.objects.unlink(temp_ob)
- bpy.data.objects.remove(temp_ob)
- bpy.context.view_layer.objects.active = self.ob
-
- if self.merge:
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.select_mode(
- use_extend=False, use_expand=False, type='VERT')
- bpy.ops.mesh.select_non_manifold(
- extend=False, use_wire=False, use_boundary=True,
- use_multi_face=False, use_non_contiguous=False, use_verts=False)
- bpy.ops.mesh.remove_doubles(
- threshold=self.merge_thres, use_unselected=False)
- bpy.ops.object.mode_set(mode='OBJECT')
- self.ob = store_parameters(self, self.ob)
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.object.mode_set(mode='OBJECT')
+ bool_tessellated = props.generator or props.component != None
+ ob0 = props.generator
+ ob1 = props.component
+ except: bool_tessellated = False
+ layout = self.layout
+ if not bool_tessellated:
+ layout.label(text="The selected object is not a Tessellated object",
+ icon='INFO')
+ else:
+ if props.error_message != "":
+ layout.label(text=props.error_message,
+ icon='ERROR')
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "bool_run", text="Animatable")
+ row.operator("object.update_tessellate", icon='FILE_REFRESH')
- # MATERIALS
- try:
- # create materials list
- polygon_materials = [p.material_index for p in ob1.data.polygons] * \
- int(len(self.ob.data.polygons) / len(ob1.data.polygons))
- # assign old material
- component_materials = [slot.material for slot in ob1.material_slots]
- for i in range(len(component_materials)):
- bpy.ops.object.material_slot_add()
- bpy.context.object.material_slots[i].material = \
- component_materials[i]
- for i in range(len(self.ob.data.polygons)):
- self.ob.data.polygons[i].material_index = polygon_materials[i]
- except:
- pass
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.label(text="BASE :")
+ row.label(text="COMPONENT :")
+ row = col.row(align=True)
- return {'FINISHED'}
+ col2 = row.column(align=True)
+ col2.prop_search(props, "generator", context.scene, "objects")
+ row.separator()
+ col2 = row.column(align=True)
+ col2.prop_search(props, "component", context.scene, "objects")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
+ row.separator()
+ try:
+ if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH':
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
+ try:
+ if not (props.component.modifiers or props.component.data.shape_keys):
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ col.separator()
- def check(self, context):
- return True
+ # Fill and Rotation
+ row = col.row(align=True)
+ row.label(text="Fill Mode:")
+ row.separator()
+ row.label(text="Rotation:")
+ row = col.row(align=True)
- def invoke(self, context, event):
- return context.window_manager.invoke_props_dialog(self, width=400)
+ # fill
+ row.prop(props, "fill_mode", text="", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ row.separator()
+ # rotation
+ col2 = row.column(align=True)
+ col2.prop(props, "rotation_mode", text="", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
-class tessellate_panel(Panel):
- bl_label = "Tissue"
- bl_category = "Create"
- bl_space_type = "VIEW_3D"
- bl_region_type = "TOOLS"
- bl_options = {'DEFAULT_CLOSED'}
+ if props.rotation_mode == 'RANDOM':
+ #row = col.row(align=True)
+ col2.prop(props, "random_seed")
- @classmethod
- def poll(cls, context):
- return context.mode in {'OBJECT', 'EDIT_MESH'}
+ if props.rotation_mode == 'UV':
+ uv_error = False
+ if props.fill_mode == 'FAN':
+ row = col.row(align=True)
+ row.label(text="UV rotation doesn't work in FAN mode",
+ icon='ERROR')
+ uv_error = True
+ if props.generator.type != 'MESH':
+ row = col.row(align=True)
+ row.label(
+ text="UV rotation supported only for Mesh objects",
+ icon='ERROR')
+ uv_error = True
+ else:
+ if len(props.generator.data.uv_layers) == 0:
+ row = col.row(align=True)
+ row.label(text="'" + props.generator.name +
+ " doesn't have UV Maps", icon='ERROR')
+ uv_error = True
+ if uv_error:
+ row = col.row(align=True)
+ row.label(text="Default rotation will be used instead",
+ icon='INFO')
- def draw(self, context):
- layout = self.layout
+ # component XY
+ row = col.row(align=True)
+ row.label(text="Component Coordinates:")
+ row = col.row(align=True)
+ row.prop(props, "mode", expand=True)
- col = layout.column(align=True)
- col.label(text="Tessellate Add:")
- col.operator("object.tessellate")
+ if props.mode != 'BOUNDS':
+ col.separator()
+ row = col.row(align=True)
+ row.label(text="X:")
+ row.prop(
+ props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
- col = layout.column(align=True)
- col.label(text="Tessellate Edit:")
- col.operator("object.settings_tessellate")
- col.operator("object.update_tessellate")
+ row = col.row(align=True)
+ row.label(text="Y:")
+ row.prop(
+ props, "bounds_y", text="Bounds X", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ # component Z
+ col.label(text="Thickness:")
+ row = col.row(align=True)
+ row.prop(props, "scale_mode", expand=True)
+ col.prop(props, "zscale", text="Scale", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ if props.mode == 'BOUNDS':
+ col.prop(props, "offset", text="Offset", icon='NONE', expand=False,
+ slider=True, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+
+ # Direction
+ row = col.row(align=True)
+ row.label(text="Direction:")
+ row = col.row(align=True)
+ row.prop(
+ props, "normals_mode", text="Direction", icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
+ row.enabled = props.fill_mode != 'PATCH'
- col = layout.column(align=True)
- col.operator("mesh.rotate_face")
+ # merge
+ col = layout.column(align=True)
+ row = col.row(align=True)
+ row.prop(props, "merge")
+ if props.merge:
+ row.prop(props, "merge_thres")
+ row = col.row(align=True)
+ row.prop(props, "bool_smooth")
+ if props.merge:
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_dissolve_seams")
+ #if props.component.type != 'MESH': col2.enabled = False
- col.separator()
- col.label(text="Other:")
- col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
+ row = col.row(align=True)
+ row.prop(props, "cap_faces")
+ if props.cap_faces:
+ col2 = row.column(align=True)
+ col2.prop(props, "open_edges_crease", text="Crease")
- act = context.active_object
- if act and act.type == 'MESH':
- col.operator("object.uv_to_mesh", icon="GROUP_UVS")
+ # Advanced Settings
+ col = layout.column(align=True)
+ col.separator()
+ col.separator()
+ row = col.row(align=True)
+ row.prop(props, "bool_advanced", icon='SETTINGS')
+ if props.bool_advanced:
+ allow_multi = False
+ allow_shapekeys = not props.com_modifiers
+ for m in ob0.data.materials:
+ try:
+ o = bpy.data.objects[m.name]
+ allow_multi = True
+ try:
+ if o.data.shape_keys is None: continue
+ elif len(o.data.shape_keys.key_blocks) < 2: continue
+ else: allow_shapekeys = not props.com_modifiers
+ except: pass
+ except: pass
+ # DATA #
+ col = layout.column(align=True)
+ col.label(text="Morphing:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX')
+ #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
+ try:
+ if len(props.generator.vertex_groups) == 0:
+ col2.enabled = False
+ except:
+ col2.enabled = False
+ row.separator()
+ col2 = row.column(align=True)
+ row2 = col2.row(align=True)
+ row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
+ row2.enabled = allow_shapekeys
+
+ # LIMITED TESSELLATION
+ col = layout.column(align=True)
+ col.label(text="Limited Tessellation:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_multi_components", icon='MOD_TINT')
+ if not allow_multi:
+ col2.enabled = False
+ col.separator()
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
+ #if props.bool_material_id or props.bool_selection or props.bool_multi_components:
+ #col2 = row.column(align=True)
+ # col2.prop(props, "bool_combine")
+ row.separator()
+ if props.generator.type != 'MESH':
+ col2.enabled = False
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID")
+ if props.bool_material_id and not props.bool_multi_components:
+ #col2 = row.column(align=True)
+ col2.prop(props, "material_id")
+ if props.bool_multi_components:
+ col2.enabled = False
+ # TRANFER DATA ### OFF
+ if props.fill_mode != 'PATCH' and False:
+ col = layout.column(align=True)
+ col.label(text="Component Data:")
+ row = col.row(align=True)
+ col2 = row.column(align=True)
+ col2.prop(props, "bool_materials", icon='MATERIAL_DATA')
+ row.separator()
+ col2 = row.column(align=True)
+ if props.fill_mode == 'PATCH':
+ col.enabled = False
+ col.label(text='Not needed in Patch mode', icon='INFO')
+
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH')
+ row.prop(props, 'iterations', text='Repeat', icon='SETTINGS')
+ col.separator()
+ row = col.row(align=True)
+ row.label(text='Combine Iterations:')
+ row = col.row(align=True)
+ row.prop(
+ props, "combine_mode", text="Combine:",icon='NONE', expand=True,
+ slider=False, toggle=False, icon_only=False, event=False,
+ full_event=False, emboss=True, index=-1)
class rotate_face(Operator):
bl_idname = "mesh.rotate_face"
@@ -1554,45 +3005,35 @@ class rotate_face(Operator):
def execute(self, context):
ob = bpy.context.active_object
me = ob.data
- bpy.ops.object.mode_set(mode='OBJECT')
- for p in [f for f in me.polygons if f.select]:
- p.vertices = p.vertices[1:] + p.vertices[:1]
+ bm = bmesh.from_edit_mesh(me)
+ mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
- bpy.ops.object.mode_set(mode='EDIT')
- bpy.ops.mesh.flip_normals()
- bpy.ops.mesh.flip_normals()
- me.update(calc_edges=True)
+ for face in bm.faces:
+ if (face.select):
+ vs = face.verts[:]
+ vs2 = vs[1:]+vs[:1]
+ 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()
+
+ # trigger UI update
+ bmesh.update_edit_mesh(me)
+ ob.select_set(False)
# update tessellated meshes
bpy.ops.object.mode_set(mode='OBJECT')
for o in [obj for obj in bpy.data.objects if
- obj.tissue_tessellate.generator == ob.name]:
+ obj.tissue_tessellate.generator == ob and obj.visible_get()]:
bpy.context.view_layer.objects.active = o
bpy.ops.object.update_tessellate()
+ o.select_set(False)
+ ob.select_set(True)
bpy.context.view_layer.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT')
+ context.tool_settings.mesh_select_mode = mesh_select_mode
return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(tissue_tessellate_prop)
- bpy.utils.register_class(tessellate)
- bpy.utils.register_class(update_tessellate)
- bpy.utils.register_class(settings_tessellate)
- bpy.utils.register_class(tessellate_panel)
- bpy.utils.register_class(rotate_face)
-
-
-def unregister():
- bpy.utils.unregister_class(tissue_tessellate_prop)
- bpy.utils.unregister_class(tessellate)
- bpy.utils.unregister_class(update_tessellate)
- bpy.utils.unregister_class(settings_tessellate)
- bpy.utils.unregister_class(tessellate_panel)
- bpy.utils.unregister_class(rotate_face)
-
-
-if __name__ == "__main__":
- register()
diff --git a/mesh_tissue/utils.py b/mesh_tissue/utils.py
new file mode 100644
index 00000000..e9aa894b
--- /dev/null
+++ b/mesh_tissue/utils.py
@@ -0,0 +1,110 @@
+import bpy
+
+#Recursivly transverse layer_collection for a particular name
+def recurLayerCollection(layerColl, collName):
+ found = None
+ if (layerColl.name == collName):
+ return layerColl
+ for layer in layerColl.children:
+ found = recurLayerCollection(layer, collName)
+ if found:
+ return found
+
+def auto_layer_collection():
+ # automatically change active layer collection
+ layer = bpy.context.view_layer.active_layer_collection
+ layer_collection = bpy.context.view_layer.layer_collection
+ if layer.hide_viewport or layer.collection.hide_viewport:
+ collections = bpy.context.object.users_collection
+ for c in collections:
+ 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 lerp(a, b, t):
+ return a + (b - a) * t
+
+def _lerp2(v1, v2, v3, v4, v):
+ v12 = v1.lerp(v2,v.x) # + (v2 - v1) * v.x
+ v34 = v3.lerp(v4,v.x) # + (v4 - v3) * v.x
+ return v12.lerp(v34, v.y)# + (v34 - v12) * v.y
+
+def lerp2(v1, v2, v3, v4, v):
+ v12 = v1 + (v2 - v1) * v.x
+ v34 = v3 + (v4 - v3) * v.x
+ return v12 + (v34 - v12) * v.y
+
+def lerp3(v1, v2, v3, v4, v):
+ loc = lerp2(v1.co, v2.co, v3.co, v4.co, v)
+ nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v)
+ nor.normalize()
+ return loc + nor * v.z
+
+def _convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
+ if not apply_modifiers:
+ mod_visibility = [m.show_viewport for m in ob.modifiers]
+ for m in ob.modifiers:
+ m.show_viewport = False
+ if preserve_status:
+ # store status
+ mode = bpy.context.object.mode
+ selected = bpy.context.selected_objects
+ active = bpy.context.object
+ # change status
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+ new_ob = ob.copy()
+ new_ob.data = ob.data.copy()
+ bpy.context.collection.objects.link(new_ob)
+ bpy.context.view_layer.objects.active = new_ob
+ new_ob.select_set(True)
+ bpy.ops.object.convert(target='MESH')
+ if preserve_status:
+ # restore status
+ bpy.ops.object.select_all(action='DESELECT')
+ for o in selected: o.select_set(True)
+ bpy.context.view_layer.objects.active = active
+ bpy.ops.object.mode_set(mode=mode)
+ if not apply_modifiers:
+ for m,vis in zip(ob.modifiers,mod_visibility):
+ m.show_viewport = vis
+ return new_ob
+
+def convert_object_to_mesh(ob, apply_modifiers=True, preserve_status=True):
+ if not ob.name: return None
+ if ob.type != 'MESH':
+ if not apply_modifiers:
+ mod_visibility = [m.show_viewport for m in ob.modifiers]
+ for m in ob.modifiers: m.show_viewport = False
+ #ob.modifiers.update()
+ #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)
+ 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:
+ for m,vis in zip(ob.modifiers,mod_visibility): m.show_viewport = vis
+ else:
+ if apply_modifiers:
+ new_ob = ob.copy()
+ new_ob.data = simple_to_mesh(ob)
+ else:
+ new_ob = ob.copy()
+ new_ob.data = ob.data.copy()
+ new_ob.modifiers.clear()
+ bpy.context.collection.objects.link(new_ob)
+ if preserve_status:
+ new_ob.select_set(False)
+ else:
+ for o in bpy.context.view_layer.objects: o.select_set(False)
+ new_ob.select_set(True)
+ bpy.context.view_layer.objects.active = new_ob
+ return new_ob
+
+def simple_to_mesh(ob):
+ 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.calc_normals()
+ return me
diff --git a/mesh_tissue/uv_to_mesh.py b/mesh_tissue/uv_to_mesh.py
index bccee0ba..87179a92 100644
--- a/mesh_tissue/uv_to_mesh.py
+++ b/mesh_tissue/uv_to_mesh.py
@@ -32,7 +32,7 @@ bl_info = {
"name": "UV to Mesh",
"author": "Alessandro Zomparelli (Co-de-iT)",
"version": (0, 1, 1),
- "blender": (2, 79, 0),
+ "blender": (2, 7, 9),
"location": "",
"description": "Create a new Mesh based on active UV",
"warning": "",
@@ -53,22 +53,22 @@ class uv_to_mesh(Operator):
bl_description = ("Create a new Mesh based on active UV")
bl_options = {'REGISTER', 'UNDO'}
- apply_modifiers: BoolProperty(
+ apply_modifiers : BoolProperty(
name="Apply Modifiers",
default=False,
description="Apply object's modifiers"
)
- vertex_groups: BoolProperty(
+ vertex_groups : BoolProperty(
name="Keep Vertex Groups",
default=False,
description="Transfer all the Vertex Groups"
)
- materials: BoolProperty(
+ materials : BoolProperty(
name="Keep Materials",
default=True,
description="Transfer all the Materials"
)
- auto_scale: BoolProperty(
+ auto_scale : BoolProperty(
name="Resize",
default=True,
description="Scale the new object in order to preserve the average surface area"
@@ -76,7 +76,7 @@ class uv_to_mesh(Operator):
def execute(self, context):
bpy.ops.object.mode_set(mode='OBJECT')
- for o in bpy.data.objects:
+ for o in bpy.data.objects and bpy.context.view_layer.objects:
o.select_set(False)
bpy.context.object.select_set(True)
@@ -85,11 +85,7 @@ class uv_to_mesh(Operator):
bpy.ops.object.convert(target='MESH')
ob0 = bpy.context.object
- if self.apply_modifiers:
- depsgraph = context.evaluated_depsgraph_get()
- me0 = bpy.data.meshes.new_from_object(ob0.evaluated_get(depsgraph))
- else:
- me0 = bpy.data.new_from_meshed(ob0)
+ me0 = ob0.to_mesh(bpy.context.depsgraph, apply_modifiers=self.apply_modifiers)
area = 0
verts = []
@@ -122,8 +118,8 @@ class uv_to_mesh(Operator):
# Link object to scene and make active
scn = bpy.context.scene
- scn.objects.link(ob)
- scn.objects.active = ob
+ bpy.context.collection.objects.link(ob)
+ bpy.context.view_layer.objects.active = ob
ob.select_set(True)
# Create mesh from given verts, faces.
@@ -144,7 +140,7 @@ class uv_to_mesh(Operator):
try:
for group in ob0.vertex_groups:
index = group.index
- ob.vertex_groups.new(name=group.name)
+ ob.vertex_groups.new(group.name)
for p in me0.polygons:
for vert, loop in zip(p.vertices, p.loop_indices):
ob.vertex_groups[index].add([loop], group.weight(vert), "ADD")
@@ -183,15 +179,3 @@ class uv_to_mesh(Operator):
bpy.context.view_layer.objects.active = ob
return {'FINISHED'}
-
-
-def register():
- bpy.utils.register_class(uv_to_mesh)
-
-
-def unregister():
- bpy.utils.unregister_class(uv_to_mesh)
-
-
-if __name__ == "__main__":
- register()