4281 lines
		
	
	
		
			166 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
		
		
			
		
	
	
			4281 lines
		
	
	
		
			166 KiB
		
	
	
	
		
			Plaintext
		
	
	
	
	
	
|   | # ##### BEGIN GPL LICENSE BLOCK ##### | ||
|  | # | ||
|  | #  This program is free software; you can redistribute it and/or | ||
|  | #  modify it under the terms of the GNU General Public License | ||
|  | #  as published by the Free Software Foundation; either version 2 | ||
|  | #  of the License, or (at your option) any later version. | ||
|  | # | ||
|  | #  This program is distributed in the hope that it will be useful, | ||
|  | #  but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
|  | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||
|  | #  GNU General Public License for more details. | ||
|  | # | ||
|  | #  You should have received a copy of the GNU General Public License | ||
|  | #  along with this program; if not, write to the Free Software Foundation, | ||
|  | #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
|  | # | ||
|  | # ##### END GPL LICENSE BLOCK ##### | ||
|  | 
 | ||
|  | # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- # | ||
|  | # ------------------------------- version 0.84 ------------------------------- # | ||
|  | #                                                                              # | ||
|  | # 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 bpy.types import ( | ||
|  |         Operator, | ||
|  |         Panel, | ||
|  |         PropertyGroup, | ||
|  |         ) | ||
|  | from bpy.props import ( | ||
|  |         BoolProperty, | ||
|  |         EnumProperty, | ||
|  |         FloatProperty, | ||
|  |         IntProperty, | ||
|  |         StringProperty, | ||
|  |         PointerProperty | ||
|  |         ) | ||
|  | from mathutils import Vector | ||
|  | import numpy as np | ||
|  | from math import * | ||
|  | import random, time, copy | ||
|  | import bmesh | ||
|  | from .utils import * | ||
|  | 
 | ||
|  | def anim_tessellate_active(self, context): | ||
|  |     ob = context.object | ||
|  |     props = ob.tissue_tessellate | ||
|  |     if not (props.bool_lock or props.bool_hold): | ||
|  |         try: | ||
|  |             props.generator.name | ||
|  |             props.component.name | ||
|  |             bpy.ops.object.tissue_update_tessellate() | ||
|  |         except: pass | ||
|  | 
 | ||
|  | def anim_tessellate_object(ob): | ||
|  |     try: | ||
|  |         #bpy.context.view_layer.objects.active = ob | ||
|  |         bpy.ops.object.tissue_update_tessellate() | ||
|  |     except: | ||
|  |         return None | ||
|  | 
 | ||
|  | #from bpy.app.handlers import persistent | ||
|  | 
 | ||
|  | def anim_tessellate(scene): | ||
|  |     try: | ||
|  |         active_object = bpy.context.object | ||
|  |         old_mode = bpy.context.object.mode | ||
|  |         selected_objects = bpy.context.selected_objects | ||
|  |     except: active_object = old_mode = selected_objects = None | ||
|  |     if old_mode in ('OBJECT', 'PAINT_WEIGHT'): | ||
|  |         update_objects = [] | ||
|  |         for ob in scene.objects: | ||
|  |             if ob.tissue_tessellate.bool_run and not ob.tissue_tessellate.bool_lock: | ||
|  |                 if ob not in update_objects: update_objects.append(ob) | ||
|  |                 update_objects = list(reversed(update_dependencies(ob, update_objects))) | ||
|  |         for ob in update_objects: | ||
|  |             override = {'object': ob} | ||
|  |             ''' | ||
|  |             win      = bpy.data.window_managers[0].windows[0]#bpy.context.window | ||
|  |             scr      = win.screen | ||
|  |             areas3d  = [area for area in scr.areas if area.type == 'VIEW_3D'] | ||
|  |             region   = [region for region in areas3d[0].regions if region.type == 'WINDOW'] | ||
|  |             override = { | ||
|  |                 'window':win, | ||
|  |                 'screen':scr, | ||
|  |                 'area'  :areas3d[0], | ||
|  |                 'region':region[0], | ||
|  |                 'scene' :scene, | ||
|  |                 'object': ob | ||
|  |             } | ||
|  |             ''' | ||
|  |             print(override) | ||
|  |             bpy.ops.object.tissue_update_tessellate(override) | ||
|  |     # restore selected objects | ||
|  |     if old_mode != None: | ||
|  |         for o in scene.objects: | ||
|  |             if not o.hide_viewport: o.select_set(o in selected_objects) | ||
|  |         bpy.context.view_layer.objects.active = active_object | ||
|  |         bpy.ops.object.mode_set(mode=old_mode) | ||
|  |     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 | ||
|  | 
 | ||
