# SPDX-FileCopyrightText: 2017 Alessandro Zomparelli # # SPDX-License-Identifier: GPL-2.0-or-later # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- # # ------------------------------- version 0.84 ------------------------------- # # # # Creates duplicates of selected mesh to active morphing the shape according # # to target faces. # # # # (c) Alessandro Zomparelli # # (2017) # # # # http://www.co-de-it.com/ # # # # ############################################################################ # import bpy from bpy.types import ( Operator, Panel, PropertyGroup, ) from bpy.props import ( BoolProperty, EnumProperty, FloatProperty, IntProperty, StringProperty, PointerProperty ) from mathutils import Vector, Quaternion, Matrix import numpy as np from math import * import random, time, copy import bmesh from .utils import * def anim_polyhedra_active(self, context): ob = context.object props = ob.tissue_polyhedra if ob.tissue.tissue_type=='POLYHEDRA' and not ob.tissue.bool_lock: props.object.name bpy.ops.object.tissue_update_polyhedra() class tissue_polyhedra_prop(PropertyGroup): object : PointerProperty( type=bpy.types.Object, name="Object", description="Source object", update = anim_polyhedra_active ) mode : EnumProperty( items=( ('POLYHEDRA', "Polyhedra", "Polyhedral Complex Decomposition, the result are disconnected polyhedra geometries"), ('WIREFRAME', "Wireframe", "Polyhedral Wireframe through edges tickening") ), default='POLYHEDRA', name="Polyhedra Mode", update = anim_polyhedra_active ) bool_modifiers : BoolProperty( name="Use Modifiers", description="", default=True, update = anim_polyhedra_active ) dissolve : EnumProperty( items=( ('NONE', "None", "Keeps original topology"), ('INNER', "Inner", "Dissolve inner loops"), ('OUTER', "Outer", "Dissolve outer loops") ), default='NONE', name="Dissolve", update = anim_polyhedra_active ) thickness : FloatProperty( name="Thickness", default=1, soft_min=0, soft_max=10, description="Thickness along the edges", update = anim_polyhedra_active ) crease : FloatProperty( name="Crease", default=0, min=0, max=1, description="Crease Inner Loops", update = anim_polyhedra_active ) segments : IntProperty( name="Segments", default=0, min=1, soft_max=20, description="Segments for every edge", update = anim_polyhedra_active ) proportional_segments : BoolProperty( name="Proportional Segments", default=True, description="The number of segments is proportional to the length of the edges", update = anim_polyhedra_active ) selective_wireframe : EnumProperty( name="Selective", items=( ('NONE', "None", "Apply wireframe to every cell"), ('THICKNESS', "Thickness", "Wireframe only on bigger cells compared to the thickness"), ('AREA', "Area", "Wireframe based on cells dimensions"), ('WEIGHT', "Weight", "Wireframe based on vertex groups") ), default='NONE', update = anim_polyhedra_active ) thickness_threshold_correction : FloatProperty( name="Correction", default=1, min=0, soft_max=2, description="Adjust threshold based on thickness", update = anim_polyhedra_active ) area_threshold : FloatProperty( name="Threshold", default=0, min=0, soft_max=10, description="Use only faces with an area greater than the threshold", update = anim_polyhedra_active ) thicken_all : BoolProperty( name="Thicken all", description="Thicken original faces as well", default=True, update = anim_polyhedra_active ) vertex_group_thickness : StringProperty( name="Thickness weight", default='', description="Vertex Group used for thickness", update = anim_polyhedra_active ) invert_vertex_group_thickness : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence", update = anim_polyhedra_active ) vertex_group_thickness_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Thickness factor to use for zero vertex group influence", update = anim_polyhedra_active ) vertex_group_selective : StringProperty( name="Thickness weight", default='', description="Vertex Group used for selective wireframe", update = anim_polyhedra_active ) invert_vertex_group_selective : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence", update = anim_polyhedra_active ) vertex_group_selective_threshold : FloatProperty( name="Threshold", default=0.5, min=0, max=1, description="Selective wireframe threshold", update = anim_polyhedra_active ) bool_smooth : BoolProperty( name="Smooth Shading", default=False, description="Output faces with smooth shading rather than flat shaded", update = anim_polyhedra_active ) error_message : StringProperty( name="Error Message", default="" ) class polyhedral_wireframe(Operator): bl_idname = "object.polyhedral_wireframe" bl_label = "Tissue Polyhedral Wireframe" bl_description = "Generate wireframes around the faces.\ \nDoesn't works with boundary edges.\ \n(Experimental)" bl_options = {'REGISTER', 'UNDO'} thickness : FloatProperty( name="Thickness", default=0.1, min=0.001, soft_max=200, description="Wireframe thickness" ) crease : FloatProperty( name="Crease", default=0, min=0, max=1, description="Crease Inner Loops" ) segments : IntProperty( name="Segments", default=1, min=1, soft_max=10, description="Segments for every edge" ) proportional_segments : BoolProperty( name="Proportional Segments", default=True, description="The number of segments is proportional to the length of the edges" ) mode : EnumProperty( items=( ('POLYHEDRA', "Polyhedra", "Polyhedral Complex Decomposition, the result are disconnected polyhedra geometries"), ('WIREFRAME', "Wireframe", "Polyhedral Wireframe through edges tickening") ), default='POLYHEDRA', name="Polyhedra Mode" ) dissolve : EnumProperty( items=( ('NONE', "None", "Keeps original topology"), ('INNER', "Inner", "Dissolve inner loops"), ('OUTER', "Outer", "Dissolve outer loops") ), default='NONE', name="Dissolve" ) selective_wireframe : EnumProperty( items=( ('NONE', "None", "Apply wireframe to every cell"), ('THICKNESS', "Thickness", "Wireframe only on bigger cells compared to the thickness"), ('AREA', "Area", "Wireframe based on cells dimensions"), ('WEIGHT', "Weight", "Wireframe based on vertex groups") ), default='NONE', name="Selective" ) thickness_threshold_correction : FloatProperty( name="Correction", default=1, min=0, soft_max=2, description="Adjust threshold based on thickness" ) area_threshold : FloatProperty( name="Threshold", default=0, min=0, soft_max=10, description="Use only faces with an area greater than the threshold" ) thicken_all : BoolProperty( name="Thicken all", description="Thicken original faces as well", default=True ) vertex_group_thickness : StringProperty( name="Thickness weight", default='', description="Vertex Group used for thickness" ) invert_vertex_group_thickness : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_thickness_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Thickness factor to use for zero vertex group influence" ) vertex_group_selective : StringProperty( name="Thickness weight", default='', description="Vertex Group used for thickness" ) invert_vertex_group_selective : BoolProperty( name="Invert", default=False, description="Invert the vertex group influence" ) vertex_group_selective_threshold : FloatProperty( name="Threshold", default=0.5, min=0, max=1, description="Selective wireframe threshold" ) bool_smooth : BoolProperty( name="Smooth Shading", default=False, description="Output faces with smooth shading rather than flat shaded" ) bool_hold : BoolProperty( name="Hold", description="Wait...", default=False ) def draw(self, context): ob = context.object layout = self.layout col = layout.column(align=True) self.bool_hold = True if self.mode == 'WIREFRAME': col.separator() col.prop(self, "thickness") col.separator() col.prop(self, "segments") return def invoke(self, context, event): return context.window_manager.invoke_props_dialog(self) def execute(self, context): ob0 = context.object self.object_name = "Polyhedral Wireframe" # 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 ob0.type not in ('MESH'): message = "Source object must be a Mesh!" self.report({'ERROR'}, message) if bpy.ops.object.select_all.poll(): bpy.ops.object.select_all(action='TOGGLE') bpy.ops.object.mode_set(mode='OBJECT') bool_update = False auto_layer_collection() new_ob = convert_object_to_mesh(ob0,False,False) new_ob.data.name = self.object_name new_ob.name = self.object_name # Store parameters props = new_ob.tissue_polyhedra lock_status = new_ob.tissue.bool_lock new_ob.tissue.bool_lock = True props.mode = self.mode props.thickness = self.thickness props.segments = self.segments props.dissolve = self.dissolve props.proportional_segments = self.proportional_segments props.crease = self.crease props.object = ob0 new_ob.tissue.tissue_type = 'POLYHEDRA' try: bpy.ops.object.tissue_update_polyhedra() except RuntimeError as e: bpy.data.objects.remove(new_ob) remove_temp_objects() self.report({'ERROR'}, str(e)) return {'CANCELLED'} if not bool_update: self.object_name = new_ob.name new_ob.location = ob0.location new_ob.matrix_world = ob0.matrix_world # Assign collection of the base object old_coll = new_ob.users_collection if old_coll != ob0.users_collection: for c in old_coll: c.objects.unlink(new_ob) for c in ob0.users_collection: c.objects.link(new_ob) context.view_layer.objects.active = new_ob # unlock new_ob.tissue.bool_lock = lock_status return {'FINISHED'} class tissue_update_polyhedra(Operator): bl_idname = "object.tissue_update_polyhedra" bl_label = "Tissue Update Polyhedral Wireframe" bl_description = "Update a previously generated polyhedral object" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): ob = context.object tissue_time(None,'Tissue: Polyhedral Wireframe of "{}"...'.format(ob.name), levels=0) start_time = time.time() begin_time = time.time() props = ob.tissue_polyhedra thickness = props.thickness merge_dist = thickness*0.0001 subs = props.segments if props.mode == 'POLYHEDRA': subs = 1 # Source mesh ob0 = props.object if props.bool_modifiers: me = simple_to_mesh(ob0) else: me = ob0.data.copy() bm = bmesh.new() bm.from_mesh(me) pre_processing(bm) polyhedral_subdivide_edges(bm, subs, props.proportional_segments) tissue_time(start_time,'Subdivide edges',levels=1) start_time = time.time() thickness = np.ones(len(bm.verts))*props.thickness if(props.vertex_group_thickness in ob.vertex_groups.keys()): dvert_lay = bm.verts.layers.deform.active group_index_thickness = ob.vertex_groups[props.vertex_group_thickness].index thickness_weight = bmesh_get_weight_numpy(group_index_thickness, dvert_lay, bm.verts) if 'invert_vertex_group_thickness' in props.keys(): if props['invert_vertex_group_thickness']: thickness_weight = 1-thickness_weight fact = 0 if 'vertex_group_thickness_factor' in props.keys(): fact = props['vertex_group_thickness_factor'] if fact > 0: thickness_weight = thickness_weight*(1-fact) + fact thickness *= thickness_weight thickness_dict = dict(zip([tuple(v.co) for v in bm.verts],thickness)) bm1 = get_double_faces_bmesh(bm) polyhedra = get_decomposed_polyhedra(bm) if(type(polyhedra) is str): bm.free() bm1.free() self.report({'ERROR'}, polyhedra) return {'CANCELLED'} selective_dict = None accurate = False if props.selective_wireframe == 'THICKNESS': filter_faces = True accurate = True area_threshold = (thickness*props.thickness_threshold_correction)**2 elif props.selective_wireframe == 'AREA': filter_faces = True area_threshold = props.area_threshold elif props.selective_wireframe == 'WEIGHT': filter_faces = True if(props.vertex_group_selective in ob.vertex_groups.keys()): dvert_lay = bm.verts.layers.deform.active group_index_selective = ob.vertex_groups[props.vertex_group_selective].index thresh = props.vertex_group_selective_threshold selective_weight = bmesh_get_weight_numpy(group_index_selective, dvert_lay, bm.verts) selective_weight = selective_weight >= thresh invert = False if 'invert_vertex_group_selective' in props.keys(): if props['invert_vertex_group_selective']: invert = True if invert: selective_weight = selective_weight <= thresh else: selective_weight = selective_weight >= thresh selective_dict = dict(zip([tuple(v.co) for v in bm.verts],selective_weight)) else: filter_faces = False else: filter_faces = False bm.free() end_time = time.time() tissue_time(start_time,'Found {} polyhedra'.format(len(polyhedra)),levels=1) start_time = time.time() bm1.faces.ensure_lookup_table() bm1.faces.index_update() #unique_verts_dict = dict(zip([tuple(v.co) for v in bm1.verts],bm1.verts)) bm1, all_faces_dict, polyhedra_faces_id, polyhedra_faces_id_neg = combine_polyhedra_faces(bm1, polyhedra) if props.mode == 'POLYHEDRA': poly_me = me.copy() bm1.to_mesh(poly_me) poly_me.update() old_me = ob.data ob.data = poly_me mesh_name = old_me.name bpy.data.meshes.remove(old_me) bpy.data.meshes.remove(me) ob.data.name = mesh_name end_time = time.time() print('Tissue: Polyhedral wireframe in {:.4f} sec'.format(end_time-start_time)) return {'FINISHED'} delete_faces = set({}) wireframe_faces = [] not_wireframe_faces = [] #flat_faces = [] count = 0 outer_faces = get_outer_faces(bm1) for faces_id in polyhedra_faces_id: delete_faces_poly = [] wireframe_faces_poly = [] for id in faces_id: if id in delete_faces: continue delete = False cen = None f = None if filter_faces: f = all_faces_dict[id] if selective_dict: for v in f.verts: if selective_dict[tuple(v.co)]: delete = True break elif accurate: cen = f.calc_center_median() for e in f.edges: v0 = e.verts[0] v1 = e.verts[1] mid = (v0.co + v1.co)/2 vec1 = v0.co - v1.co vec2 = mid - cen ang = Vector.angle(vec1,vec2) length = vec2.length length = sin(ang)*length thick0 = thickness_dict[tuple(v0.co)] thick1 = thickness_dict[tuple(v1.co)] thick = (thick0 + thick1)/4 if length < thick*props.thickness_threshold_correction: delete = True break else: delete = f.calc_area() < area_threshold if delete: if props.thicken_all: delete_faces_poly.append(id) else: wireframe_faces_poly.append(id) if len(wireframe_faces_poly) <= 2: delete_faces.update(set([id for id in faces_id])) not_wireframe_faces += [polyhedra_faces_id_neg[id] for id in faces_id] else: wireframe_faces += wireframe_faces_poly #flat_faces += delete_faces_poly wireframe_faces_id = [i for i in wireframe_faces if i not in not_wireframe_faces] wireframe_faces = [all_faces_dict[i] for i in wireframe_faces_id] #flat_faces = [all_faces_dict[i] for i in flat_faces] delete_faces = [all_faces_dict[i] for i in delete_faces if all_faces_dict[i] not in outer_faces] tissue_time(start_time,'Merge and delete',levels=1) start_time = time.time() ############# FRAME ############# new_faces, outer_wireframe_faces = create_frame_faces( bm1, wireframe_faces, wireframe_faces_id, polyhedra_faces_id_neg, thickness_dict, outer_faces ) faces_to_delete = wireframe_faces+delete_faces outer_wireframe_faces += [f for f in outer_faces if not f in faces_to_delete] bmesh.ops.delete(bm1, geom=faces_to_delete, context='FACES') bm1.verts.ensure_lookup_table() bm1.edges.ensure_lookup_table() bm1.faces.ensure_lookup_table() bm1.verts.index_update() wireframe_indexes = [f.index for f in new_faces] outer_indexes = [f.index for f in outer_wireframe_faces] edges_to_crease = [f.edges[2].index for f in new_faces] layer_is_wireframe = bm1.faces.layers.int.new('tissue_is_wireframe') for id in wireframe_indexes: bm1.faces[id][layer_is_wireframe] = 1 layer_is_outer = bm1.faces.layers.int.new('tissue_is_outer') for id in outer_indexes: bm1.faces[id][layer_is_outer] = 1 if props.crease > 0 and props.dissolve != 'INNER': crease_layer = bm1.edges.layers.float.new('crease_edge') bm1.edges.index_update() crease_edges = [] for edge_index in edges_to_crease: bm1.edges[edge_index][crease_layer] = props.crease tissue_time(start_time,'Generate frames',levels=1) start_time = time.time() ### Displace vertices ### corners = [[] for i in range(len(bm1.verts))] normals = [0]*len(bm1.verts) vertices = [0]*len(bm1.verts) # Define vectors direction for f in bm1.faces: v0 = f.verts[0] v1 = f.verts[1] id = v0.index corners[id].append((v1.co - v0.co).normalized()) v0.normal_update() normals[id] = v0.normal.copy() vertices[id] = v0 # Displace vertices for i, vecs in enumerate(corners): if len(vecs) > 0: v = vertices[i] nor = normals[i] ang = 0 for vec in vecs: if nor == Vector((0,0,0)): continue ang += nor.angle(vec) ang /= len(vecs) div = sin(ang) if div == 0: div = 1 v.co += nor*thickness_dict[tuple(v.co)]/div tissue_time(start_time,'Corners displace',levels=1) start_time = time.time() if props.dissolve != 'NONE': if props.dissolve == 'INNER': dissolve_id = 2 if props.dissolve == 'OUTER': dissolve_id = 0 bm1.edges.index_update() dissolve_edges = [] for f in bm1.faces: e = f.edges[dissolve_id] if e not in dissolve_edges: dissolve_edges.append(e) bmesh.ops.dissolve_edges(bm1, edges=dissolve_edges, use_verts=True, use_face_split=False) for v in bm1.verts: v.select_set(False) for f in bm1.faces: f.select_set(False) dissolve_verts = [v for v in bm1.verts if len(v.link_edges) < 3] bmesh.ops.dissolve_verts(bm1, verts=dissolve_verts, use_face_split=False, use_boundary_tear=False) # clean meshes bm1.to_mesh(me) if props.bool_smooth: me.shade_smooth() me.update() old_me = ob.data ob.data = me mesh_name = old_me.name bpy.data.meshes.remove(old_me) ob.data.name = mesh_name bm1.free() bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.select_all(action='SELECT') bpy.ops.uv.reset() bpy.ops.object.mode_set(mode='OBJECT') tissue_time(start_time,'Clean mesh',levels=1) start_time = time.time() tissue_time(begin_time,'Polyhedral Wireframe',levels=0) return {'FINISHED'} def pre_processing(bm): delete = [e for e in bm.edges if len(e.link_faces) < 2] while len(delete) > 0: bmesh.ops.delete(bm, geom=delete, context='EDGES') bm.faces.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.verts.ensure_lookup_table() delete = [e for e in bm.edges if len(e.link_faces) < 2] return bm def get_outer_faces(bm): bm_copy = bm.copy() bmesh.ops.recalc_face_normals(bm_copy, faces=bm_copy.faces) outer = [] for f1, f2 in zip(bm.faces, bm_copy.faces): f1.normal_update() if f1.normal == f2.normal: outer.append(f1) return outer def create_frame_faces( bm, wireframe_faces, wireframe_faces_id, polyhedra_faces_id_neg, thickness_dict, outer_faces ): new_faces = [] for f in wireframe_faces: f.normal_update() all_loops = [[loop for loop in f.loops] for f in wireframe_faces] is_outer = [f in outer_faces for f in wireframe_faces] outer_wireframe_faces = [] frames_verts_dict = {} for loops_index, loops in enumerate(all_loops): n_loop = len(loops) frame_id = wireframe_faces_id[loops_index] single_face_id = min(frame_id,polyhedra_faces_id_neg[frame_id]) verts_inner = [] loops_keys = [tuple(loop.vert.co) + tuple((single_face_id,)) for loop in loops] if loops_keys[0] in frames_verts_dict: verts_inner = [frames_verts_dict[key] for key in loops_keys] else: tangents = [] nor = wireframe_faces[loops_index].normal for loop in loops: tan = loop.calc_tangent() #nor.cross(loop.calc_tangent().cross(nor)).normalized() thickness = thickness_dict[tuple(loop.vert.co)] tangents.append(tan/sin(loop.calc_angle()/2)*thickness) for i in range(n_loop): loop = loops[i] new_co = loop.vert.co + tangents[i] new_vert = bm.verts.new(new_co) frames_verts_dict[loops_keys[i]] = new_vert verts_inner.append(new_vert) # add faces loops += [loops[0]] verts_inner += [verts_inner[0]] for i in range(n_loop): v0 = loops[i].vert v1 = loops[i+1].vert v2 = verts_inner[i+1] v3 = verts_inner[i] face_verts = [v0,v1,v2,v3] new_face = bm.faces.new(face_verts) new_face.select = True new_faces.append(new_face) if is_outer[loops_index]: outer_wireframe_faces.append(new_face) new_face.normal_update() return new_faces, outer_wireframe_faces def polyhedral_subdivide_edges(bm, subs, proportional_segments): if subs > 1: if proportional_segments: wire_length = [e.calc_length() for e in bm.edges] all_edges = list(bm.edges) max_segment = max(wire_length)/subs+0.00001 # prevent out_of_bounds split_edges = [[] for i in range(subs)] for e, l in zip(all_edges, wire_length): split_edges[int(l//max_segment)].append(e) for i in range(1,subs): bmesh.ops.bisect_edges(bm, edges=split_edges[i], cuts=i) else: bmesh.ops.bisect_edges(bm, edges=bm.edges, cuts=subs-1) def get_double_faces_bmesh(bm): double_faces = [] for f in bm.faces: verts0 = [v.co for v in f.verts] verts1 = verts0.copy() verts1.reverse() double_faces.append(verts0) double_faces.append(verts1) bm1 = bmesh.new() for verts_co in double_faces: bm1.faces.new([bm1.verts.new(v) for v in verts_co]) bm1.verts.ensure_lookup_table() bm1.edges.ensure_lookup_table() bm1.faces.ensure_lookup_table() return bm1 def get_decomposed_polyhedra(bm): polyhedra_from_facekey = {} count = 0 to_merge = [] for e in bm.edges: done = [] # ERROR: Naked edges link_faces = e.link_faces n_radial_faces = len(link_faces) if n_radial_faces < 2: return "Naked edges are not allowed" vert0 = e.verts[0] vert1 = e.verts[1] edge_vec = vert1.co - vert0.co for id1 in range(n_radial_faces-1): f1 = link_faces[id1] facekey1 = f1.index+1 verts1 = [v.index for v in f1.verts] v0_index = verts1.index(vert0.index) v1_index = verts1.index(vert1.index) ref_loop_dir = v0_index == (v1_index+1)%len(verts1) edge_vec1 = edge_vec if ref_loop_dir else -edge_vec tan1 = f1.normal.cross(edge_vec1) # faces to compare with faceskeys2, normals2 = get_second_faces( link_faces, vert0.index, vert1.index, ref_loop_dir, f1 ) tangents2 = [nor.cross(-edge_vec1) for nor in normals2] # positive side facekey2_pos = get_closest_face( faceskeys2, tangents2, tan1, edge_vec1, True ) polyhedra_from_facekey, count, to_merge = store_neighbor_faces( facekey1, facekey2_pos, polyhedra_from_facekey, count, to_merge ) # negative side facekey2_neg = get_closest_face( faceskeys2, tangents2, tan1, edge_vec1, False ) polyhedra_from_facekey, count, to_merge = store_neighbor_faces( -facekey1, facekey2_neg, polyhedra_from_facekey, count, to_merge ) polyhedra = [ [] for i in range(count)] unique_index = get_unique_polyhedra_index(count, to_merge) for key, val in polyhedra_from_facekey.items(): polyhedra[unique_index[val]].append(key) polyhedra = list(set(tuple(i) for i in polyhedra if i)) polyhedra = remove_double_faces_from_polyhedra(polyhedra) return polyhedra def remove_double_faces_from_polyhedra(polyhedra): new_polyhedra = [] for polyhedron in polyhedra: new_polyhedron = [key for key in polyhedron if not -key in polyhedron] new_polyhedra.append(new_polyhedron) return new_polyhedra def get_unique_polyhedra_index(count, to_merge): out = list(range(count)) keep_going = True while keep_going: keep_going = False for pair in to_merge: if out[pair[1]] != out[pair[0]]: out[pair[0]] = out[pair[1]] = min(out[pair[0]], out[pair[1]]) keep_going = True return out def get_closest_face(faces, tangents, ref_vector, axis, is_positive): facekey = None min_angle = 1000000 for fk, tangent in zip(faces, tangents): rot_axis = -axis if is_positive else axis angle = round_angle_with_axis(ref_vector, tangent, rot_axis) if angle < min_angle: facekey = fk min_angle = angle return facekey if is_positive else -facekey def get_second_faces(face_list, edge_v0, edge_v1, reference_loop_dir, self): nFaces = len(face_list)-1 facekeys = [None]*nFaces normals = [None]*nFaces count = 0 for face in face_list: if(face == self): continue verts = [v.index for v in face.verts] v0_index = verts.index(edge_v0) v1_index = verts.index(edge_v1) loop_dir = v0_index == (v1_index+1)%len(verts) if reference_loop_dir != loop_dir: facekeys[count] = face.index+1 normals[count] = face.normal else: facekeys[count] = -(face.index+1) normals[count] = -face.normal count+=1 return facekeys, normals def store_neighbor_faces( key1, key2, polyhedra, polyhedra_count, to_merge ): poly1 = polyhedra.get(key1) poly2 = polyhedra.get(key2) if poly1 and poly2: if poly1 != poly2: to_merge.append((poly1, poly2)) elif poly1: polyhedra[key2] = poly1 elif poly2: polyhedra[key1] = poly2 else: polyhedra[key1] = polyhedra[key2] = polyhedra_count polyhedra_count += 1 return polyhedra, polyhedra_count, to_merge def add_polyhedron(bm,source_faces): faces_verts_key = [[tuple(v.co) for v in f.verts] for f in source_faces] polyhedron_verts_key = [key for face_key in faces_verts_key for key in face_key] polyhedron_verts = [bm.verts.new(co) for co in polyhedron_verts_key] polyhedron_verts_dict = dict(zip(polyhedron_verts_key, polyhedron_verts)) new_faces = [None]*len(faces_verts_key) count = 0 for verts_keys in faces_verts_key: new_faces[count] = bm.faces.new([polyhedron_verts_dict.get(key) for key in verts_keys]) count+=1 bm.faces.ensure_lookup_table() bm.faces.index_update() return new_faces def combine_polyhedra_faces(bm,polyhedra): new_bm = bmesh.new() polyhedra_faces_id = [None]*len(polyhedra) all_faces_dict = {} #polyhedra_faces_pos = {} polyhedra_faces_id_neg = {} vertices_key = [tuple(v.co) for v in bm.verts] count = 0 for p in polyhedra: faces_id = [(f-1)*2 if f > 0 else (-f-1)*2+1 for f in p] faces_id_neg = [(-f-1)*2 if f < 0 else (f-1)*2+1 for f in p] new_faces = add_polyhedron(new_bm,[bm.faces[f_id] for f_id in faces_id]) faces_dict = {} for i in range(len(new_faces)): face = new_faces[i] id = faces_id[i] id_neg = faces_id_neg[i] polyhedra_faces_id_neg[id] = id_neg all_faces_dict[id] = face polyhedra_faces_id[count] = faces_id count+=1 return new_bm, all_faces_dict, polyhedra_faces_id, polyhedra_faces_id_neg class TISSUE_PT_polyhedra_object(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_label = "Tissue Polyhedra" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: ob = context.object return ob.type == 'MESH' and ob.tissue.tissue_type == 'POLYHEDRA' except: return False def draw(self, context): ob = context.object props = ob.tissue_polyhedra tissue_props = ob.tissue bool_polyhedra = tissue_props.tissue_type == 'POLYHEDRA' layout = self.layout if not bool_polyhedra: layout.label(text="The selected object is not a Polyhedral object", icon='INFO') else: if props.error_message != "": layout.label(text=props.error_message, icon='ERROR') col = layout.column(align=True) row = col.row(align=True) #set_tessellate_handler(self,context) row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #### lock_icon = 'LOCKED' if tissue_props.bool_lock else 'UNLOCKED' #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED' deps_icon = 'LINKED' if tissue_props.bool_dependencies else 'UNLINKED' row.prop(tissue_props, "bool_dependencies", text="", icon=deps_icon) row.prop(tissue_props, "bool_lock", text="", icon=lock_icon) col2 = row.column(align=True) col2.prop(tissue_props, "bool_run", text="",icon='TIME') col2.enabled = not tissue_props.bool_lock col2 = row.column(align=True) col2.operator("mesh.tissue_remove", text="", icon='X') #layout.use_property_split = True #layout.use_property_decorate = False # No animation. col = layout.column(align=True) col.label(text='Polyhedral Mode:') col.prop(props, 'mode', text='') col.separator() col.label(text='Source object:') row = col.row(align=True) row.prop_search(props, "object", context.scene, "objects", text='') col2 = row.column(align=True) col2.prop(props, "bool_modifiers", text='Use Modifiers',icon='MODIFIER') if props.mode == 'WIREFRAME': col.separator() col.prop(props, 'thickness') row = col.row(align=True) ob0 = props.object row.prop_search(props, 'vertex_group_thickness', ob0, "vertex_groups", text='') col2 = row.column(align=True) row2 = col2.row(align=True) row2.prop(props, "invert_vertex_group_thickness", text="", toggle=True, icon='ARROW_LEFTRIGHT') row2.prop(props, "vertex_group_thickness_factor") row2.enabled = props.vertex_group_thickness in ob0.vertex_groups.keys() col.prop(props, 'bool_smooth') col.separator() col.label(text='Selective Wireframe:') col.prop(props, 'selective_wireframe', text='Mode') col.separator() if props.selective_wireframe == 'THICKNESS': col.prop(props, 'thickness_threshold_correction') elif props.selective_wireframe == 'AREA': col.prop(props, 'area_threshold') elif props.selective_wireframe == 'WEIGHT': row = col.row(align=True) row.prop_search(props, 'vertex_group_selective', ob0, "vertex_groups", text='') col2 = row.column(align=True) row2 = col2.row(align=True) row2.prop(props, "invert_vertex_group_selective", text="", toggle=True, icon='ARROW_LEFTRIGHT') row2.prop(props, "vertex_group_selective_threshold") row2.enabled = props.vertex_group_selective in ob0.vertex_groups.keys() #if props.selective_wireframe != 'NONE': # col.prop(props, 'thicken_all') col.separator() col.label(text='Subdivide edges:') row = col.row() row.prop(props, 'segments') row.prop(props, 'proportional_segments', text='Proportional') col.separator() col.label(text='Loops:') col.prop(props, 'dissolve') col.separator() col.prop(props, 'crease')