diff --git a/mesh_looptools.py b/mesh_looptools.py index 3f38833f50ecbf7d6e84f1e749ace413205203af..0e4d26adb7b2491cb3a93476a5cd7fa301c9a378 100644 --- a/mesh_looptools.py +++ b/mesh_looptools.py @@ -19,7 +19,7 @@ bl_info = { "name": "LoopTools", "author": "Bart Crouch", - "version": (4, 5, 2), + "version": (4, 6, 0), "blender": (2, 69, 3), "location": "View3D > Toolbar and View3D > Specials (W-key)", "warning": "", @@ -741,7 +741,22 @@ def initialise(): # move the vertices to their new locations -def move_verts(object, bm, mapping, move, influence): +def move_verts(object, bm, mapping, move, lock, influence): + if lock: + lock_x, lock_y, lock_z = lock + orientation = bpy.context.space_data.transform_orientation + custom = bpy.context.space_data.current_orientation + if custom: + mat = custom.matrix.to_4x4().inverted() * object.matrix_world.copy() + elif orientation == 'LOCAL': + mat = mathutils.Matrix.Identity(4) + elif orientation == 'VIEW': + mat = bpy.context.region_data.view_matrix.copy() * \ + object.matrix_world.copy() + else: # orientation == 'GLOBAL' + mat = object.matrix_world.copy() + mat_inv = mat.inverted() + for loop in move: for index, loc in loop: if mapping: @@ -749,11 +764,22 @@ def move_verts(object, bm, mapping, move, influence): continue else: index = mapping[index] - if influence >= 0: - bm.verts[index].co = loc*(influence/100) + \ - bm.verts[index].co*((100-influence)/100) + if lock: + delta = (loc - bm.verts[index].co) * mat_inv + if lock_x: + delta[0] = 0 + if lock_y: + delta[1] = 0 + if lock_z: + delta[2] = 0 + delta = delta * mat + loc = bm.verts[index].co + delta + if influence < 0: + new_loc = loc else: - bm.verts[index].co = loc + new_loc = loc*(influence/100) + \ + bm.verts[index].co*((100-influence)/100) + bm.verts[index].co = new_loc bm.normal_update() object.data.update() @@ -1139,7 +1165,6 @@ def bridge_calculate_lines(bm, loops, mode, twist, reverse): tri, quad = [(bm.verts[loop1[i+1]].co - bm.verts[loop2[j]].co).length for j in [prev_vert2, 0]] - circle_full = 2 elif len(loop1) - 1 - i == len(loop2) - 1 - prev_vert2 and \ not circle_full: @@ -2130,7 +2155,6 @@ def curve_calculate_t(bm_mod, knots, points, pknots, regular, circular): if circular: tknots[-1] = tpoints[-1] - return(tknots, tpoints) @@ -2516,7 +2540,6 @@ def gstretch_align_pairs(ls_pairs, object, bm_mod, method): if ls_pairs: for (loop, stroke) in ls_pairs: - distance_loop_stroke total_dist = distance_loop_stroke(loop, stroke, object, bm_mod, method) loop[0].reverse() @@ -2553,6 +2576,7 @@ def gstretch_calculate_verts(loop, stroke, object, bm_mod, method): vert_edges = dict_vert_edges(bm_mod) for v_index in loop[0]: + intersection = None for ek in vert_edges[v_index]: v1, v2 = ek v1 = bm_mod.verts[v1] @@ -3280,6 +3304,15 @@ class Circle(bpy.types.Operator): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) radius = bpy.props.FloatProperty(name = "Radius", description = "Custom radius for circle", default = 1.0, @@ -3311,7 +3344,21 @@ class Circle(bpy.types.Operator): col.prop(self, "regular") col.separator() - col.prop(self, "influence") + col_move = col.column(align=True) + row = col_move.row(align=True) + if self.lock_x: + row.prop(self, "lock_x", text = "X", icon='LOCKED') + else: + row.prop(self, "lock_x", text = "X", icon='UNLOCKED') + if self.lock_y: + row.prop(self, "lock_y", text = "Y", icon='LOCKED') + else: + row.prop(self, "lock_y", text = "Y", icon='UNLOCKED') + if self.lock_z: + row.prop(self, "lock_z", text = "Z", icon='LOCKED') + else: + row.prop(self, "lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(self, "influence") def invoke(self, context, event): # load custom settings @@ -3375,7 +3422,11 @@ class Circle(bpy.types.Operator): normal, single_loops[i])) # move vertices to new locations - move_verts(object, bm, mapping, move, -1) + if self.lock_x or self.lock_y or self.lock_z: + lock = [self.lock_x, self.lock_y, self.lock_z] + else: + lock = False + move_verts(object, bm, mapping, move, lock, -1) # cleaning up if derived: @@ -3408,6 +3459,15 @@ class Curve(bpy.types.Operator): ("linear", "Linear", "Simple and fast linear algorithm")), description = "Algorithm used for interpolation", default = 'cubic') + lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) regular = bpy.props.BoolProperty(name = "Regular", description = "Distribute vertices at constant distances along the" \ "curve", @@ -3436,7 +3496,21 @@ class Curve(bpy.types.Operator): col.prop(self, "regular") col.separator() - col.prop(self, "influence") + col_move = col.column(align=True) + row = col_move.row(align=True) + if self.lock_x: + row.prop(self, "lock_x", text = "X", icon='LOCKED') + else: + row.prop(self, "lock_x", text = "X", icon='UNLOCKED') + if self.lock_y: + row.prop(self, "lock_y", text = "Y", icon='LOCKED') + else: + row.prop(self, "lock_y", text = "Y", icon='UNLOCKED') + if self.lock_z: + row.prop(self, "lock_z", text = "Z", icon='LOCKED') + else: + row.prop(self, "lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(self, "influence") def invoke(self, context, event): # load custom settings @@ -3480,7 +3554,11 @@ class Curve(bpy.types.Operator): self.restriction)) # move vertices to new locations - move_verts(object, bm, mapping, move, self.influence) + if self.lock_x or self.lock_y or self.lock_z: + lock = [self.lock_x, self.lock_y, self.lock_z] + else: + lock = False + move_verts(object, bm, mapping, move, lock, self.influence) # cleaning up if derived: @@ -3504,6 +3582,15 @@ class Flatten(bpy.types.Operator): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) plane = bpy.props.EnumProperty(name = "Plane", items = (("best_fit", "Best fit", "Calculate a best fitting plane"), ("normal", "Normal", "Derive plane from averaging vertex "\ @@ -3532,7 +3619,21 @@ class Flatten(bpy.types.Operator): #col.prop(self, "restriction") col.separator() - col.prop(self, "influence") + col_move = col.column(align=True) + row = col_move.row(align=True) + if self.lock_x: + row.prop(self, "lock_x", text = "X", icon='LOCKED') + else: + row.prop(self, "lock_x", text = "X", icon='UNLOCKED') + if self.lock_y: + row.prop(self, "lock_y", text = "Y", icon='LOCKED') + else: + row.prop(self, "lock_y", text = "Y", icon='UNLOCKED') + if self.lock_z: + row.prop(self, "lock_z", text = "Z", icon='LOCKED') + else: + row.prop(self, "lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(self, "influence") def invoke(self, context, event): # load custom settings @@ -3566,7 +3667,13 @@ class Flatten(bpy.types.Operator): move.append(to_move) else: move.append(to_move) - move_verts(object, bm, False, move, self.influence) + + # move vertices to new locations + if self.lock_x or self.lock_y or self.lock_z: + lock = [self.lock_x, self.lock_y, self.lock_z] + else: + lock = False + move_verts(object, bm, False, move, lock, self.influence) # cleaning up terminate(global_undo) @@ -3633,6 +3740,15 @@ class GStretch(bpy.types.Operator): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) method = bpy.props.EnumProperty(name = "Method", items = (("project", "Project", "Project vertices onto the stroke, "\ "using vertex normals and connected edges"), @@ -3669,7 +3785,21 @@ class GStretch(bpy.types.Operator): col_conv.prop(self, "conversion_vertices") col.separator() - col.prop(self, "influence") + col_move = col.column(align=True) + row = col_move.row(align=True) + if self.lock_x: + row.prop(self, "lock_x", text = "X", icon='LOCKED') + else: + row.prop(self, "lock_x", text = "X", icon='UNLOCKED') + if self.lock_y: + row.prop(self, "lock_y", text = "Y", icon='LOCKED') + else: + row.prop(self, "lock_y", text = "Y", icon='UNLOCKED') + if self.lock_z: + row.prop(self, "lock_z", text = "Z", icon='LOCKED') + else: + row.prop(self, "lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(self, "influence") def invoke(self, context, event): # flush cached strokes @@ -3688,25 +3818,40 @@ class GStretch(bpy.types.Operator): cached, safe_strokes, loops, derived, mapping = cache_read("Gstretch", object, bm, False, False) if cached: + straightening = False if safe_strokes: strokes = gstretch_safe_to_true_strokes(safe_strokes) # cached strokes were flushed (see operator's invoke function) elif object.grease_pencil: strokes = gstretch_get_strokes(object) else: - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + # straightening function (no GP) -> loops ignore modifiers + straightening = True + derived = False + bm_mod = bm.copy() strokes = gstretch_get_fake_strokes(object, bm_mod, loops) - derived, bm_mod = get_derived_bmesh(object, bm, context.scene) + if not straightening: + derived, bm_mod = get_derived_bmesh(object, bm, context.scene) else: - # find loops - derived, bm_mod, loops = get_connected_input(object, bm, - context.scene, input='selected') - mapping = get_mapping(derived, bm, bm_mod, False, False, loops) - loops = check_loops(loops, mapping, bm_mod) - # get strokes + # get loops and strokes if object.grease_pencil: + # find loops + derived, bm_mod, loops = get_connected_input(object, bm, + context.scene, input='selected') + mapping = get_mapping(derived, bm, bm_mod, False, False, loops) + loops = check_loops(loops, mapping, bm_mod) + # get strokes strokes = gstretch_get_strokes(object) else: + # straightening function (no GP) -> loops ignore modifiers + derived = False + mapping = False + bm_mod = bm.copy() + edge_keys = [edgekey(edge) for edge in bm_mod.edges if \ + edge.select and not edge.hide] + loops = get_connected_selections(edge_keys) + loops = check_loops(loops, mapping, bm_mod) + # create fake strokes strokes = gstretch_get_fake_strokes(object, bm_mod, loops) # saving cache for faster execution next time @@ -3741,20 +3886,24 @@ class GStretch(bpy.types.Operator): # in case of cached fake stroke, get the real one if object.grease_pencil: strokes = gstretch_get_strokes(object) - ls_pairs = gstretch_match_loops_strokes(loops, - strokes, object, bm_mod) - ls_pairs = gstretch_align_pairs(ls_pairs, object, - bm_mod, self.method) - for (l, s) in ls_pairs: - if l == loop: - stroke = s - break + if loops and strokes: + ls_pairs = gstretch_match_loops_strokes(loops, + strokes, object, bm_mod) + ls_pairs = gstretch_align_pairs(ls_pairs, + object, bm_mod, self.method) + for (l, s) in ls_pairs: + if l == loop: + stroke = s + break gstretch_erase_stroke(stroke, context) # move vertices to new locations - bmesh.update_edit_mesh(object.data, tessface=True, - destructive=True) - move_verts(object, bm, mapping, move, self.influence) + if self.lock_x or self.lock_y or self.lock_z: + lock = [self.lock_x, self.lock_y, self.lock_z] + else: + lock = False + bmesh.update_edit_mesh(object.data, tessface=True, destructive=True) + move_verts(object, bm, mapping, move, lock, self.influence) # cleaning up if derived: @@ -3846,7 +3995,7 @@ class Relax(bpy.types.Operator): tknots[i], knots[i])) move = [relax_calculate_verts(bm_mod, self.interpolation, tknots, knots, tpoints, points, splines)] - move_verts(object, bm, mapping, move, -1) + move_verts(object, bm, mapping, move, False, -1) # cleaning up if derived: @@ -3881,6 +4030,15 @@ class Space(bpy.types.Operator): ("linear", "Linear", "Vertices are projected on existing edges")), description = "Algorithm used for interpolation", default = 'cubic') + lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) @classmethod def poll(cls, context): @@ -3895,7 +4053,21 @@ class Space(bpy.types.Operator): col.prop(self, "input") col.separator() - col.prop(self, "influence") + col_move = col.column(align=True) + row = col_move.row(align=True) + if self.lock_x: + row.prop(self, "lock_x", text = "X", icon='LOCKED') + else: + row.prop(self, "lock_x", text = "X", icon='UNLOCKED') + if self.lock_y: + row.prop(self, "lock_y", text = "Y", icon='LOCKED') + else: + row.prop(self, "lock_y", text = "Y", icon='UNLOCKED') + if self.lock_z: + row.prop(self, "lock_z", text = "Z", icon='LOCKED') + else: + row.prop(self, "lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(self, "influence") def invoke(self, context, event): # load custom settings @@ -3920,6 +4092,7 @@ class Space(bpy.types.Operator): # saving cache for faster execution next time if not cached: + print(loops) cache_write("Space", object, bm, self.input, False, False, loops, derived, mapping) @@ -3934,7 +4107,11 @@ class Space(bpy.types.Operator): move.append(space_calculate_verts(bm_mod, self.interpolation, tknots, tpoints, loop[0][:-1], splines)) # move vertices to new locations - move_verts(object, bm, mapping, move, self.influence) + if self.lock_x or self.lock_y or self.lock_z: + lock = [self.lock_x, self.lock_y, self.lock_z] + else: + lock = False + move_verts(object, bm, mapping, move, lock, self.influence) # cleaning up if derived: @@ -4037,7 +4214,21 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "circle_regular") box.separator() - box.prop(lt, "circle_influence") + col_move = box.column(align=True) + row = col_move.row(align=True) + if lt.circle_lock_x: + row.prop(lt, "circle_lock_x", text = "X", icon='LOCKED') + else: + row.prop(lt, "circle_lock_x", text = "X", icon='UNLOCKED') + if lt.circle_lock_y: + row.prop(lt, "circle_lock_y", text = "Y", icon='LOCKED') + else: + row.prop(lt, "circle_lock_y", text = "Y", icon='UNLOCKED') + if lt.circle_lock_z: + row.prop(lt, "circle_lock_z", text = "Z", icon='LOCKED') + else: + row.prop(lt, "circle_lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(lt, "circle_influence") # curve - first line split = col.split(percentage=0.15, align=True) @@ -4055,7 +4246,21 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "curve_regular") box.separator() - box.prop(lt, "curve_influence") + col_move = box.column(align=True) + row = col_move.row(align=True) + if lt.curve_lock_x: + row.prop(lt, "curve_lock_x", text = "X", icon='LOCKED') + else: + row.prop(lt, "curve_lock_x", text = "X", icon='UNLOCKED') + if lt.curve_lock_y: + row.prop(lt, "curve_lock_y", text = "Y", icon='LOCKED') + else: + row.prop(lt, "curve_lock_y", text = "Y", icon='UNLOCKED') + if lt.curve_lock_z: + row.prop(lt, "curve_lock_z", text = "Z", icon='LOCKED') + else: + row.prop(lt, "curve_lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(lt, "curve_influence") # flatten - first line split = col.split(percentage=0.15, align=True) @@ -4071,7 +4276,21 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): #box.prop(lt, "flatten_restriction") box.separator() - box.prop(lt, "flatten_influence") + col_move = box.column(align=True) + row = col_move.row(align=True) + if lt.flatten_lock_x: + row.prop(lt, "flatten_lock_x", text = "X", icon='LOCKED') + else: + row.prop(lt, "flatten_lock_x", text = "X", icon='UNLOCKED') + if lt.flatten_lock_y: + row.prop(lt, "flatten_lock_y", text = "Y", icon='LOCKED') + else: + row.prop(lt, "flatten_lock_y", text = "Y", icon='UNLOCKED') + if lt.flatten_lock_z: + row.prop(lt, "flatten_lock_z", text = "Z", icon='LOCKED') + else: + row.prop(lt, "flatten_lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(lt, "flatten_influence") # gstretch - first line split = col.split(percentage=0.15, align=True) @@ -4099,7 +4318,21 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): col_conv.prop(lt, "gstretch_conversion_vertices") box.separator() - box.prop(lt, "gstretch_influence") + col_move = box.column(align=True) + row = col_move.row(align=True) + if lt.gstretch_lock_x: + row.prop(lt, "gstretch_lock_x", text = "X", icon='LOCKED') + else: + row.prop(lt, "gstretch_lock_x", text = "X", icon='UNLOCKED') + if lt.gstretch_lock_y: + row.prop(lt, "gstretch_lock_y", text = "Y", icon='LOCKED') + else: + row.prop(lt, "gstretch_lock_y", text = "Y", icon='UNLOCKED') + if lt.gstretch_lock_z: + row.prop(lt, "gstretch_lock_z", text = "Z", icon='LOCKED') + else: + row.prop(lt, "gstretch_lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(lt, "gstretch_influence") # loft - first line split = col.split(percentage=0.15, align=True) @@ -4167,7 +4400,21 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "space_input") box.separator() - box.prop(lt, "space_influence") + col_move = box.column(align=True) + row = col_move.row(align=True) + if lt.space_lock_x: + row.prop(lt, "space_lock_x", text = "X", icon='LOCKED') + else: + row.prop(lt, "space_lock_x", text = "X", icon='UNLOCKED') + if lt.space_lock_y: + row.prop(lt, "space_lock_y", text = "Y", icon='LOCKED') + else: + row.prop(lt, "space_lock_y", text = "Y", icon='UNLOCKED') + if lt.space_lock_z: + row.prop(lt, "space_lock_z", text = "Z", icon='LOCKED') + else: + row.prop(lt, "space_lock_z", text = "Z", icon='UNLOCKED') + col_move.prop(lt, "space_influence") # property group containing all properties for the gui in the panel @@ -4273,6 +4520,15 @@ class LoopToolsProps(bpy.types.PropertyGroup): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + circle_lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + circle_lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + circle_lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) circle_radius = bpy.props.FloatProperty(name = "Radius", description = "Custom radius for circle", default = 1.0, @@ -4300,6 +4556,15 @@ class LoopToolsProps(bpy.types.PropertyGroup): ("linear", "Linear", "Simple and fast linear algorithm")), description = "Algorithm used for interpolation", default = 'cubic') + curve_lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + curve_lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + curve_lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) curve_regular = bpy.props.BoolProperty(name = "Regular", description = "Distribute vertices at constant distances along the " \ "curve", @@ -4321,6 +4586,15 @@ class LoopToolsProps(bpy.types.PropertyGroup): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + flatten_lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + flatten_lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + flatten_lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) flatten_plane = bpy.props.EnumProperty(name = "Plane", items = (("best_fit", "Best fit", "Calculate a best fitting plane"), ("normal", "Normal", "Derive plane from averaging vertex "\ @@ -4389,6 +4663,15 @@ class LoopToolsProps(bpy.types.PropertyGroup): max = 100.0, precision = 1, subtype = 'PERCENTAGE') + gstretch_lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + gstretch_lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + gstretch_lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) gstretch_method = bpy.props.EnumProperty(name = "Method", items = (("project", "Project", "Project vertices onto the stroke, "\ "using vertex normals and connected edges"), @@ -4444,6 +4727,15 @@ class LoopToolsProps(bpy.types.PropertyGroup): ("linear", "Linear", "Vertices are projected on existing edges")), description = "Algorithm used for interpolation", default = 'cubic') + space_lock_x = bpy.props.BoolProperty(name = "Lock X", + description = "Lock editing of the x-coordinate", + default = False) + space_lock_y = bpy.props.BoolProperty(name = "Lock Y", + description = "Lock editing of the y-coordinate", + default = False) + space_lock_z = bpy.props.BoolProperty(name = "Lock Z", + description = "Lock editing of the z-coordinate", + default = False) # draw function for integration in menus