|  | class tissue_tessellate_prop(PropertyGroup): | ||
|  |     bool_lock : BoolProperty( | ||
|  |         name="Lock", | ||
|  |         description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.", | ||
|  |         default=False | ||
|  |         ) | ||
|  |     bool_hold : BoolProperty( | ||
|  |         name="Hold", | ||
|  |         description="Wait...", | ||
|  |         default=False | ||
|  |         ) | ||
|  |     bool_dependencies : BoolProperty( | ||
|  |         name="Update Dependencies", | ||
|  |         description="Automatically updates base and components as well, if results of other tessellations", | ||
|  |         default=False | ||
|  |         ) | ||
|  |     bool_run : BoolProperty( | ||
|  |         name="Animatable Tessellation", | ||
|  |         description="Automatically recompute the tessellation when the frame is changed. Currently is not working during  Render Animation", | ||
|  |         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', "Relative", "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"), | ||
|  |                ('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"), | ||
|  |                ('DEFAULT', "Default", "Default rotation")), | ||
|  |         default='DEFAULT', | ||
|  |         name="Component Rotation", | ||
|  |         update = anim_tessellate_active | ||
|  |         ) | ||
|  |     rotation_direction : EnumProperty( | ||
|  |         items=(('ORTHO', "Orthogonal", "Component main directions in XY"), | ||
|  |                ('DIAG', "Diagonal", "Component main direction aligned with diagonal")), | ||
|  |         default='ORTHO', | ||
|  |         name="Direction", | ||
|  |         update = anim_tessellate_active | ||
|  |         ) | ||
|  |     rotation_shift : IntProperty( | ||
|  |         name="Shift", | ||
|  |         default=0, | ||
|  |         soft_min=0, | ||
|  |         soft_max=3, | ||
|  |         description="Shift components 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'), | ||
|  |             ('FRAME', 'Frame', 'Essellation along the edges of each face')), | ||
|  |         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', 'Normals', 'Consistent direction based on vertices normal'), | ||
|  |             ('FACES', 'Individual Faces', 'Based on individual faces normal'), | ||
|  |             ('CUSTOM', 'Custom', "According to Base object's shape keys")), | ||
|  |         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 | ||
|  |             ) | ||
|  |     close_mesh : EnumProperty( | ||
|  |             items=( | ||
|  |                 ('NONE', 'None', 'Keep the mesh open'), | ||
|  |                 ('CAP', 'Cap Holes', 'Automatically cap open loops'), | ||
|  |                 ('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')), | ||
|  |             default='NONE', | ||
|  |             name="Close Mesh", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     cap_faces : BoolProperty( | ||
|  |             name="Cap Holes", | ||
|  |             default=False, | ||
|  |             description="Cap open edges loops", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     frame_boundary : BoolProperty( | ||
|  |             name="Frame Boundary", | ||
|  |             default=False, | ||
|  |             description="Support face boundaries", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     fill_frame : BoolProperty( | ||
|  |             name="Fill Frame", | ||
|  |             default=False, | ||
|  |             description="Fill inner faces with Fan tessellation", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     frame_boundary_mat : IntProperty( | ||
|  |             name="Material Offset", | ||
|  |             default=0, | ||
|  |             description="Material Offset for boundaries", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     fill_frame_mat : IntProperty( | ||
|  |             name="Material Offset", | ||
|  |             default=0, | ||
|  |             description="Material Offset for inner faces", | ||
|  |             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 | ||
|  |             ) | ||
|  |     bridge_smoothness : FloatProperty( | ||
|  |             name="Smoothness", | ||
|  |             default=1, | ||
|  |             min=0, | ||
|  |             max=1, | ||
|  |             description="Bridge Smoothness", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     frame_thickness : FloatProperty( | ||
|  |             name="Frame Thickness", | ||
|  |             default=0.2, | ||
|  |             min=0, | ||
|  |             soft_max=2, | ||
|  |             description="Frame Thickness", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     frame_mode : EnumProperty( | ||
|  |             items=( | ||
|  |                 ('CONSTANT', 'Constant', 'Even thickness'), | ||
|  |                 ('RELATIVE', 'Relative', 'Frame offset depends on face areas')), | ||
|  |             default='CONSTANT', | ||
|  |             name="Offset", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     bridge_cuts : IntProperty( | ||
|  |             name="Cuts", | ||
|  |             default=0, | ||
|  |             min=0, | ||
|  |             max=20, | ||
|  |             description="Bridge Cuts", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     cap_material_index : IntProperty( | ||
|  |             name="Material", | ||
|  |             default=0, | ||
|  |             min=0, | ||
|  |             description="Material index for the cap/bridge faces", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  |     patch_subs : IntProperty( | ||
|  |             name="Patch Subdivisions", | ||
|  |             default=1, | ||
|  |             min=0, | ||
|  |             description="Subdivisions levels for Patch tessellation after the first iteration", | ||
|  |             update = anim_tessellate_active | ||
|  |             ) | ||
|  | 
 | ||
|  | def store_parameters(operator, ob): | ||
|  |     ob.tissue_tessellate.bool_hold = True | ||
|  |     ob.tissue_tessellate.bool_lock = operator.bool_lock | ||
|  |     ob.tissue_tessellate.bool_dependencies = operator.bool_dependencies | ||
|  |     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.rotation_shift = operator.rotation_shift | ||
|  |     ob.tissue_tessellate.rotation_direction = operator.rotation_direction | ||
|  |     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.close_mesh = operator.close_mesh | ||
|  |     ob.tissue_tessellate.bridge_cuts = operator.bridge_cuts | ||
|  |     ob.tissue_tessellate.bridge_smoothness = operator.bridge_smoothness | ||
|  |     ob.tissue_tessellate.frame_thickness = operator.frame_thickness | ||
|  |     ob.tissue_tessellate.frame_mode = operator.frame_mode | ||
|  |     ob.tissue_tessellate.frame_boundary = operator.frame_boundary | ||
|  |     ob.tissue_tessellate.fill_frame = operator.fill_frame | ||
|  |     ob.tissue_tessellate.frame_boundary_mat = operator.frame_boundary_mat | ||
|  |     ob.tissue_tessellate.fill_frame_mat = operator.fill_frame_mat | ||
|  |     ob.tissue_tessellate.cap_material_index = operator.cap_material_index | ||
|  |     ob.tissue_tessellate.patch_subs = operator.patch_subs | ||
|  |     ob.tissue_tessellate.bool_hold = False | ||
|  |     return ob | ||
|  | 
 | ||
|  | def load_parameters(operator, ob): | ||
|  |     operator.bool_lock = ob.tissue_tessellate.bool_lock | ||
|  |     operator.bool_dependencies = ob.tissue_tessellate.bool_dependencies | ||
|  |     operator.generator = ob.tissue_tessellate.generator.name | ||
|  |     operator.component = ob.tissue_tessellate.component.name | ||
|  |     operator.zscale = ob.tissue_tessellate.zscale | ||
|  |     operator.offset = ob.tissue_tessellate.offset | ||
|  |     operator.gen_modifiers = ob.tissue_tessellate.gen_modifiers | ||
|  |     operator.com_modifiers = ob.tissue_tessellate.com_modifiers | ||
|  |     operator.mode = ob.tissue_tessellate.mode | ||
|  |     operator.rotation_mode = ob.tissue_tessellate.rotation_mode | ||
|  |     operator.rotation_shift = ob.tissue_tessellate.rotation_shift | ||
|  |     operator.rotation_direction = ob.tissue_tessellate.rotation_direction | ||
|  |     operator.merge = ob.tissue_tessellate.merge | ||
|  |     operator.merge_thres = ob.tissue_tessellate.merge_thres | ||
|  |     operator.scale_mode = ob.tissue_tessellate.scale_mode | ||
|  |     operator.bool_random = ob.tissue_tessellate.bool_random | ||
|  |     operator.random_seed = ob.tissue_tessellate.random_seed | ||
|  |     operator.fill_mode = ob.tissue_tessellate.fill_mode | ||
|  |     operator.bool_vertex_group = ob.tissue_tessellate.bool_vertex_group | ||
|  |     operator.bool_selection = ob.tissue_tessellate.bool_selection | ||
|  |     operator.bool_shapekeys = ob.tissue_tessellate.bool_shapekeys | ||
|  |     operator.bool_smooth = ob.tissue_tessellate.bool_smooth | ||
|  |     operator.bool_materials = ob.tissue_tessellate.bool_materials | ||
|  |     operator.bool_material_id = ob.tissue_tessellate.bool_material_id | ||
|  |     operator.material_id = ob.tissue_tessellate.material_id | ||
|  |     operator.bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams | ||
|  |     operator.iterations = ob.tissue_tessellate.iterations | ||
|  |     operator.bool_advanced = ob.tissue_tessellate.bool_advanced | ||
|  |     operator.normals_mode = ob.tissue_tessellate.normals_mode | ||
|  |     operator.bool_combine = ob.tissue_tessellate.bool_combine | ||
|  |     operator.bool_multi_components = ob.tissue_tessellate.bool_multi_components | ||
|  |     operator.combine_mode = ob.tissue_tessellate.combine_mode | ||
|  |     operator.bounds_x = ob.tissue_tessellate.bounds_x | ||
|  |     operator.bounds_y = ob.tissue_tessellate.bounds_y | ||
|  |     operator.cap_faces = ob.tissue_tessellate.cap_faces | ||
|  |     operator.close_mesh = ob.tissue_tessellate.close_mesh | ||
|  |     operator.bridge_cuts = ob.tissue_tessellate.bridge_cuts | ||
|  |     operator.bridge_smoothness = ob.tissue_tessellate.bridge_smoothness | ||
|  |     operator.cap_material_index = ob.tissue_tessellate.cap_material_index | ||
|  |     operator.patch_subs = ob.tissue_tessellate.patch_subs | ||
|  |     operator.frame_boundary = ob.tissue_tessellate.frame_boundary | ||
|  |     operator.fill_frame = ob.tissue_tessellate.fill_frame | ||
|  |     operator.frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat | ||
|  |     operator.fill_frame_mat = ob.tissue_tessellate.fill_frame_mat | ||
|  |     operator.frame_thickness = ob.tissue_tessellate.frame_thickness | ||
|  |     operator.frame_mode = ob.tissue_tessellate.frame_mode | ||
|  |     return ob | ||
|  | 
 | ||
|  | def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode, | ||
|  |                scale_mode, rotation_mode, rotation_shift, rand_seed, bool_vertex_group, | ||
|  |                bool_selection, bool_shapekeys, bool_material_id, material_id, | ||
|  |                normals_mode, bounds_x, bounds_y): | ||
|  |     random.seed(rand_seed) | ||
|  | 
 | ||
|  |     if normals_mode == 'CUSTOM': | ||
|  |         if _ob0.data.shape_keys != None: | ||
|  |             ob0_sk = convert_object_to_mesh(_ob0) | ||
|  |             me0_sk = ob0_sk.data | ||
|  |             key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks] | ||
|  |             for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0 | ||
|  |         else: normals_mode = 'VERTS' | ||
|  | 
 | ||
|  |     ob0 = convert_object_to_mesh(_ob0) | ||
|  |     me0 = ob0.data | ||
|  | 
 | ||
|  |     # base normals | ||
|  |     normals0 = [] | ||
|  |     if normals_mode == 'CUSTOM': | ||
|  |         for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val | ||
|  |         for v0, v1 in zip(ob0.data.vertices, me0_sk.vertices): | ||
|  |             normals0.append(v1.co - v0.co) | ||
|  |         bpy.data.objects.remove(ob0_sk) | ||
|  |     else: | ||
|  |         ob0.data.update() | ||
|  |         normals0 = [v.normal for v in ob0.data.vertices] | ||
|  | 
 | ||
|  | #    ob0 = convert_object_to_mesh(_ob0) | ||
|  |     ob0.name = _ob0.name + "_apply_mod" | ||
|  |     me0 = _ob0.data | ||
|  | 
 | ||
|  |     # Check if zero faces are selected | ||
|  |     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: | ||
|  |             bpy.data.meshes.remove(ob0.data) | ||
|  |             #bpy.data.objects.remove(ob0) | ||
|  |             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 | ||
|  |         if not visible: continue | ||
|  |         #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: | ||
|  |             bpy.data.meshes.remove(ob0.data) | ||
|  |             #bpy.data.meshes.remove(me0) | ||
|  |             return "modifiers_error" | ||
|  | 
 | ||
|  |     before = _ob0.copy() | ||
|  |     before.name = _ob0.name + "_before_subs" | ||
|  |     bpy.context.collection.objects.link(before) | ||
|  |     #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() | ||
|  |     before_bm.edges.ensure_lookup_table() | ||
|  |     before_bm.verts.ensure_lookup_table() | ||
|  | 
 | ||
|  |     error = "" | ||
|  |     for f in before_bm.faces: | ||
|  |         if len(f.loops) != 4: | ||
|  |             error = "topology_error" | ||
|  |             break | ||
|  |     for e in before_bm.edges: | ||
|  |         if len(e.link_faces) == 0: | ||
|  |             error = "wires_error" | ||
|  |             break | ||
|  |     for v in before_bm.verts: | ||
|  |         if len(v.link_faces) == 0: | ||
|  |             error = "verts_error" | ||
|  |             break | ||
|  |     if error != "": | ||
|  |         bpy.data.meshes.remove(ob0.data) | ||
|  |         #bpy.data.meshes.remove(me0) | ||
|  |         bpy.data.meshes.remove(before_subsurf) | ||
|  |         bpy.data.objects.remove(before) | ||
|  |         return error | ||
|  | 
 | ||
|  |     me0 = ob0.data | ||
|  |     verts0 = me0.vertices   # Collect generator vertices | ||
|  | 
 | ||
|  |     if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False | ||
|  | 
 | ||
|  |     # set Shape Keys to zero | ||
|  |     if bool_shapekeys or not com_modifiers: | ||
|  |         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': | ||
|  |         ob1.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) | ||
|  | 
 | ||
|  |     # Create empty lists | ||
|  |     new_verts = [] | ||
|  |     new_edges = [] | ||
|  |     new_faces = [] | ||
|  |     new_verts_np = np.array(()) | ||
|  | 
 | ||
|  |     # Component bounding box | ||
|  |     min_c = Vector((0, 0, 0)) | ||
|  |     max_c = Vector((0, 0, 0)) | ||
|  |     first = True | ||
|  |     for v in me1.vertices: | ||
|  |         vert = v.co | ||
|  |         if vert[0] < min_c[0] or first: | ||
|  |             min_c[0] = vert[0] | ||
|  |         if vert[1] < min_c[1] or first: | ||
|  |             min_c[1] = vert[1] | ||
|  |         if vert[2] < min_c[2] or first: | ||
|  |             min_c[2] = vert[2] | ||
|  |         if vert[0] > max_c[0] or first: | ||
|  |             max_c[0] = vert[0] | ||
|  |         if vert[1] > max_c[1] or first: | ||
|  |             max_c[1] = vert[1] | ||
|  |         if vert[2] > max_c[2] or first: | ||
|  |             max_c[2] = vert[2] | ||
|  |         first = False | ||
|  |     bb = max_c - min_c | ||
|  | 
 | ||
|  |     # adaptive XY | ||
|  |     verts1 = [] | ||
|  |     for v in me1.vertices: | ||
|  |         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] / bb[2] if bb[2] != 0 else 0 | ||
|  |             vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale | ||
|  |         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 = 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] | ||
|  |     n_verts1 = len(verts1) | ||
|  | 
 | ||
|  |     patch_faces = 4**levels | ||
|  |     sides = int(sqrt(patch_faces)) | ||
|  |     step = 1/sides | ||
|  |     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': | ||
|  |         com_area = bb[0]*bb[1] | ||
|  |         if mode != 'BOUNDS' or com_area == 0: com_area = 1 | ||
|  |         #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() | ||
|  |             area = area/len(faces)*patch_faces/com_area | ||
|  |             #area*=mult* | ||
|  |             verts_area.append(sqrt(area)*bb[2]) | ||
|  | 
 | ||
|  |     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)] | ||
|  | 
 | ||
|  |     # find relative UV component's vertices | ||
|  |     verts1_uv_quads = [0]*len(verts1) | ||
|  |     verts1_uv = [0]*len(verts1) | ||
|  |     for i, vert in enumerate(verts1): | ||
|  |         # 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 | ||
|  |         verts1_uv_quads[i] = (u,v,u1,v1) | ||
|  |         # factor coordinates | ||
|  |         fu = (vert[0]-u*step)/step | ||
|  |         fv = (vert[1]-v*step)/step | ||
|  |         fw = vert.z | ||
|  |         # interpolate Z scaling factor | ||
|  |         verts1_uv[i] = Vector((fu,fv,fw)) | ||
|  | 
 | ||
|  |     sk_uv_quads = [] | ||
|  |     sk_uv = [] | ||
|  |     if bool_shapekeys: | ||
|  |         for sk in ob1.data.shape_keys.key_blocks: | ||
|  |             source = sk.data | ||
|  |             _sk_uv_quads = [0]*len(verts1) | ||
|  |             _sk_uv = [0]*len(verts1) | ||
|  |             for i, sk_v in enumerate(source): | ||
|  |                 if mode == 'BOUNDS': | ||
|  |                     sk_vert = sk_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] / bb[2] if bb[2] != 0 else sk_vert[2]) | ||
|  |                     sk_vert[2] = (sk_vert[2] - 0.5 + offset * 0.5) * zscale | ||
|  |                 elif mode == 'LOCAL': | ||
|  |                     sk_vert = sk_v.co | ||
|  |                     sk_vert[2] *= zscale | ||
|  |                 elif mode == 'GLOBAL': | ||
|  |                     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 | ||
|  |                 _sk_uv_quads[i] = (u,v,u1,v1) | ||
|  |                 # factor coordinates | ||
|  |                 fu = (sk_vert[0]-u*step)/step | ||
|  |                 fv = (sk_vert[1]-v*step)/step | ||
|  |                 fw = sk_vert.z | ||
|  |                 _sk_uv[i] = Vector((fu,fv,fw)) | ||
|  |             sk_uv_quads.append(_sk_uv_quads) | ||
|  |             sk_uv.append(_sk_uv) | ||
|  | 
 | ||
|  |     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' or rotation_shift != 0: | ||
|  |             if rotation_mode == 'RANDOM': rot = random.randint(0, 3) | ||
|  |             else: rot = rotation_shift%4 | ||
|  |             if rot == 1: | ||
|  |                 verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)] | ||
|  |             elif rot == 2: | ||
|  |                 verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)] | ||
|  |             elif rot == 3: | ||
|  |                 verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)] | ||
|  | 
 | ||
|  |         # UV rotation | ||
|  |         if 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)] | ||
|  | 
 | ||
|  |         if True: | ||
|  |             verts_xyz = np.array([[v.co for v in _verts] for _verts in verts]) | ||
|  |             #verts_norm = np.array([[v.normal for v in _verts] for _verts in verts]) | ||
|  |             verts_norm = np.array([[normals0[v.index] for v in _verts] for _verts in verts]) | ||
|  |             if normals_mode == 'FACES': | ||
|  |                 verts_norm = np.mean(verts_norm, axis=(0,1)) | ||
|  |                 verts_norm = np.expand_dims(verts_norm, axis=0) | ||
|  |                 verts_norm = np.repeat(verts_norm,len(verts),axis=0) | ||
|  |                 verts_norm = np.expand_dims(verts_norm, axis=0) | ||
|  |                 verts_norm = np.repeat(verts_norm,len(verts),axis=0) | ||
|  |             np_verts1_uv = np.array(verts1_uv) | ||
|  |             verts1_uv_quads = np.array(verts1_uv_quads) | ||
|  |             u = verts1_uv_quads[:,0] | ||
|  |             v = verts1_uv_quads[:,1] | ||
|  |             u1 = verts1_uv_quads[:,2] | ||
|  |             v1 = verts1_uv_quads[:,3] | ||
|  |             v00 = verts_xyz[u,v] | ||
|  |             v10 = verts_xyz[u1,v] | ||
|  |             v01 = verts_xyz[u,v1] | ||
|  |             v11 = verts_xyz[u1,v1] | ||
|  |             n00 = verts_norm[u,v] | ||
|  |             n10 = verts_norm[u1,v] | ||
|  |             n01 = verts_norm[u,v1] | ||
|  |             n11 = verts_norm[u1,v1] | ||
|  |             vx = np_verts1_uv[:,0].reshape((n_verts1,1)) | ||
|  |             vy = np_verts1_uv[:,1].reshape((n_verts1,1)) | ||
|  |             vz = np_verts1_uv[:,2].reshape((n_verts1,1)) | ||
|  |             co2 = np_lerp2(v00,v10,v01,v11,vx,vy) | ||
|  |             n2 = np_lerp2(n00,n10,n01,n11,vx,vy) | ||
|  |             if scale_mode == 'ADAPTIVE': | ||
|  |                 areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts]) | ||
|  |                 a00 = areas[u,v].reshape((n_verts1,1)) | ||
|  |                 a10 = areas[u1,v].reshape((n_verts1,1)) | ||
|  |                 a01 = areas[u,v1].reshape((n_verts1,1)) | ||
|  |                 a11 = areas[u1,v1].reshape((n_verts1,1)) | ||
|  |                 # remapped z scale | ||
|  |                 a2 = np_lerp2(a00,a10,a01,a11,vx,vy) | ||
|  |                 co3 = co2 + n2 * vz * a2 | ||
|  |             else: | ||
|  |                 co3 = co2 + n2 * vz | ||
|  |             coordinates = co3.flatten().tolist() | ||
|  |             new_patch.data.vertices.foreach_set('co',coordinates) | ||
|  | 
 | ||
