diff --git a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY b/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY deleted file mode 100644 index a66f7748..00000000 --- a/◯ᴥᗱᗴᗝИNᗱᗴᙁ⚭ⵙ⚭ᙁᗱᗴИNᗝᗱᗴᴥ◯/2.90/SCRIPTS/ADDONS/TISSUE-MASTER/TESSELLATE_NUMPY.PY +++ /dev/null @@ -1,4280 +0,0 @@ -# ##### 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