# SPDX-License-Identifier: GPL-2.0-or-later # # # (c) Alessandro Zomparelli # # (2017) # # # # http://www.co-de-it.com/ # # # # ############################################################################ # import bpy, bmesh from bpy.types import Operator from bpy.props import ( IntProperty, BoolProperty, EnumProperty, PointerProperty, StringProperty, FloatProperty ) from bpy.types import ( Operator, Panel, PropertyGroup, ) import numpy as np from mathutils import Vector from math import pi from .utils import ( find_curves, update_curve_from_pydata, simple_to_mesh, convert_object_to_mesh, get_weight_numpy, loops_from_bmesh, get_mesh_before_subs ) import time def anim_curve_active(self, context): ob = context.object props = ob.tissue_to_curve try: props.object.name if not ob.tissue.bool_lock: bpy.ops.object.tissue_convert_to_curve_update() except: pass class tissue_to_curve_prop(PropertyGroup): object : PointerProperty( type=bpy.types.Object, name="", description="Source object", update = anim_curve_active ) bool_smooth : BoolProperty( name="Smooth Shading", default=True, description="Output faces with smooth shading rather than flat shaded", update = anim_curve_active ) bool_lock : BoolProperty( name="Lock", description="Prevent automatic update on settings changes or if other objects have it in the hierarchy.", default=False, update = anim_curve_active ) bool_dependencies : BoolProperty( name="Update Dependencies", description="Automatically updates source object as well, when possible", default=False, update = anim_curve_active ) bool_run : BoolProperty( name="Animatable Curve", description="Automatically recompute the conversion when the frame is changed.", default = False ) use_modifiers : BoolProperty( name="Use Modifiers", default=True, description="Automatically apply Modifiers and Shape Keys", update = anim_curve_active ) subdivision_mode : EnumProperty( items=( ('ALL', "All", ""), ('CAGE', "Cage", ""), ('INNER', "Inner", "") ), default='CAGE', name="Subdivided Edges", update = anim_curve_active ) use_endpoint_u : BoolProperty( name="Endpoint U", default=True, description="Make all open nurbs curve meet the endpoints", update = anim_curve_active ) clean_distance : FloatProperty( name="Merge Distance", default=0, min=0, soft_max=10, description="Merge Distance", update = anim_curve_active ) nurbs_order : IntProperty( name="Order", default=4, min=2, max=6, description="Nurbs order", update = anim_curve_active ) system : IntProperty( name="System", default=0, min=0, description="Particle system index", update = anim_curve_active ) bounds_selection : EnumProperty( items=( ('ALL', "All", ""), ('BOUNDS', "Boundaries", ""), ('INNER', "Inner", "") ), default='ALL', name="Boundary Selection", update = anim_curve_active ) periodic_selection : EnumProperty( items=( ('ALL', "All", ""), ('OPEN', "Open", ""), ('CLOSED', "Closed", "") ), default='ALL', name="Periodic Selection", update = anim_curve_active ) spline_type : EnumProperty( items=( ('POLY', "Poly", ""), ('BEZIER', "Bezier", ""), ('NURBS', "NURBS", "") ), default='POLY', name="Spline Type", update = anim_curve_active ) mode : EnumProperty( items=( ('LOOPS', "Loops", ""), ('EDGES', "Edges", ""), ('PARTICLES', "Particles", "") ), default='LOOPS', name="Conversion Mode", update = anim_curve_active ) vertex_group : StringProperty( name="Radius", default='', description="Vertex Group used for variable radius", update = anim_curve_active ) invert_vertex_group : BoolProperty(default=False, description='Inverte the value of the Vertex Group', update = anim_curve_active ) vertex_group_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Depth bevel factor to use for zero vertex group influence", update = anim_curve_active ) only_sharp : BoolProperty( default=False, name="Only Sharp Edges", description='Convert only Sharp edges', update = anim_curve_active ) pattern_depth : FloatProperty( name="Depth", default=0.02, min=0, soft_max=10, description="Displacement pattern depth", update = anim_curve_active ) pattern_offset : FloatProperty( name="Offset", default=0, soft_min=-1, soft_max=1, description="Displacement pattern offset", update = anim_curve_active ) pattern0 : IntProperty( name="Step 0", default=0, min=0, soft_max=10, description="Pattern step 0", update = anim_curve_active ) pattern1 : IntProperty( name="Step 1", default=0, min=0, soft_max=10, description="Pattern step 1", update = anim_curve_active ) class tissue_convert_to_curve(Operator): bl_idname = "object.tissue_convert_to_curve" bl_label = "Tissue Convert to Curve" bl_description = "Convert selected mesh to Curve object" bl_options = {'REGISTER', 'UNDO'} object : StringProperty( name="", description="Source object", default = "" ) bool_smooth : BoolProperty( name="Smooth Shading", default=True, description="Output faces with smooth shading rather than flat shaded" ) use_modifiers : BoolProperty( name="Use Modifiers", default=True, description="Automatically apply Modifiers and Shape Keys" ) subdivision_mode : EnumProperty( items=( ('ALL', "All", ""), ('CAGE', "Cage", ""), ('INNER', "Inner", "") ), default='CAGE', name="Subdivided Edges" ) use_endpoint_u : BoolProperty( name="Endpoint U", default=True, description="Make all open nurbs curve meet the endpoints" ) nurbs_order : IntProperty( name="Order", default=4, min=2, max=6, description="Nurbs order" ) system : IntProperty( name="System", default=0, min=0, description="Particle system index" ) clean_distance : FloatProperty( name="Merge Distance", default=0, min=0, soft_max=10, description="Merge Distance" ) spline_type : EnumProperty( items=( ('POLY', "Poly", ""), ('BEZIER', "Bezier", ""), ('NURBS', "NURBS", "") ), default='POLY', name="Spline Type" ) bounds_selection : EnumProperty( items=( ('ALL', "All", ""), ('BOUNDS', "Boundaries", ""), ('INNER', "Inner", "") ), default='ALL', name="Boundary Selection" ) periodic_selection : EnumProperty( items=( ('ALL', "All", ""), ('OPEN', "Open", ""), ('CLOSED', "Closed", "") ), default='ALL', name="Periodic Selection" ) mode : EnumProperty( items=( ('LOOPS', "Loops", ""), ('EDGES', "Edges", ""), ('PARTICLES', "Particles", "") ), default='LOOPS', name="Conversion Mode" ) vertex_group : StringProperty( name="Radius", default='', description="Vertex Group used for variable radius" ) invert_vertex_group : BoolProperty(default=False, description='Inverte the value of the Vertex Group' ) vertex_group_factor : FloatProperty( name="Factor", default=0, min=0, max=1, description="Depth bevel factor to use for zero vertex group influence" ) only_sharp : BoolProperty( default=False, name="Only Sharp Edges", description='Convert only Sharp edges' ) pattern_depth : FloatProperty( name="Depth", default=0.02, min=0, soft_max=10, description="Displacement pattern depth" ) pattern_offset : FloatProperty( name="Offset", default=0, soft_min=-1, soft_max=1, description="Displacement pattern offset" ) pattern0 : IntProperty( name="Step 0", default=0, min=0, soft_max=10, description="Pattern step 0" ) pattern1 : IntProperty( name="Step 1", default=0, min=0, soft_max=10, description="Pattern step 1" ) @classmethod def poll(cls, context): try: #bool_tessellated = context.object.tissue_tessellate.generator != None ob = context.object return ob.type in ('MESH','CURVE','SURFACE','FONT') and ob.mode == 'OBJECT'# and bool_tessellated except: return False def invoke(self, context, event): self.object = context.object.name return context.window_manager.invoke_props_dialog(self) def draw(self, context): ob = context.object ob0 = bpy.data.objects[self.object] #props = ob.tissue_to_curve layout = self.layout col = layout.column(align=True) row = col.row(align=True) #row.label(text='Object: ' + self.object) #row.prop_search(self, "object", context.scene, "objects") #row.prop(self, "use_modifiers")#, icon='MODIFIER', text='') col.separator() col.label(text='Conversion Mode:') row = col.row(align=True) row.prop( self, "mode", text="Conversion Mode", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if self.mode == 'PARTICLES': col.separator() col.prop(self, "system") col.separator() if self.mode in ('LOOPS', 'EDGES'): row = col.row(align=True) row.prop(self, "use_modifiers") col2 = row.column(align=True) if self.use_modifiers: col2.prop(self, "subdivision_mode", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col2.enabled = False for m in bpy.data.objects[self.object].modifiers: if m.type in ('SUBSURF','MULTIRES'): col2.enabled = True col.separator() row = col.row(align=True) row.label(text='Filter Edges:') col2 = row.column(align=True) col2.prop(self, "bounds_selection", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col2.prop(self, 'only_sharp') col.separator() if self.mode == 'LOOPS': row = col.row(align=True) row.label(text='Filter Loops:') row.prop(self, "periodic_selection", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col.separator() col.label(text='Spline Type:') row = col.row(align=True) row.prop( self, "spline_type", text="Spline Type", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if self.spline_type == 'NURBS': col.separator() col.label(text='Nurbs splines:') row = col.row(align=True) row.prop(self, "use_endpoint_u") row.prop(self, "nurbs_order") col.separator() col.prop(self, "bool_smooth") if ob0.type == 'MESH' and self.mode != 'PARTICLES': col.separator() col.label(text='Variable Radius:') row = col.row(align=True) row.prop_search(self, 'vertex_group', ob0, "vertex_groups", text='') row.prop(self, "invert_vertex_group", text="", toggle=True, icon='ARROW_LEFTRIGHT') row.prop(self, "vertex_group_factor") col.separator() col.label(text='Clean curves:') col.prop(self, "clean_distance") col.separator() col.label(text='Displacement Pattern:') row = col.row(align=True) row.prop(self, "pattern0") row.prop(self, "pattern1") row = col.row(align=True) row.prop(self, "pattern_depth") row.prop(self, "pattern_offset") def execute(self, context): ob = context.active_object crv = bpy.data.curves.new(ob.name + '_Curve', type='CURVE') crv.dimensions = '3D' new_ob = bpy.data.objects.new(ob.name + '_Curve', crv) bpy.context.collection.objects.link(new_ob) context.view_layer.objects.active = new_ob new_ob.select_set(True) ob.select_set(False) new_ob.matrix_world = ob.matrix_world new_ob.tissue.tissue_type = 'TO_CURVE' new_ob.tissue.bool_lock = True props = new_ob.tissue_to_curve props.object = ob props.use_modifiers = self.use_modifiers props.subdivision_mode = self.subdivision_mode props.clean_distance = self.clean_distance props.spline_type = self.spline_type props.mode = self.mode props.use_endpoint_u = self.use_endpoint_u props.nurbs_order = self.nurbs_order props.vertex_group = self.vertex_group props.vertex_group_factor = self.vertex_group_factor props.invert_vertex_group = self.invert_vertex_group props.bool_smooth = self.bool_smooth props.system = self.system props.periodic_selection = self.periodic_selection props.bounds_selection = self.bounds_selection props.only_sharp = self.only_sharp props.pattern0 = self.pattern0 props.pattern1 = self.pattern1 props.pattern_depth = self.pattern_depth props.pattern_offset = self.pattern_offset new_ob.tissue.bool_lock = False bpy.ops.object.tissue_convert_to_curve_update() return {'FINISHED'} class tissue_convert_to_curve_update(Operator): bl_idname = "object.tissue_convert_to_curve_update" bl_label = "Tissue Update Curve" bl_description = "Update Curve object" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): try: ob = context.object bool_curve = ob.tissue_to_curve.object != None return ob.type == 'CURVE' and ob.mode == 'OBJECT' and bool_curve except: return False def execute(self, context): start_time = time.time() ob = context.object props = ob.tissue_to_curve ob0 = props.object if props.mode == 'PARTICLES': eval_ob = ob0.evaluated_get(context.evaluated_depsgraph_get()) system_id = min(props.system, len(eval_ob.particle_systems)) psystem = eval_ob.particle_systems[system_id] ob.data.splines.clear() particles = psystem.particles for id,p in enumerate(particles): s = ob.data.splines.new('POLY') if psystem.settings.type == 'HAIR': n_pts = len(p.hair_keys) pts = [0]*3*n_pts p.hair_keys.foreach_get('co',pts) co = np.array(pts).reshape((-1,3)) else: n_pts = 2**psystem.settings.display_step + 1 pts = [] for i in range(n_pts): vec = psystem.co_hair(eval_ob, particle_no=id,step=i) vec = ob0.matrix_world.inverted() @ vec pts.append(vec) co = np.array(pts) w = np.ones(n_pts).reshape((n_pts,1)) co = np.concatenate((co,w),axis=1).reshape((n_pts*4)) s.points.add(n_pts-1) s.points.foreach_set('co',co) else: _ob0 = ob0 ob0 = convert_object_to_mesh(ob0, apply_modifiers=props.use_modifiers) me = ob0.data n_verts = len(me.vertices) verts = [0]*n_verts*3 me.vertices.foreach_get('co',verts) verts = np.array(verts).reshape((-1,3)) normals = [0]*n_verts*3 me.vertices.foreach_get('normal',normals) normals = np.array(normals).reshape((-1,3)) #tilt = np.degrees(np.arcsin(normals[:,2])) #tilt = np.arccos(normals[:,2])/2 verts = np.array(verts).reshape((-1,3)) if props.mode in ('LOOPS','EDGES'): bm = bmesh.new() bm.from_mesh(me) bm.verts.ensure_lookup_table() bm.edges.ensure_lookup_table() bm.faces.ensure_lookup_table() todo_edges = list(bm.edges) if props.use_modifiers and props.subdivision_mode != 'ALL': me0, subs = get_mesh_before_subs(_ob0) n_edges0 = len(me0.edges) bpy.data.meshes.remove(me0) if props.subdivision_mode == 'CAGE': todo_edges = todo_edges[:n_edges0*(2**subs)] elif props.subdivision_mode == 'INNER': todo_edges = todo_edges[n_edges0*(2**subs):] if props.only_sharp: _todo_edges = [] sharp_verts = [] for e in todo_edges: edge = me.edges[e.index] if edge.use_edge_sharp: _todo_edges.append(e) sharp_verts.append(edge.vertices[0]) sharp_verts.append(edge.vertices[1]) todo_edges = _todo_edges if props.bounds_selection == 'BOUNDS': todo_edges = [e for e in todo_edges if len(e.link_faces)<2] elif props.bounds_selection == 'INNER': todo_edges = [e for e in todo_edges if len(e.link_faces)>1] if props.mode == 'EDGES': ordered_points = [[e.verts[0].index, e.verts[1].index] for e in todo_edges] elif props.mode == 'LOOPS': vert_loops, edge_loops = loops_from_bmesh(todo_edges) if props.only_sharp: ordered_points = [] for loop in vert_loops: loop_points = [] for v in loop: if v.index in sharp_verts: loop_points.append(v.index) else: if len(loop_points)>1: ordered_points.append(loop_points) loop_points = [] if len(loop_points)>1: ordered_points.append(loop_points) #ordered_points = [[v.index for v in loop if v.index in sharp_verts] for loop in vert_loops] else: ordered_points = [[v.index for v in loop] for loop in vert_loops] if props.periodic_selection == 'CLOSED': ordered_points = [points for points in ordered_points if points[0] == points[-1]] elif props.periodic_selection == 'OPEN': ordered_points = [points for points in ordered_points if points[0] != points[-1]] else: try: ordered_points = find_curves(edges, n_verts) except: bpy.data.objects.remove(ob0) return {'CANCELLED'} try: weight = get_weight_numpy(ob0.vertex_groups[props.vertex_group], n_verts) if props.invert_vertex_group: weight = 1-weight fact = props.vertex_group_factor if fact > 0: weight = weight*(1-fact) + fact except: weight = None # Set curves Tilt ''' tilt = [] for points in ordered_points: if points[0] == points[-1]: # Closed curve pts0 = [points[-1]] + points[:-1] # i-1 pts1 = points[:] # i pts2 = points[1:] + [points[0]] # 1+1 else: # Open curve pts0 = [points[0]] + points[:-1] # i-1 pts1 = points[:] # i pts2 = points[1:] + [points[-1]] # i+1 curve_tilt = [] for i0, i1, i2 in zip(pts0, pts1, pts2): pt0 = Vector(verts[i0]) pt1 = Vector(verts[i1]) pt2 = Vector(verts[i2]) tan1 = (pt1-pt0).normalized() tan2 = (pt2-pt1).normalized() vec_tan = -(tan1 + tan2).normalized() vec2 = vec_tan.cross(Vector((0,0,1))) vec_z = vec_tan.cross(vec2) nor = normals[i1] if vec_z.length == 0: vec_z = Vector(nor) ang = vec_z.angle(nor) if nor[2] < 0: ang = 2*pi-ang #if vec_tan[0] > vec_tan[1] and nor[0]>0: ang = -ang #if vec_tan[0] > vec_tan[2] and nor[0]>0: ang = -ang #if vec_tan[0] < vec_tan[1] and nor[1]>0: ang = -ang #if nor[0]*nor[1]*nor[2] < 0: ang = -ang if nor[2] == 0: ang = -5*pi/4 #ang = max(ang, np.arccos(nor[2])) curve_tilt.append(ang) #curve_tilt.append(np.arccos(nor[2])) tilt.append(curve_tilt) ''' depth = props.pattern_depth offset = props.pattern_offset pattern = [props.pattern0,props.pattern1] update_curve_from_pydata(ob.data, verts, normals, weight, ordered_points, merge_distance=props.clean_distance, pattern=pattern, depth=depth, offset=offset) bpy.data.objects.remove(ob0) for s in ob.data.splines: s.type = props.spline_type if s.type == 'NURBS': s.use_endpoint_u = props.use_endpoint_u s.order_u = props.nurbs_order ob.data.splines.update() if not props.bool_smooth: bpy.ops.object.shade_flat() end_time = time.time() print('Tissue: object "{}" converted to Curve in {:.4f} sec'.format(ob.name, end_time-start_time)) return {'FINISHED'} class TISSUE_PT_convert_to_curve(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "data" bl_label = "Tissue Convert to Curve" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): try: #bool_curve = context.object.tissue_to_curve.object != None ob = context.object return ob.type == 'CURVE' and ob.tissue.tissue_type == 'TO_CURVE' except: return False def draw(self, context): ob = context.object props = ob.tissue_to_curve layout = self.layout #layout.use_property_split = True #layout.use_property_decorate = False col = layout.column(align=True) row = col.row(align=True) #col.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='Refresh') row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #### lock_icon = 'LOCKED' if ob.tissue.bool_lock else 'UNLOCKED' #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED' deps_icon = 'LINKED' if ob.tissue.bool_dependencies else 'UNLINKED' row.prop(ob.tissue, "bool_dependencies", text="", icon=deps_icon) row.prop(ob.tissue, "bool_lock", text="", icon=lock_icon) col2 = row.column(align=True) col2.prop(ob.tissue, "bool_run", text="",icon='TIME') col2.enabled = not ob.tissue.bool_lock col.separator() row = col.row(align=True) row.prop_search(props, "object", context.scene, "objects") row.prop(props, "use_modifiers", icon='MODIFIER', text='') col.separator() col.label(text='Conversion Mode:') row = col.row(align=True) row.prop( props, "mode", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.mode == 'PARTICLES': col.separator() col.prop(props, "system") col.separator() if props.mode in ('LOOPS', 'EDGES'): row = col.row(align=True) row.prop(props, "use_modifiers") col2 = row.column(align=True) if props.use_modifiers: col2.prop(props, "subdivision_mode", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col2.enabled = False for m in props.object.modifiers: if m.type in ('SUBSURF','MULTIRES'): col2.enabled = True col.separator() row = col.row(align=True) row.label(text='Filter Edges:') col2 = row.column(align=True) col2.prop(props, "bounds_selection", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col2.prop(props, 'only_sharp') col.separator() if props.mode == 'LOOPS': row = col.row(align=True) row.label(text='Filter Loops:') row.prop(props, "periodic_selection", text='', icon='NONE', expand=False, slider=True, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) col.separator() col.label(text='Spline Type:') row = col.row(align=True) row.prop( props, "spline_type", text="Spline Type", icon='NONE', expand=True, slider=False, toggle=False, icon_only=False, event=False, full_event=False, emboss=True, index=-1) if props.spline_type == 'NURBS': col.separator() col.label(text='Nurbs Splines:') row = col.row(align=True) row.prop(props, "use_endpoint_u") row.prop(props, "nurbs_order") col.separator() col.prop(props, "bool_smooth") if props.object.type == 'MESH': col.separator() col.label(text='Variable Radius:') row = col.row(align=True) row.prop_search(props, 'vertex_group', props.object, "vertex_groups", text='') row.prop(props, "invert_vertex_group", text="", toggle=True, icon='ARROW_LEFTRIGHT') row.prop(props, "vertex_group_factor") col.separator() col.label(text='Clean Curves:') col.prop(props, "clean_distance") col.separator() col.label(text='Displacement Pattern:') row = col.row(align=True) row.prop(props, "pattern0") row.prop(props, "pattern1") row = col.row(align=True) row.prop(props, "pattern_depth") row.prop(props, "pattern_offset")