|  |             # vertex groups | ||
|  |             if bool_vertex_group: | ||
|  |                 for _weight, vg in zip(weight, new_patch.vertex_groups): | ||
|  |                     np_weight = np.array([[_weight[v.index] for v in verts_v] for verts_v in verts]) | ||
|  |                     w00 = np_weight[u,v].reshape((n_verts1,1)) | ||
|  |                     w10 = np_weight[u1,v].reshape((n_verts1,1)) | ||
|  |                     w01 = np_weight[u,v1].reshape((n_verts1,1)) | ||
|  |                     w11 = np_weight[u1,v1].reshape((n_verts1,1)) | ||
|  |                     # remapped z scale | ||
|  |                     w2 = np_lerp2(w00,w10,w01,w11,vx,vy) | ||
|  |                     for vert_id in range(n_verts1): | ||
|  |                         vg.add([vert_id], w2[vert_id], "ADD") | ||
|  | 
 | ||
|  |             if bool_shapekeys: | ||
|  |                 for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks): | ||
|  |                     np_verts1_uv = np.array(sk_uv[i_sk]) | ||
|  |                     np_sk_uv_quads = np.array(sk_uv_quads[i_sk]) | ||
|  |                     u = np_sk_uv_quads[:,0] | ||
|  |                     v = np_sk_uv_quads[:,1] | ||
|  |                     u1 = np_sk_uv_quads[:,2] | ||
|  |                     v1 = np_sk_uv_quads[:,3] | ||
|  |                     v00 = verts_xyz[u,v] | ||
|  |                     v10 = verts_xyz[u1,v] | ||
|  |                     v01 = verts_xyz[u,v1] | ||
|  |                     v11 = verts_xyz[u1,v1] | ||
|  |                     vx = np_verts1_uv[:,0].reshape((n_verts1,1)) | ||
|  |                     vy = np_verts1_uv[:,1].reshape((n_verts1,1)) | ||
|  |                     vz = np_verts1_uv[:,2].reshape((n_verts1,1)) | ||
|  |                     co2 = np_lerp2(v00,v10,v01,v11,vx,vy) | ||
|  |                     n2 = np_lerp2(n00,n10,n01,n11,vx,vy) | ||
|  |                     if scale_mode == 'ADAPTIVE': | ||
|  |                         areas = np.array([[verts_area[v.index] for v in verts_v] for verts_v in verts]) | ||
|  |                         a00 = areas[u,v].reshape((n_verts1,1)) | ||
|  |                         a10 = areas[u1,v].reshape((n_verts1,1)) | ||
|  |                         a01 = areas[u,v1].reshape((n_verts1,1)) | ||
|  |                         a11 = areas[u1,v1].reshape((n_verts1,1)) | ||
|  |                         # remapped z scale | ||
|  |                         a2 = np_lerp2(a00,a10,a01,a11,vx,vy) | ||
|  |                         co3 = co2 + n2 * vz * a2 | ||
|  |                     else: | ||
|  |                         co3 = co2 + n2 * vz | ||
|  |                     coordinates = co3.flatten().tolist() | ||
|  |                     new_patch.data.shape_keys.key_blocks[sk.name].data.foreach_set('co', coordinates) | ||
|  |                     #new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].co = sk_co | ||
|  |         else: | ||
|  |             for _fvec, uv_quad, patch_vert in zip(verts1_uv, verts1_uv_quads, new_patch.data.vertices): | ||
|  |                 u = uv_quad[0] | ||
|  |                 v = uv_quad[1] | ||
|  |                 u1 = uv_quad[2] | ||
|  |                 v1 = uv_quad[3] | ||
|  |                 v00 = verts[u][v] | ||
|  |                 v10 = verts[u1][v] | ||
|  |                 v01 = verts[u][v1] | ||
|  |                 v11 = verts[u1][v1] | ||
|  |                 # interpolate Z scaling factor | ||
|  |                 fvec = _fvec.copy() | ||
|  |                 if scale_mode == 'ADAPTIVE': | ||
|  |                     a00 = verts_area[v00.index] | ||
|  |                     a10 = verts_area[v10.index] | ||
|  |                     a01 = verts_area[v01.index] | ||
|  |                     a11 = verts_area[v11.index] | ||
|  |                     fvec[2]*=lerp2(a00,a10,a01,a11,fvec) | ||
|  |                 # 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, fvec) | ||
|  |                         vg.add([patch_vert.index], wuv, "ADD") | ||
|  | 
 | ||
|  |             if bool_shapekeys: | ||
|  |                 for i_sk, sk in enumerate(ob1.data.shape_keys.key_blocks): | ||
|  |                     for i_vert, _fvec, _sk_uv_quad in zip(range(len(new_patch.data.vertices)), sk_uv[i_sk], sk_uv_quads[i_sk]): | ||
|  |                         u = _sk_uv_quad[0] | ||
|  |                         v = _sk_uv_quad[1] | ||
|  |                         u1 = _sk_uv_quad[2] | ||
|  |                         v1 = _sk_uv_quad[3] | ||
|  |                         v00 = verts[u][v] | ||
|  |                         v10 = verts[u1][v] | ||
|  |                         v01 = verts[u][v1] | ||
|  |                         v11 = verts[u1][v1] | ||
|  | 
 | ||
|  |                         fvec = _fvec.copy() | ||
|  |                         if scale_mode == 'ADAPTIVE': | ||
|  |                             a00 = verts_area[v00.index] | ||
|  |                             a10 = verts_area[v10.index] | ||
|  |                             a01 = verts_area[v01.index] | ||
|  |                             a11 = verts_area[v11.index] | ||
|  |                             fvec[2]*=lerp2(a00, a10, a01, a11, fvec) | ||
|  |                         sk_co = lerp3(v00, v10, v01, v11, fvec) | ||
|  | 
 | ||
|  |                         new_patch.data.shape_keys.key_blocks[sk.name].data[i_vert].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 | ||
|  |     else: | ||
|  |         try: | ||
|  |             for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): | ||
|  |                 sk.value = val | ||
|  |         except: pass | ||
|  | 
 | ||
|  |     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, rotation_shift, rotation_direction, 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 | ||
|  | 
 | ||
|  |     if normals_mode == 'CUSTOM': | ||
|  |         if _ob0.data.shape_keys != None: | ||
|  |             ob0_sk = convert_object_to_mesh(_ob0, True, True) | ||
|  |             me0_sk = ob0_sk.data | ||
|  |             key_values0 = [sk.value for sk in _ob0.data.shape_keys.key_blocks] | ||
|  |             for sk in _ob0.data.shape_keys.key_blocks: sk.value = 0 | ||
|  |         else: normals_mode == 'VERTS' | ||
|  | 
 | ||
|  |     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 normals | ||
|  |     normals0 = [] | ||
|  |     if normals_mode == 'CUSTOM' and _ob0.data.shape_keys != None: | ||
|  |         for sk, val in zip(_ob0.data.shape_keys.key_blocks, key_values0): sk.value = val | ||
|  |         for v0, v1 in zip(me0.vertices, me0_sk.vertices): | ||
|  |             normals0.append(v1.co - v0.co) | ||
|  |         bpy.data.objects.remove(ob0_sk) | ||
|  |     else: | ||
|  |         me0.update() | ||
|  |         normals0 = [v.normal for v in me0.vertices] | ||
|  | 
 | ||
|  |     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: | ||
|  |         bpy.data.objects.remove(ob0) | ||
|  |         bpy.data.objects.remove(ob1) | ||
|  |         bpy.data.meshes.remove(me1) | ||
|  |         bpy.data.meshes.remove(me0) | ||
|  |         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) | ||
|  |         ob1.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 | ||
|  |     if len(me1.vertices) == 0: | ||
|  |         bpy.data.objects.remove(ob0) | ||
|  |         bpy.data.objects.remove(ob1) | ||
|  |         return 0 | ||
|  | 
 | ||
|  |     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]) / bb[2] if bb[2] != 0 else 0 | ||
|  |         vz = (vz - 0.5 + offset * 0.5) * zscale | ||
|  |         #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 = 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 | ||
|  |     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 | ||
|  | 
 | ||
|  |             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 | ||
|  | 
 | ||
|  |             shapekeys = [] | ||
|  |             for v in source: | ||
|  |                 if mode == 'BOUNDS': | ||
|  |                     vert = 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] / bb[2] if bb[2] != 0 else vert[2]) | ||
|  |                     vert[2] = (vert[2] - 0.5 + offset * 0.5) * zscale | ||
|  |                 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 = 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.append(key1[:, 0]) | ||
|  |             vy_key.append(key1[:, 1]) | ||
|  |             vz_key.append(key1[:, 2]) | ||
|  |             #sk_np.append([]) | ||
|  | 
 | ||
|  |     # All vertex group | ||
|  |     if bool_vertex_group or rotation_mode == 'WEIGHT': | ||
|  |         try: | ||
|  |             weight = [] | ||
|  |             for vg in ob0.vertex_groups: | ||
|  |                 _weight = [] | ||
|  |                 for i,v in enumerate(me0.vertices): | ||
|  |                     try: | ||
|  |                         _weight.append(vg.weight(i)) | ||
|  |                     except: | ||
|  |                         _weight.append(0) | ||
|  |                 weight.append(_weight) | ||
|  |         except: | ||
|  |             bool_vertex_group = False | ||
|  | 
 | ||
|  |     # Adaptive Z | ||
|  |     if scale_mode == 'ADAPTIVE': | ||
|  |         com_area = bb[0]*bb[1] | ||
|  |         if mode != 'BOUNDS' or com_area == 0: com_area = 1 | ||
|  |         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) # average area | ||
|  |                 area/=com_area | ||
|  |                 verts_area.append(sqrt(area)*bb[2]) | ||
|  |                 #verts_area.append(area) | ||
|  |             except: | ||
|  |                 verts_area.append(1) | ||
|  | 
 | ||
|  |     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 | ||
|  |     n_vg = len(ob0.vertex_groups) | ||
|  |     _w0 = [[0]*n_faces for i in range(n_vg)] | ||
|  |     np_faces = [np.array(p) for p in fs1] | ||
|  |     new_faces = [0]*n_faces*n_faces1 | ||
|  |     face1_count = 0 | ||
|  | 
 | ||
|  |     for j, p in enumerate(base_polygons): | ||
|  | 
 | ||
|  |         bool_correct = True | ||
|  |         if rotation_mode in ['UV', 'WEIGHT'] and ob0.type != 'MESH': | ||
|  |             rotation_mode = 'DEFAULT' | ||
|  | 
 | ||
|  |         ordered = p.vertices | ||
|  | 
 | ||
|  |         # 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]) | ||
|  |             if scale_mode == 'ADAPTIVE': | ||
|  |                 verts_area0 = np.array([verts_area[i] for i in shifted_vertices]) | ||
|  |             ordered = shifted_vertices | ||
|  | 
 | ||
|  |         # UV rotation | ||
|  |         elif rotation_mode == 'UV': | ||
|  |             if len(ob0.data.uv_layers) > 0: | ||
|  |                 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) | ||
|  |                 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] | ||
|  |                 ordered = vertUV | ||
|  |                 count += len(p.vertices) | ||
|  | 
 | ||
|  |         # Weight Rotation | ||
|  |         elif rotation_mode == 'WEIGHT': | ||
|  |             if len(weight) > 0: | ||
|  |                 active_weight = weight[ob0.vertex_groups.active_index] | ||
|  |                 i = p.index | ||
|  |                 face_weights = [active_weight[v] for v in p.vertices] | ||
|  |                 face_weights*=2 | ||
|  |                 if rotation_direction == 'DIAG': | ||
|  |                     differential = [face_weights[ii]-face_weights[ii+2] for ii in range(4)] | ||
|  |                 else: | ||
|  |                     differential = [face_weights[ii]+face_weights[ii+1]-face_weights[ii+2]- face_weights[ii+3] for ii in range(4)] | ||
|  |                 starting = differential.index(max(differential)) | ||
|  | 
 | ||
