diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2017-05-22 03:01:22 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2017-05-22 03:01:22 +0300 |
commit | cfc5f457766db91e26fd00c9317ee1261d688181 (patch) | |
tree | 7010da1902ef8ac4b0405910bbc05965241c66ed /mesh_tissue | |
parent | 05fa3eed2b86275098573fe166db083d1c68db0c (diff) |
initial commit mesh tissue tesselation: T51508 by @Alessandro Zomparelli (alessandrozompa)
Diffstat (limited to 'mesh_tissue')
-rw-r--r-- | mesh_tissue/README.md | 14 | ||||
-rw-r--r-- | mesh_tissue/__init__.py | 77 | ||||
-rw-r--r-- | mesh_tissue/colors_groups_exchanger.py | 300 | ||||
-rw-r--r-- | mesh_tissue/dual_mesh.py | 243 | ||||
-rw-r--r-- | mesh_tissue/tessellate_numpy.py | 1322 |
5 files changed, 1956 insertions, 0 deletions
diff --git a/mesh_tissue/README.md b/mesh_tissue/README.md new file mode 100644 index 00000000..9998d1cf --- /dev/null +++ b/mesh_tissue/README.md @@ -0,0 +1,14 @@ +# Tissue +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 + +Releases: https://github.com/alessandro-zomparelli/tissue/releases + +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. diff --git a/mesh_tissue/__init__.py b/mesh_tissue/__init__.py new file mode 100644 index 00000000..1c0945ed --- /dev/null +++ b/mesh_tissue/__init__.py @@ -0,0 +1,77 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# --------------------------------- TISSUE ------------------------------------# +#-------------------------------- version 0.29 --------------------------------# +# # +# Creates duplicates of selected mesh to active morphing the shape according # +# to target faces. # +# # +# Alessandro Zomparelli # +# (2017) # +# # +# http://www.co-de-it.com/ # +# http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/Mesh/Tissue # +# # +################################################################################ + + +if "bpy" in locals(): + import importlib + importlib.reload(tessellate_numpy) + importlib.reload(colors_groups_exchanger) + importlib.reload(dual_mesh) + +else: + from . import tessellate_numpy + from . import colors_groups_exchanger + from . import dual_mesh + +import bpy +from mathutils import Vector +#bpy.types.Object.vertexgroup = bpy.props.StringProperty() +#bpy.types.Panel.vertexgroup = bpy.props.StringProperty() + +bl_info = { + "name": "Tissue", + "author": "Alessandro Zomparelli (Co-de-iT)", + "version": (0, 2, 9), + "blender": (2, 7, 8), + "location": "", + "description": "Tools for Computational Design", + "warning": "", + "wiki_url": ("http://wiki.blender.org/index.php/Extensions:2.6/Py/Scripts/M" + "esh/Tissue"), + "tracker_url": "https://plus.google.com/u/0/+AlessandroZomparelli/", + "category": "Mesh"} + + +def register(): + bpy.utils.register_module(__name__) + bpy.types.Object.tissue_tessellate = bpy.props.PointerProperty( + type=tessellate_numpy.tissue_tessellate_prop) + + +def unregister(): + tessellate_numpy.unregister() + colors_groups_exchanger.unregister() + dual_mesh.unregister() + + +if __name__ == "__main__": + register() diff --git a/mesh_tissue/colors_groups_exchanger.py b/mesh_tissue/colors_groups_exchanger.py new file mode 100644 index 00000000..c2e45e6f --- /dev/null +++ b/mesh_tissue/colors_groups_exchanger.py @@ -0,0 +1,300 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +#-------------------------- COLORS / GROUPS EXCHANGER -------------------------# +# # +# Vertex Color to Vertex Group allow you to convert colors channles to weight # +# maps. # +# The main purpose is to use vertex colors to store information when importing # +# files from other softwares. The script works with the active vertex color # +# slot. # +# For use the command "Vertex Clors to Vertex Groups" use the search bar # +# (space bar). # +# # +# (c) Alessandro Zomparelli # +# (2017) # +# # +# http://www.co-de-it.com/ # +# # +################################################################################ + +import bpy +import math + +bl_info = { + "name": "Colors/Groups Exchanger", + "author": "Alessandro Zomparelli (Co-de-iT)", + "version": (0, 2), + "blender": (2, 7, 8), + "location": "", + "description": ("Convert vertex colors channels to vertex groups and vertex" + " groups to colors"), + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +class vertex_colors_to_vertex_groups(bpy.types.Operator): + bl_idname = "object.vertex_colors_to_vertex_groups" + bl_label = "Weight from Colors" + bl_options = {'REGISTER', 'UNDO'} + + 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") + + def execute(self, context): + obj = bpy.context.active_object + 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 + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + + if(self.red and boolCol): + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + id_red = id + obj.vertex_groups[id_red].name = col_name + '_red' + id+=1 + if(self.green and boolCol): + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + id_green = id + obj.vertex_groups[id_green].name = col_name + '_green' + id+=1 + if(self.blue and boolCol): + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + id_blue = id + obj.vertex_groups[id_blue].name = col_name + '_blue' + id+=1 + if(self.value and boolCol): + bpy.ops.object.vertex_group_add() + bpy.ops.object.vertex_group_assign() + id_value = id + obj.vertex_groups[id_value].name = col_name + '_value' + 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 + + 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.r + if(self.green): gr[min(len(gr)-sub_green, id_green)].weight\ + = self.invert + mult * v_colors[i].color.g + if(self.blue): gr[min(len(gr)-sub_blue, id_blue)].weight = \ + self.invert + mult * v_colors[i].color.b + if(self.value): gr[min(len(gr)-sub_value, id_value)].weight\ + = self.invert + mult * v_colors[i].color.v + i+=1 + bpy.ops.paint.weight_paint_toggle() + return {'FINISHED'} + + +class vertex_group_to_vertex_colors(bpy.types.Operator): + bl_idname = "object.vertex_group_to_vertex_colors" + bl_label = "Colors from Weight" + bl_options = {'REGISTER', 'UNDO'} + + 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") + + def execute(self, context): + obj = bpy.context.active_object + group_id = obj.vertex_groups.active_index + if (group_id == -1): + return {'FINISHED'} + + bpy.ops.object.mode_set(mode='OBJECT') + group_name = obj.vertex_groups[group_id].name + bpy.ops.mesh.vertex_color_add() + 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" + 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 + + 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) + + for g in gr: + if g.group == group_id: + 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) + else: + v_colors[i].color = (1,1-(g.weight-0.75)*4,0) + elif(self.channel == 'Value'): + v_colors[i].color = ( + self.invert + mult * g.weight, + self.invert + mult * g.weight, + self.invert + mult * g.weight) + elif(self.channel == 'Red'): + v_colors[i].color = ( + self.invert + mult * g.weight,0,0) + elif(self.channel == 'Green'): + v_colors[i].color = ( + 0, self.invert + mult * g.weight,0) + elif(self.channel == 'Blue'): + v_colors[i].color = ( + 0,0, self.invert + mult * g.weight) + i+=1 + bpy.ops.paint.vertex_paint_toggle() + bpy.context.object.data.vertex_colors[colors_id].active_render = True + return {'FINISHED'} + + +class face_area_to_vertex_groups(bpy.types.Operator): + bl_idname = "object.face_area_to_vertex_groups" + bl_label = "Weight from Faces Area" + bl_options = {'REGISTER', 'UNDO'} + invert = bpy.props.BoolProperty( + name="invert", default=False, description="invert values") + + 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 + + if self.invert: mult = -1 + + 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) + print(len(values)) + for p in obj.data.polygons: + for v in p.vertices: + n_values[v] += 1 + values[v] += p.area + + 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 + + id = len(obj.vertex_groups) + 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 + bpy.ops.paint.weight_paint_toggle() + return {'FINISHED'} + + +class colors_groups_exchanger_panel(bpy.types.Panel): + bl_label = "Colors-Weight Exchanger" + bl_category = "Create" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + #bl_context = "objectmode" + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + col.operator("object.vertex_group_to_vertex_colors", icon="GROUP_VCOL") + col.operator( + "object.vertex_colors_to_vertex_groups", icon="GROUP_VERTEX") + col.separator() + col.operator("object.face_area_to_vertex_groups", icon="SNAP_FACE") + + +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) + + +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) + + +if __name__ == "__main__": + register() diff --git a/mesh_tissue/dual_mesh.py b/mesh_tissue/dual_mesh.py new file mode 100644 index 00000000..a4610254 --- /dev/null +++ b/mesh_tissue/dual_mesh.py @@ -0,0 +1,243 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### +#---------------------------------- DUAL MESH ---------------------------------# +#--------------------------------- version 0.3 --------------------------------# +# # +# Convert a generic mesh to its dual. With open meshes it can get some wired # +# effect on the borders. # +# # +# (c) Alessandro Zomparelli # +# (2017) # +# # +# http://www.co-de-it.com/ # +# # +################################################################################ + +import bpy +import bmesh + +bl_info = { + "name": "Dual Mesh", + "author": "Alessandro Zomparelli (Co-de-iT)", + "version": (0, 3), + "blender": (2, 7, 8), + "location": "", + "description": "Convert a generic mesh to its dual", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Mesh"} + +class dual_mesh(bpy.types.Operator): + bl_idname = "object.dual_mesh" + bl_label = "Dual Mesh" + bl_options = {'REGISTER', 'UNDO'} + + quad_method = bpy.props.EnumProperty( + items=[('BEAUTY', 'Beauty', + 'Split the quads in nice triangles, slower method'), + ('FIXED', 'Fixed', 'Split the quads on the 1st and 3rd vertices'), + ('FIXED_ALTERNATE', 'Fixed Alternate', ('Split the quads on the 2nd and' + ' 4th vertices')), + ('SHORTEST_DIAGONAL', 'Shortest Diagonal', ('Split the quads based on ' + 'the distance between the vertices'))], + name="Quad Method", + description="Method for splitting the quads into triangles", + default="FIXED", options={'LIBRARY_EDITABLE'}) + + polygon_method = bpy.props.EnumProperty( + items=[ + ('BEAUTY', 'Beauty', 'Arrange the new triangles evenly'), + ('CLIP', 'Clip', + 'Split the polygons with an ear clipping algorithm')], + name="Polygon Method", + description="Method for splitting the polygons into triangles", + default="BEAUTY", options={'LIBRARY_EDITABLE'}) + + preserve_borders = bpy.props.BoolProperty( + name="Preserve Borders", default=True, + description="Preserve original borders") + + def execute(self, context): + act = bpy.context.active_object + sel = bpy.context.selected_objects + doneMeshes = [] + for ob0 in sel: + if ob0.type != 'MESH': continue + if ob0.data.name in doneMeshes: continue + ##ob = bpy.data.objects.new("dual_mesh_wip", ob0.data.copy()) + ob = ob0 + mesh_name = ob0.data.name + + # store linked objects + clones = [] + n_users = ob0.data.users + count = 0 + for o in bpy.data.objects: + if o.type != 'MESH': continue + if o.data.name == mesh_name: + count+=1 + clones.append(o) + if count == n_users: break + ob.data = ob.data.copy() + bpy.ops.object.select_all(action='DESELECT') + ob.select = True + bpy.context.scene.objects.active = ob0 + bpy.ops.object.mode_set(mode = 'EDIT') + + if self.preserve_borders: + 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.extrude_region_move( + MESH_OT_extrude_region={"mirror":False}, + TRANSFORM_OT_translate={"value":(0, 0, 0), + "constraint_axis":(False, False, False), + "constraint_orientation":'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}) + + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='VERT', + action='TOGGLE') + bpy.ops.mesh.select_all(action = 'SELECT') + bpy.ops.mesh.quads_convert_to_tris( + quad_method=self.quad_method, ngon_method=self.polygon_method) + bpy.ops.mesh.select_all(action = 'DESELECT') + bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.modifier_add(type='SUBSURF') + ob.modifiers[-1].name = "dual_mesh_subsurf" + 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 + 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') + + 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.select_more() + + # find boundaries + bpy.ops.object.mode_set(mode = 'OBJECT') + bound_v = [v.index for v in ob.data.vertices if v.select] + bound_e = [e.index for e in ob.data.edges if e.select] + bound_p = [p.index for p in ob.data.polygons if p.select] + bpy.ops.object.mode_set(mode = 'EDIT') + + # select quad faces + bpy.context.tool_settings.mesh_select_mode = (False, False, True) + bpy.ops.mesh.select_face_by_sides(number=4, extend=False) + + # deselect boundaries + bpy.ops.object.mode_set(mode = 'OBJECT') + for i in bound_v: + bpy.context.active_object.data.vertices[i].select = False + for i in bound_e: + bpy.context.active_object.data.edges[i].select = False + for i in bound_p: + bpy.context.active_object.data.polygons[i].select = False + + bpy.ops.object.mode_set(mode = 'EDIT') + + bpy.context.tool_settings.mesh_select_mode = (False, False, True) + bpy.ops.mesh.edge_face_add() + bpy.context.tool_settings.mesh_select_mode = (True, False, False) + bpy.ops.mesh.select_all(action = 'DESELECT') + + # delete boundaries + bpy.ops.mesh.select_non_manifold( + extend=False, use_wire=True, use_boundary=True, + use_multi_face=False, use_non_contiguous=False, use_verts=True) + bpy.ops.mesh.delete(type='VERT') + + # remove middle vertices + 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) + + # dissolve + bpy.ops.mesh.dissolve_verts() + bpy.ops.mesh.select_all(action = 'DESELECT') + + # clean wires + bpy.ops.mesh.select_non_manifold( + extend=False, use_wire=True, use_boundary=False, + use_multi_face=False, use_non_contiguous=False, use_verts=False) + bpy.ops.mesh.delete(type='EDGE') + + bpy.ops.object.mode_set(mode = 'OBJECT') + ob0.data.name = mesh_name + doneMeshes.append(mesh_name) + for o in clones: o.data = ob.data + for o in sel: o.select = True + bpy.context.scene.objects.active = act + return {'FINISHED'} + + +class dual_mesh_panel(bpy.types.Panel): + bl_label = "Dual Mesh" + bl_category = "Create" + 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/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py new file mode 100644 index 00000000..65afeee7 --- /dev/null +++ b/mesh_tissue/tessellate_numpy.py @@ -0,0 +1,1322 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# ---------------------------- ADAPTIVE DUPLIFACES ----------------------------# +#-------------------------------- version 0.83 --------------------------------# +# # +# Creates duplicates of selected mesh to active morphing the shape according # +# to target faces. # +# # +# (c) Alessandro Zomparelli # +# (2017) # +# # +# http://www.co-de-it.com/ # +# # +################################################################################ + + +import bpy +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 + + +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 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): + random.seed(rand_seed) + old_me0 = ob0.data # Store generator mesh + if gen_modifiers: # Apply generator modifiers + me0 = ob0.to_mesh(bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + else: me0 = ob0.data + ob0.data = me0 + base_polygons = [] + + # 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 + + # Apply component modifiers + if com_modifiers: + me1 = ob1.to_mesh(bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + else: me1 = ob1.data + + verts0 = me0.vertices # Collect generator vertices + + # 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 = [] + new_edges = [] + new_faces = [] + new_verts_np = np.array(()) + + # Component bounding box + min = Vector((0,0,0)) + max = Vector((0,0,0)) + first = True + for v in me1.vertices: + vert = v.co + if vert[0] < min[0] or first: + min[0] = vert[0] + if vert[1] < min[1] or first: + min[1] = vert[1] + if vert[2] < min[2] or first: + min[2] = vert[2] + if vert[0] > max[0] or first: + max[0] = vert[0] + if vert[1] > max[1] or first: + max[1] = vert[1] + if vert[2] > max[2] or first: + max[2] = vert[2] + first = False + bb = max-min + + # adaptive XY + verts1 = [] + for v in me1.vertices: + if mode=="ADAPTIVE": + vert = v.co - min#( ob1.matrix_world * v.co ) - min + 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: + vert = v.co.xyz + vert[2] = (vert[2] - min[2] + (-0.5 + offset*0.5)*bb[2])*zscale + verts1.append(vert) + + # component vertices + vs1 = np.array([v for v in verts1]).reshape(len(verts1),3,1) + vx = vs1[:,0] + vy = vs1[:,1] + vz = vs1[:,2] + + # 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] + 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: + do_shapekeys = True + + # Read active key + active_key = ob1.active_shape_key_index + if active_key == 0: active_key = 1 + + for v in me1.shape_keys.key_blocks[active_key].data: + if mode=="ADAPTIVE": + vert = v.co - min + #vert = ( ob1.matrix_world * v.co ) - min + 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: + vert = v.co.xyz + vert[2] = (vert[2] - min[2] + (-0.5 + offset*0.5)*bb[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] + + # Active 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) + except: + bool_vertex_group = False + + # FAN tessellation mode + if fill_mode == 'FAN': + fan_verts = [v.co.to_tuple() for v in me0.vertices] + fan_polygons = [] + selected_faces = [] + for p in base_polygons: + #if bool_selection and not p.select: continue + fan_center = Vector((0,0,0)) + for v in p.vertices: + fan_center += me0.vertices[v].co + fan_center /= len(p.vertices) + last_vert = len(fan_verts) + fan_verts.append(fan_center.to_tuple()) + + # Vertex Group + if bool_vertex_group: + center_weight = sum([weight[i] for i in p.vertices])/ \ + len(p.vertices) + weight.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) + fan_me = bpy.data.meshes.new('Fan.Mesh') + fan_me.from_pydata(tuple(fan_verts), [], tuple(fan_polygons)) + me0 = fan_me + verts0 = me0.vertices + base_polygons = me0.polygons + #for i in range(len(selected_faces)): + # fan_me.polygons[i].select = selected_faces[i] + count = 0 # necessary for UV calculation + + # TESSELLATION + j = 0 + for p in base_polygons: + # Random rotation + if rotation_mode == 'RANDOM': + shifted_vertices = [] + n_poly_verts = len(p.vertices) + rand = random.randint(0,n_poly_verts) + for i in range(n_poly_verts): + shifted_vertices.append(p.vertices[(i+rand)%n_poly_verts]) + vs0 = np.array([verts0[i].co for i in shifted_vertices]) + 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) + + # 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 + \ + 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] + else: vertUV = p.vertices[3:] + p.vertices[:3] + else: + 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 i in vertUV: + try: ws0.append(weight[i]) + except: ws0.append(0) + ws0 = np.array(ws0) + + count += len(p.vertices) + + # Default rotation + else: + 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) + + # 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 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]) + + j += 1 + + 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, []) + new_me.update(calc_edges=True) + new_ob = bpy.data.objects.new("tessellate_temp", new_me) + + # vertex group + if bool_vertex_group: + new_ob.vertex_groups.new("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 + return new_ob + + +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 + + +class tissue_tessellate_prop(bpy.types.PropertyGroup): + generator = bpy.props.StringProperty() + component = bpy.props.StringProperty() + offset = bpy.props.FloatProperty() + zscale = bpy.props.FloatProperty(default=1) + merge = bpy.props.BoolProperty() + merge_thres = bpy.props.FloatProperty() + gen_modifiers = bpy.props.BoolProperty() + com_modifiers = bpy.props.BoolProperty() + mode = bpy.props.StringProperty() + rotation_mode = bpy.props.StringProperty() + scale_mode = bpy.props.StringProperty() + fill_mode = bpy.props.StringProperty() + bool_random = bpy.props.BoolProperty() + random_seed = bpy.props.IntProperty() + vertexgroup = bpy.props.StringProperty() + bool_vertex_group = bpy.props.BoolProperty() + bool_selection = bpy.props.BoolProperty() + bool_shapekeys = bpy.props.BoolProperty() + + +class tessellate(bpy.types.Operator): + bl_idname = "object.tessellate" + bl_label = "Tessellate" + bl_description = ("Create a copy of selected object on the active object's " + "faces, adapting the shape to the different faces.") + bl_options = {'REGISTER', 'UNDO'} + + object_name = bpy.props.StringProperty( + name="", description="Name of the generated object") + zscale = bpy.props.FloatProperty( + name="Scale", default=1, soft_min=0, soft_max=10, + description="Scale factor for the component thickness") + scale_mode = bpy.props.EnumProperty( + items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")), + default='CONSTANT', name="Z-Scale according to faces size") + offset = bpy.props.FloatProperty( + name="Surface Offset", default=0, min=-1, max=1, soft_min=-1, + soft_max=1, description="Surface offset") + mode = bpy.props.EnumProperty( + items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")), + default='ADAPTIVE', name="Component Mode") + rotation_mode = bpy.props.EnumProperty( + items=(('RANDOM', "Random", ""), ('UV', "Active UV", ""), + ('DEFAULT', "Default", "")), default='DEFAULT', + name="Component Rotation") + fill_mode = bpy.props.EnumProperty( + items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")), default='QUAD', + name="Fill Mode") + gen_modifiers = bpy.props.BoolProperty( + name="Generator Modifiers", default=False, + description="Apply modifiers to base object") + com_modifiers = bpy.props.BoolProperty( + name="Component Modifiers", default=False, + description="Apply modifiers to component object") + merge = bpy.props.BoolProperty( + name="Merge", default=False, + description="Merge vertices in adjacent duplicates") + merge_thres = bpy.props.FloatProperty( + name="Distance", default=0.001, soft_min=0, soft_max=10, + description="Limit below which to merge vertices") + generator = bpy.props.StringProperty( + name="", description="Base object for the tessellation") + component = bpy.props.StringProperty( + name="", description="Component object for the tessellation") + bool_random = bpy.props.BoolProperty( + name="Randomize", default=False, + description="Randomize component rotation") + random_seed = bpy.props.IntProperty( + name="Seed", default=0, soft_min=0, soft_max=10, + description="Random seed") + bool_vertex_group = bpy.props.BoolProperty( + name="Map Vertex Group", default=False, description=("Map the active " + "Vertex Group from the Base object to generated geometry")) + bool_selection = bpy.props.BoolProperty( + name="On selected Faces", default=False, + description="Create Tessellation only on selected faces") + bool_shapekeys = bpy.props.BoolProperty( + name="Use Shape Keys", default=False, description=("Use component's " + "active Shape Key according to active Vertex Group of the base object")) + working_on = "" + + def draw(self, context): + try: + bool_working = self.working_on == self.object_name and \ + self.working_on != "" + except: + bool_working = False + + sel = bpy.context.selected_objects + + bool_meshes = False + if len(sel) == 2: + bool_meshes = True + for o in sel: + if o.type != 'MESH': bool_meshes = 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 = self.layout + layout.label(icon='INFO') + layout.label(text="Please, select two Mesh objects") + else: + try: + ob0 = bpy.data.Objects[self.generator] + except: + 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 + no_component = False + break + + # Checks for Tool Shelf panel, it loose the original Selection + if bpy.context.active_object.name == 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 + + # new object name + if self.object_name == "": + if self.generator == "": self.object_name = "Tessellation" + else: self.object_name = self.generator + "_Tessellation" + + layout = self.layout + # Base and Component + col = layout.column(align=True) + row = col.row(align=True) + row.label(text="BASE : " + self.generator) + row.label(text="COMPONENT : " + self.component) + 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.separator() + + # General + col = layout.column(align=True) + col.label(text="New Object Name:") + col.prop(self, "object_name") + + # Count number of faces + try: + polygons = 0 + if self.gen_modifiers: me_temp = ob0.to_mesh(bpy.context.scene, + apply_modifiers=True, settings = 'PREVIEW') + else: 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 self.com_modifiers: me_temp = bpy.data.objects[ + self.component].to_mesh(bpy.context.scene, + apply_modifiers=True, settings = 'PREVIEW') + else: me_temp = bpy.data.objects[self.component].data + polygons *= len(me_temp.polygons) + + 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() + + # 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) + 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 + 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 == None: + col2.enabled = False + bool_shapekeys = False + elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ + bpy.data.objects[self.component].data.shape_keys != None: + if len(bpy.data.objects[ + self.component].data.shape_keys.key_blocks) < 2: + col2.enabled = False + bool_shapekeys = False + + def execute(self, context): + try: + ob0 = bpy.context.active_object + self.generator = ob0.name + 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 loose 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" + + if bpy.data.objects[self.component].type != 'MESH': + message = "Component must be Mesh Objects!" + self.report({'ERROR'}, message) + self.component = "" + if bpy.data.objects[self.generator].type != 'MESH': + message = "Generator must be Mesh Objects!" + self.report({'ERROR'}, message) + self.generator = "" + if self.component != "" and self.generator != "": + bpy.ops.object.select_all(action='TOGGLE') + + 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 + + scene = bpy.context.scene + scene.objects.link(new_ob) + new_ob.select = True + bpy.context.scene.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') + 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 + # 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] + + return {'FINISHED'} + + def check(self, context): + return True + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + +class update_tessellate(bpy.types.Operator): +#class adaptive_duplifaces(bpy.types.Panel): + bl_idname = "object.update_tessellate" + bl_label = "Refresh" + bl_description = ("Fast update the tessellated mesh according to base and " + "component changes") + bl_options = {'REGISTER', 'UNDO'} + go = False + ob = bpy.types.Object + + @classmethod + def poll(cls, context): + try: + return context.active_object.tissue_tessellate.generator != "" and \ + context.active_object.tissue_tessellate.component != "" + except: return False + + def execute(self, context): + ob = bpy.context.active_object + if not self.go: + generator = ob.tissue_tessellate.generator + component = ob.tissue_tessellate.component + zscale = ob.tissue_tessellate.zscale + scale_mode = ob.tissue_tessellate.scale_mode + rotation_mode = ob.tissue_tessellate.rotation_mode + offset = ob.tissue_tessellate.offset + merge = ob.tissue_tessellate.merge + merge_thres = ob.tissue_tessellate.merge_thres + gen_modifiers = ob.tissue_tessellate.gen_modifiers + com_modifiers = ob.tissue_tessellate.com_modifiers + bool_random = ob.tissue_tessellate.bool_random + random_seed = ob.tissue_tessellate.random_seed + fill_mode = ob.tissue_tessellate.fill_mode + bool_vertex_group = ob.tissue_tessellate.bool_vertex_group + bool_selection = ob.tissue_tessellate.bool_selection + bool_shapekeys = ob.tissue_tessellate.bool_shapekeys + mode = ob.tissue_tessellate.mode + + if(generator == "" or component == ""): + self.report({'ERROR'}, + "Active object must be Tessellate before Update") + return {'CANCELLED'} + + ob0 = bpy.data.objects[generator] + ob1 = bpy.data.objects[component] + me0 = ob0.data + verts = me0.vertices + + 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) + + if temp_ob == 0: + message = "Zero faces selected in the Base mesh!" + self.report({'ERROR'}, message) + return {'CANCELLED'} + + ob.data = temp_ob.data + bpy.data.objects.remove(temp_ob) + if 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=merge_thres, use_unselected=False) + bpy.ops.object.mode_set(mode = 'OBJECT') + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.object.mode_set(mode='OBJECT') + + # MATERIALS + # 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] + + return {'FINISHED'} + + def check(self, context): + return True + + +class settings_tessellate(bpy.types.Operator): +#class adaptive_duplifaces(bpy.types.Panel): + bl_idname = "object.settings_tessellate" + bl_label = "Settings" + bl_description = ("Update the tessellated mesh according to base and " + "component changes. Allow also to change tessellation's parameters") + bl_options = {'REGISTER', 'UNDO'} + + object_name = bpy.props.StringProperty( + name="", description="Name of the generated object") + zscale = bpy.props.FloatProperty( + name="Scale", default=1, soft_min=0, soft_max=10, + description="Scale factor for the component thickness") + scale_mode = bpy.props.EnumProperty( + items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")), + default='ADAPTIVE', name="Scale variation") + offset = bpy.props.FloatProperty( + name="Surface Offset", default=0, min=-1, max=1, soft_min=-1, + soft_max=1, description="Surface offset") + mode = bpy.props.EnumProperty( + items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")), + default='ADAPTIVE', name="Component Mode") + rotation_mode = bpy.props.EnumProperty( + items=(('RANDOM', "Random", ""), ('UV', "Active UV", ""), + ('DEFAULT', "Default", "")), default='DEFAULT', + name="Component Rotation") + fill_mode = bpy.props.EnumProperty( + items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")), default='QUAD', + name="Fill Mode") + gen_modifiers = bpy.props.BoolProperty( + name="Generator Modifiers", default=False, + description="Apply modifiers to base object") + com_modifiers = bpy.props.BoolProperty( + name="Component Modifiers", default=False, + description="Apply modifiers to component object") + merge = bpy.props.BoolProperty( + name="Merge", default=False, + description="Merge vertices in adjacent duplicates") + merge_thres = bpy.props.FloatProperty( + name="Distance", default=0.001, soft_min=0, soft_max=10, + description="Limit below which to merge vertices") + generator = bpy.props.StringProperty( + name="", description="Base object for the tessellation") + component = bpy.props.StringProperty( + name="", description="Component object for the tessellation") + bool_random = bpy.props.BoolProperty( + name="Randomize", default=False, + description="Randomize component rotation") + random_seed = bpy.props.IntProperty( + name="Seed", default=0, soft_min=0, soft_max=10, + description="Random seed") + bool_vertex_group = bpy.props.BoolProperty( + name="Map Vertex Group", default=False, description=("Map on generated " + "geometry the active Vertex Group from the base object")) + bool_selection = bpy.props.BoolProperty( + name="On selected Faces", default=False, + description="Create Tessellation only on select faces") + bool_shapekeys = bpy.props.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 + ob = bpy.types.Object + + @classmethod + def poll(cls, context): + try: + return context.active_object.tissue_tessellate.generator != "" and \ + context.active_object.tissue_tessellate.component != "" + except: return False + + 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 + #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.separator() + + # Count number of faces + try: + polygons = 0 + if self.gen_modifiers: + me_temp = bpy.data.objects[self.generator].to_mesh( + bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + else: 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 self.com_modifiers: + me_temp = bpy.data.objects[self.component].to_mesh( + bpy.context.scene, apply_modifiers=True, + settings = 'PREVIEW') + else: me_temp = bpy.data.objects[self.component].data + polygons *= len(me_temp.polygons) + + 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() + + # 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 + 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 == None: + col2.enabled = False + bool_shapekeys = False + elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ + bpy.data.objects[self.component].data.shape_keys != None: + if len(bpy.data.objects[self.component].data.shape_keys.key_blocks) < 2: + col2.enabled = False + bool_shapekeys = False + self.go = True + + def execute(self, context): + self.ob = bpy.context.active_object + old_material = None + if(len(self.ob.material_slots) > 0): + old_material = self.ob.material_slots[0].material + 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(self.generator == "" or self.component == ""): + self.report({'ERROR'}, + "Active object must be Tessellate 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] + me0 = ob0.data + verts = me0.vertices + + 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'} + + # Transfer mesh data + self.ob.data = temp_ob.data + + # Create object in order to transfer vertex group + scene = bpy.context.scene + scene.objects.link(temp_ob) + temp_ob.select = True + bpy.context.scene.objects.active = temp_ob + try: + bpy.ops.object.vertex_group_copy_to_linked() + except: + pass + scene.objects.unlink(temp_ob) + bpy.data.objects.remove(temp_ob) + bpy.context.scene.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') + + # MATERIALS + # 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] + return {'FINISHED'} + + def check(self, context): + return True + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self) + + +class tessellate_panel(bpy.types.Panel): + bl_label = "Tessellate" + bl_category = "Create" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + #bl_context = "objectmode", "editmode" + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + col.label(text="Add:") + col.operator("object.tessellate")#, icon="STRANDS") + #col.enable = False + #col.operator("object.adaptive_duplifaces", icon="MESH_CUBE") + col = layout.column(align=True) + col.label(text="Edit:") + col.operator("object.settings_tessellate") + col.operator("object.update_tessellate") + col = layout.column(align=True) + col.operator("mesh.rotate_face") + act = context.active_object + sel = act #context.selected_objects[0] + + for ob1 in context.selected_objects: + if(ob1.name == act.name or ob1.type != 'MESH'): continue + sel = ob1 + + +class rotate_face(bpy.types.Operator): + bl_idname = "mesh.rotate_face" + bl_label = "Rotate Faces" + bl_description = "Rotate selected faces and update tessellated meshes." + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return context.mode == 'EDIT_MESH' + + 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] + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.flip_normals() + bpy.ops.mesh.flip_normals() + #me.vertices[0].co[0] = 10 + me.update(calc_edges=True) + + #update tessellated meshes + bpy.ops.object.mode_set(mode='OBJECT') + for o in [object for object in bpy.data.objects if \ + object.tissue_tessellate.generator == ob.name]: + bpy.context.scene.objects.active = o + bpy.ops.object.update_tessellate() + bpy.context.scene.objects.active = ob + bpy.ops.object.mode_set(mode='EDIT') + 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() |