diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2019-07-01 14:06:16 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2019-07-01 14:06:16 +0300 |
commit | 498d912a9d5854c0cfc1f54ed8b4216d442b89f1 (patch) | |
tree | 66e565094b4eec07c2e05c9106724ee5e4649454 /mesh_tissue/tessellate_numpy.py | |
parent | fffaf5d2759d38d4166f608eab8871fcd59a7e11 (diff) |
mesh_tissue: initial update 2.80
Diffstat (limited to 'mesh_tissue/tessellate_numpy.py')
-rw-r--r-- | mesh_tissue/tessellate_numpy.py | 3589 |
1 files changed, 2515 insertions, 1074 deletions
diff --git a/mesh_tissue/tessellate_numpy.py b/mesh_tissue/tessellate_numpy.py index e5f6db5c..9526d115 100644 --- a/mesh_tissue/tessellate_numpy.py +++ b/mesh_tissue/tessellate_numpy.py @@ -42,78 +42,548 @@ from bpy.props import ( FloatProperty, IntProperty, StringProperty, + PointerProperty ) from mathutils import Vector import numpy as np from math import sqrt -import random - - -def lerp(a, b, t): - return a + (b - a) * t - - -def lerp2(v1, v2, v3, v4, v): - v12 = v1 + (v2 - v1) * v.x - v43 = v4 + (v3 - v4) * v.x - return v12 + (v43 - v12) * v.y - +import random, time +import bmesh +from .utils import * + +def anim_tessellate_active(self, context): + ob = context.object + props = ob.tissue_tessellate + if not props.bool_hold: + try: + props.generator.name + props.component.name + bpy.ops.object.update_tessellate() + except: pass + +def anim_tessellate_object(ob): + try: + #bpy.context.view_layer.objects.active = ob + bpy.ops.object.update_tessellate() + except: + return None + +#from bpy.app.handlers import persistent + +#@persistent +def anim_tessellate(scene): + # store selected objects + #scene = context.scene + try: active_object = bpy.context.object + except: active_object = None + try: selected_objects = bpy.context.selected_objects + except: selected_objects = [] + if bpy.context.mode in ('OBJECT', 'PAINT_WEIGHT'): + old_mode = bpy.context.mode + if old_mode == 'PAINT_WEIGHT': old_mode = 'WEIGHT_PAINT' + for ob in scene.objects: + if ob.tissue_tessellate.bool_run: + hidden = ob.hide_viewport + ob.hide_viewport = False + for o in scene.objects: + if not o.hide_viewport: ob.select_set(False) + bpy.context.view_layer.objects.active = ob + ob.select_set(True) + try: + bpy.ops.object.update_tessellate() + except: pass + ob.hide_viewport = hidden + # restore selected objects + for o in scene.objects: + if not o.hide_viewport: o.select_set(False) + for o in selected_objects: + if not o.hide_viewport: o.select_set(True) + bpy.context.view_layer.objects.active = active_object + try: bpy.ops.object.mode_set(mode=old_mode) + except: pass + return + +def set_tessellate_handler(self, context): + old_handlers = [] + blender_handlers = bpy.app.handlers.frame_change_post + for h in blender_handlers: + if "anim_tessellate" in str(h): + old_handlers.append(h) + for h in old_handlers: blender_handlers.remove(h) + for o in context.scene.objects: + if o.tissue_tessellate.bool_run: + blender_handlers.append(anim_tessellate) + break + return -def lerp3(v1, v2, v3, v4, v): - loc = lerp2(v1.co, v2.co, v3.co, v4.co, v) - nor = lerp2(v1.normal, v2.normal, v3.normal, v4.normal, v) - nor.normalize() - return loc + nor * v.z +class tissue_tessellate_prop(PropertyGroup): + bool_hold : BoolProperty( + name="Hold Update", + description="Prevent automatic update while other properties are changed", + default=False + ) + bool_run : BoolProperty( + name="Animatable Tessellation", + description="Automatically recompute the tessellation when the frame is changed", + default = False, + update = set_tessellate_handler + ) + zscale : FloatProperty( + name="Scale", default=1, soft_min=0, soft_max=10, + description="Scale factor for the component thickness", + update = anim_tessellate_active + ) + scale_mode : EnumProperty( + items=( + ('CONSTANT', "Constant", "Uniform thinkness"), + ('ADAPTIVE', "Proportional", "Preserve component's proportions") + ), + default='ADAPTIVE', + name="Z-Scale according to faces size", + update = anim_tessellate_active + ) + offset : FloatProperty( + name="Surface Offset", + default=1, + min=-1, + max=1, + soft_min=-1, + soft_max=1, + description="Surface offset", + update = anim_tessellate_active + ) + mode : EnumProperty( + items=( + ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"), + ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"), + ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")), + default='BOUNDS', + name="Component Mode", + update = anim_tessellate_active + ) + rotation_mode : EnumProperty( + items=(('RANDOM', "Random", "Random faces rotation"), + ('UV', "Active UV", "Rotate according to UV coordinates"), + ('DEFAULT', "Default", "Default rotation")), + default='DEFAULT', + name="Component Rotation", + update = anim_tessellate_active + ) + fill_mode : EnumProperty( + items=( + ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'), + ('FAN', 'Fan', 'Radial tessellation for polygonal faces'), + ('PATCH', 'Patch', 'Curved tessellation according to the last ' + + 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' + + 'patches.\nAfter the last Subsurf (or Multires) only ' + + 'deformation\nmodifiers can be used')), + default='QUAD', + name="Fill Mode", + update = anim_tessellate_active + ) + combine_mode : EnumProperty( + items=( + ('LAST', 'Last', 'Show only the last iteration'), + ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'), + ('ALL', 'All', 'Combine the result of all iterations')), + default='LAST', + name="Combine Mode", + update = anim_tessellate_active + ) + gen_modifiers : BoolProperty( + name="Generator Modifiers", + default=False, + description="Apply Modifiers and Shape Keys to the base object", + update = anim_tessellate_active + ) + com_modifiers : BoolProperty( + name="Component Modifiers", + default=False, + description="Apply Modifiers and Shape Keys to the component object", + update = anim_tessellate_active + ) + merge : BoolProperty( + name="Merge", + default=False, + description="Merge vertices in adjacent duplicates", + update = anim_tessellate_active + ) + merge_thres : FloatProperty( + name="Distance", + default=0.001, + soft_min=0, + soft_max=10, + description="Limit below which to merge vertices", + update = anim_tessellate_active + ) + generator : PointerProperty( + type=bpy.types.Object, + name="", + description="Base object for the tessellation", + update = anim_tessellate_active + ) + component : PointerProperty( + type=bpy.types.Object, + name="", + description="Component object for the tessellation", + #default="", + update = anim_tessellate_active + ) + bool_random : BoolProperty( + name="Randomize", + default=False, + description="Randomize component rotation", + update = anim_tessellate_active + ) + random_seed : IntProperty( + name="Seed", + default=0, + soft_min=0, + soft_max=10, + description="Random seed", + update = anim_tessellate_active + ) + bool_vertex_group : BoolProperty( + name="Map Vertex Group", + default=False, + description="Transfer all Vertex Groups from Base object", + update = anim_tessellate_active + ) + bool_selection : BoolProperty( + name="On selected Faces", + default=False, + description="Create Tessellation only on selected faces", + update = anim_tessellate_active + ) + bool_shapekeys : BoolProperty( + name="Use Shape Keys", + default=False, + description="Transfer Component's Shape Keys. If the name of Vertex " + "Groups and Shape Keys are the same, they will be " + "automatically combined", + update = anim_tessellate_active + ) + bool_smooth : BoolProperty( + name="Smooth Shading", + default=False, + description="Output faces with smooth shading rather than flat shaded", + update = anim_tessellate_active + ) + bool_materials : BoolProperty( + name="Transfer Materials", + default=False, + description="Preserve component's materials", + update = anim_tessellate_active + ) + bool_material_id : BoolProperty( + name="Tessellation on Material ID", + default=False, + description="Apply the component only on the selected Material", + update = anim_tessellate_active + ) + material_id : IntProperty( + name="Material ID", + default=0, + min=0, + description="Material ID", + update = anim_tessellate_active + ) + bool_dissolve_seams : BoolProperty( + name="Dissolve Seams", + default=False, + description="Dissolve all seam edges", + update = anim_tessellate_active + ) + iterations : IntProperty( + name="Iterations", + default=1, + min=1, + soft_max=5, + description="Automatically repeat the Tessellation using the " + + "generated geometry as new base object.\nUsefull for " + + "for branching systems. Dangerous!", + update = anim_tessellate_active + ) + bool_combine : BoolProperty( + name="Combine unused", + default=False, + description="Combine the generated geometry with unused faces", + update = anim_tessellate_active + ) + bool_advanced : BoolProperty( + name="Advanced Settings", + default=False, + description="Show more settings" + ) + normals_mode : EnumProperty( + items=( + ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'), + ('FACES', 'Individual Faces', 'Based on individual faces normal')), + default='VERTS', + name="Direction", + update = anim_tessellate_active + ) + bool_multi_components : BoolProperty( + name="Multi Components", + default=False, + description="Combine different components according to materials name", + update = anim_tessellate_active + ) + error_message : StringProperty( + name="Error Message", + default="" + ) + warning_message : StringProperty( + name="Warning Message", + default="" + ) + bounds_x : EnumProperty( + items=( + ('EXTEND', 'Extend', 'Default X coordinates'), + ('CLIP', 'Clip', 'Trim out of bounds in X direction'), + ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')), + default='EXTEND', + name="Bounds X", + update = anim_tessellate_active + ) + bounds_y : EnumProperty( + items=( + ('EXTEND', 'Extend', 'Default Y coordinates'), + ('CLIP', 'Clip', 'Trim out of bounds in Y direction'), + ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')), + default='EXTEND', + name="Bounds Y", + update = anim_tessellate_active + ) + cap_faces : BoolProperty( + name="Cap Holes", + default=False, + description="Cap open edges loops", + update = anim_tessellate_active + ) + open_edges_crease : FloatProperty( + name="Open Edges Crease", + default=0, + min=0, + max=1, + description="Automatically set crease for open edges", + update = anim_tessellate_active + ) +def store_parameters(operator, ob): + ob.tissue_tessellate.bool_hold = True + ob.tissue_tessellate.generator = bpy.data.objects[operator.generator] + ob.tissue_tessellate.component = bpy.data.objects[operator.component] + ob.tissue_tessellate.zscale = operator.zscale + ob.tissue_tessellate.offset = operator.offset + ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers + ob.tissue_tessellate.com_modifiers = operator.com_modifiers + ob.tissue_tessellate.mode = operator.mode + ob.tissue_tessellate.rotation_mode = operator.rotation_mode + ob.tissue_tessellate.merge = operator.merge + ob.tissue_tessellate.merge_thres = operator.merge_thres + ob.tissue_tessellate.scale_mode = operator.scale_mode + ob.tissue_tessellate.bool_random = operator.bool_random + ob.tissue_tessellate.random_seed = operator.random_seed + ob.tissue_tessellate.fill_mode = operator.fill_mode + ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group + ob.tissue_tessellate.bool_selection = operator.bool_selection + ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys + ob.tissue_tessellate.bool_smooth = operator.bool_smooth + ob.tissue_tessellate.bool_materials = operator.bool_materials + ob.tissue_tessellate.bool_material_id = operator.bool_material_id + ob.tissue_tessellate.material_id = operator.material_id + ob.tissue_tessellate.bool_dissolve_seams = operator.bool_dissolve_seams + ob.tissue_tessellate.iterations = operator.iterations + ob.tissue_tessellate.bool_advanced = operator.bool_advanced + ob.tissue_tessellate.normals_mode = operator.normals_mode + ob.tissue_tessellate.bool_combine = operator.bool_combine + ob.tissue_tessellate.bool_multi_components = operator.bool_multi_components + ob.tissue_tessellate.combine_mode = operator.combine_mode + ob.tissue_tessellate.bounds_x = operator.bounds_x + ob.tissue_tessellate.bounds_y = operator.bounds_y + ob.tissue_tessellate.cap_faces = operator.cap_faces + ob.tissue_tessellate.bool_hold = False + return ob -def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode, - scale_mode, rotation_mode, rand_seed, fill_mode, - bool_vertex_group, bool_selection, bool_shapekeys): +def tessellate_patch(_ob0, _ob1, offset, zscale, com_modifiers, mode, + scale_mode, rotation_mode, rand_seed, bool_vertex_group, + bool_selection, bool_shapekeys, bool_material_id, material_id, + bounds_x, bounds_y): random.seed(rand_seed) - old_me0 = ob0.data # Store generator mesh - - if gen_modifiers or com_modifiers: - depsgraph = bpy.context.evaluated_depsgraph_get() - else: - depsgraph = None - me0_owner = None - if gen_modifiers: # Apply generator modifiers - me0_owner = ob0.evaluated_get(depsgraph) - me0 = me0_owner.to_mesh() - else: - me0 = ob0.data - ob0.data = me0 - base_polygons = [] + ob0 = convert_object_to_mesh(_ob0) + me0 = _ob0.data # Check if zero faces are selected - if bool_selection: - for p in ob0.data.polygons: - if p.select: - base_polygons.append(p) - else: - base_polygons = ob0.data.polygons - if len(base_polygons) == 0: - return 0 + if _ob0.type == 'MESH': + bool_cancel = True + for p in me0.polygons: + check_sel = check_mat = False + if not bool_selection or p.select: check_sel = True + if not bool_material_id or p.material_index == material_id: check_mat = True + if check_sel and check_mat: + bool_cancel = False + break + if bool_cancel: + return 0 + + levels = 0 + sculpt_levels = 0 + render_levels = 0 + bool_multires = False + multires_name = "" + not_allowed = ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD', + 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH', + 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN', + 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE'] + modifiers0 = list(_ob0.modifiers)#[m for m in ob0.modifiers] + show_modifiers = [m.show_viewport for m in _ob0.modifiers] + show_modifiers.reverse() + modifiers0.reverse() + for m in modifiers0: + visible = m.show_viewport + #m.show_viewport = False + if m.type in ('SUBSURF', 'MULTIRES') and visible: + levels = m.levels + multires_name = m.name + if m.type == 'MULTIRES': + bool_multires = True + multires_name = m.name + sculpt_levels = m.sculpt_levels + render_levels = m.render_levels + else: bool_multires = False + break + elif m.type in not_allowed: + #ob0.data = old_me0 + #bpy.data.meshes.remove(me0) + return "modifiers_error" + + before = _ob0.copy() + #if ob0.type == 'MESH': before.data = me0 + before_mod = list(before.modifiers) + before_mod.reverse() + for m in before_mod: + if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport: + before.modifiers.remove(m) + break + else: before.modifiers.remove(m) + + before_subsurf = simple_to_mesh(before) + + before_bm = bmesh.new() + before_bm.from_mesh(before_subsurf) + before_bm.faces.ensure_lookup_table() + for f in before_bm.faces: + if len(f.loops) != 4: + return "topology_error" + before_bm.edges.ensure_lookup_table() + for e in before_bm.edges: + if len(e.link_faces) == 0: + return "wires_error" + before_bm.verts.ensure_lookup_table() + for v in before_bm.verts: + if len(v.link_faces) == 0: + return "verts_error" + + me0 = ob0.data + verts0 = me0.vertices # Collect generator vertices - # Apply component modifiers - me1_owner = ob1.evaluated_get(depsgraph) - if com_modifiers: - me1 = me1_owner.to_mesh() - else: - me1 = ob1.data + if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False - verts0 = me0.vertices # Collect generator vertices + # set Shape Keys to zero + if bool_shapekeys: + try: + original_key_values = [] + for sk in _ob1.data.shape_keys.key_blocks: + original_key_values.append(sk.value) + sk.value = 0 + except: + bool_shapekeys = False + + if not com_modifiers and not bool_shapekeys: + mod_visibility = [] + for m in _ob1.modifiers: + mod_visibility.append(m.show_viewport) + m.show_viewport = False + com_modifiers = True + + ob1 = convert_object_to_mesh(_ob1, com_modifiers, False) + me1 = ob1.data + + if mode != 'BOUNDS': + bpy.context.object.active_shape_key_index = 0 + # Bound X + if bounds_x != 'EXTEND': + if mode == 'GLOBAL': + planes_co = ((0,0,0),(1,1,1)) + plane_no = (1,0,0) + if mode == 'LOCAL': + planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0))) + plane_no = planes_co[0]-planes_co[1] + bpy.ops.object.mode_set(mode='EDIT') + for co in planes_co: + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) + bpy.ops.mesh.mark_seam() + bpy.ops.object.mode_set(mode='OBJECT') + _faces = ob1.data.polygons + if mode == 'GLOBAL': + for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]: + f.select = True + for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]: + f.select = True + else: + for f in [f for f in _faces if f.center.x > 1]: + f.select = True + for f in [f for f in _faces if f.center.x < 0]: + f.select = True + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='FACE') + if bounds_x == 'CLIP': + bpy.ops.mesh.delete(type='FACE') + bpy.ops.object.mode_set(mode='OBJECT') + if bounds_x == 'CYCLIC': + bpy.ops.mesh.split() + bpy.ops.object.mode_set(mode='OBJECT') + # Bound Y + if bounds_y != 'EXTEND': + if mode == 'GLOBAL': + planes_co = ((0,0,0),(1,1,1)) + plane_no = (0,1,0) + if mode == 'LOCAL': + planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0))) + plane_no = planes_co[0]-planes_co[1] + bpy.ops.object.mode_set(mode='EDIT') + for co in planes_co: + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) + bpy.ops.mesh.mark_seam() + bpy.ops.object.mode_set(mode='OBJECT') + _faces = ob1.data.polygons + if mode == 'GLOBAL': + for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]: + f.select = True + for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]: + f.select = True + else: + for f in [f for f in _faces if f.center.y > 1]: + f.select = True + for f in [f for f in _faces if f.center.y < 0]: + f.select = True + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='FACE') + if bounds_y == 'CLIP': + bpy.ops.mesh.delete(type='FACE') + bpy.ops.object.mode_set(mode='OBJECT') + if bounds_y == 'CYCLIC': + bpy.ops.mesh.split() + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') # Component statistics n_verts = len(me1.vertices) - # n_edges = len(me1.edges) - # n_faces = len(me1.polygons) - - # Component transformations - # loc = ob1.location - # dim = ob1.dimensions - # scale = ob1.scale # Create empty lists new_verts = [] @@ -145,112 +615,763 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode, # adaptive XY verts1 = [] for v in me1.vertices: - if mode == "ADAPTIVE": + if mode == 'BOUNDS': vert = v.co - min_c # (ob1.matrix_world * v.co) - min_c vert[0] = (vert[0] / bb[0] if bb[0] != 0 else 0.5) vert[1] = (vert[1] / bb[1] if bb[1] != 0 else 0.5) vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale - else: + elif mode == 'LOCAL': vert = v.co.xyz - vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale - verts1.append(vert) + vert[2] *= zscale + #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale + elif mode == 'GLOBAL': + vert = ob1.matrix_world @ v.co + vert[2] *= zscale + try: + for sk in me1.shape_keys.key_blocks: + sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co + except: pass + #verts1.append(vert) + v.co = vert + + # Bounds X, Y + if mode != 'BOUNDS': + if bounds_x == 'CYCLIC': + move_verts = [] + for f in [f for f in me1.polygons if (f.center).x > 1]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.x -= 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.x -= 1 + except: pass + move_verts = [] + for f in [f for f in me1.polygons if (f.center).x < 0]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.x += 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.x += 1 + except: pass + if bounds_y == 'CYCLIC': + move_verts = [] + for f in [f for f in me1.polygons if (f.center).y > 1]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.y -= 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.y -= 1 + except: pass + move_verts = [] + for f in [f for f in me1.polygons if (f.center).y < 0]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.y += 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.y += 1 + except: pass + verts1 = [v.co for v in me1.vertices] + + patch_faces = 4**levels + sides = int(sqrt(patch_faces)) + sides0 = sides-2 + patch_faces0 = int((sides-2)**2) + n_patches = int(len(me0.polygons)/patch_faces) + if len(me0.polygons)%patch_faces != 0: + #ob0.data = old_me0 + return "topology_error" + + new_verts = [] + new_edges = [] + new_faces = [] + + for o in bpy.context.view_layer.objects: o.select_set(False) + new_patch = None + + # All vertex group + if bool_vertex_group: + try: + weight = [] + for vg in ob0.vertex_groups: + _weight = [] + for v in me0.vertices: + try: + _weight.append(vg.weight(v.index)) + except: + _weight.append(0) + weight.append(_weight) + except: + bool_vertex_group = False + + # Adaptive Z + if scale_mode == 'ADAPTIVE': + if mode == 'BOUNDS': com_area = (bb[0]*bb[1]) + else: com_area = 1 + mult = 1/com_area*patch_faces + verts_area = [] + bm = bmesh.new() + bm.from_mesh(me0) + bm.verts.ensure_lookup_table() + for v in bm.verts: + area = 0 + faces = v.link_faces + for f in faces: + area += f.calc_area() + area/=len(faces) + area*=mult + verts_area.append(sqrt(area)) + + random.seed(rand_seed) + bool_correct = False + + _faces = [[[0] for ii in range(sides)] for jj in range(sides)] + _verts = [[[0] for ii in range(sides+1)] for jj in range(sides+1)] + + for i in range(n_patches): + poly = me0.polygons[i*patch_faces] + if bool_selection and not poly.select: continue + if bool_material_id and not poly.material_index == material_id: continue + + bool_correct = True + new_patch = bpy.data.objects.new("patch", me1.copy()) + bpy.context.collection.objects.link(new_patch) + + new_patch.select_set(True) + bpy.context.view_layer.objects.active = new_patch + + for area in bpy.context.screen.areas: + for space in area.spaces: + try: new_patch.local_view_set(space, True) + except: pass + + # Vertex Group + if bool_vertex_group: + for vg in ob0.vertex_groups: + new_patch.vertex_groups.new(name=vg.name) + + # find patch faces + faces = _faces.copy() + verts = _verts.copy() + shift1 = sides + shift2 = sides*2-1 + shift3 = sides*3-2 + for j in range(patch_faces): + if j < patch_faces0: + if levels == 0: + u = j%sides0 + v = j//sides0 + else: + u = j%sides0+1 + v = j//sides0+1 + elif j < patch_faces0 + shift1: + u = j-patch_faces0 + v = 0 + elif j < patch_faces0 + shift2: + u = sides-1 + v = j-(patch_faces0 + sides)+1 + elif j < patch_faces0 + shift3: + jj = j-(patch_faces0 + shift2) + u = sides-jj-2 + v = sides-1 + else: + jj = j-(patch_faces0 + shift3) + u = 0 + v = sides-jj-2 + face = me0.polygons[j+i*patch_faces] + faces[u][v] = face + verts[u][v] = verts0[face.vertices[0]] + if u == sides-1: + verts[sides][v] = verts0[face.vertices[1]] + if v == sides-1: + verts[u][sides] = verts0[face.vertices[3]] + if u == v == sides-1: + verts[sides][sides] = verts0[face.vertices[2]] + + # Random rotation + if rotation_mode == 'RANDOM': + rand = random.randint(0, 3) + if rand == 1: + verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)] + elif rand == 2: + verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)] + elif rand == 3: + verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)] + + # UV rotation + elif rotation_mode == 'UV' and ob0.type == 'MESH': + if len(ob0.data.uv_layers) > 0: + uv0 = me0.uv_layers.active.data[faces[0][0].index*4].uv + uv1 = me0.uv_layers.active.data[faces[0][-1].index*4 + 3].uv + uv2 = me0.uv_layers.active.data[faces[-1][-1].index*4 + 2].uv + uv3 = me0.uv_layers.active.data[faces[-1][0].index*4 + 1].uv + v01 = (uv0 + uv1) + v32 = (uv3 + uv2) + v0132 = v32 - v01 + v0132.normalize() + v12 = (uv1 + uv2) + v03 = (uv0 + uv3) + v1203 = v03 - v12 + v1203.normalize() + + vertUV = [] + dot1203 = v1203.x + dot0132 = v0132.x + if(abs(dot1203) < abs(dot0132)): + if (dot0132 > 0): + pass + else: + verts = [[verts[k][w] for w in range(sides,-1,-1)] for k in range(sides,-1,-1)] + else: + if(dot1203 < 0): + verts = [[verts[w][k] for w in range(sides,-1,-1)] for k in range(sides+1)] + else: + verts = [[verts[w][k] for w in range(sides+1)] for k in range(sides,-1,-1)] + + step = 1/sides + for vert, patch_vert in zip(verts1, new_patch.data.vertices): + # grid coordinates + u = int(vert[0]//step) + v = int(vert[1]//step) + u1 = min(u+1, sides) + v1 = min(v+1, sides) + if mode != 'BOUNDS': + if u > sides-1: + u = sides-1 + u1 = sides + if u < 0: + u = 0 + u1 = 1 + if v > sides-1: + v = sides-1 + v1 = sides + if v < 0: + v = 0 + v1 = 1 + v00 = verts[u][v] + v10 = verts[u1][v] + v01 = verts[u][v1] + v11 = verts[u1][v1] + # factor coordinates + fu = (vert[0]-u*step)/step + fv = (vert[1]-v*step)/step + fw = vert.z + # interpolate Z scaling factor + fvec2d = Vector((fu,fv,0)) + if scale_mode == 'ADAPTIVE': + a00 = verts_area[v00.index] + a10 = verts_area[v10.index] + a01 = verts_area[v01.index] + a11 = verts_area[v11.index] + fw*=lerp2(a00,a10,a01,a11,fvec2d) + # build factor vector + fvec = Vector((fu,fv,fw)) + # interpolate vertex on patch + patch_vert.co = lerp3(v00, v10, v01, v11, fvec) + + # Vertex Group + if bool_vertex_group: + for _weight, vg in zip(weight, new_patch.vertex_groups): + w00 = _weight[v00.index] + w10 = _weight[v10.index] + w01 = _weight[v01.index] + w11 = _weight[v11.index] + wuv = lerp2(w00,w10,w01,w11, fvec2d) + vg.add([patch_vert.index], wuv, "ADD") + + if bool_shapekeys: + for sk in ob1.data.shape_keys.key_blocks: + source = sk.data + for sk_v, _v in zip(source, me1.vertices): + if mode == 'BOUNDS': + sk_vert = sk_v.co - min_c # (ob1.matrix_world * v.co) - min_c + sk_vert[0] = (sk_vert[0] / bb[0] if bb[0] != 0 else 0.5) + sk_vert[1] = (sk_vert[1] / bb[1] if bb[1] != 0 else 0.5) + sk_vert[2] = (sk_vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale + elif mode == 'LOCAL': + sk_vert = sk_v.co#.xyzco + #sk_vert[2] *= zscale + #sk_vert[2] = (sk_vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale + elif mode == 'GLOBAL': + #sk_vert = ob1.matrix_world @ sk_v.co + sk_vert = sk_v.co + #sk_vert[2] *= zscale + + # grid coordinates + u = int(sk_vert[0]//step) + v = int(sk_vert[1]//step) + u1 = min(u+1, sides) + v1 = min(v+1, sides) + if mode != 'BOUNDS': + if u > sides-1: + u = sides-1 + u1 = sides + if u < 0: + u = 0 + u1 = 1 + if v > sides-1: + v = sides-1 + v1 = sides + if v < 0: + v = 0 + v1 = 1 + v00 = verts[u][v] + v10 = verts[u1][v] + v01 = verts[u][v1] + v11 = verts[u1][v1] + # factor coordinates + fu = (sk_vert[0]-u*step)/step + fv = (sk_vert[1]-v*step)/step + fw = sk_vert.z + + if scale_mode == 'ADAPTIVE': + a00 = verts_area[v00.index] + a10 = verts_area[v10.index] + a01 = verts_area[v01.index] + a11 = verts_area[v11.index] + fw*=lerp2(a00,a10,a01,a11,Vector((fu,fv,0))) + + fvec = Vector((fu,fv,fw)) + sk_co = lerp3(v00, v10, v01, v11, fvec) + + new_patch.data.shape_keys.key_blocks[sk.name].data[_v.index].co = sk_co + + #if ob0.type == 'MESH': ob0.data = old_me0 + if not bool_correct: return 0 + + bpy.ops.object.join() + + if bool_shapekeys: + # set original values and combine Shape Keys and Vertex Groups + for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): + sk.value = val + new_patch.data.shape_keys.key_blocks[sk.name].value = val + if bool_vertex_group: + for sk in new_patch.data.shape_keys.key_blocks: + for vg in new_patch.vertex_groups: + if sk.name == vg.name: + sk.vertex_group = vg.name - # component vertices - vs1 = np.array([v for v in verts1]).reshape(len(verts1), 3, 1) - vx = vs1[:, 0] - vy = vs1[:, 1] - vz = vs1[:, 2] + new_name = ob0.name + "_" + ob1.name + new_patch.name = "tessellate_temp" + + if bool_multires: + for m in ob0.modifiers: + if m.type == 'MULTIRES' and m.name == multires_name: + m.levels = levels + m.sculpt_levels = sculpt_levels + m.render_levels = render_levels + # restore original modifiers visibility for component object + try: + for m, vis in zip(_ob1.modifiers, mod_visibility): + m.show_viewport = vis + except: pass + + bpy.data.objects.remove(before) + bpy.data.objects.remove(ob0) + bpy.data.objects.remove(ob1) + return new_patch + +def tessellate_original(_ob0, _ob1, offset, zscale, gen_modifiers, com_modifiers, mode, + scale_mode, rotation_mode, rand_seed, fill_mode, + bool_vertex_group, bool_selection, bool_shapekeys, + bool_material_id, material_id, normals_mode, bounds_x, bounds_y): + + if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False + random.seed(rand_seed) + + if bool_shapekeys: + try: + original_key_values = [] + for sk in _ob1.data.shape_keys.key_blocks: + original_key_values.append(sk.value) + sk.value = 0 + except: + bool_shapekeys = False + + ob0 = convert_object_to_mesh(_ob0, gen_modifiers, True) + me0 = ob0.data + ob1 = convert_object_to_mesh(_ob1, com_modifiers, True) + me1 = ob1.data + + base_polygons = [] + base_face_normals = [] + + n_faces0 = len(me0.polygons) + + # Check if zero faces are selected + if (bool_selection and ob0.type == 'MESH') or bool_material_id: + for p in me0.polygons: + if (bool_selection and ob0.type == 'MESH'): + is_sel = p.select + else: is_sel = True + if bool_material_id: + is_mat = p.material_index == material_id + else: is_mat = True + if is_sel and is_mat: + base_polygons.append(p) + base_face_normals.append(p.normal) + else: + base_polygons = me0.polygons + base_face_normals = [p.normal for p in me0.polygons] + + # numpy test: slower + #base_face_normals = np.zeros(n_faces0*3) + #me0.polygons.foreach_get("normal", base_face_normals) + #base_face_normals = base_face_normals.reshape((n_faces0,3)) + + if len(base_polygons) == 0: + return 0 + + if mode != 'BOUNDS': + + bpy.ops.object.select_all(action='DESELECT') + for o in bpy.context.view_layer.objects: o.select_set(False) + bpy.context.view_layer.objects.active = ob1 + ob1.select_set(True) + bpy.context.object.active_shape_key_index = 0 + # Bound X + if bounds_x != 'EXTEND': + if mode == 'GLOBAL': + planes_co = ((0,0,0),(1,1,1)) + plane_no = (1,0,0) + if mode == 'LOCAL': + planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((1,0,0))) + plane_no = planes_co[0]-planes_co[1] + bpy.ops.object.mode_set(mode='EDIT') + for co in planes_co: + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) + bpy.ops.mesh.mark_seam() + bpy.ops.object.mode_set(mode='OBJECT') + _faces = ob1.data.polygons + if mode == 'GLOBAL': + for f in [f for f in _faces if (ob1.matrix_world @ f.center).x > 1]: + f.select = True + for f in [f for f in _faces if (ob1.matrix_world @ f.center).x < 0]: + f.select = True + else: + for f in [f for f in _faces if f.center.x > 1]: + f.select = True + for f in [f for f in _faces if f.center.x < 0]: + f.select = True + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='FACE') + if bounds_x == 'CLIP': + bpy.ops.mesh.delete(type='FACE') + bpy.ops.object.mode_set(mode='OBJECT') + if bounds_x == 'CYCLIC': + bpy.ops.mesh.split() + bpy.ops.object.mode_set(mode='OBJECT') + # Bound Y + if bounds_y != 'EXTEND': + if mode == 'GLOBAL': + planes_co = ((0,0,0),(1,1,1)) + plane_no = (0,1,0) + if mode == 'LOCAL': + planes_co = (ob1.matrix_world @ Vector((0,0,0)), ob1.matrix_world @ Vector((0,1,0))) + plane_no = planes_co[0]-planes_co[1] + bpy.ops.object.mode_set(mode='EDIT') + for co in planes_co: + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.bisect(plane_co=co, plane_no=plane_no) + bpy.ops.mesh.mark_seam() + bpy.ops.object.mode_set(mode='OBJECT') + _faces = ob1.data.polygons + if mode == 'GLOBAL': + for f in [f for f in _faces if (ob1.matrix_world @ f.center).y > 1]: + f.select = True + for f in [f for f in _faces if (ob1.matrix_world @ f.center).y < 0]: + f.select = True + else: + for f in [f for f in _faces if f.center.y > 1]: + f.select = True + for f in [f for f in _faces if f.center.y < 0]: + f.select = True + + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='FACE') + if bounds_y == 'CLIP': + bpy.ops.mesh.delete(type='FACE') + bpy.ops.object.mode_set(mode='OBJECT') + if bounds_y == 'CYCLIC': + bpy.ops.mesh.split() + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.mode_set(mode='OBJECT') + #ob1 = new_ob1 + + me1 = ob1.data + + verts0 = me0.vertices # Collect generator vertices + + # Component statistics + n_verts1 = len(me1.vertices) + n_edges1 = len(me1.edges) + n_faces1 = len(me1.polygons) + + # Create empty lists + new_verts = [] + new_edges = [] + new_faces = [] + new_verts_np = np.array(()) + + # Component Coordinates + co1 = [0]*n_verts1*3 + + if mode == 'GLOBAL': + for v in me1.vertices: + v.co = ob1.matrix_world @ v.co + try: + for sk in me1.shape_keys.key_blocks: + sk.data[v.index].co = ob1.matrix_world @ sk.data[v.index].co + except: pass + if mode != 'BOUNDS': + if bounds_x == 'CYCLIC': + move_verts = [] + for f in [f for f in me1.polygons if (f.center).x > 1]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.x -= 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.x -= 1 + except: pass + move_verts = [] + for f in [f for f in me1.polygons if (f.center).x < 0]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.x += 1 + try: + _ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.x += 1 + except: pass + if bounds_y == 'CYCLIC': + move_verts = [] + for f in [f for f in me1.polygons if (f.center).y > 1]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.y -= 1 + try: + #new_ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.y -= 1 + except: pass + move_verts = [] + for f in [f for f in me1.polygons if (f.center).y < 0]: + for v in f.vertices: + if v not in move_verts: move_verts.append(v) + for v in move_verts: + me1.vertices[v].co.y += 1 + try: + #new_ob1.active_shape_key_index = 0 + for sk in me1.shape_keys.key_blocks: + sk.data[v].co.y += 1 + except: pass + + + me1.vertices.foreach_get("co", co1) + co1 = np.array(co1) + vx = co1[0::3].reshape((n_verts1,1)) + vy = co1[1::3].reshape((n_verts1,1)) + vz = co1[2::3].reshape((n_verts1,1)) + min_c = Vector((vx.min(), vy.min(), vz.min())) # Min BB Corner + max_c = Vector((vx.max(), vy.max(), vz.max())) # Max BB Corner + bb = max_c - min_c # Bounding Box + + # Component Coordinates + if mode == 'BOUNDS': + vx = (vx - min_c[0]) / bb[0] if bb[0] != 0 else 0.5 + vy = (vy - min_c[1]) / bb[1] if bb[1] != 0 else 0.5 + vz = ((vz - min_c[2]) + (-0.5 + offset * 0.5) * bb[2]) * zscale + else: + vz *= zscale # Component polygons fs1 = [[i for i in p.vertices] for p in me1.polygons] new_faces = fs1[:] # Component edges - es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose] + es1 = np.array([[i for i in e.vertices] for e in me1.edges]) + #es1 = [[i for i in e.vertices] for e in me1.edges if e.is_loose] new_edges = es1[:] # SHAPE KEYS - shapekeys = [] - do_shapekeys = False - if me1.shape_keys is not None and bool_shapekeys: - if len(me1.shape_keys.key_blocks) > 1: + if bool_shapekeys: + basis = True #com_modifiers + vx_key = [] + vy_key = [] + vz_key = [] + sk_np = [] + for sk in ob1.data.shape_keys.key_blocks: do_shapekeys = True + # set all keys to 0 + for _sk in ob1.data.shape_keys.key_blocks: _sk.value = 0 + sk.value = 1 - # Read active key - active_key = ob1.active_shape_key_index - if active_key == 0: - active_key = 1 + if basis: + basis = False + continue + + # Apply component modifiers + if com_modifiers: + sk_ob = convert_object_to_mesh(_ob1) + sk_data = sk_ob.data + source = sk_data.vertices + else: + source = sk.data - for v in me1.shape_keys.key_blocks[active_key].data: - if mode == "ADAPTIVE": + shapekeys = [] + for v in source: + if mode == 'BOUNDS': vert = v.co - min_c vert[0] = vert[0] / bb[0] vert[1] = vert[1] / bb[1] vert[2] = (vert[2] + (-0.5 + offset * 0.5) * bb[2]) * zscale - else: + elif mode == 'LOCAL': + vert = v.co.xyz + vert[2] *= zscale + #vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \ + # zscale + elif mode == 'GLOBAL': vert = v.co.xyz - vert[2] = (vert[2] - min_c[2] + (-0.5 + offset * 0.5) * bb[2]) * \ - zscale + #vert = ob1.matrix_world @ v.co + vert[2] *= zscale shapekeys.append(vert) # Component vertices key1 = np.array([v for v in shapekeys]).reshape(len(shapekeys), 3, 1) - vx_key = key1[:, 0] - vy_key = key1[:, 1] - vz_key = key1[:, 2] + vx_key.append(key1[:, 0]) + vy_key.append(key1[:, 1]) + vz_key.append(key1[:, 2]) + #sk_np.append([]) - # Active vertex group + # All vertex group if bool_vertex_group: try: weight = [] - group_index = ob0.vertex_groups.active_index - active_vertex_group = ob0.vertex_groups[group_index] - for v in me0.vertices: - try: - weight.append(active_vertex_group.weight(v.index)) - except: - weight.append(0) + vertex_groups = ob0.vertex_groups + for vg in vertex_groups: + _weight = [] + for v in me0.vertices: + try: + _weight.append(vg.weight(v.index)) + #print(vg.weight(v.index)) + #_weight.append(v.groups[0]) + except: + _weight.append(0) + weight.append(_weight) except: bool_vertex_group = False + # Adaptive Z + if scale_mode == 'ADAPTIVE': + if mode == 'BOUNDS': com_area = (bb[0]*bb[1]) + else: com_area = 1 + if com_area == 0: mult = 1 + else: mult = 1/com_area + verts_area = [] + bm = bmesh.new() + bm.from_mesh(me0) + bm.verts.ensure_lookup_table() + for v in bm.verts: + area = 0 + faces = v.link_faces + for f in faces: + area += f.calc_area() + try: + area/=len(faces) + area*=mult + verts_area.append(sqrt(area)) + except: + verts_area.append(1) + # FAN tessellation mode if fill_mode == 'FAN': fan_verts = [v.co.to_tuple() for v in me0.vertices] fan_polygons = [] + fan_select = [] + fan_material = [] + fan_normals = [] # selected_faces = [] for p in base_polygons: - # if bool_selection and not p.select: continue fan_center = Vector((0, 0, 0)) + center_area = 0 for v in p.vertices: fan_center += me0.vertices[v].co + if scale_mode == 'ADAPTIVE': + center_area += verts_area[v] fan_center /= len(p.vertices) + center_area /= len(p.vertices) + last_vert = len(fan_verts) fan_verts.append(fan_center.to_tuple()) + #fan_verts.append(fan_center) + if scale_mode == 'ADAPTIVE': + verts_area.append(center_area) # Vertex Group if bool_vertex_group: - center_weight = sum([weight[i] for i in p.vertices]) / len(p.vertices) - weight.append(center_weight) + for w in weight: + center_weight = sum([w[i] for i in p.vertices]) / len(p.vertices) + w.append(center_weight) for i in range(len(p.vertices)): fan_polygons.append((p.vertices[i], p.vertices[(i + 1) % len(p.vertices)], last_vert, last_vert)) - # if bool_selection: selected_faces.append(p.select) + + if bool_material_id: fan_material.append(p.material_index) + if bool_selection: fan_select.append(p.select) + if normals_mode == 'FACES': + fan_normals.append(p.normal) + fan_me = bpy.data.meshes.new('Fan.Mesh') fan_me.from_pydata(tuple(fan_verts), [], tuple(fan_polygons)) - me0 = fan_me + me0 = fan_me.copy() + bpy.data.meshes.remove(fan_me) verts0 = me0.vertices base_polygons = me0.polygons - """ - for i in range(len(selected_faces)): - fan_me.polygons[i].select = selected_faces[i] - """ + if normals_mode == 'FACES': base_face_normals = fan_normals + count = 0 # necessary for UV calculation # TESSELLATION j = 0 + jj = -1 + bool_correct = False + + # optimization test + n_faces = len(base_polygons) + _vs0 = [0]*n_faces + _nvs0 = [0]*n_faces + _sz = [0]*n_faces + _w0 = [[0]*n_faces]*len(ob0.vertex_groups) + np_faces = [np.array(p) for p in fs1] + new_faces = [0]*n_faces*n_faces1 + face1_count = 0 + for p in base_polygons: + + bool_correct = True + if rotation_mode == 'UV' and ob0.type != 'MESH': + rotation_mode = 'DEFAULT' + # Random rotation if rotation_mode == 'RANDOM': shifted_vertices = [] @@ -258,211 +1379,333 @@ def tassellate(ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, mode, rand = random.randint(0, n_poly_verts) for i in range(n_poly_verts): shifted_vertices.append(p.vertices[(i + rand) % n_poly_verts]) + if scale_mode == 'ADAPTIVE': + verts_area0 = np.array([verts_area[i] for i in shifted_vertices]) vs0 = np.array([verts0[i].co for i in shifted_vertices]) nvs0 = np.array([verts0[i].normal for i in shifted_vertices]) + if normals_mode == 'VERTS': + nvs0 = np.array([verts0[i].normal for i in shifted_vertices]) # vertex weight if bool_vertex_group: ws0 = [] - for i in shifted_vertices: - try: - ws0.append(weight[i]) - except: - ws0.append(0) - - ws0 = np.array(ws0) + for w in weight: + _ws0 = [] + for i in shifted_vertices: + try: + _ws0.append(w[i]) + except: + _ws0.append(0) + ws0.append(np.array(_ws0)) # UV rotation - elif rotation_mode == 'UV' and len(ob0.data.uv_layers) > 0 and \ - fill_mode != 'FAN': - i = p.index - v01 = (me0.uv_layers.active.data[count].uv + - me0.uv_layers.active.data[count + 1].uv) - if len(p.vertices) > 3: - v32 = (me0.uv_layers.active.data[count + 3].uv + - me0.uv_layers.active.data[count + 2].uv) - else: - v32 = (me0.uv_layers.active.data[count].uv + + elif rotation_mode == 'UV': + if len(ob0.data.uv_layers) > 0 and fill_mode != 'FAN': + i = p.index + if bool_material_id: + count = sum([len(p.vertices) for p in me0.polygons[:i]]) + #if i == 0: count = 0 + v01 = (me0.uv_layers.active.data[count].uv + + me0.uv_layers.active.data[count + 1].uv) + if len(p.vertices) > 3: + v32 = (me0.uv_layers.active.data[count + 3].uv + + me0.uv_layers.active.data[count + 2].uv) + else: + v32 = (me0.uv_layers.active.data[count].uv + + me0.uv_layers.active.data[count + 2].uv) + v0132 = v32 - v01 + v0132.normalize() + + v12 = (me0.uv_layers.active.data[count + 1].uv + me0.uv_layers.active.data[count + 2].uv) - v0132 = v32 - v01 - v0132.normalize() - - v12 = (me0.uv_layers.active.data[count + 1].uv + - me0.uv_layers.active.data[count + 2].uv) - if len(p.vertices) > 3: - v03 = (me0.uv_layers.active.data[count].uv + - me0.uv_layers.active.data[count + 3].uv) - else: - v03 = (me0.uv_layers.active.data[count].uv + - me0.uv_layers.active.data[count].uv) - v1203 = v03 - v12 - v1203.normalize() - - vertUV = [] - dot1203 = v1203.x - dot0132 = v0132.x - if(abs(dot1203) < abs(dot0132)): - if (dot0132 > 0): - vertUV = p.vertices[1:] + p.vertices[:1] + if len(p.vertices) > 3: + v03 = (me0.uv_layers.active.data[count].uv + + me0.uv_layers.active.data[count + 3].uv) else: - vertUV = p.vertices[3:] + p.vertices[:3] - else: - if(dot1203 < 0): - vertUV = p.vertices[:] + v03 = (me0.uv_layers.active.data[count].uv + + me0.uv_layers.active.data[count].uv) + v1203 = v03 - v12 + v1203.normalize() + + vertUV = [] + dot1203 = v1203.x + dot0132 = v0132.x + if(abs(dot1203) < abs(dot0132)): + if (dot0132 > 0): + vertUV = p.vertices[1:] + p.vertices[:1] + else: + vertUV = p.vertices[3:] + p.vertices[:3] else: - vertUV = p.vertices[2:] + p.vertices[:2] - vs0 = np.array([verts0[i].co for i in vertUV]) - nvs0 = np.array([verts0[i].normal for i in vertUV]) - - # Vertex weight - if bool_vertex_group: - ws0 = [] - for i in vertUV: - try: - ws0.append(weight[i]) - except: - ws0.append(0) - ws0 = np.array(ws0) - - count += len(p.vertices) + if(dot1203 < 0): + vertUV = p.vertices[:] + else: + vertUV = p.vertices[2:] + p.vertices[:2] + vs0 = np.array([verts0[i].co for i in vertUV]) + nvs0 = np.array([verts0[i].normal for i in vertUV]) + + # Vertex weight + if bool_vertex_group: + ws0 = [] + for w in weight: + _ws0 = [] + for i in vertUV: + try: + _ws0.append(w[i]) + except: + _ws0.append(0) + ws0.append(np.array(_ws0)) + + count += len(p.vertices) + else: rotation_mode = 'DEFAULT' # Default rotation - else: + if rotation_mode == 'DEFAULT': vs0 = np.array([verts0[i].co for i in p.vertices]) nvs0 = np.array([verts0[i].normal for i in p.vertices]) # Vertex weight if bool_vertex_group: ws0 = [] - for i in p.vertices: - try: - ws0.append(weight[i]) - except: - ws0.append(0) - ws0 = np.array(ws0) + for w in weight: + _ws0 = [] + for i in p.vertices: + try: + _ws0.append(w[i]) + except: + _ws0.append(0) + ws0.append(np.array(_ws0)) + + # optimization test + _vs0[j] = (vs0[0], vs0[1], vs0[2], vs0[-1]) + if normals_mode == 'VERTS': + _nvs0[j] = (nvs0[0], nvs0[1], nvs0[2], nvs0[-1]) + #else: + # _nvs0[j] = base_face_normals[j] - # considering only 4 vertices - vs0 = np.array((vs0[0], vs0[1], vs0[2], vs0[-1])) - nvs0 = np.array((nvs0[0], nvs0[1], nvs0[2], nvs0[-1])) - - # remapped vertex coordinates - v0 = vs0[0] + (vs0[1] - vs0[0]) * vx - v1 = vs0[3] + (vs0[2] - vs0[3]) * vx - v2 = v0 + (v1 - v0) * vy - - # remapped vertex normal - nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx - nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx - nv2 = nv0 + (nv1 - nv0) * vy # vertex z to normal - v3 = v2 + nv2 * vz * (sqrt(p.area) if scale_mode == "ADAPTIVE" else 1) + if scale_mode == 'ADAPTIVE': + poly_faces = (p.vertices[0], p.vertices[1], p.vertices[2], p.vertices[-1]) + if rotation_mode == 'RANDOM': sz = verts_area0 + else: sz = np.array([verts_area[i] for i in poly_faces]) + + _sz[j] = sz if bool_vertex_group: - ws0 = np.array((ws0[0], ws0[1], ws0[2], ws0[-1])) - # Interpolate vertex weight - w0 = ws0[0] + (ws0[1] - ws0[0]) * vx - w1 = ws0[3] + (ws0[2] - ws0[3]) * vx - w2 = w0 + (w1 - w0) * vy - - # Shapekeys - if do_shapekeys: - # remapped vertex coordinates - v0 = vs0[0] + (vs0[1] - vs0[0]) * vx_key - v1 = vs0[3] + (vs0[2] - vs0[3]) * vx_key - v2 = v0 + (v1 - v0) * vy_key - # remapped vertex normal - nv0 = nvs0[0] + (nvs0[1] - nvs0[0]) * vx_key - nv1 = nvs0[3] + (nvs0[2] - nvs0[3]) * vx_key - nv2 = nv0 + (nv1 - nv0) * vy_key - # vertex z to normal - v3_key = v2 + nv2 * vz_key * (sqrt(p.area) if - scale_mode == "ADAPTIVE" else 1) - v3 = v3 + (v3_key - v3) * w2 - - if j == 0: - new_verts_np = v3 - if bool_vertex_group: - new_vertex_group_np = w2 - else: - # Appending vertices - new_verts_np = np.concatenate((new_verts_np, v3), axis=0) - # Appending vertex group - if bool_vertex_group: - new_vertex_group_np = np.concatenate((new_vertex_group_np, w2), - axis=0) - # Appending faces - for p in fs1: - new_faces.append([i + n_verts * j for i in p]) - # Appending edges - for e in es1: - new_edges.append([i + n_verts * j for i in e]) + vg_count = 0 + for _ws0 in ws0: + _w0[vg_count][j] = (_ws0[0], _ws0[1], _ws0[2], _ws0[-1]) + vg_count += 1 + + for p in fs1: + new_faces[face1_count] = [i + n_verts1 * j for i in p] + face1_count += 1 j += 1 + # build edges list + n_edges1 = new_edges.shape[0] + new_edges = new_edges.reshape((1, n_edges1, 2)) + new_edges = new_edges.repeat(n_faces,axis=0) + new_edges = new_edges.reshape((n_edges1*n_faces, 2)) + increment = np.arange(n_faces)*n_verts1 + increment = increment.repeat(n_edges1, axis=0) + increment = increment.reshape((n_faces*n_edges1,1)) + new_edges = new_edges + increment + + # optimization test + _vs0 = np.array(_vs0) + _sz = np.array(_sz) + + _vs0_0 = _vs0[:,0].reshape((n_faces,1,3)) + _vs0_1 = _vs0[:,1].reshape((n_faces,1,3)) + _vs0_2 = _vs0[:,2].reshape((n_faces,1,3)) + _vs0_3 = _vs0[:,3].reshape((n_faces,1,3)) + + # remapped vertex coordinates + v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx + v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx + v2 = v0 + (v1 - v0) * vy + + # remapped vertex normal + if normals_mode == 'VERTS': + _nvs0 = np.array(_nvs0) + _nvs0_0 = _nvs0[:,0].reshape((n_faces,1,3)) + _nvs0_1 = _nvs0[:,1].reshape((n_faces,1,3)) + _nvs0_2 = _nvs0[:,2].reshape((n_faces,1,3)) + _nvs0_3 = _nvs0[:,3].reshape((n_faces,1,3)) + nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx + nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx + nv2 = nv0 + (nv1 - nv0) * vy + else: + nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) + + if bool_vertex_group: + n_vg = len(_w0) + w = np.array(_w0) + #for w in _w0: + #w = np.array(w) + w_0 = w[:,:,0].reshape((n_vg, n_faces,1,1)) + w_1 = w[:,:,1].reshape((n_vg, n_faces,1,1)) + w_2 = w[:,:,2].reshape((n_vg, n_faces,1,1)) + w_3 = w[:,:,3].reshape((n_vg, n_faces,1,1)) + # remapped weight + w0 = w_0 + (w_1 - w_0) * vx + w1 = w_3 + (w_2 - w_3) * vx + w = w0 + (w1 - w0) * vy + w = w.reshape((n_vg, n_faces*n_verts1)) + #w = w2.tolist() + + if scale_mode == 'ADAPTIVE': + _sz_0 = _sz[:,0].reshape((n_faces,1,1)) + _sz_1 = _sz[:,1].reshape((n_faces,1,1)) + _sz_2 = _sz[:,2].reshape((n_faces,1,1)) + _sz_3 = _sz[:,3].reshape((n_faces,1,1)) + # remapped z scale + sz0 = _sz_0 + (_sz_1 - _sz_0) * vx + sz1 = _sz_3 + (_sz_2 - _sz_3) * vx + sz2 = sz0 + (sz1 - sz0) * vy + v3 = v2 + nv2 * vz * sz2 + else: + v3 = v2 + nv2 * vz + + new_verts_np = v3.reshape((n_faces*n_verts1,3)) + + if bool_shapekeys: + n_sk = len(vx_key) + sk_np = [0]*n_sk + for i in range(n_sk): + vx = np.array(vx_key) + vy = np.array(vy_key) + vz = np.array(vz_key) + + # remapped vertex coordinates + v0 = _vs0_0 + (_vs0_1 - _vs0_0) * vx + v1 = _vs0_3 + (_vs0_2 - _vs0_3) * vx + v2 = v0 + (v1 - v0) * vy + + # remapped vertex normal + if normals_mode == 'VERTS': + nv0 = _nvs0_0 + (_nvs0_1 - _nvs0_0) * vx + nv1 = _nvs0_3 + (_nvs0_2 - _nvs0_3) * vx + nv2 = nv0 + (nv1 - nv0) * vy + else: + nv2 = np.array(base_face_normals).reshape((n_faces,1,3)) + + if scale_mode == 'ADAPTIVE': + # remapped z scale + sz0 = _sz_0 + (_sz_1 - _sz_0) * vx + sz1 = _sz_3 + (_sz_2 - _sz_3) * vx + sz2 = sz0 + (sz1 - sz0) * vy + v3 = v2 + nv2 * vz * sz2 + else: + v3 = v2 + nv2 * vz + + sk_np[i] = v3.reshape((n_faces*n_verts1,3)) + + #if ob0.type == 'MESH': ob0.data = old_me0 + + if not bool_correct: return 0 + new_verts = new_verts_np.tolist() new_name = ob0.name + "_" + ob1.name new_me = bpy.data.meshes.new(new_name) - new_me.from_pydata(new_verts, new_edges, new_faces) + new_me.from_pydata(new_verts, new_edges.tolist(), new_faces) new_me.update(calc_edges=True) new_ob = bpy.data.objects.new("tessellate_temp", new_me) # vertex group + if bool_vertex_group and False: + for vg in ob0.vertex_groups: + new_ob.vertex_groups.new(name=vg.name) + for i in range(len(vg_np[vg.index])): + new_ob.vertex_groups[vg.name].add([i], vg_np[vg.index][i],"ADD") + # vertex group if bool_vertex_group: - new_ob.vertex_groups.new(name="generator_group") - for i in range(len(new_vertex_group_np)): - new_ob.vertex_groups["generator_group"].add([i], - new_vertex_group_np[i], - "ADD") - ob0.data = old_me0 + for vg in ob0.vertex_groups: + new_ob.vertex_groups.new(name=vg.name) + for i in range(len(w[vg.index])): + new_ob.vertex_groups[vg.name].add([i], w[vg.index,i],"ADD") + + if bool_shapekeys: + basis = com_modifiers + sk_count = 0 + for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values): + sk.value = val + new_ob.shape_key_add(name=sk.name) + new_ob.data.shape_keys.key_blocks[sk.name].value = val + # set shape keys vertices + sk_data = new_ob.data.shape_keys.key_blocks[sk.name].data + if sk_count == 0: + sk_count += 1 + continue + for id in range(len(sk_data)): + sk_data[id].co = sk_np[sk_count-1][id] + sk_count += 1 + if bool_vertex_group: + for sk in new_ob.data.shape_keys.key_blocks: + for vg in new_ob.vertex_groups: + if sk.name == vg.name: + sk.vertex_group = vg.name + + # EDGES SEAMS + edge_data = [0]*n_edges1 + me1.edges.foreach_get("use_seam",edge_data) + if any(edge_data): + edge_data = edge_data*n_faces + new_ob.data.edges.foreach_set("use_seam",edge_data) + + # EDGES SHARP + edge_data = [0]*n_edges1 + me1.edges.foreach_get("use_edge_sharp",edge_data) + if any(edge_data): + edge_data = edge_data*n_faces + new_ob.data.edges.foreach_set("use_edge_sharp",edge_data) + + bpy.ops.object.select_all(action='DESELECT') + bpy.context.collection.objects.link(new_ob) + new_ob.select_set(True) + bpy.context.view_layer.objects.active = new_ob + + # EDGES BEVEL + edge_data = [0]*n_edges1 + me1.edges.foreach_get("bevel_weight",edge_data) + if any(edge_data): + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.transform.edge_bevelweight(value=1) + bpy.ops.object.mode_set(mode='OBJECT') + edge_data = edge_data*n_faces + new_ob.data.edges.foreach_set("bevel_weight",edge_data) - if me0_owner: - me0_owner.to_mesh_clear() + # EDGE CREASES + edge_data = [0]*n_edges1 + me1.edges.foreach_get("crease",edge_data) + if any(edge_data): + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.transform.edge_crease(value=1) + bpy.ops.object.mode_set(mode='OBJECT') + edge_data = edge_data*n_faces + new_ob.data.edges.foreach_set('crease', edge_data) - if me1_owner: - me1_owner.to_mesh_clear() + # MATERIALS + for slot in ob1.material_slots: new_ob.data.materials.append(slot.material) - return new_ob + polygon_materials = [0]*n_faces1 + me1.polygons.foreach_get("material_index", polygon_materials) + polygon_materials *= n_faces + new_ob.data.polygons.foreach_set("material_index", polygon_materials) + new_ob.data.update() ### -def store_parameters(operator, ob): - ob.tissue_tessellate.generator = operator.generator - ob.tissue_tessellate.component = operator.component - ob.tissue_tessellate.zscale = operator.zscale - ob.tissue_tessellate.offset = operator.offset - ob.tissue_tessellate.gen_modifiers = operator.gen_modifiers - ob.tissue_tessellate.com_modifiers = operator.com_modifiers - ob.tissue_tessellate.mode = operator.mode - ob.tissue_tessellate.rotation_mode = operator.rotation_mode - ob.tissue_tessellate.merge = operator.merge - ob.tissue_tessellate.merge_thres = operator.merge_thres - ob.tissue_tessellate.scale_mode = operator.scale_mode - ob.tissue_tessellate.bool_random = operator.bool_random - ob.tissue_tessellate.random_seed = operator.random_seed - ob.tissue_tessellate.fill_mode = operator.fill_mode - ob.tissue_tessellate.bool_vertex_group = operator.bool_vertex_group - ob.tissue_tessellate.bool_selection = operator.bool_selection - ob.tissue_tessellate.bool_shapekeys = operator.bool_shapekeys - return ob - + try: + bpy.data.objects.remove(new_ob1) + except: pass -class tissue_tessellate_prop(PropertyGroup): - generator: StringProperty() - component: StringProperty() - offset: FloatProperty() - zscale: FloatProperty(default=1) - merge: BoolProperty() - merge_thres: FloatProperty() - gen_modifiers: BoolProperty() - com_modifiers: BoolProperty() - mode: StringProperty() - rotation_mode: StringProperty() - scale_mode: StringProperty() - fill_mode: StringProperty() - bool_random: BoolProperty() - random_seed: IntProperty() - vertexgroup: StringProperty() - bool_vertex_group: BoolProperty() - bool_selection: BoolProperty() - bool_shapekeys: BoolProperty() + bpy.data.objects.remove(ob0) + bpy.data.meshes.remove(me0) + bpy.data.objects.remove(ob1) + bpy.data.meshes.remove(me1) + return new_ob class tessellate(Operator): @@ -472,179 +1715,277 @@ class tessellate(Operator): "faces, adapting the shape to the different faces") bl_options = {'REGISTER', 'UNDO'} - object_name: StringProperty( + + object_name : StringProperty( name="", description="Name of the generated object" ) - zscale: FloatProperty( + zscale : FloatProperty( name="Scale", default=1, soft_min=0, soft_max=10, description="Scale factor for the component thickness" ) - scale_mode: EnumProperty( - items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")), - default='CONSTANT', + scale_mode : EnumProperty( + items=( + ('CONSTANT', "Constant", "Uniform thickness"), + ('ADAPTIVE', "Proportional", "Preserve component's proportions") + ), + default='ADAPTIVE', name="Z-Scale according to faces size" ) - offset: FloatProperty( + offset : FloatProperty( name="Surface Offset", - default=0, + default=1, min=-1, max=1, soft_min=-1, soft_max=1, description="Surface offset" ) - mode: EnumProperty( - items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")), - default='ADAPTIVE', + mode : EnumProperty( + items=( + ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"), + ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"), + ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")), + default='BOUNDS', name="Component Mode" ) - rotation_mode: EnumProperty( - items=(('RANDOM', "Random", ""), - ('UV', "Active UV", ""), - ('DEFAULT', "Default", "")), + rotation_mode : EnumProperty( + items=(('RANDOM', "Random", "Random faces rotation"), + ('UV', "Active UV", "Face rotation is based on UV coordinates"), + ('DEFAULT', "Default", "Default rotation")), default='DEFAULT', name="Component Rotation" ) - fill_mode: EnumProperty( - items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")), + fill_mode : EnumProperty( + items=( + ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'), + ('FAN', 'Fan', 'Radial tessellation for polygonal faces'), + ('PATCH', 'Patch', 'Curved tessellation according to the last ' + + 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' + + 'patches.\nAfter the last Subsurf (or Multires) only ' + + 'deformation\nmodifiers can be used')), default='QUAD', name="Fill Mode" ) - gen_modifiers: BoolProperty( + combine_mode : EnumProperty( + items=( + ('LAST', 'Last', 'Show only the last iteration'), + ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'), + ('ALL', 'All', 'Combine the result of all iterations')), + default='LAST', + name="Combine Mode", + ) + gen_modifiers : BoolProperty( name="Generator Modifiers", default=False, - description="Apply modifiers to base object" + description="Apply Modifiers and Shape Keys to the base object" ) - com_modifiers: BoolProperty( + com_modifiers : BoolProperty( name="Component Modifiers", default=False, - description="Apply modifiers to component object" + description="Apply Modifiers and Shape Keys to the component object" ) - merge: BoolProperty( + merge : BoolProperty( name="Merge", default=False, description="Merge vertices in adjacent duplicates" ) - merge_thres: FloatProperty( + merge_thres : FloatProperty( name="Distance", default=0.001, soft_min=0, soft_max=10, description="Limit below which to merge vertices" ) - generator: StringProperty( - name="", - description="Base object for the tessellation" - ) - component: StringProperty( - name="", - description="Component object for the tessellation" - ) - bool_random: BoolProperty( + bool_random : BoolProperty( name="Randomize", default=False, description="Randomize component rotation" ) - random_seed: IntProperty( + random_seed : IntProperty( name="Seed", default=0, soft_min=0, soft_max=10, description="Random seed" ) - bool_vertex_group: BoolProperty( - name="Map Vertex Group", + bool_vertex_group : BoolProperty( + name="Map Vertex Groups", default=False, - description="Map the active " - "Vertex Group from the Base object to generated geometry" + description="Transfer all Vertex Groups from Base object" ) - bool_selection: BoolProperty( + bool_selection : BoolProperty( name="On selected Faces", default=False, description="Create Tessellation only on selected faces" ) - bool_shapekeys: BoolProperty( + bool_shapekeys : BoolProperty( name="Use Shape Keys", default=False, - description="Use component's active Shape Key according to " - "active Vertex Group of the base object" + description="Transfer Component's Shape Keys. If the name of Vertex " + "Groups and Shape Keys are the same, they will be " + "automatically combined" ) - working_on = "" - - @staticmethod - def check_gen_comp(checking): - # note pass the stored name key in here to check it out - return checking in bpy.data.objects.keys() + bool_smooth : BoolProperty( + name="Smooth Shading", + default=False, + description="Output faces with smooth shading rather than flat shaded" + ) + bool_materials : BoolProperty( + name="Transfer Materials", + default=True, + description="Preserve component's materials" + ) + generator : StringProperty( + name="", + description="Base object for the tessellation", + default = "" + ) + component : StringProperty( + name="", + description="Component object for the tessellation", + default = "" + ) + bool_material_id : BoolProperty( + name="Tessellation on Material ID", + default=False, + description="Apply the component only on the selected Material" + ) + bool_dissolve_seams : BoolProperty( + name="Dissolve Seams", + default=False, + description="Dissolve all seam edges" + ) + material_id : IntProperty( + name="Material ID", + default=0, + min=0, + description="Material ID" + ) + iterations : IntProperty( + name="Iterations", + default=1, + min=1, + soft_max=5, + description="Automatically repeat the Tessellation using the " + + "generated geometry as new base object.\nUsefull for " + + "for branching systems. Dangerous!" + ) + bool_combine : BoolProperty( + name="Combine unused", + default=False, + description="Combine the generated geometry with unused faces" + ) + bool_advanced : BoolProperty( + name="Advanced Settings", + default=False, + description="Show more settings" + ) + normals_mode : EnumProperty( + items=( + ('VERTS', 'Along Normals', 'Consistent direction based on vertices normal'), + ('FACES', 'Individual Faces', 'Based on individual faces normal')), + default='VERTS', + name="Direction" + ) + bool_multi_components : BoolProperty( + name="Multi Components", + default=False, + description="Combine different components according to materials name" + ) + bounds_x : EnumProperty( + items=( + ('EXTEND', 'Extend', 'Default X coordinates'), + ('CLIP', 'Clip', 'Trim out of bounds in X direction'), + ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')), + default='EXTEND', + name="Bounds X", + ) + bounds_y : EnumProperty( + items=( + ('EXTEND', 'Extend', 'Default Y coordinates'), + ('CLIP', 'Clip', 'Trim out of bounds in Y direction'), + ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')), + default='EXTEND', + name="Bounds Y", + ) + cap_faces : BoolProperty( + name="Cap Holes", + default=False, + description="Cap open edges loops" + ) + open_edges_crease : FloatProperty( + name="Open Edges Crease", + default=0, + min=0, + max=1, + description="Automatically set crease for open edges" + ) + working_on : "" def draw(self, context): + allowed_obj = ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META') + ''' try: bool_working = self.working_on == self.object_name and \ self.working_on != "" except: bool_working = False + ''' + + bool_working = False + bool_allowed = False + ob0 = None + ob1 = None sel = bpy.context.selected_objects + if len(sel) == 1: + try: + ob0 = sel[0].tissue_tessellate.generator + ob1 = sel[0].tissue_tessellate.component + self.generator = ob0.name + self.component = ob1.name + bool_working = True + bool_allowed = True + except: + pass - bool_meshes = False if len(sel) == 2: - bool_meshes = True + bool_allowed = True for o in sel: - if o.type != 'MESH': - bool_meshes = False + if o.type not in allowed_obj: + bool_allowed = False if len(sel) != 2 and not bool_working: layout = self.layout layout.label(icon='INFO') layout.label(text="Please, select two different objects") layout.label(text="Select first the Component object, then select") - layout.label(text="the Base mesh.") - elif not bool_meshes and not bool_working: + layout.label(text="the Base object.") + elif not bool_allowed and not bool_working: layout = self.layout layout.label(icon='INFO') - layout.label(text="Please, select two Mesh objects") + layout.label(text="Only Mesh, Curve, Surface or Text objects are allowed") else: - try: - ob0 = bpy.data.Objects[self.generator] - except: + if ob0 == ob1 == None: ob0 = bpy.context.active_object self.generator = ob0.name - - for o in sel: - if (o.name == ob0.name or o.type != 'MESH'): - continue - else: - ob1 = o - self.component = o.name - self.no_component = False - break - - # Checks for Tool Shelf panel, it lost the original Selection - if bpy.context.active_object.name == self.object_name: - # checks if the objects were deleted - if self.check_gen_comp( - bpy.context.active_object.tissue_tessellate.component): - ob1 = bpy.data.objects[ - bpy.context.active_object.tissue_tessellate.component - ] - self.component = ob1.name - - if self.check_gen_comp( - bpy.context.active_object.tissue_tessellate.generator): - ob0 = bpy.data.objects[ - bpy.context.active_object.tissue_tessellate.generator - ] - self.generator = ob0.name - self.no_component = False + for o in sel: + if o != ob0: + ob1 = o + self.component = o.name + self.no_component = False + break # new object name if self.object_name == "": if self.generator == "": self.object_name = "Tessellation" else: - self.object_name = self.generator + "_Tessellation" + #self.object_name = self.generator + "_Tessellation" + self.object_name = "Tessellation" layout = self.layout # Base and Component @@ -652,100 +1993,54 @@ class tessellate(Operator): row = col.row(align=True) row.label(text="BASE : " + self.generator) row.label(text="COMPONENT : " + self.component) + + # Base Modifiers row = col.row(align=True) col2 = row.column(align=True) - col2.prop(self, "gen_modifiers", text="Use Modifiers") - - if not self.check_gen_comp(self.generator) or \ - len(bpy.data.objects[self.generator].modifiers) == 0: + col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER') + base = bpy.data.objects[self.generator] + try: + if not (base.modifiers or base.data.shape_keys): + col2.enabled = False + self.gen_modifiers = False + except: col2.enabled = False self.gen_modifiers = False - col2 = row.column(align=True) - col2.prop(self, "com_modifiers", text="Use Modifiers") - - if not self.check_gen_comp(self.component) or \ - len(bpy.data.objects[self.component].modifiers) == 0: - col2.enabled = False - self.com_modifiers = False - # On selected faces - row = col.row(align=True) - row.prop(self, "bool_selection", text="On selected Faces") - col.separator() - - # General - col = layout.column(align=True) - col.label(text="New Object Name:") - col.prop(self, "object_name") - - # Count number of faces - if self.gen_modifiers or self.com_modifiers: - depsgraph = context.evaluated_depsgraph_get() - else: - depsgraph = None + # Component Modifiers + row.separator() + col3 = row.column(align=True) + col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER') + component = bpy.data.objects[self.component] try: - polygons = 0 - me_temp_owner = None - if self.gen_modifiers: - me_temp_owner = ob0.evaluated_get(depsgraph) - me_temp = me_temp_owner.to_mesh() - else: - me_temp_owner = None - me_temp = ob0.data - - for p in me_temp.polygons: - if not self.bool_selection or p.select: - if self.fill_mode == "FAN": - polygons += len(p.vertices) - else: - polygons += 1 - - if me_temp_owner: - me_temp_owner.to_mesh_clear() - - if self.com_modifiers: - me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph) - me_temp = me_temp_owner.to_mesh() - else: - me_temp_owner = None - me_temp = bpy.data.objects[self.component].data - polygons *= len(me_temp.polygons) - if me_temp_owner: - me_temp_owner.to_mesh_clear() - - str_polygons = '{:0,.0f}'.format(polygons) - if polygons > 200000: - col.label(text=str_polygons + " polygons will be created!", - icon='ERROR') - else: - col.label(text=str_polygons + " faces will be created!", - icon='INFO') + if not (component.modifiers or component.data.shape_keys): + col3.enabled = False + self.com_modifiers = False except: - pass + col3.enabled = False + self.com_modifiers = False col.separator() - # Fill and Rotation row = col.row(align=True) row.label(text="Fill Mode:") - row.separator() row.label(text="Rotation:") row = col.row(align=True) - - # Fill + #col2 = row.column(align=True) row.prop( self, "fill_mode", text="", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) - row.separator() # Rotation - row.prop( + row.separator() + col2 = row.column(align=True) + col2.prop( self, "rotation_mode", text="", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if self.rotation_mode == 'RANDOM': - row = col.row(align=True) - row.prop(self, "random_seed") + col2.prop(self, "random_seed") + if self.rotation_mode == 'UV': uv_error = False if self.fill_mode == 'FAN': @@ -754,14 +2049,19 @@ class tessellate(Operator): icon='ERROR') uv_error = True - if not self.check_gen_comp(self.generator) or \ - len(bpy.data.objects[self.generator].data.uv_layers) == 0: + if ob0.type != 'MESH': row = col.row(align=True) - check_name = bpy.data.objects[self.generator].name if \ - self.check_gen_comp(self.generator) else "None" - row.label(text="'" + check_name + - "' doesn't have UV Maps", icon='ERROR') + row.label( + text="UV rotation supported only for Mesh objects", + icon='ERROR') uv_error = True + else: + if len(ob0.data.uv_layers) == 0: + row = col.row(align=True) + check_name = self.generator + row.label(text="'" + check_name + + "' doesn't have UV Maps", icon='ERROR') + uv_error = True if uv_error: row = col.row(align=True) row.label(text="Default rotation will be used instead", @@ -769,15 +2069,31 @@ class tessellate(Operator): # Component XY row = col.row(align=True) - row.label(text="Component XY:") + row.label(text="Component Coordinates:") row = col.row(align=True) row.prop( self, "mode", text="Component XY", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) + if self.mode != 'BOUNDS': + col.separator() + row = col.row(align=True) + row.label(text="X:") + row.prop( + self, "bounds_x", text="Bounds X", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + + row = col.row(align=True) + row.label(text="Y:") + row.prop( + self, "bounds_y", text="Bounds X", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + # Component Z - col.label(text="Component Z:") + col.label(text="Thickness:") row = col.row(align=True) row.prop( self, "scale_mode", text="Scale Mode", icon='NONE', expand=True, @@ -787,166 +2103,184 @@ class tessellate(Operator): self, "zscale", text="Scale", icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) - col.prop( - self, "offset", text="Offset", icon='NONE', expand=False, - slider=True, toggle=False, icon_only=False, event=False, + if self.mode == 'BOUNDS': + col.prop( + self, "offset", text="Offset", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + + # Direction + row = col.row(align=True) + row.label(text="Direction:") + row = col.row(align=True) + row.prop( + self, "normals_mode", text="Direction", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) + row.enabled = self.fill_mode != 'PATCH' # Merge col = layout.column(align=True) row = col.row(align=True) row.prop(self, "merge") - if self.merge: row.prop(self, "merge_thres") row = col.row(align=True) - # ADVANCED - col = layout.column(align=True) - col.label(text="Advanced Settings:") - # vertex group + shape keys row = col.row(align=True) - col2 = row.column(align=True) - col2.prop(self, "bool_vertex_group") - - if self.check_gen_comp(self.generator) and \ - len(bpy.data.objects[self.generator].vertex_groups) == 0: - col2.enabled = False - self.bool_vertex_group = False - - col2 = row.column(align=True) - - if not self.check_gen_comp(self.generator) or \ - not self.check_gen_comp(self.component): - return + row.prop(self, "bool_smooth") + if self.merge: + col2 = row.column(align=True) + col2.prop(self, "bool_dissolve_seams") + #if ob1.type != 'MESH': col2.enabled = False - col2.prop(self, "bool_shapekeys", text="Use Shape Keys") + row = col.row(align=True) + row.prop(self, "cap_faces") + if self.cap_faces: + col2 = row.column(align=True) + col2.prop(self, "open_edges_crease", text="Crease") - if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ - bpy.data.objects[self.component].data.shape_keys is None: - col2.enabled = False - self.bool_shapekeys = False - elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ - bpy.data.objects[self.component].data.shape_keys is not None: - if len(bpy.data.objects[ - self.component].data.shape_keys.key_blocks) < 2: + # Advanced Settings + col = layout.column(align=True) + col.separator() + col.separator() + row = col.row(align=True) + row.prop(self, "bool_advanced", icon='SETTINGS') + if self.bool_advanced: + allow_multi = False + allow_shapekeys = not self.com_modifiers + for m in ob0.data.materials: + try: + o = bpy.data.objects[m.name] + allow_multi = True + try: + if o.data.shape_keys is None: continue + elif len(o.data.shape_keys.key_blocks) < 2: continue + else: allow_shapekeys = not self.com_modifiers + except: pass + except: pass + # DATA # + col = layout.column(align=True) + col.label(text="Morphing:") + # vertex group + shape keys + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(self, "bool_vertex_group", icon='GROUP_VERTEX') + #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups") + try: + if len(ob0.vertex_groups) == 0: + col2.enabled = False + except: col2.enabled = False - self.bool_shapekeys = False + row.separator() + col2 = row.column(align=True) + row2 = col2.row(align=True) + row2.prop(self, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA') + row2.enabled = allow_shapekeys + + # LIMITED TESSELLATION + col = layout.column(align=True) + col.label(text="Limited Tessellation:") + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(self, "bool_multi_components", icon='MOD_TINT') + if not allow_multi: + col2.enabled = False + self.bool_multi_components = False + col.separator() + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(self, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') + #if self.bool_material_id or self.bool_selection or self.bool_multi_components: + #col2 = row.column(align=True) + # col2.prop(self, "bool_combine") + row.separator() + if ob0.type != 'MESH': + col2.enabled = False + col2 = row.column(align=True) + col2.prop(self, "bool_material_id", icon='MATERIAL_DATA', text="Material ID") + if self.bool_material_id and not self.bool_multi_components: + #col2 = row.column(align=True) + col2.prop(self, "material_id") + col2.enabled = not self.bool_multi_components + + col.separator() + row = col.row(align=True) + row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH') + row.prop(self, 'iterations', text='Repeat', icon='SETTINGS') + + col.separator() + row = col.row(align=True) + row.label(text='Combine Iterations:') + row = col.row(align=True) + row.prop( + self, "combine_mode", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) def execute(self, context): + allowed_obj = ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') try: - ob0 = bpy.context.active_object - self.generator = ob0.name + ob0 = bpy.data.objects[self.generator] + ob1 = bpy.data.objects[self.component] except: - self.report({'ERROR'}, "A Generator mesh object must be selected") - - # component object - sel = bpy.context.selected_objects - no_component = True - for o in sel: - if (o.name == ob0.name or o.type != 'MESH'): - continue - else: - ob1 = o - self.component = o.name - no_component = False - break - - # Checks for Tool Shelf panel, it lost the original Selection - if bpy.context.active_object == self.object_name: - ob1 = bpy.data.objects[ - bpy.context.active_object.tissue_tessellate.component] - self.component = ob1.name - ob0 = bpy.data.objects[ - bpy.context.active_object.tissue_tessellate.generator] - self.generator = ob0.name - no_component = False - - if no_component: - # self.report({'ERROR'}, "A component mesh object must be selected") return {'CANCELLED'} - # new object name - if self.object_name == "": - if self.generator == "": - self.object_name = "Tessellation" - else: - self.object_name = self.generator + "_Tessellation" + self.object_name = "Tessellation" + # Check if existing object with same name + names = [o.name for o in bpy.data.objects] + if self.object_name in names: + count_name = 1 + while True: + test_name = self.object_name + '.{:03d}'.format(count_name) + if not (test_name in names): + self.object_name = test_name + break + count_name += 1 - if bpy.data.objects[self.component].type != 'MESH': - message = "Component must be Mesh Objects!" + if ob1.type not in allowed_obj: + message = "Component must be Mesh, Curve, Surface, Text or Meta object!" self.report({'ERROR'}, message) - self.component = "" + self.component = None - if bpy.data.objects[self.generator].type != 'MESH': - message = "Generator must be Mesh Objects!" + if ob0.type not in allowed_obj: + message = "Generator must be Mesh, Curve, Surface, Text or Meta object!" self.report({'ERROR'}, message) self.generator = "" - if self.component != "" and self.generator != "": + if True:#self.component not in ("",None) and self.generator not in ("",None): if bpy.ops.object.select_all.poll(): bpy.ops.object.select_all(action='TOGGLE') + bpy.ops.object.mode_set(mode='OBJECT') - new_ob = tassellate( - ob0, ob1, self.offset, self.zscale, self.gen_modifiers, - self.com_modifiers, self.mode, self.scale_mode, - self.rotation_mode, self.random_seed, self.fill_mode, - self.bool_vertex_group, self.bool_selection, - self.bool_shapekeys - ) - - if new_ob == 0: - message = "Zero faces selected in the Base mesh!" - self.report({'ERROR'}, message) - return {'CANCELLED'} - - new_ob.name = self.object_name - # new_ob = bpy.data.objects.new(self.object_name, new_me) - - new_ob.location = ob0.location - new_ob.matrix_world = ob0.matrix_world - - bpy.context.collection.objects.link(new_ob) - new_ob.select_set(True) - bpy.context.view_layer.objects.active = new_ob - if self.merge: - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode( - use_extend=False, use_expand=False, type='VERT') - bpy.ops.mesh.select_non_manifold( - extend=False, use_wire=False, use_boundary=True, - use_multi_face=False, use_non_contiguous=False, - use_verts=False) - bpy.ops.mesh.remove_doubles( - threshold=self.merge_thres, use_unselected=False) - bpy.ops.object.mode_set(mode='OBJECT') + #data0 = ob0.to_mesh(False) + #data0 = ob0.data.copy() + bool_update = False + if bpy.context.object == ob0: + auto_layer_collection() + #new_ob = bpy.data.objects.new(self.object_name, data0) + new_ob = convert_object_to_mesh(ob0,False,False) + new_ob.data.name = self.object_name + #bpy.context.collection.objects.link(new_ob) + #bpy.context.view_layer.objects.active = new_ob + new_ob.name = self.object_name + #new_ob.select_set(True) + else: + new_ob = bpy.context.object + bool_update = True new_ob = store_parameters(self, new_ob) - self.object_name = new_ob.name - self.working_on = self.object_name - - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.mode_set(mode='OBJECT') - - # MATERIALS - try: - # create materials list - polygon_materials = [p.material_index for p in ob1.data.polygons] * int( - len(new_ob.data.polygons) / len(ob1.data.polygons)) - # assign old material - component_materials = [slot.material for slot in ob1.material_slots] - for i in range(len(component_materials)): - bpy.ops.object.material_slot_add() - bpy.context.object.material_slots[i].material = \ - component_materials[i] - for i in range(len(new_ob.data.polygons)): - new_ob.data.polygons[i].material_index = polygon_materials[i] - except: - pass + try: bpy.ops.object.update_tessellate() + except RuntimeError as e: + bpy.data.objects.remove(new_ob) + self.report({'ERROR'}, str(e)) + return {'CANCELLED'} + if not bool_update: + self.object_name = new_ob.name + #self.working_on = self.object_name + new_ob.location = ob0.location + new_ob.matrix_world = ob0.matrix_world - return {'FINISHED'} - - def check(self, context): - return True + return {'FINISHED'} def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) @@ -963,9 +2297,10 @@ class update_tessellate(Operator): @classmethod def poll(cls, context): - try: - return context.active_object.tissue_tessellate.generator != "" and \ - context.active_object.tissue_tessellate.component != "" + #try: + try: #context.object == None: return False + return context.object.tissue_tessellate.generator != None and \ + context.object.tissue_tessellate.component != None except: return False @@ -975,7 +2310,9 @@ class update_tessellate(Operator): return checking in bpy.data.objects.keys() def execute(self, context): - ob = bpy.context.active_object + start_time = time.time() + + ob = bpy.context.object if not self.go: generator = ob.tissue_tessellate.generator component = ob.tissue_tessellate.component @@ -994,35 +2331,287 @@ class update_tessellate(Operator): bool_selection = ob.tissue_tessellate.bool_selection bool_shapekeys = ob.tissue_tessellate.bool_shapekeys mode = ob.tissue_tessellate.mode + bool_smooth = ob.tissue_tessellate.bool_smooth + bool_materials = ob.tissue_tessellate.bool_materials + bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams + bool_material_id = ob.tissue_tessellate.bool_material_id + material_id = ob.tissue_tessellate.material_id + iterations = ob.tissue_tessellate.iterations + bool_combine = ob.tissue_tessellate.bool_combine + normals_mode = ob.tissue_tessellate.normals_mode + bool_advanced = ob.tissue_tessellate.bool_advanced + bool_multi_components = ob.tissue_tessellate.bool_multi_components + combine_mode = ob.tissue_tessellate.combine_mode + bounds_x = ob.tissue_tessellate.bounds_x + bounds_y = ob.tissue_tessellate.bounds_y + cap_faces = ob.tissue_tessellate.cap_faces + open_edges_crease = ob.tissue_tessellate.open_edges_crease - if not self.check_gen_comp(generator) or \ - not self.check_gen_comp(component): - self.report({'ERROR'}, - "Base or Component Objects are missing from the data " - "(Most likely deleted or renamed)") - return {'CANCELLED'} - - if (generator == "" or component == ""): + try: + generator.name + component.name + except: self.report({'ERROR'}, "Active object must be Tessellate before Update") return {'CANCELLED'} - ob0 = bpy.data.objects[generator] - ob1 = bpy.data.objects[component] + # Solve Local View issues + local_spaces = [] + local_ob0 = [] + local_ob1 = [] + for area in bpy.context.screen.areas: + for space in area.spaces: + print(space) + try: + if ob.local_view_get(space): + local_spaces.append(space) + local_ob0 = ob0.local_view_get(space) + ob0.local_view_set(space, True) + local_ob1 = ob1.local_view_get(space) + ob1.local_view_set(space, True) + except: + pass - temp_ob = tassellate( - ob0, ob1, offset, zscale, gen_modifiers, com_modifiers, - mode, scale_mode, rotation_mode, random_seed, fill_mode, - bool_vertex_group, bool_selection, bool_shapekeys - ) + starting_mode = bpy.context.object.mode + #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT' + bpy.ops.object.mode_set(mode='OBJECT') - if temp_ob == 0: + ob0 = generator + ob1 = component + auto_layer_collection() + + ob0_hide = ob0.hide_get() + ob0_hidev = ob0.hide_viewport + ob0_hider = ob0.hide_render + ob1_hide = ob1.hide_get() + ob1_hidev = ob1.hide_viewport + ob1_hider = ob1.hide_render + ob0.hide_set(False) + ob0.hide_viewport = False + ob0.hide_render = False + ob1.hide_set(False) + ob1.hide_viewport = False + ob1.hide_render = False + + if ob0.type == 'META': + base_ob = convert_object_to_mesh(ob0, False, True) + else: + base_ob = ob0.copy() + base_ob.data = ob0.data.copy() + bpy.context.collection.objects.link(base_ob) + + # In Blender 2.80 cache of copied objects is lost, must be re-baked + bool_update_cloth = False + for m in base_ob.modifiers: + if m.type == 'CLOTH': + m.point_cache.frame_end = bpy.context.scene.frame_current + bool_update_cloth = True + if bool_update_cloth: + bpy.ops.ptcache.free_bake_all() + bpy.ops.ptcache.bake_all() + + #new_ob.location = ob.location + #new_ob.matrix_world = ob.matrix_world + base_ob.modifiers.update() + bpy.ops.object.select_all(action='DESELECT') + iter_objects = [base_ob] + #base_ob = new_ob#.copy() + + for iter in range(iterations): + same_iteration = [] + matched_materials = [] + if bool_multi_components: mat_iter = len(base_ob.material_slots) + else: mat_iter = 1 + for m_id in range(mat_iter): + if bool_multi_components: + try: + mat = base_ob.material_slots[m_id].material + ob1 = bpy.data.objects[mat.name] + material_id = m_id + matched_materials.append(m_id) + bool_material_id = True + except: + continue + if com_modifiers: + data1 = simple_to_mesh(ob1) + else: data1 = ob1.data.copy() + n_edges1 = len(data1.edges) + + if iter != 0: gen_modifiers = True + if fill_mode == 'PATCH': + new_ob = tessellate_patch( + base_ob, ob1, offset, zscale, com_modifiers, mode, scale_mode, + rotation_mode, random_seed, bool_vertex_group, + bool_selection, bool_shapekeys, bool_material_id, material_id, + bounds_x, bounds_y + ) + else: + new_ob = tessellate_original( + base_ob, ob1, offset, zscale, gen_modifiers, + com_modifiers, mode, scale_mode, rotation_mode, + random_seed, fill_mode, bool_vertex_group, + bool_selection, bool_shapekeys, bool_material_id, + material_id, normals_mode, bounds_x, bounds_y + ) + if type(new_ob) is bpy.types.Object: + bpy.context.view_layer.objects.active = new_ob + else: + continue + n_components = int(len(new_ob.data.edges) / n_edges1) + # SELECTION + if bool_selection: + try: + # create selection list + polygon_selection = [p.select for p in ob1.data.polygons] * int( + len(new_ob.data.polygons) / len(ob1.data.polygons)) + new_ob.data.polygons.foreach_set("select", polygon_selection) + except: + pass + + if type(new_ob) == str: break + + if bool_multi_components and type(new_ob) not in (int,str): + same_iteration.append(new_ob) + new_ob.select_set(True) + bpy.context.view_layer.objects.active = new_ob + + if type(new_ob) == str: break + + #bpy.data.objects.remove(base_ob) + if bool_multi_components: + bpy.context.view_layer.update() + bpy.context.view_layer.objects.active.select_set(True) + for o in bpy.data.objects: + if o in same_iteration: + o.select_set(True) + o.location = ob.location + else: + try: + o.select_set(False) + except: pass + bpy.ops.object.join() + new_ob = bpy.context.view_layer.objects.active + new_ob.select_set(True) + new_ob.data.update() + + #try: + # combine object + if (bool_selection or bool_material_id) and combine_mode == 'UNUSED': + # remove faces from last mesh + bm = bmesh.new() + + last_mesh = iter_objects[-1].data.copy() + + bm.from_mesh(last_mesh) + bm.faces.ensure_lookup_table() + if bool_multi_components: + remove_materials = matched_materials + elif bool_material_id: + remove_materials = [material_id] + else: remove_materials = [] + if bool_selection: + remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select] + else: + remove_faces = [f for f in bm.faces if f.material_index in remove_materials] + bmesh.ops.delete(bm, geom=remove_faces, context='FACES') + bm.to_mesh(last_mesh) + last_mesh.update() + + if len(last_mesh.vertices) > 0: + iter_objects[-1].data = last_mesh.copy() + iter_objects[-1].data.update() + else: + bpy.data.objects.remove(iter_objects[-1]) + iter_objects = iter_objects[:-1] + + base_ob = convert_object_to_mesh(new_ob,True,True) + #bpy.context.collection.objects.unlink(base_ob) + if iter < iterations-1: new_ob.data = base_ob.data + + iter_objects.append(new_ob) + new_ob.location = ob.location + new_ob.matrix_world = ob.matrix_world + try: + bpy.data.objects.remove(bpy.data.objects['_Tessellation_Base']) + except: pass + base_ob.name = "_Tessellation_Base" + elif combine_mode == 'ALL': + base_ob = new_ob.copy() + iter_objects.append(new_ob) + new_ob.location = ob.location + new_ob.matrix_world = ob.matrix_world + else: + if base_ob != new_ob: + bpy.data.objects.remove(base_ob) + base_ob = new_ob + iter_objects = [new_ob] + + if new_ob == 0: + #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis message = "Zero faces selected in the Base mesh!" + bpy.ops.object.mode_set(mode=starting_mode) + self.report({'ERROR'}, message) + return {'CANCELLED'} + errors = {} + errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \ + "after the last Subsurf (or Multires) are not allowed." + errors["topology_error"] = "Make sure that the topology of the mesh before \n" \ + "the last Subsurf (or Multires) is quads only." + errors["wires_error"] = "Please remove all wire edges in the base object." + errors["verts_error"] = "Please remove all floating vertices in the base object" + if new_ob in errors: + for o in iter_objects: bpy.data.objects.remove(o) + bpy.context.view_layer.objects.active = ob + ob.select_set(True) + message = errors[new_ob] + ob.tissue_tessellate.error_message = message + bpy.ops.object.mode_set(mode=starting_mode) self.report({'ERROR'}, message) return {'CANCELLED'} - ob.data = temp_ob.data - bpy.data.objects.remove(temp_ob) + new_ob.location = ob.location + new_ob.matrix_world = ob.matrix_world + + ### REPEAT + if combine_mode != 'LAST' and len(iter_objects)>0: + if base_ob not in iter_objects: bpy.data.objects.remove(base_ob) + for o in iter_objects: + o.location = ob.location + o.select_set(True) + bpy.ops.object.join() + new_ob.data.update() + + # update data and preserve name + if ob.type != 'MESH': + loc, matr = ob.location, ob.matrix_world + ob = convert_object_to_mesh(ob,False,True) + ob.location, ob.matrix_world = loc, matr + data_name = ob.data.name + old_data = ob.data + ob.data = new_ob.data + bpy.data.meshes.remove(old_data) + ob.data.name = data_name + + # copy vertex group + if bool_vertex_group: + for vg in new_ob.vertex_groups: + if not vg.name in ob.vertex_groups.keys(): + ob.vertex_groups.new(name=vg.name) + new_vg = ob.vertex_groups[vg.name] + for i in range(len(ob.data.vertices)): + try: + weight = vg.weight(i) + except: + weight = 0 + new_vg.add([i], weight, 'REPLACE') + + selected_objects = [o for o in bpy.context.selected_objects] + for o in selected_objects: o.select_set(False) + + ob.select_set(True) + bpy.context.view_layer.objects.active = ob + bpy.data.objects.remove(new_ob) + if merge: bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_mode( @@ -1030,516 +2619,378 @@ class update_tessellate(Operator): bpy.ops.mesh.select_non_manifold( extend=False, use_wire=False, use_boundary=True, use_multi_face=False, use_non_contiguous=False, use_verts=False) + bpy.ops.mesh.remove_doubles( threshold=merge_thres, use_unselected=False) + bpy.ops.object.mode_set(mode='OBJECT') + if bool_dissolve_seams: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode(type='EDGE') + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.mode_set(mode='OBJECT') + for e in ob.data.edges: + e.select = e.use_seam + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.dissolve_edges() + if cap_faces: + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_mode( + use_extend=False, use_expand=False, type='EDGE') + bpy.ops.mesh.select_non_manifold( + extend=False, use_wire=False, use_boundary=True, + use_multi_face=False, use_non_contiguous=False, use_verts=False) + bpy.ops.mesh.edge_face_add() + if open_edges_crease != 0: + bpy.ops.transform.edge_crease(value=open_edges_crease) + bpy.ops.object.mode_set(mode='EDIT') bpy.ops.object.mode_set(mode='OBJECT') - # MATERIALS - try: - # create materials list - polygon_materials = [p.material_index for p in ob1.data.polygons] * int( - len(ob.data.polygons) / len(ob1.data.polygons)) - # assign old material - component_materials = [slot.material for slot in ob1.material_slots] - for i in range(len(component_materials)): - bpy.ops.object.material_slot_add() - bpy.context.object.material_slots[i].material = \ - component_materials[i] - for i in range(len(ob.data.polygons)): - ob.data.polygons[i].material_index = polygon_materials[i] - except: - pass + if bool_smooth: bpy.ops.object.shade_smooth() + ####values = [True] * len(ob.data.polygons) + ####ob.data.polygons.foreach_set("use_smooth", values) + + #for m, vis in zip(ob.modifiers, mod_visibility): m.show_viewport = vis + + end_time = time.time() + print('Tissue: object "{}" tessellated in {:.4f} sec'.format(ob.name, end_time-start_time)) + + for mesh in bpy.data.meshes: + if not mesh.users: bpy.data.meshes.remove(mesh) + + for o in selected_objects: + try: o.select_set(True) + except: pass + + bpy.ops.object.mode_set(mode=starting_mode) + + # clean objects + for o in bpy.data.objects: + if o.name not in context.view_layer.objects and "temp" in o.name: + bpy.data.objects.remove(o) + + ob.tissue_tessellate.error_message = "" + + # Restore Base visibility + ob0.hide_set(ob0_hide) + ob0.hide_viewport = ob0_hidev + ob0.hide_render = ob0_hider + # Restore Component visibility + ob1.hide_set(ob1_hide) + ob1.hide_viewport = ob1_hidev + ob1.hide_render = ob1_hider + # Restore Local visibility + for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1): + ob0.local_view_set(space, local0) + ob1.local_view_set(space, local1) + print(local_ob0) + print(local_ob1) return {'FINISHED'} def check(self, context): return True - -class settings_tessellate(Operator): - bl_idname = "object.settings_tessellate" - bl_label = "Settings" - bl_description = ("Update the tessellated mesh according to base and component changes\n" - "Allow also to change tessellation's parameters") - bl_options = {'REGISTER', 'UNDO'} - - object_name: StringProperty( - name="", - description="Name of the generated object" - ) - zscale: FloatProperty( - name="Scale", - default=1, - soft_min=0, - soft_max=10, - description="Scale factor for the component thickness" - ) - scale_mode: EnumProperty( - items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Proportional", "")), - default='ADAPTIVE', - name="Scale variation" - ) - offset: FloatProperty( - name="Surface Offset", - default=0, - min=-1, max=1, - soft_min=-1, - soft_max=1, - description="Surface offset" - ) - mode: EnumProperty( - items=(('CONSTANT', "Constant", ""), ('ADAPTIVE', "Adaptive", "")), - default='ADAPTIVE', - name="Component Mode" - ) - rotation_mode: EnumProperty( - items=(('RANDOM', "Random", ""), ('UV', "Active UV", ""), - ('DEFAULT', "Default", "")), - default='DEFAULT', - name="Component Rotation" - ) - fill_mode: EnumProperty( - items=(('QUAD', "Quad", ""), ('FAN', "Fan", "")), - default='QUAD', - name="Fill Mode" - ) - gen_modifiers: BoolProperty( - name="Generator Modifiers", - default=False, - description="Apply modifiers to base object" - ) - com_modifiers: BoolProperty( - name="Component Modifiers", - default=False, - description="Apply modifiers to component object" - ) - merge: BoolProperty( - name="Merge", - default=False, - description="Merge vertices in adjacent duplicates" - ) - merge_thres: FloatProperty( - name="Distance", - default=0.001, - soft_min=0, - soft_max=10, - description="Limit below which to merge vertices" - ) - generator: StringProperty( - name="", - description="Base object for the tessellation" - ) - component: StringProperty( - name="", - description="Component object for the tessellation" - ) - bool_random: BoolProperty( - name="Randomize", - default=False, - description="Randomize component rotation" - ) - random_seed: IntProperty( - name="Seed", - default=0, - soft_min=0, - soft_max=10, - description="Random seed" - ) - bool_vertex_group: BoolProperty( - name="Map Vertex Group", - default=False, - description="Map on generated " - "geometry the active Vertex Group from the base object" - ) - bool_selection: BoolProperty( - name="On selected Faces", - default=False, - description="Create Tessellation only on select faces" - ) - bool_shapekeys: BoolProperty( - name="Use Shape Keys", - default=False, - description="Use component's active Shape Key according to active " - "Vertex Group of the base object" - ) - go = False +class TISSUE_PT_tessellate(Panel): + bl_label = "Tissue Tools" + bl_category = "Tissue" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + #bl_options = {'DEFAULT_OPEN'} @classmethod def poll(cls, context): - try: - return context.active_object.tissue_tessellate.generator != "" and \ - context.active_object.tissue_tessellate.component != "" - except: - return False - - @staticmethod - def check_gen_comp(checking): - # note pass the stored name key in here to check it out - return checking in bpy.data.objects.keys() + return context.mode in {'OBJECT', 'EDIT_MESH'} def draw(self, context): layout = self.layout - ob0 = bpy.context.active_object - - if not self.go: - self.generator = ob0.tissue_tessellate.generator - self.component = ob0.tissue_tessellate.component - self.zscale = ob0.tissue_tessellate.zscale - self.scale_mode = ob0.tissue_tessellate.scale_mode - self.rotation_mode = ob0.tissue_tessellate.rotation_mode - self.offset = ob0.tissue_tessellate.offset - self.merge = ob0.tissue_tessellate.merge - self.merge_thres = ob0.tissue_tessellate.merge_thres - self.gen_modifiers = ob0.tissue_tessellate.gen_modifiers - self.com_modifiers = ob0.tissue_tessellate.com_modifiers - self.bool_random = ob0.tissue_tessellate.bool_random - self.random_seed = ob0.tissue_tessellate.random_seed - self.fill_mode = ob0.tissue_tessellate.fill_mode - self.bool_vertex_group = ob0.tissue_tessellate.bool_vertex_group - self.bool_selection = ob0.tissue_tessellate.bool_selection - self.bool_shapekeys = ob0.tissue_tessellate.bool_shapekeys - self.mode = ob0.tissue_tessellate.mode - - # start drawing - layout = self.layout - # check for keys in data - as the objects can be deleted or renamed - if not self.check_gen_comp(self.generator) or \ - not self.check_gen_comp(self.component): - - layout.label(text="Base or Component Objects are missing from the data", - icon="INFO") - layout.label(text="(Most likely deleted or renamed)", - icon="BLANK1") - layout.label(text="Settings could not be altered anymore", - icon="BLANK1") - layout.label(text="Please re-run Tessellate with two new selected objects", - icon="BLANK1") - return - - # ob0 = bpy.context.active_object - # Base and Component col = layout.column(align=True) - row = col.row(align=True) - row.label(text="BASE :") - row.label(text="COMPONENT :") - row = col.row(align=True) - - col2 = row.column(align=True) - col2.prop_search(self, "generator", bpy.data, "objects") - row.separator() - col2 = row.column(align=True) - col2.prop_search(self, "component", bpy.data, "objects") - - row = col.row(align=True) - col2 = row.column(align=True) - col2.prop(self, "gen_modifiers", text="Use Modifiers") - - if len(bpy.data.objects[self.generator].modifiers) == 0: - col2.enabled = False - self.gen_modifiers = False - - col2 = row.column(align=True) - col2.prop(self, "com_modifiers", text="Use Modifiers") - - if len(bpy.data.objects[self.component].modifiers) == 0: - col2.enabled = False - self.com_modifiers = False - - # On selected faces - row = col.row(align=True) - row.prop(self, "bool_selection", text="On selected Faces") + col.label(text="Tessellate:") + col.operator("object.tessellate") + col.operator("object.dual_mesh_tessellated") col.separator() + #col = layout.column(align=True) + #col.label(text="Tessellate Edit:") + #col.operator("object.settings_tessellate") + col.operator("object.update_tessellate", icon='FILE_REFRESH') - if self.gen_modifiers or self.com_modifiers: - depsgraph = context.evaluated_depsgraph_get() - - # Count number of faces - try: - polygons = 0 - me_temp_owner = None - if self.gen_modifiers: - me_temp_owner = bpy.data.objects[self.generator].evaluated_get(depsgraph) - me_temp = me_temp_owner.to_mesh() - else: - me_temp_owner = None - me_temp = bpy.data.objects[self.generator].data - - for p in me_temp.polygons: - if not self.bool_selection or p.select: - if self.fill_mode == "FAN": - polygons += len(p.vertices) - else: - polygons += 1 - - if me_temp_owner: - me_temp_owner.to_mesh_clear() - - if self.com_modifiers: - me_temp_owner = bpy.data.objects[self.component].evaluated_get(depsgraph) - me_temp = me_temp_owner.to_mesh() - else: - me_temp_owner = None - me_temp = bpy.data.objects[self.component].data - polygons *= len(me_temp.polygons) - - if me_temp_owner: - me_temp_owner.to_mesh_clear() + #col = layout.column(align=True) + col.operator("mesh.rotate_face", icon='NDOF_TURN') - str_polygons = '{:0,.0f}'.format(polygons) - if polygons > 200000: - col.label(text=str_polygons + " polygons will be created!", - icon='ERROR') - else: - col.label(text=str_polygons + " faces will be created!", - icon='INFO') - except: - pass col.separator() + col.label(text="Other:") + col.operator("object.dual_mesh") + col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE") - # Fill and Rotation - row = col.row(align=True) - row.label(text="Fill Mode:") - row.separator() - row.label(text="Rotation:") - row = col.row(align=True) - - # fill - row.prop(self, "fill_mode", text="", icon='NONE', expand=False, - slider=True, toggle=False, icon_only=False, event=False, - full_event=False, emboss=True, index=-1) - row.separator() - - # rotation - row.prop(self, "rotation_mode", text="", icon='NONE', expand=False, - slider=True, toggle=False, icon_only=False, event=False, - full_event=False, emboss=True, index=-1) - - if self.rotation_mode == 'RANDOM': - row = col.row(align=True) - row.prop(self, "random_seed") - - if self.rotation_mode == 'UV': - uv_error = False - if self.fill_mode == 'FAN': - row = col.row(align=True) - row.label(text="UV rotation doesn't work in FAN mode", - icon='ERROR') - uv_error = True - - if len(bpy.data.objects[self.generator].data.uv_layers) == 0: - row = col.row(align=True) - row.label(text="'" + bpy.data.objects[self.generator].name + - " doesn't have UV Maps", icon='ERROR') - uv_error = True - if uv_error: - row = col.row(align=True) - row.label(text="Default rotation will be used instead", - icon='INFO') - - # component XY - row = col.row(align=True) - row.label(text="Component XY:") - row = col.row(align=True) - row.prop(self, "mode", text="Component XY", icon='NONE', expand=True, - slider=False, toggle=False, icon_only=False, event=False, - full_event=False, emboss=True, index=-1) - - # component Z - col.label(text="Component Z:") - row = col.row(align=True) - row.prop(self, "scale_mode", text="Scale Mode", icon='NONE', - expand=True, slider=False, toggle=False, icon_only=False, - event=False, full_event=False, emboss=True, index=-1) - col.prop(self, "zscale", text="Scale", icon='NONE', expand=False, - slider=True, toggle=False, icon_only=False, event=False, - full_event=False, emboss=True, index=-1) - col.prop(self, "offset", text="Offset", icon='NONE', expand=False, - slider=True, toggle=False, icon_only=False, event=False, - full_event=False, emboss=True, index=-1) - - # merge - col = layout.column(align=True) - row = col.row(align=True) - row.prop(self, "merge") - if self.merge: - row.prop(self, "merge_thres") - row = col.row(align=True) - - # ADVANCED # - col = layout.column(align=True) - tessellate.rotation_mode - - col.label(text="Advanced Settings:") - # vertex group + shape keys - row = col.row(align=True) - col2 = row.column(align=True) - col2.prop(self, "bool_vertex_group") - - if len(bpy.data.objects[self.generator].vertex_groups) == 0: - col2.enabled = False - self.bool_vertex_group = False - col2 = row.column(align=True) - col2.prop(self, "bool_shapekeys", text="Use Shape Keys") - - if len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ - bpy.data.objects[self.component].data.shape_keys is None: - col2.enabled = False - self.bool_shapekeys = False - elif len(bpy.data.objects[self.generator].vertex_groups) == 0 or \ - bpy.data.objects[self.component].data.shape_keys is not None: - if len(bpy.data.objects[self.component].data.shape_keys.key_blocks) < 2: - col2.enabled = False - self.bool_shapekeys = False - self.go = True - - def execute(self, context): - self.ob = bpy.context.active_object - - if not self.go: - self.generator = self.ob.tissue_tessellate.generator - self.component = self.ob.tissue_tessellate.component - self.zscale = self.ob.tissue_tessellate.zscale - self.scale_mode = self.ob.tissue_tessellate.scale_mode - self.rotation_mode = self.ob.tissue_tessellate.rotation_mode - self.offset = self.ob.tissue_tessellate.offset - self.merge = self.ob.tissue_tessellate.merge - self.merge_thres = self.ob.tissue_tessellate.merge_thres - self.gen_modifiers = self.ob.tissue_tessellate.gen_modifiers - self.com_modifiers = self.ob.tissue_tessellate.com_modifiers - self.bool_random = self.ob.tissue_tessellate.bool_random - self.random_seed = self.ob.tissue_tessellate.random_seed - self.fill_mode = self.ob.tissue_tessellate.fill_mode - self.bool_vertex_group = self.ob.tissue_tessellate.bool_vertex_group - self.bool_selection = self.ob.tissue_tessellate.bool_selection - self.bool_shapekeys = self.ob.tissue_tessellate.bool_shapekeys - - if not self.check_gen_comp(self.generator) or \ - not self.check_gen_comp(self.component): - # do nothing as the Warning was already done in it UI - return {'CANCELLED'} - - if (self.generator == "" or self.component == ""): - self.report({'ERROR'}, - "Active object must be Tessellated before Update") - return {'CANCELLED'} - - if (bpy.data.objects[self.generator].type != 'MESH'): - self.report({'ERROR'}, "Base object must be a Mesh") - return {'CANCELLED'} - - if (bpy.data.objects[self.component].type != 'MESH'): - self.report({'ERROR'}, "Component object must be a Mesh") - return {'CANCELLED'} - - ob0 = bpy.data.objects[self.generator] - ob1 = bpy.data.objects[self.component] + act = context.active_object + if act and act.type == 'MESH': + col.operator("object.uv_to_mesh", icon="UV") - temp_ob = tassellate( - ob0, ob1, self.offset, self.zscale, self.gen_modifiers, - self.com_modifiers, self.mode, self.scale_mode, self.rotation_mode, - self.random_seed, self.fill_mode, self.bool_vertex_group, - self.bool_selection, self.bool_shapekeys - ) - if temp_ob == 0: - message = "Zero faces selected in the Base mesh" - self.report({'ERROR'}, message) - - return {'CANCELLED'} +class TISSUE_PT_tessellate_object(Panel): + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "data" + bl_label = "Tissue - Tessellate" + bl_options = {'DEFAULT_CLOSED'} - # Transfer mesh data - self.ob.data = temp_ob.data + @classmethod + def poll(cls, context): + try: return context.object.type == 'MESH' + except: return False - # Create object in order to transfer vertex group - bpy.context.collection.objects.link(temp_ob) - temp_ob.select_set(True) - bpy.context.view_layer.objects.active = temp_ob + def draw(self, context): + ob = context.object + props = ob.tissue_tessellate + allowed_obj = ('MESH','CURVE','SURFACE','FONT', 'META') try: - bpy.ops.object.vertex_group_copy_to_linked() - except: - pass - - bpy.context.scene.objects.unlink(temp_ob) - bpy.data.objects.remove(temp_ob) - bpy.context.view_layer.objects.active = self.ob - - if self.merge: - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.select_mode( - use_extend=False, use_expand=False, type='VERT') - bpy.ops.mesh.select_non_manifold( - extend=False, use_wire=False, use_boundary=True, - use_multi_face=False, use_non_contiguous=False, use_verts=False) - bpy.ops.mesh.remove_doubles( - threshold=self.merge_thres, use_unselected=False) - bpy.ops.object.mode_set(mode='OBJECT') - self.ob = store_parameters(self, self.ob) - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.object.mode_set(mode='OBJECT') + bool_tessellated = props.generator or props.component != None + ob0 = props.generator + ob1 = props.component + except: bool_tessellated = False + layout = self.layout + if not bool_tessellated: + layout.label(text="The selected object is not a Tessellated object", + icon='INFO') + else: + if props.error_message != "": + layout.label(text=props.error_message, + icon='ERROR') + col = layout.column(align=True) + row = col.row(align=True) + row.prop(props, "bool_run", text="Animatable") + row.operator("object.update_tessellate", icon='FILE_REFRESH') - # MATERIALS - try: - # create materials list - polygon_materials = [p.material_index for p in ob1.data.polygons] * \ - int(len(self.ob.data.polygons) / len(ob1.data.polygons)) - # assign old material - component_materials = [slot.material for slot in ob1.material_slots] - for i in range(len(component_materials)): - bpy.ops.object.material_slot_add() - bpy.context.object.material_slots[i].material = \ - component_materials[i] - for i in range(len(self.ob.data.polygons)): - self.ob.data.polygons[i].material_index = polygon_materials[i] - except: - pass + col = layout.column(align=True) + row = col.row(align=True) + row.label(text="BASE :") + row.label(text="COMPONENT :") + row = col.row(align=True) - return {'FINISHED'} + col2 = row.column(align=True) + col2.prop_search(props, "generator", context.scene, "objects") + row.separator() + col2 = row.column(align=True) + col2.prop_search(props, "component", context.scene, "objects") + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(props, "gen_modifiers", text="Use Modifiers", icon='MODIFIER') + row.separator() + try: + if not (ob0.modifiers or ob0.data.shape_keys) or props.fill_mode == 'PATCH': + col2.enabled = False + except: + col2.enabled = False + col2 = row.column(align=True) + col2.prop(props, "com_modifiers", text="Use Modifiers", icon='MODIFIER') + try: + if not (props.component.modifiers or props.component.data.shape_keys): + col2.enabled = False + except: + col2.enabled = False + col.separator() - def check(self, context): - return True + # Fill and Rotation + row = col.row(align=True) + row.label(text="Fill Mode:") + row.separator() + row.label(text="Rotation:") + row = col.row(align=True) - def invoke(self, context, event): - return context.window_manager.invoke_props_dialog(self, width=400) + # fill + row.prop(props, "fill_mode", text="", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + row.separator() + # rotation + col2 = row.column(align=True) + col2.prop(props, "rotation_mode", text="", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) -class tessellate_panel(Panel): - bl_label = "Tissue" - bl_category = "Create" - bl_space_type = "VIEW_3D" - bl_region_type = "TOOLS" - bl_options = {'DEFAULT_CLOSED'} + if props.rotation_mode == 'RANDOM': + #row = col.row(align=True) + col2.prop(props, "random_seed") - @classmethod - def poll(cls, context): - return context.mode in {'OBJECT', 'EDIT_MESH'} + if props.rotation_mode == 'UV': + uv_error = False + if props.fill_mode == 'FAN': + row = col.row(align=True) + row.label(text="UV rotation doesn't work in FAN mode", + icon='ERROR') + uv_error = True + if props.generator.type != 'MESH': + row = col.row(align=True) + row.label( + text="UV rotation supported only for Mesh objects", + icon='ERROR') + uv_error = True + else: + if len(props.generator.data.uv_layers) == 0: + row = col.row(align=True) + row.label(text="'" + props.generator.name + + " doesn't have UV Maps", icon='ERROR') + uv_error = True + if uv_error: + row = col.row(align=True) + row.label(text="Default rotation will be used instead", + icon='INFO') - def draw(self, context): - layout = self.layout + # component XY + row = col.row(align=True) + row.label(text="Component Coordinates:") + row = col.row(align=True) + row.prop(props, "mode", expand=True) - col = layout.column(align=True) - col.label(text="Tessellate Add:") - col.operator("object.tessellate") + if props.mode != 'BOUNDS': + col.separator() + row = col.row(align=True) + row.label(text="X:") + row.prop( + props, "bounds_x", text="Bounds X", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) - col = layout.column(align=True) - col.label(text="Tessellate Edit:") - col.operator("object.settings_tessellate") - col.operator("object.update_tessellate") + row = col.row(align=True) + row.label(text="Y:") + row.prop( + props, "bounds_y", text="Bounds X", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + + # component Z + col.label(text="Thickness:") + row = col.row(align=True) + row.prop(props, "scale_mode", expand=True) + col.prop(props, "zscale", text="Scale", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + if props.mode == 'BOUNDS': + col.prop(props, "offset", text="Offset", icon='NONE', expand=False, + slider=True, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + + # Direction + row = col.row(align=True) + row.label(text="Direction:") + row = col.row(align=True) + row.prop( + props, "normals_mode", text="Direction", icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) + row.enabled = props.fill_mode != 'PATCH' - col = layout.column(align=True) - col.operator("mesh.rotate_face") + # merge + col = layout.column(align=True) + row = col.row(align=True) + row.prop(props, "merge") + if props.merge: + row.prop(props, "merge_thres") + row = col.row(align=True) + row.prop(props, "bool_smooth") + if props.merge: + col2 = row.column(align=True) + col2.prop(props, "bool_dissolve_seams") + #if props.component.type != 'MESH': col2.enabled = False - col.separator() - col.label(text="Other:") - col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE") + row = col.row(align=True) + row.prop(props, "cap_faces") + if props.cap_faces: + col2 = row.column(align=True) + col2.prop(props, "open_edges_crease", text="Crease") - act = context.active_object - if act and act.type == 'MESH': - col.operator("object.uv_to_mesh", icon="GROUP_UVS") + # Advanced Settings + col = layout.column(align=True) + col.separator() + col.separator() + row = col.row(align=True) + row.prop(props, "bool_advanced", icon='SETTINGS') + if props.bool_advanced: + allow_multi = False + allow_shapekeys = not props.com_modifiers + for m in ob0.data.materials: + try: + o = bpy.data.objects[m.name] + allow_multi = True + try: + if o.data.shape_keys is None: continue + elif len(o.data.shape_keys.key_blocks) < 2: continue + else: allow_shapekeys = not props.com_modifiers + except: pass + except: pass + # DATA # + col = layout.column(align=True) + col.label(text="Morphing:") + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX') + #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups") + try: + if len(props.generator.vertex_groups) == 0: + col2.enabled = False + except: + col2.enabled = False + row.separator() + col2 = row.column(align=True) + row2 = col2.row(align=True) + row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA') + row2.enabled = allow_shapekeys + + # LIMITED TESSELLATION + col = layout.column(align=True) + col.label(text="Limited Tessellation:") + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(props, "bool_multi_components", icon='MOD_TINT') + if not allow_multi: + col2.enabled = False + col.separator() + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF') + #if props.bool_material_id or props.bool_selection or props.bool_multi_components: + #col2 = row.column(align=True) + # col2.prop(props, "bool_combine") + row.separator() + if props.generator.type != 'MESH': + col2.enabled = False + col2 = row.column(align=True) + col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material ID") + if props.bool_material_id and not props.bool_multi_components: + #col2 = row.column(align=True) + col2.prop(props, "material_id") + if props.bool_multi_components: + col2.enabled = False + # TRANFER DATA ### OFF + if props.fill_mode != 'PATCH' and False: + col = layout.column(align=True) + col.label(text="Component Data:") + row = col.row(align=True) + col2 = row.column(align=True) + col2.prop(props, "bool_materials", icon='MATERIAL_DATA') + row.separator() + col2 = row.column(align=True) + if props.fill_mode == 'PATCH': + col.enabled = False + col.label(text='Not needed in Patch mode', icon='INFO') + + col.separator() + row = col.row(align=True) + row.label(text='Reiterate Tessellation:', icon='FILE_REFRESH') + row.prop(props, 'iterations', text='Repeat', icon='SETTINGS') + col.separator() + row = col.row(align=True) + row.label(text='Combine Iterations:') + row = col.row(align=True) + row.prop( + props, "combine_mode", text="Combine:",icon='NONE', expand=True, + slider=False, toggle=False, icon_only=False, event=False, + full_event=False, emboss=True, index=-1) class rotate_face(Operator): bl_idname = "mesh.rotate_face" @@ -1554,45 +3005,35 @@ class rotate_face(Operator): def execute(self, context): ob = bpy.context.active_object me = ob.data - bpy.ops.object.mode_set(mode='OBJECT') - for p in [f for f in me.polygons if f.select]: - p.vertices = p.vertices[1:] + p.vertices[:1] + bm = bmesh.from_edit_mesh(me) + mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode] - bpy.ops.object.mode_set(mode='EDIT') - bpy.ops.mesh.flip_normals() - bpy.ops.mesh.flip_normals() - me.update(calc_edges=True) + for face in bm.faces: + if (face.select): + vs = face.verts[:] + vs2 = vs[1:]+vs[:1] + material_index = face.material_index + bm.faces.remove(face) + f2 = bm.faces.new(vs2) + f2.select = True + f2.material_index = material_index + bm.normal_update() + + # trigger UI update + bmesh.update_edit_mesh(me) + ob.select_set(False) # update tessellated meshes bpy.ops.object.mode_set(mode='OBJECT') for o in [obj for obj in bpy.data.objects if - obj.tissue_tessellate.generator == ob.name]: + obj.tissue_tessellate.generator == ob and obj.visible_get()]: bpy.context.view_layer.objects.active = o bpy.ops.object.update_tessellate() + o.select_set(False) + ob.select_set(True) bpy.context.view_layer.objects.active = ob bpy.ops.object.mode_set(mode='EDIT') + context.tool_settings.mesh_select_mode = mesh_select_mode return {'FINISHED'} - - -def register(): - bpy.utils.register_class(tissue_tessellate_prop) - bpy.utils.register_class(tessellate) - bpy.utils.register_class(update_tessellate) - bpy.utils.register_class(settings_tessellate) - bpy.utils.register_class(tessellate_panel) - bpy.utils.register_class(rotate_face) - - -def unregister(): - bpy.utils.unregister_class(tissue_tessellate_prop) - bpy.utils.unregister_class(tessellate) - bpy.utils.unregister_class(update_tessellate) - bpy.utils.unregister_class(settings_tessellate) - bpy.utils.unregister_class(tessellate_panel) - bpy.utils.unregister_class(rotate_face) - - -if __name__ == "__main__": - register() |