|  |                 ordered = p.vertices[starting:] + p.vertices[:starting] | ||
|  | 
 | ||
|  |         if rotation_mode != 'RANDOM': | ||
|  |             ordered = np.roll(np.array(ordered),rotation_shift) | ||
|  |         ordered = np.array((ordered[0], ordered[1], ordered[2], ordered[-1])) | ||
|  | 
 | ||
|  |         # assign vertices and values | ||
|  |         vs0 = np.array([verts0[i].co for i in ordered]) | ||
|  |         #nvs0 = np.array([verts0[i].normal for i in ordered]) | ||
|  |         nvs0 = np.array([normals0[i] for i in ordered]) | ||
|  |         if scale_mode == 'ADAPTIVE': | ||
|  |             np_verts_area = np.array([verts_area[i] for i in ordered]) | ||
|  |             _sz[j] = np_verts_area | ||
|  |         # Vertex weight | ||
|  |         if bool_vertex_group: | ||
|  |             ws0 = [] | ||
|  |             for w in weight: | ||
|  |                 _ws0 = [] | ||
|  |                 for i in ordered: | ||
|  |                     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 != 'FACES': | ||
|  |             _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1]) | ||
|  | 
 | ||
|  |         if bool_vertex_group: | ||
|  |             for i_vg, ws0_face in enumerate(ws0): | ||
|  |                 _w0[i_vg][j] = (ws0_face[0], ws0_face[1], ws0_face[2], ws0_face[-1]) | ||
|  | 
 | ||
|  |         for p in fs1: | ||
|  |             new_faces[face1_count] = [i + n_verts1 * j for i in p] | ||
|  |             face1_count += 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 | ||
|  |     v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy) | ||
|  | 
 | ||
|  |     # remapped vertex normal | ||
|  |     if normals_mode != 'FACES': | ||
|  |         _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)) | ||
|  |         nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy) | ||
|  |     else: | ||
|  |         nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) | ||
|  | 
 | ||
|  |     # interpolate vertex groups | ||
|  |     if bool_vertex_group: | ||
|  |         w = np.array(_w0) | ||
|  |         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 | ||
|  |         w = np_lerp2(w_0, w_1, w_3, w_2, vx, vy) | ||
|  |         w = w.reshape((n_vg, n_faces*n_verts1)) | ||
|  | 
 | ||
|  |     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 | ||
|  |         sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, 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[i]) | ||
|  |             vy = np.array(vy_key[i]) | ||
|  |             vz = np.array(vz_key[i]) | ||
|  | 
 | ||
|  |             # remapped vertex coordinates | ||
|  |             v2 = np_lerp2(_vs0_0, _vs0_1, _vs0_3, _vs0_2, vx, vy) | ||
|  | 
 | ||
|  |             # remapped vertex normal | ||
|  |             if normals_mode != 'FACES': | ||
|  |                 nv2 = np_lerp2(_nvs0_0, _nvs0_1, _nvs0_3, _nvs0_2, vx, vy) | ||
|  |             else: | ||
|  |                 nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) | ||
|  | 
 | ||
|  |             if scale_mode == 'ADAPTIVE': | ||
|  |                 # remapped z scale | ||
|  |                 sz2 = np_lerp2(_sz_0, _sz_1, _sz_3, _sz_2, vx, 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: | ||
|  |         #bpy.data.objects.remove(ob1) | ||
|  |         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.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: | ||
|  |         for vg in ob0.vertex_groups: | ||
|  |             new_ob.vertex_groups.new(name=vg.name) | ||
|  |             for i, vertex_weight in enumerate(w[vg.index]): | ||
|  |                 new_ob.vertex_groups[vg.name].add([i], vertex_weight,"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, from_mix=False) | ||
|  |             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) | ||
|  | 
 | ||
|  |     # 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) | ||
|  | 
 | ||
|  |     # MATERIALS | ||
|  |     for slot in ob1.material_slots: new_ob.data.materials.append(slot.material) | ||
|  | 
 | ||
|  |     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() ### | ||
|  | 
 | ||
|  |     try: | ||
|  |         bpy.data.objects.remove(new_ob1) | ||
|  |     except: pass | ||
|  | 
 | ||
|  |     bpy.data.objects.remove(ob0) | ||
|  |     bpy.data.meshes.remove(me0) | ||
|  |     bpy.data.objects.remove(ob1) | ||
|  |     bpy.data.meshes.remove(me1) | ||
|  | 
 | ||
|  |     return new_ob | ||
|  | 
 | ||
|  | 
 | ||
|  | class tissue_tessellate(Operator): | ||
|  |     bl_idname = "object.tissue_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'} | ||
|  | 
 | ||
|  | 
 | ||
|  |     bool_hold : BoolProperty( | ||
|  |             name="Hold", | ||
|  |             description="Wait...", | ||
|  |             default=False | ||
|  |     ) | ||
|  |     bool_lock : BoolProperty( | ||
|  |             name="Lock", | ||
|  |             description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.", | ||
|  |             default=False | ||
|  |     ) | ||
|  |     bool_dependencies : BoolProperty( | ||
|  |             name="Update Dependencies", | ||
|  |             description="Automatically updates base and components as well, if results of other tessellations", | ||
|  |             default=False | ||
|  |     ) | ||
|  |     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", "Uniform thickness"), | ||
|  |                 ('ADAPTIVE', "Relative", "Preserve component's proportions") | ||
|  |                 ), | ||
|  |             default='ADAPTIVE', | ||
|  |             name="Z-Scale according to faces size" | ||
|  |             ) | ||
|  |     offset : FloatProperty( | ||
|  |             name="Surface Offset", | ||
|  |             default=1, | ||
|  |             min=-1, max=1, | ||
|  |             soft_min=-1, | ||
|  |             soft_max=1, | ||
|  |             description="Surface offset" | ||
|  |             ) | ||
|  |     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", "Random faces rotation"), | ||
|  |                    ('UV', "Active UV", "Face rotation is based on UV coordinates"), | ||
|  |                    ('WEIGHT', "Active Weight", "Rotate according to Vertex Group gradient"), | ||
|  |                    ('DEFAULT', "Default", "Default rotation")), | ||
|  |             default='DEFAULT', | ||
|  |             name="Component Rotation" | ||
|  |             ) | ||
|  |     rotation_direction : EnumProperty( | ||
|  |             items=(('ORTHO', "Orthogonal", "Component main directions in XY"), | ||
|  |                    ('DIAG', "Diagonal", "Component main direction aligned with diagonal")), | ||
|  |             default='ORTHO', | ||
|  |             name="Direction" | ||
|  |             ) | ||
|  |     rotation_shift : IntProperty( | ||
|  |             name="Shift", | ||
|  |             default=0, | ||
|  |             soft_min=0, | ||
|  |             soft_max=3, | ||
|  |             description="Shift components rotation" | ||
|  |             ) | ||
|  |     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'), | ||
|  |                 ('FRAME', 'Frame', 'Essellation along the edges of each face')), | ||
|  |             default='QUAD', | ||
|  |             name="Fill Mode" | ||
|  |             ) | ||
|  |     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 and Shape Keys to the base object" | ||
|  |             ) | ||
|  |     com_modifiers : BoolProperty( | ||
|  |             name="Component Modifiers", | ||
|  |             default=False, | ||
|  |             description="Apply Modifiers and Shape Keys to the 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" | ||
|  |             ) | ||
|  |     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 Groups", | ||
|  |             default=False, | ||
|  |             description="Transfer all Vertex Groups from Base object" | ||
|  |             ) | ||
|  |     bool_selection : BoolProperty( | ||
|  |             name="On selected Faces", | ||
|  |             default=False, | ||
|  |             description="Create Tessellation only on selected faces" | ||
|  |             ) | ||
|  |     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" | ||
|  |             ) | ||
|  |     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', 'Normals', 'Consistent direction based on vertices normal'), | ||
|  |                 ('FACES', 'Individual Faces', 'Based on individual faces normal'), | ||
|  |                 ('CUSTOM', 'Custom', "According to Base object's shape keys")), | ||
|  |             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", | ||
|  |             ) | ||
|  |     close_mesh : EnumProperty( | ||
|  |             items=( | ||
|  |                 ('NONE', 'None', 'Keep the mesh open'), | ||
|  |                 ('CAP', 'Cap Holes', 'Automatically cap open loops'), | ||
|  |                 ('BRIDGE', 'Bridge Loops', 'Automatically bridge loop pairs')), | ||
|  |             default='NONE', | ||
|  |             name="Close Mesh" | ||
|  |             ) | ||
|  |     cap_faces : BoolProperty( | ||
|  |             name="Cap Holes", | ||
|  |             default=False, | ||
|  |             description="Cap open edges loops" | ||
|  |             ) | ||
|  |     frame_boundary : BoolProperty( | ||
|  |             name="Frame Boundary", | ||
|  |             default=False, | ||
|  |             description="Support face boundaries" | ||
|  |             ) | ||
|  |     fill_frame : BoolProperty( | ||
|  |             name="Fill Frame", | ||
|  |             default=False, | ||
|  |             description="Fill inner faces with Fan tessellation" | ||
|  |             ) | ||
|  |     frame_boundary_mat : IntProperty( | ||
|  |             name="Material Offset", | ||
|  |             default=0, | ||
|  |             description="Material Offset for boundaries" | ||
|  |             ) | ||
|  |     fill_frame_mat : IntProperty( | ||
|  |             name="Material Offset", | ||
|  |             default=0, | ||
|  |             description="Material Offset for inner faces" | ||
|  |             ) | ||
|  |     open_edges_crease : FloatProperty( | ||
|  |             name="Open Edges Crease", | ||
|  |             default=0, | ||
|  |             min=0, | ||
|  |             max=1, | ||
|  |             description="Automatically set crease for open edges" | ||
|  |             ) | ||
|  |     bridge_smoothness : FloatProperty( | ||
|  |             name="Smoothness", | ||
|  |             default=1, | ||
|  |             min=0, | ||
|  |             max=1, | ||
|  |             description="Bridge Smoothness" | ||
|  |             ) | ||
|  |     frame_thickness : FloatProperty( | ||
|  |             name="Frame Thickness", | ||
|  |             default=0.2, | ||
|  |             min=0, | ||
|  |             soft_max=2, | ||
|  |             description="Frame Thickness" | ||
|  |             ) | ||
|  |     frame_mode : EnumProperty( | ||
|  |             items=( | ||
|  |                 ('CONSTANT', 'Constant', 'Even thickness'), | ||
|  |                 ('RELATIVE', 'Relative', 'Frame offset depends on face areas')), | ||
|  |             default='CONSTANT', | ||
|  |             name="Offset" | ||
|  |             ) | ||
|  |     bridge_cuts : IntProperty( | ||
|  |             name="Cuts", | ||
|  |             default=0, | ||
|  |             min=0, | ||
|  |             max=20, | ||
|  |             description="Bridge Cuts" | ||
|  |             ) | ||
|  |     cap_material_index : IntProperty( | ||
|  |             name="Material", | ||
|  |             default=0, | ||
|  |             min=0, | ||
|  |             description="Material index for the cap/bridge faces" | ||
|  |             ) | ||
|  |     patch_subs : IntProperty( | ||
|  |             name="Patch Subdivisions", | ||
|  |             default=1, | ||
|  |             min=0, | ||
|  |             description="Subdivisions levels for Patch tessellation after the first iteration" | ||
|  |             ) | ||
|  |     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 | ||
|  |                 if self.working_on == '': | ||
|  |                     load_parameters(self,sel[0]) | ||
|  |                     self.working_on = sel[0].name | ||
|  |                 bool_working = True | ||
|  |                 bool_allowed = True | ||
|  |             except: | ||
|  |                 pass | ||
|  | 
 | ||
|  |         if len(sel) == 2: | ||
|  |             bool_allowed = True | ||
|  |             for o in sel: | ||
|  |                 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 object.") | ||
|  |         elif not bool_allowed and not bool_working: | ||
|  |             layout = self.layout | ||
|  |             layout.label(icon='INFO') | ||
|  |             layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed") | ||
|  |         else: | ||
|  |             if ob0 == ob1 == None: | ||
|  |                 ob0 = bpy.context.active_object | ||
|  |                 self.generator = ob0.name | ||
|  |                 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 = "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) | ||
|  | 
 | ||
|  |             # Base Modifiers | ||
|  |             row = col.row(align=True) | ||
|  |             col2 = row.column(align=True) | ||
|  |             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 | ||
|  | 
 | ||
|  |             # 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: | ||
|  |                 if not (component.modifiers or component.data.shape_keys): | ||
|  |                     col3.enabled = False | ||
|  |                     self.com_modifiers = False | ||
|  |             except: | ||
|  |                 col3.enabled = False | ||
|  |                 self.com_modifiers = False | ||
|  |             col.separator() | ||
|  |             # Fill and Rotation | ||
|  |             row = col.row(align=True) | ||
|  |             row.label(text="Fill Mode:") | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop( | ||
|  |                 self, "fill_mode", icon='NONE', expand=True, | ||
|  |                 slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                 full_event=False, emboss=True, index=-1) | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(self, "bool_smooth") | ||
|  | 
 | ||
|  |             # frame settings | ||
|  |             if self.fill_mode == 'FRAME': | ||
|  |                 col.separator() | ||
|  |                 col.label(text="Frame Settings:") | ||
|  |                 row = col.row(align=True) | ||
|  |                 row.prop(self, "frame_mode", expand=True) | ||
|  |                 col.prop(self, "frame_thickness", text='Thickness', icon='NONE') | ||
|  |                 col.separator() | ||
|  |                 row = col.row(align=True) | ||
|  |                 row.prop(self, "fill_frame", icon='NONE') | ||
|  |                 show_frame_mat = self.bool_multi_components or self.bool_material_id | ||
|  |                 if self.fill_frame and show_frame_mat: | ||
|  |                     row.prop(self, "fill_frame_mat", icon='NONE') | ||
|  |                 row = col.row(align=True) | ||
|  |                 row.prop(self, "frame_boundary", text='Boundary', icon='NONE') | ||
|  |                 if self.frame_boundary and show_frame_mat: | ||
|  |                     row.prop(self, "frame_boundary_mat", icon='NONE') | ||
|  | 
 | ||
|  |             if self.rotation_mode == 'UV': | ||
|  |                 uv_error = False | ||
|  | 
 | ||
|  |                 if ob0.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(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", | ||
|  |                               icon='INFO') | ||
|  | 
 | ||
|  |             # Component Z | ||
|  |             col.separator() | ||
|  |             col.label(text="Thickness:") | ||
|  |             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) | ||
|  |             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) | ||
|  | 
 | ||
|  |             # Component XY | ||
|  |             col.separator() | ||
|  |             row = col.row(align=True) | ||
|  |             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) | ||
|  | 
 | ||
|  | 
 | ||
|  |             # merge settings | ||
|  |             col = layout.column(align=True) | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(self, "merge") | ||
|  |             if self.merge: | ||
|  |                 row.prop(self, "merge_thres") | ||
|  |                 col.separator() | ||
|  |                 row = col.row(align=True) | ||
|  |                 col2 = row.column(align=True) | ||
|  |                 col2.label(text='Close Mesh:') | ||
|  |                 col2 = row.column(align=True) | ||
|  |                 col2.prop(self, "close_mesh",text='') | ||
|  |                 if self.close_mesh != 'NONE': | ||
|  |                     row = col.row(align=True) | ||
|  |                     row.prop(self, "open_edges_crease", text="Crease") | ||
|  |                     row.prop(self, "cap_material_index") | ||
|  |                     if self.close_mesh == 'BRIDGE': | ||
|  |                         row = col.row(align=True) | ||
|  |                         row.prop(self, "bridge_cuts") | ||
|  |                         row.prop(self, "bridge_smoothness") | ||
|  |                 row = col.row(align=True) | ||
|  |                 row.prop(self, "bool_dissolve_seams") | ||
|  | 
 | ||
|  |             # 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: | ||
|  |                 # rotation | ||
|  |                 layout.use_property_split = True | ||
|  |                 layout.use_property_decorate = False  # No animation. | ||
|  |                 col = layout.column(align=True) | ||
|  |                 col.prop(self, "rotation_mode", text='Rotation', icon='NONE', expand=False, | ||
|  |                          slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                          full_event=False, emboss=True, index=-1) | ||
|  |                 if self.rotation_mode == 'WEIGHT': | ||
|  |                     col.prop(self, "rotation_direction", expand=False, | ||
|  |                               slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                               full_event=False, emboss=True, index=-1) | ||
|  |                 if self.rotation_mode == 'RANDOM': | ||
|  |                     col.prop(self, "random_seed") | ||
|  |                 else: | ||
|  |                     col.prop(self, "rotation_shift") | ||
|  | 
 | ||
|  |                 if self.rotation_mode == 'UV': | ||
|  |                     uv_error = False | ||
|  |                     if self.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(self.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') | ||
|  |                 layout.use_property_split = False | ||
|  | 
 | ||
|  |                 # Direction | ||
|  |                 col = layout.column(align=True) | ||
|  |                 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' | ||
|  | 
 | ||
|  |                 allow_multi = False | ||
|  |                 allow_shapekeys = not self.com_modifiers | ||
|  |                 if self.com_modifiers: self.bool_shapekeys = False | ||
|  |                 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="Weight and Morphing:") | ||
|  |                 # vertex group + shape keys | ||
|  |                 row = col.row(align=True) | ||
|  |                 col2 = row.column(align=True) | ||
|  |                 col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX') | ||
|  |                 try: | ||
|  |                     if len(ob0.vertex_groups) == 0: | ||
|  |                         col2.enabled = False | ||
|  |                 except: | ||
|  |                     col2.enabled = 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') | ||
|  |                 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') | ||
|  |                 if self.iterations > 1 and self.fill_mode == 'PATCH': | ||
|  |                     col.separator() | ||
|  |                     row = col.row(align=True) | ||
|  |                     row.prop(self, 'patch_subs') | ||
|  |                 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.data.objects[self.generator] | ||
|  |             ob1 = bpy.data.objects[self.component] | ||
|  |         except: | ||
|  |             return {'CANCELLED'} | ||
|  | 
 | ||
|  |         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 ob1.type not in allowed_obj: | ||
|  |             message = "Component must be Mesh, Curve, Surface, Text or Meta object!" | ||
|  |             self.report({'ERROR'}, message) | ||
|  |             self.component = None | ||
|  | 
 | ||
|  |         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 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') | ||
|  | 
 | ||
|  |             #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) | ||
|  |             try: bpy.ops.object.tissue_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 invoke(self, context, event): | ||
|  |         return context.window_manager.invoke_props_dialog(self) | ||
|  | 
 | ||
|  | 
 | ||
|  | def update_dependencies(ob, objects): | ||
|  |     ob0 = ob.tissue_tessellate.generator | ||
|  |     ob1 = ob.tissue_tessellate.component | ||
|  |     deps = [ob0, ob1] | ||
|  |     for o in deps: | ||
|  |         if o.tissue_tessellate.bool_lock: continue | ||
|  |         o0 = o.tissue_tessellate.generator | ||
|  |         o1 = o.tissue_tessellate.component | ||
|  |         deps_deps = [o0, o1] | ||
|  |         try: | ||
|  |             o0.name | ||
|  |             o1.name | ||
|  |             if o0 not in objects and o1 not in objects: | ||
|  |                 objects.append(o) | ||
|  |                 objects = update_dependencies(o, objects) | ||
|  |         except: | ||
|  |             continue | ||
|  |     return objects | ||
|  | 
 | ||
|  | 
 | ||
|  | class tissue_refresh_tessellate(Operator): | ||
|  |     bl_idname = "object.tissue_refresh_tessellate" | ||
|  |     bl_label = "Refresh" | ||
|  |     bl_description = ("Fast update the tessellated mesh according to base and " | ||
|  |                       "component changes") | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     go = False | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             return context.object.tissue_tessellate.generator != None and \ | ||
|  |                 context.object.tissue_tessellate.component != None | ||
|  |         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() | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         ob = bpy.context.object | ||
|  |         ob0 = ob.tissue_tessellate.generator | ||
|  |         ob1 = ob.tissue_tessellate.component | ||
|  |         try: | ||
|  |             ob0.name | ||
|  |             ob1.name | ||
|  |         except: | ||
|  |             self.report({'ERROR'}, | ||
|  |                         "Active object must be Tessellate before Update") | ||
|  |             return {'CANCELLED'} | ||
|  | 
 | ||
|  |         if ob.tissue_tessellate.bool_dependencies: | ||
|  |             update_objects = list(reversed(update_dependencies(ob, [ob]))) | ||
|  |         else: | ||
|  |             update_objects = [ob] | ||
|  |         for o in update_objects: | ||
|  |             override = {'object': o} | ||
|  |             bpy.ops.object.tissue_update_tessellate(override) | ||
|  | 
 | ||
|  |         return {'FINISHED'} | ||
|  | 
 | ||
|  | 
 | ||
|  | class tissue_update_tessellate(Operator): | ||
|  |     bl_idname = "object.tissue_update_tessellate" | ||
|  |     bl_label = "Refresh" | ||
|  |     bl_description = ("Fast update the tessellated mesh according to base and " | ||
|  |                       "component changes") | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     go = False | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         #try: | ||
|  |         try: #context.object == None: return False | ||
|  |             return context.object.tissue_tessellate.generator != None and \ | ||
|  |                 context.object.tissue_tessellate.component != None | ||
|  |         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() | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         start_time = time.time() | ||
|  | 
 | ||
|  |         ob = context.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 | ||
|  |             rotation_shift = ob.tissue_tessellate.rotation_shift | ||
|  |             rotation_direction = ob.tissue_tessellate.rotation_direction | ||
|  |             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 | ||
|  |             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 | ||
|  |             close_mesh = ob.tissue_tessellate.close_mesh | ||
|  |             open_edges_crease = ob.tissue_tessellate.open_edges_crease | ||
|  |             bridge_smoothness = ob.tissue_tessellate.bridge_smoothness | ||
|  |             frame_thickness = ob.tissue_tessellate.frame_thickness | ||
|  |             frame_mode = ob.tissue_tessellate.frame_mode | ||
|  |             frame_boundary = ob.tissue_tessellate.frame_boundary | ||
|  |             fill_frame = ob.tissue_tessellate.fill_frame | ||
|  |             frame_boundary_mat = ob.tissue_tessellate.frame_boundary_mat | ||
|  |             fill_frame_mat = ob.tissue_tessellate.fill_frame_mat | ||
|  |             bridge_cuts = ob.tissue_tessellate.bridge_cuts | ||
|  |             cap_material_index = ob.tissue_tessellate.cap_material_index | ||
|  |             patch_subs = ob.tissue_tessellate.patch_subs | ||
|  |         try: | ||
|  |             generator.name | ||
|  |             component.name | ||
|  |         except: | ||
|  |             self.report({'ERROR'}, | ||
|  |                         "Active object must be Tessellate before Update") | ||
|  |             return {'CANCELLED'} | ||
|  | 
 | ||
|  |         # Solve Local View issues | ||
|  |         local_spaces = [] | ||
|  |         local_ob0 = [] | ||
|  |         local_ob1 = [] | ||
|  |         for area in context.screen.areas: | ||
|  |             for space in area.spaces: | ||
|  |                 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 | ||
|  | 
 | ||
|  |         starting_mode = context.object.mode | ||
|  | 
 | ||
|  |         #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT' | ||
|  |         bpy.ops.object.mode_set(mode='OBJECT') | ||
|  | 
 | ||
|  |         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# | ||
|  |             context.collection.objects.link(base_ob) | ||
|  |         base_ob.name = '_tissue_tmp_base' | ||
|  | 
 | ||
|  |         # 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 = context.scene.frame_current | ||
|  |                 bool_update_cloth = True | ||
|  |         if bool_update_cloth: | ||
|  |             bpy.ops.ptcache.free_bake_all() | ||
|  |             bpy.ops.ptcache.bake_all() | ||
|  |         base_ob.modifiers.update() | ||
|  | 
 | ||
|  | 
 | ||
|  |         #new_ob.location = ob.location | ||
|  |         #new_ob.matrix_world = ob.matrix_world | ||
|  |         #bpy.ops.object.select_all(action='DESELECT') | ||
|  |         if bool_selection: | ||
|  |             faces = base_ob.data.polygons | ||
|  |             selections = [False]*len(faces) | ||
|  |             faces.foreach_get('select',selections) | ||
|  |             selections = np.array(selections) | ||
|  |             if not selections.any(): | ||
|  |                 message = "There are no faces selected." | ||
|  |                 context.view_layer.objects.active = ob | ||
|  |                 ob.select_set(True) | ||
|  |                 bpy.ops.object.mode_set(mode=starting_mode) | ||
|  |                 bpy.data.objects.remove(base_ob) | ||
|  |                 self.report({'ERROR'}, message) | ||
|  |                 return {'CANCELLED'} | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  |         iter_objects = [base_ob] | ||
|  |         ob_location = ob.location | ||
|  |         ob_matrix_world = ob.matrix_world | ||
|  |         #base_ob = new_ob#.copy() | ||
|  | 
 | ||
|  |         for iter in range(iterations): | ||
|  | 
 | ||
|  |             if iter > 0 and len(iter_objects) == 0: break | ||
|  |             if iter > 0 and normals_mode == 'CUSTOM': normals_mode = 'VERTS' | ||
|  |             same_iteration = [] | ||
|  |             matched_materials = [] | ||
|  |             # iterate base object materials (needed for multi-components) | ||
|  |             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: | ||
|  |                     # check if material and components match | ||
|  |                     try: | ||
|  |                         mat = base_ob.material_slots[m_id].material | ||
|  |                         ob1 = bpy.data.objects[mat.name] | ||
|  |                         if ob1.type not in ('MESH', 'CURVE','SURFACE','FONT', 'META'): | ||
|  |                             continue | ||
|  |                         material_id = m_id | ||
|  |                         matched_materials.append(m_id) | ||
|  |                         bool_material_id = True | ||
|  |                     except: | ||
|  |                         continue | ||
|  |                 if com_modifiers or ob1.type != 'MESH': | ||
|  |                     data1 = simple_to_mesh(ob1) | ||
|  |                 else: | ||
|  |                     data1 = ob1.data.copy() | ||
|  |                 n_edges1 = len(data1.edges) | ||
|  |                 bpy.data.meshes.remove(data1) | ||
|  | 
 | ||
|  |                 if iter != 0: gen_modifiers = True | ||
|  | 
 | ||
|  |                 if fill_mode == 'PATCH': | ||
|  |                     # patch subdivisions for additional iterations | ||
|  |                     if iter > 0: | ||
|  |                         base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF') | ||
|  |                         base_ob.modifiers['Tissue_Subsurf'].levels = patch_subs | ||
|  |                         temp_mod = base_ob.modifiers['Tissue_Subsurf'] | ||
|  |                     # patch tessellation | ||
|  |                     new_ob = tessellate_patch( | ||
|  |                             base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode, | ||
|  |                             rotation_mode, rotation_shift, random_seed, bool_vertex_group, | ||
|  |                             bool_selection, bool_shapekeys, bool_material_id, material_id, | ||
|  |                             normals_mode, bounds_x, bounds_y | ||
|  |                             ) | ||
|  |                     if iter > 0: | ||
|  |                         base_ob.modifiers.remove(temp_mod) | ||
|  |                 else: | ||
|  |                     ### FRAME and FAN ### | ||
|  |                     if fill_mode in ('FRAME','FAN'): | ||
|  | 
 | ||
|  |                         if fill_mode == 'FRAME': convert_function = convert_to_frame | ||
|  |                         else: convert_function = convert_to_fan | ||
|  | 
 | ||
|  |                         if normals_mode == 'CUSTOM' and base_ob.data.shape_keys != None: | ||
|  |                             ## base key | ||
|  |                             sk_values = [sk.value for sk in base_ob.data.shape_keys.key_blocks] | ||
|  |                             for sk in ob0.data.shape_keys.key_blocks: sk.value = 0 | ||
|  |                             _base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers) | ||
|  |                             for i, sk in enumerate(ob0.data.shape_keys.key_blocks): | ||
|  |                                 sk.value = sk_values[i] | ||
|  |                             ## key 1 | ||
|  |                             # hide modifiers | ||
|  |                             if not gen_modifiers and len(base_ob.modifiers) > 0: | ||
|  |                                 mod_visibility = [m.show_viewport for m in base_ob.modifiers] | ||
|  |                                 for m in base_ob.modifiers: m.show_viewport = False | ||
|  |                                 base_ob.modifiers.update() | ||
|  |                             base_ob_sk = convert_function(ob0, ob.tissue_tessellate, True) | ||
|  |                             ## combine shapekeys | ||
|  |                             _base_ob.shape_key_add(name='Basis', from_mix=False) | ||
|  |                             _base_ob.shape_key_add(name='Key1', from_mix=False) | ||
|  |                             sk_block = _base_ob.data.shape_keys.key_blocks[1] | ||
|  |                             sk_block.value = 1 | ||
|  |                             for vert, sk in zip(base_ob_sk.data.vertices, sk_block.data): | ||
|  |                                 sk.co = vert.co | ||
|  |                             bpy.data.objects.remove(base_ob_sk) | ||
|  |                             # set original modifiers | ||
|  |                             if not gen_modifiers and len(base_ob.modifiers) > 0: | ||
|  |                                 for i,m in enumerate(base_ob.modifiers): | ||
|  |                                     m.show_viewport = mod_visibility[i] | ||
|  |                                 base_ob.modifiers.update() | ||
|  |                         else: | ||
|  |                             _base_ob = convert_function(base_ob, ob.tissue_tessellate, gen_modifiers) | ||
|  |                         bpy.data.objects.remove(base_ob) | ||
|  |                         base_ob = _base_ob | ||
|  |                     # quad tessellation | ||
|  |                     new_ob = tessellate_original( | ||
|  |                             base_ob, ob1, offset, zscale, gen_modifiers, | ||
|  |                             com_modifiers, mode, scale_mode, rotation_mode, | ||
|  |                             rotation_shift, rotation_direction, | ||
|  |                             random_seed, fill_mode, bool_vertex_group, | ||
|  |                             bool_selection, bool_shapekeys, bool_material_id, | ||
|  |                             material_id, normals_mode, bounds_x, bounds_y | ||
|  |                             ) | ||
|  | 
 | ||
|  |                 # if empty or error, continue | ||
|  |                 if type(new_ob) is not bpy.types.Object: | ||
|  |                     continue | ||
|  | 
 | ||
|  |                 # prepare base object | ||
|  |                 if iter == 0 and gen_modifiers: | ||
|  |                     temp_base_ob = convert_object_to_mesh(base_ob, True, True) | ||
|  |                     bpy.data.objects.remove(base_ob) | ||
|  |                     base_ob = temp_base_ob | ||
|  |                     iter_objects = [base_ob] | ||
|  | 
 | ||
|  |                 # rename, make active and change transformations | ||
|  |                 new_ob.name = '_tissue_tmp_{}_{}'.format(iter,m_id) | ||
|  |                 new_ob.select_set(True) | ||
|  |                 context.view_layer.objects.active = new_ob | ||
|  |                 new_ob.location = ob_location | ||
|  |                 new_ob.matrix_world = ob_matrix_world | ||
|  | 
 | ||
|  |                 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 bool_multi_components: same_iteration.append(new_ob) | ||
|  | 
 | ||
|  |             base_ob.location = ob_location | ||
|  |             base_ob.matrix_world = ob_matrix_world | ||
|  | 
 | ||
|  |             # join together multiple components iterations | ||
|  |             if bool_multi_components: | ||
|  |                 if len(same_iteration) > 0: | ||
|  |                     context.view_layer.update() | ||
|  |                     for o in context.view_layer.objects: | ||
|  |                         o.select_set(o in same_iteration) | ||
|  |                     bpy.ops.object.join() | ||
|  |                     new_ob = context.view_layer.objects.active | ||
|  |                     new_ob.select_set(True) | ||
|  |                     #new_ob.data.update() | ||
|  | 
 | ||
|  |             if type(new_ob) in (int,str): | ||
|  |                 if iter == 0: | ||
|  |                     try: | ||
|  |                         bpy.data.objects.remove(iter_objects[0]) | ||
|  |                         iter_objects = [] | ||
|  |                     except: continue | ||
|  |                 continue | ||
|  | 
 | ||
|  |             # Clean last iteration, needed for 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: | ||
|  |                     if bool_multi_components or bool_material_id: | ||
|  |                         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.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() | ||
|  |                 last_mesh.name = '_tissue_tmp_previous_unused' | ||
|  | 
 | ||
|  |                 # delete previous iteration if empty or update it | ||
|  |                 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] | ||
|  |                 # set new base object for next iteration | ||
|  |                 base_ob = convert_object_to_mesh(new_ob,True,True) | ||
|  |                 if iter < iterations-1: new_ob.data = base_ob.data | ||
|  |                 # store new iteration and set transformations | ||
|  |                 iter_objects.append(new_ob) | ||
|  |                 #try: | ||
|  |                 #    bpy.data.objects.remove(bpy.data.objects['_tissue_tmp_base']) | ||
|  |                 #except: | ||
|  |                 #    pass | ||
|  |                 base_ob.name = '_tissue_tmp_base' | ||
|  |             elif combine_mode == 'ALL': | ||
|  |                 base_ob = new_ob.copy() | ||
|  |                 iter_objects.append(new_ob) | ||
|  |             else: | ||
|  |                 if base_ob != new_ob: | ||
|  |                     bpy.data.objects.remove(base_ob) | ||
|  |                 base_ob = new_ob | ||
|  |                 iter_objects = [new_ob] | ||
|  | 
 | ||
|  |             # Combine | ||
|  |             if combine_mode != 'LAST' and len(iter_objects)>0: | ||
|  |                 if base_ob not in iter_objects and type(base_ob) == bpy.types.Object: | ||
|  |                     bpy.data.objects.remove(base_ob) | ||
|  |                 for o in context.view_layer.objects: | ||
|  |                     o.select_set(o in iter_objects) | ||
|  |                 bpy.ops.object.join() | ||
|  |                 new_ob.data.update() | ||
|  |                 iter_objects = [new_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=True, 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) | ||
|  | 
 | ||
|  |                 if bool_dissolve_seams: | ||
|  |                     bpy.ops.mesh.select_mode(type='EDGE') | ||
|  |                     bpy.ops.mesh.select_all(action='DESELECT') | ||
|  |                     bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |                     for e in new_ob.data.edges: | ||
|  |                         e.select = e.use_seam | ||
|  |                     bpy.ops.object.mode_set(mode='EDIT') | ||
|  |                     bpy.ops.mesh.dissolve_edges() | ||
|  |                 bpy.ops.object.mode_set(mode='OBJECT') | ||
|  | 
 | ||
|  |                 if close_mesh != 'NONE': | ||
|  |                     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) | ||
|  |                     if open_edges_crease != 0: | ||
|  |                         bpy.ops.transform.edge_crease(value=open_edges_crease) | ||
|  |                     if close_mesh == 'CAP': | ||
|  |                         bpy.ops.mesh.edge_face_add() | ||
|  |                     if close_mesh == 'BRIDGE': | ||
|  |                         try: | ||
|  |                             bpy.ops.mesh.bridge_edge_loops( | ||
|  |                                 type='PAIRS', | ||
|  |                                 number_cuts=bridge_cuts, | ||
|  |                                 interpolation='SURFACE', | ||
|  |                                 smoothness=bridge_smoothness) | ||
|  |                         except: pass | ||
|  |                     bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |                     for f in new_ob.data.polygons: | ||
|  |                         if f.select: f.material_index = cap_material_index | ||
|  |             base_ob = context.view_layer.objects.active | ||
|  | 
 | ||
|  |         # Combine iterations | ||
|  |         if combine_mode != 'LAST' and len(iter_objects)>0: | ||
|  |             #if base_ob not in iter_objects and type(base_ob) == bpy.types.Object: | ||
|  |             #    bpy.data.objects.remove(base_ob) | ||
|  |             for o in context.view_layer.objects: | ||
|  |                 o.select_set(o in iter_objects) | ||
|  |             bpy.ops.object.join() | ||
|  |             new_ob = context.view_layer.objects.active | ||
|  |         elif combine_mode == 'LAST' and type(new_ob) != bpy.types.Object: | ||
|  |             # if last iteration gives error, then use the last correct iteration | ||
|  |             try: | ||
|  |                 if type(iter_objects[-1]) == bpy.types.Object: | ||
|  |                     new_ob = iter_objects[-1] | ||
|  |             except: pass | ||
|  | 
 | ||
|  |         if new_ob == 0: | ||
|  |             #bpy.data.objects.remove(base_ob.data) | ||
|  |             try: bpy.data.objects.remove(base_ob) | ||
|  |             except: pass | ||
|  |             message = "The generated object is an empty geometry!" | ||
|  |             context.view_layer.objects.active = ob | ||
|  |             ob.select_set(True) | ||
|  |             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: | ||
|  |                 try: bpy.data.objects.remove(o) | ||
|  |                 except: pass | ||
|  |             try: bpy.data.meshes.remove(data1) | ||
|  |             except: pass | ||
|  |             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'} | ||
|  | 
 | ||
|  |         #new_ob.location = ob_location | ||
|  |         #new_ob.matrix_world = ob_matrix_world | ||
|  | 
 | ||
|  |         # 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 = bpy.data.meshes.new_from_object(new_ob)# | ||
|  |         ob.data = new_ob.data.copy() | ||
|  |         ob.data.name = data_name | ||
|  |         bpy.data.meshes.remove(old_data) | ||
|  | 
 | ||
|  |         # 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 context.selected_objects] | ||
|  |         for o in selected_objects: o.select_set(False) | ||
|  | 
 | ||
|  |         ob.select_set(True) | ||
|  |         context.view_layer.objects.active = ob | ||
|  | 
 | ||
|  |         if merge: | ||
|  |             try: | ||
|  |                 bpy.ops.object.mode_set(mode='EDIT') | ||
|  |                 #bpy.ops.mesh.select_mode( | ||
|  |                 #    use_extend=False, use_expand=False, type='VERT') | ||
|  |                 bpy.ops.mesh.select_mode(type='VERT') | ||
|  |                 bpy.ops.mesh.select_non_manifold( | ||
|  |                     extend=False, use_wire=True, 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() | ||
|  |             except: pass | ||
|  |             if close_mesh != 'NONE': | ||
|  |                 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) | ||
|  |                 if open_edges_crease != 0: | ||
|  |                     bpy.ops.transform.edge_crease(value=open_edges_crease) | ||
|  |                 if close_mesh == 'CAP': | ||
|  |                     bpy.ops.mesh.edge_face_add() | ||
|  |                 if close_mesh == 'BRIDGE': | ||
|  |                     try: | ||
|  |                         bpy.ops.mesh.bridge_edge_loops( | ||
|  |                             type='PAIRS', | ||
|  |                             number_cuts=bridge_cuts, | ||
|  |                             interpolation='SURFACE', | ||
|  |                             smoothness=bridge_smoothness) | ||
|  |                     except: | ||
|  |                         pass | ||
|  |                 bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |                 for f in ob.data.polygons: | ||
|  |                     if f.select: f.material_index = cap_material_index | ||
|  |         #else: | ||
|  | 
 | ||
|  |         try: | ||
|  |             bpy.ops.object.mode_set(mode='EDIT') | ||
|  |             bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |         except: pass | ||
|  | 
 | ||
|  |         if bool_smooth: bpy.ops.object.shade_smooth() | ||
|  | 
 | ||
|  |         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) | ||
|  | 
 | ||
|  |         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) | ||
|  | 
 | ||
|  |         bpy.data.objects.remove(new_ob) | ||
|  | 
 | ||
|  |         # clean objects | ||
|  |         for o in bpy.data.objects: | ||
|  |             #if o.name not in context.view_layer.objects and "_tissue_tmp" in o.name: | ||
|  |             if "_tissue_tmp" in o.name: | ||
|  |                 bpy.data.objects.remove(o) | ||
|  | 
 | ||
|  |         end_time = time.time() | ||
|  |         print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time)) | ||
|  |         return {'FINISHED'} | ||
|  | 
 | ||
|  |     def check(self, context): | ||
|  |         return True | ||
|  | 
 | ||
|  | 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): | ||
|  |         return context.mode in {'OBJECT', 'EDIT_MESH'} | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         layout = self.layout | ||
|  | 
 | ||
|  |         col = layout.column(align=True) | ||
|  |         col.label(text="Tessellate:") | ||
|  |         col.operator("object.tissue_tessellate") | ||
|  |         col.operator("object.dual_mesh_tessellated") | ||
|  |         col.separator() | ||
|  |         col.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH') | ||
|  | 
 | ||
|  |         col.separator() | ||
|  |         col.label(text="Rotate Faces:") | ||
|  |         row = col.row(align=True) | ||
|  |         row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK') | ||
|  |         row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS') | ||
|  | 
 | ||
|  |         col.separator() | ||
|  |         col.label(text="Other:") | ||
|  |         col.operator("object.dual_mesh") | ||
|  |         col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE") | ||
|  | 
 | ||
|  |         act = context.object | ||
|  |         if act and act.type == 'MESH': | ||
|  |             col.operator("object.uv_to_mesh", icon="UV") | ||
|  | 
 | ||
|  |             if act.mode == 'EDIT': | ||
|  |                 col.separator() | ||
|  |                 col.label(text="Weight:") | ||
|  |                 col.operator("object.tissue_weight_distance", icon="TRACKING") | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_object(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_label = "Tessellate Settings" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: return context.object.type == 'MESH' | ||
|  |         except: return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             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) | ||
|  | 
 | ||
|  |             set_tessellate_handler(self,context) | ||
|  |             set_animatable_fix_handler(self,context) | ||
|  |             row.operator("object.tissue_refresh_tessellate", icon='FILE_REFRESH') | ||
|  |             lock_icon = 'LOCKED' if props.bool_lock else 'UNLOCKED' | ||
|  |             #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED' | ||
|  |             deps_icon = 'LINKED' if props.bool_dependencies else 'UNLINKED' | ||
|  |             row.prop(props, "bool_dependencies", text="", icon=deps_icon) | ||
|  |             row.prop(props, "bool_lock", text="", icon=lock_icon) | ||
|  |             col2 = row.column(align=True) | ||
|  |             col2.prop(props, "bool_run", text="",icon='TIME') | ||
|  |             col2.enabled = not props.bool_lock | ||
|  |             ''' | ||
|  |             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(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 | ||
|  |             ''' | ||
|  |             layout.use_property_split = True | ||
|  |             layout.use_property_decorate = False  # No animation. | ||
|  |             col = layout.column(align=True) | ||
|  |             row = col.row(align=True) | ||
|  |             row.label(text='Base:') | ||
|  |             row.prop_search(props, "generator", context.scene, "objects") | ||
|  |             col2 = row.column(align=True) | ||
|  |             col2.prop(props, "gen_modifiers", text='',icon='MODIFIER') | ||
|  |             try: | ||
|  |                 if not (props.generator.modifiers or props.generator.data.shape_keys): | ||
|  |                     col2.enabled = False | ||
|  |             except: | ||
|  |                     col2.enabled = False | ||
|  |             col.separator() | ||
|  |             row = col.row(align=True) | ||
|  |             row.label(text='Component:') | ||
|  |             row.prop_search(props, "component", context.scene, "objects") | ||
|  |             col2 = row.column(align=True) | ||
|  |             col2.prop(props, "com_modifiers", text='',icon='MODIFIER') | ||
|  |             try: | ||
|  |                 if not (props.component.modifiers or props.component.data.shape_keys): | ||
|  |                     col2.enabled = False | ||
|  |             except: | ||
|  |                     col2.enabled = False | ||
|  |             layout.use_property_split = False | ||
|  | 
 | ||
|  |             # Fill | ||
|  |             col = layout.column(align=True) | ||
|  |             col.label(text="Fill Mode:") | ||
|  | 
 | ||
|  |             # fill | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "fill_mode", icon='NONE', expand=True, | ||
|  |                      slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                      full_event=False, emboss=True, index=-1) | ||
|  | 
 | ||
|  |             #layout.use_property_split = True | ||
|  |             col = layout.column(align=True) | ||
|  |             col.prop(props, "bool_smooth") | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_frame(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Frame Settings" | ||
|  |     #bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME' | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_frame and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         if bool_tessellated: | ||
|  |             col = layout.column(align=True) | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "frame_mode", expand=True) | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "frame_thickness", icon='NONE', expand=True) | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "fill_frame", icon='NONE') | ||
|  |             show_frame_mat = props.bool_multi_components or props.bool_material_id | ||
|  |             if props.fill_frame and show_frame_mat: | ||
|  |                 row.prop(props, "fill_frame_mat", icon='NONE') | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "frame_boundary", text='Boundary', icon='NONE') | ||
|  |             if props.frame_boundary and show_frame_mat: | ||
|  |                 row.prop(props, "frame_boundary_mat", icon='NONE') | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_coordinates(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Component Coordinates" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         if bool_tessellated: | ||
|  |             col = layout.column(align=True) | ||
|  |             # component XY | ||
|  |             row = col.row(align=True) | ||
|  |             row.prop(props, "mode", expand=True) | ||
|  | 
 | ||
|  |             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) | ||
|  | 
 | ||
|  |                 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) | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_rotation(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Rotation" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         if bool_tessellated: | ||
|  |             # rotation | ||
|  |             layout.use_property_split = True | ||
|  |             layout.use_property_decorate = False  # No animation. | ||
|  |             col = layout.column(align=True) | ||
|  |             col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False, | ||
|  |                      slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                      full_event=False, emboss=True, index=-1) | ||
|  |             if props.rotation_mode == 'WEIGHT': | ||
|  |                 col.prop(props, "rotation_direction", expand=False, | ||
|  |                           slider=True, toggle=False, icon_only=False, event=False, | ||
|  |                           full_event=False, emboss=True, index=-1) | ||
|  |             if props.rotation_mode == 'RANDOM': | ||
|  |                 col.prop(props, "random_seed") | ||
|  |             else: | ||
|  |                 col.prop(props, "rotation_shift") | ||
|  | 
 | ||
|  |             if props.rotation_mode == 'UV': | ||
|  |                 uv_error = False | ||
|  |                 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') | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_thickness(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Thickness" | ||
|  |     #bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         #layout.use_property_split = True | ||
|  |         if bool_tessellated: | ||
|  |             col = layout.column(align=True) | ||
|  |             # component Z | ||
|  |             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 | ||
|  |             col = layout.column(align=True) | ||
|  |             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) | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_options(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = " " | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw_header(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         self.layout.prop(props, "merge") | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         layout.use_property_split = True | ||
|  |         layout.use_property_decorate = False  # No animation. | ||
|  |         if bool_tessellated: | ||
|  |             col = layout.column(align=True) | ||
|  |             if props.merge: | ||
|  |                 col.prop(props, "merge_thres") | ||
|  |                 col.prop(props, "bool_dissolve_seams") | ||
|  |                 col.prop(props, "close_mesh") | ||
|  |                 if props.close_mesh != 'NONE': | ||
|  |                     #row = col.row(align=True) | ||
|  |                     col.separator() | ||
|  |                     col.prop(props, "open_edges_crease", text="Crease") | ||
|  |                     col.prop(props, "cap_material_index", text='Material Index') | ||
|  |                     if props.close_mesh == 'BRIDGE': | ||
|  |                         col.separator() | ||
|  |                         col.prop(props, "bridge_cuts") | ||
|  |                         col.prop(props, "bridge_smoothness") | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_morphing(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Weight and Morphing" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         if bool_tessellated: | ||
|  |             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 | ||
|  |             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 | ||
|  |             if not allow_shapekeys: | ||
|  |                 col2 = layout.column(align=True) | ||
|  |                 row2 = col2.row(align=True) | ||
|  |                 row2.label(text="Use Shape Keys is not compatible with Use Modifiers", icon='INFO') | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_selective(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Selective" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         if bool_tessellated: | ||
|  |             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 | ||
|  |             # 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_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') | ||
|  |             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 | ||
|  | 
 | ||
|  |             col.separator() | ||
|  |             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 | ||
|  | 
 | ||
|  | 
 | ||
|  | class TISSUE_PT_tessellate_iterations(Panel): | ||
|  |     bl_space_type = 'PROPERTIES' | ||
|  |     bl_region_type = 'WINDOW' | ||
|  |     bl_context = "data" | ||
|  |     bl_parent_id = "TISSUE_PT_tessellate_object" | ||
|  |     bl_label = "Iterations" | ||
|  |     bl_options = {'DEFAULT_CLOSED'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             return context.object.type == 'MESH' and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def draw(self, context): | ||
|  |         ob = context.object | ||
|  |         props = ob.tissue_tessellate | ||
|  |         allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') | ||
|  | 
 | ||
|  |         try: | ||
|  |             bool_tessellated = props.generator or props.component != None | ||
|  |             ob0 = props.generator | ||
|  |             ob1 = props.component | ||
|  |         except: bool_tessellated = False | ||
|  |         layout = self.layout | ||
|  |         layout.use_property_split = True | ||
|  |         layout.use_property_decorate = False  # No animation. | ||
|  |         if bool_tessellated: | ||
|  |             col = layout.column(align=True) | ||
|  |             row = col.row(align=True) | ||
|  |             #row.label(text='', icon='FILE_REFRESH') | ||
|  |             col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH') | ||
|  |             if props.iterations > 1 and props.fill_mode == 'PATCH': | ||
|  |                 col.separator() | ||
|  |                 #row = col.row(align=True) | ||
|  |                 col.prop(props, 'patch_subs') | ||
|  |             layout.use_property_split = False | ||
|  |             col = layout.column(align=True) | ||
|  |             #row = col.row(align=True) | ||
|  |             col.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 tissue_rotate_face_right(Operator): | ||
|  |     bl_idname = "mesh.tissue_rotate_face_right" | ||
|  |     bl_label = "Rotate Faces Right" | ||
|  |     bl_description = "Rotate clockwise selected faces and update tessellated meshes" | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             #bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             ob = context.object | ||
|  |             return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         ob = context.active_object | ||
|  |         me = ob.data | ||
|  | 
 | ||
|  |         bm = bmesh.from_edit_mesh(me) | ||
|  |         mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] | ||
|  | 
 | ||
|  |         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 and obj.visible_get()]: | ||
|  |             context.view_layer.objects.active = o | ||
|  |             bpy.ops.object.tissue_update_tessellate() | ||
|  |             o.select_set(False) | ||
|  |         ob.select_set(True) | ||
|  |         context.view_layer.objects.active = ob | ||
|  |         bpy.ops.object.mode_set(mode='EDIT') | ||
|  |         context.tool_settings.mesh_select_mode = mesh_select_mode | ||
|  | 
 | ||
|  |         return {'FINISHED'} | ||
|  | 
 | ||
|  | class tissue_rotate_face_left(Operator): | ||
|  |     bl_idname = "mesh.tissue_rotate_face_left" | ||
|  |     bl_label = "Rotate Faces Left" | ||
|  |     bl_description = "Rotate counterclockwise selected faces and update tessellated meshes" | ||
|  |     bl_options = {'REGISTER', 'UNDO'} | ||
|  | 
 | ||
|  |     @classmethod | ||
|  |     def poll(cls, context): | ||
|  |         try: | ||
|  |             #bool_tessellated = context.object.tissue_tessellate.generator != None | ||
|  |             ob = context.object | ||
|  |             return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated | ||
|  |         except: | ||
|  |             return False | ||
|  | 
 | ||
|  |     def execute(self, context): | ||
|  |         ob = context.active_object | ||
|  |         me = ob.data | ||
|  | 
 | ||
|  |         bm = bmesh.from_edit_mesh(me) | ||
|  |         mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] | ||
|  | 
 | ||
|  |         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 and obj.visible_get()]: | ||
|  |             context.view_layer.objects.active = o | ||
|  |             bpy.ops.object.tissue_update_tessellate() | ||
|  |             o.select_set(False) | ||
|  |         ob.select_set(True) | ||
|  |         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 convert_to_frame(ob, props, use_modifiers): | ||
|  |     new_ob = convert_object_to_mesh(ob, use_modifiers, True) | ||
|  | 
 | ||
|  |     # create bmesh | ||
|  |     bm = bmesh.new() | ||
|  |     bm.from_mesh(new_ob.data) | ||
|  |     bm.verts.ensure_lookup_table() | ||
|  |     bm.edges.ensure_lookup_table() | ||
|  |     bm.faces.ensure_lookup_table() | ||
|  |     if props.bool_selection: | ||
|  |         original_faces = [f for f in bm.faces if f.select] | ||
|  |     else: | ||
|  |         original_faces = list(bm.faces) | ||
|  |     # detect edge loops | ||
|  | 
 | ||
|  |     loops = [] | ||
|  |     boundaries_mat = [] | ||
|  |     neigh_face_center = [] | ||
|  |     face_normals = [] | ||
|  |     # append boundary loops | ||
|  |     if props.frame_boundary: | ||
|  |         #selected_edges = [e for e in bm.edges if e.select] | ||
|  |         selected_edges = [e for e in bm.edges if e.is_boundary] | ||
|  |         if len(selected_edges) > 0: | ||
|  |             loop = [] | ||
|  |             count = 0 | ||
|  |             e0 = selected_edges[0] | ||
|  |             face = e0.link_faces[0] | ||
|  |             boundary_mat = [face.material_index] | ||
|  |             face_center = [face.calc_center_median()] | ||
|  |             loop_normals = [face.normal] | ||
|  |             selected_edges = selected_edges[1:] | ||
|  |             if props.bool_vertex_group: | ||
|  |                 n_verts = len(new_ob.data.vertices) | ||
|  |                 base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups] | ||
|  |                 ''' | ||
|  |                 base_vg = [] | ||
|  |                 for vg in new_ob.vertex_groups: | ||
|  |                     vertex_group = [] | ||
|  |                     for v in bm.verts: | ||
|  |                         try: | ||
|  |                             vertex_group.append(vg.weight(v.index)) | ||
|  |                         except: | ||
|  |                             vertex_group.append(0) | ||
|  |                     base_vg.append(vertex_group) | ||
|  |                 ''' | ||
|  |             while True: | ||
|  |                 new_vert = None | ||
|  |                 face = None | ||
|  |                 for e1 in selected_edges: | ||
|  |                     if e1.verts[0] in e0.verts: new_vert = e1.verts[1] | ||
|  |                     elif e1.verts[1] in e0.verts: new_vert = e1.verts[0] | ||
|  |                     if new_vert != None: | ||
|  |                         if len(loop)==0: | ||
|  |                             loop = [v for v in e1.verts if v != new_vert] | ||
|  |                         loop.append(new_vert) | ||
|  |                         e0 = e1 | ||
|  |                         face = e0.link_faces[0] | ||
|  |                         boundary_mat.append(face.material_index) | ||
|  |                         face_center.append(face.calc_center_median()) | ||
|  |                         loop_normals.append(face.normal) | ||
|  |                         selected_edges.remove(e0) | ||
|  |                         break | ||
|  |                 if new_vert == None: | ||
|  |                     try: | ||
|  |                         loops.append(loop) | ||
|  |                         loop = [] | ||
|  |                         e0 = selected_edges[0] | ||
|  |                         selected_edges = selected_edges[1:] | ||
|  |                         boundaries_mat.append(boundary_mat) | ||
|  |                         neigh_face_center.append(face_center) | ||
|  |                         face_normals.append(loop_normals) | ||
|  |                         face = e0.link_faces[0] | ||
|  |                         boundary_mat = [face.material_index] | ||
|  |                         face_center = [face.calc_center_median()] | ||
|  |                         loop_normals = [face.normal] | ||
|  |                     except: break | ||
|  |             boundaries_mat.append(boundary_mat) | ||
|  |             neigh_face_center.append(face_center) | ||
|  |             face_normals.append(loop_normals) | ||
|  |     # compute boundary frames | ||
|  |     new_faces = [] | ||
|  |     vert_ids = [] | ||
|  | 
 | ||
|  |     # append regular faces | ||
|  |     for f in original_faces:#bm.faces: | ||
|  |         loop = list(f.verts) | ||
|  |         loops.append(loop) | ||
|  |         boundaries_mat.append([f.material_index for v in loop]) | ||
|  |         face_normals.append([f.normal for v in loop]) | ||
|  | 
 | ||
|  |     # calc areas for relative frame mode | ||
|  |     if props.frame_mode == 'RELATIVE': | ||
|  |         verts_area = [] | ||
|  |         for v in bm.verts: | ||
|  |             linked_faces = v.link_faces | ||
|  |             if len(linked_faces) > 0: | ||
|  |                 area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2 | ||
|  |                 area /= len(linked_faces) | ||
|  |             else: area = 0 | ||
|  |             verts_area.append(area) | ||
|  | 
 | ||
|  |     for loop_index, loop in enumerate(loops): | ||
|  |         is_boundary = loop_index < len(neigh_face_center) | ||
|  |         materials = boundaries_mat[loop_index] | ||
|  |         new_loop = [] | ||
|  |         loop_ext = [loop[-1]] + loop + [loop[0]] | ||
|  | 
 | ||
|  |         # calc tangents | ||
|  |         tangents = [] | ||
|  |         for i in range(len(loop)): | ||
|  |             # vertices | ||
|  |             vert0 = loop_ext[i] | ||
|  |             vert = loop_ext[i+1] | ||
|  |             vert1 = loop_ext[i+2] | ||
|  |             # edge vectors | ||
|  |             vec0 = (vert0.co - vert.co).normalized() | ||
|  |             vec1 = (vert.co - vert1.co).normalized() | ||
|  |             # tangent | ||
|  |             _vec1 = -vec1 | ||
|  |             _vec0 = -vec0 | ||
|  |             ang = (pi - vec0.angle(vec1))/2 | ||
|  |             normal = face_normals[loop_index][i] | ||
|  |             tan0 = normal.cross(vec0) | ||
|  |             tan1 = normal.cross(vec1) | ||
|  |             tangent = (tan0 + tan1).normalized()/sin(ang)*props.frame_thickness | ||
|  |             tangents.append(tangent) | ||
|  | 
 | ||
|  |         # calc correct direction for boundaries | ||
|  |         mult = -1 | ||
|  |         if is_boundary: | ||
|  |             dir_val = 0 | ||
|  |             for i in range(len(loop)): | ||
|  |                 surf_point = neigh_face_center[loop_index][i] | ||
|  |                 tangent = tangents[i] | ||
|  |                 vert = loop_ext[i+1] | ||
|  |                 dir_val += tangent.dot(vert.co - surf_point) | ||
|  |             if dir_val > 0: mult = 1 | ||
|  | 
 | ||
|  |         # add vertices | ||
|  |         for i in range(len(loop)): | ||
|  |             vert = loop_ext[i+1] | ||
|  |             if props.frame_mode == 'RELATIVE': area = verts_area[vert.index] | ||
|  |             else: area = 1 | ||
|  |             new_co = vert.co + tangents[i] * mult * area | ||
|  |             # add vertex | ||
|  |             new_vert = bm.verts.new(new_co) | ||
|  |             new_loop.append(new_vert) | ||
|  |             vert_ids.append(vert.index) | ||
|  |         new_loop.append(new_loop[0]) | ||
|  | 
 | ||
|  |         # add faces | ||
|  |         materials += [materials[0]] | ||
|  |         for i in range(len(loop)): | ||
|  |              v0 = loop_ext[i+1] | ||
|  |              v1 = loop_ext[i+2] | ||
|  |              v2 = new_loop[i+1] | ||
|  |              v3 = new_loop[i] | ||
|  |              face_verts = [v1,v0,v3,v2] | ||
|  |              if mult == -1: face_verts = [v0,v1,v2,v3] | ||
|  |              new_face = bm.faces.new(face_verts) | ||
|  |              new_face.material_index = materials[i+1] + props.frame_boundary_mat | ||
|  |              new_face.select = True | ||
|  |              new_faces.append(new_face) | ||
|  |         # fill frame | ||
|  |         if props.fill_frame and not is_boundary: | ||
|  |             n_verts = len(new_loop)-1 | ||
|  |             loop_center = Vector((0,0,0)) | ||
|  |             for v in new_loop[1:]: loop_center += v.co | ||
|  |             loop_center /= n_verts | ||
|  |             center = bm.verts.new(loop_center) | ||
|  |             for i in range(n_verts): | ||
|  |                 v0 = new_loop[i+1] | ||
|  |                 v1 = new_loop[i] | ||
|  |                 face_verts = [v1,v0,center] | ||
|  |                 new_face = bm.faces.new(face_verts) | ||
|  |                 new_face.material_index = materials[i] + props.frame_boundary_mat | ||
|  |                 new_face.select = True | ||
|  |                 new_faces.append(new_face) | ||
|  |     bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |     #for f in bm.faces: f.select_set(f not in new_faces) | ||
|  |     for f in original_faces: bm.faces.remove(f) | ||
|  |     bm.to_mesh(new_ob.data) | ||
|  |     # propagate vertex groups | ||
|  |     if props.bool_vertex_group: | ||
|  |         base_vg = [] | ||
|  |         for vg in new_ob.vertex_groups: | ||
|  |             vertex_group = [] | ||
|  |             for v in bm.verts: | ||
|  |                 try: | ||
|  |                     vertex_group.append(vg.weight(v.index)) | ||
|  |                 except: | ||
|  |                     vertex_group.append(0) | ||
|  |             base_vg.append(vertex_group) | ||
|  |         new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts)) | ||
|  |         for vg_id, vg in enumerate(new_ob.vertex_groups): | ||
|  |             for ii, jj in zip(vert_ids, new_vert_ids): | ||
|  |                 vg.add([jj], base_vg[vg_id][ii], 'REPLACE') | ||
|  |     new_ob.data.update() | ||
|  |     return new_ob | ||
|  | 
 | ||
|  | def convert_to_fan(ob, props, use_modifiers): | ||
|  |     new_ob = convert_object_to_mesh(ob, use_modifiers, True) | ||
|  |     # make base object selected and active | ||
|  |     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 | ||
|  |     sk_index0 = new_ob.active_shape_key_index | ||
|  |     new_ob.active_shape_key_index = 0 | ||
|  | 
 | ||
|  |     bpy.ops.object.mode_set(mode='EDIT') | ||
|  |     bpy.ops.mesh.select_mode(type='FACE') | ||
|  |     if not props.bool_selection: | ||
|  |         bpy.ops.mesh.select_all(action='SELECT') | ||
|  |     bpy.ops.mesh.poke() | ||
|  |     bpy.ops.object.mode_set(mode='OBJECT') | ||
|  |     return new_ob |