From 4b7f9c6819318b772767dad19621bbbaf5827177 Mon Sep 17 00:00:00 2001 From: Bart Crouch Date: Wed, 18 Dec 2013 19:54:45 +0100 Subject: Fix T34801: bridge tool, internal faces deletion Internal faces are now correctly deleted and smooth shading is applied. Fix: matrix operations Based on report in anonymous forum. Credits to: http://toro.2ch.net/test/read.cgi/cg/1361851855/570-571n Fix: GUI layout, aligned buttons New functionality: GStretch converts grease pencil strokes to vertices. First commit via git, please don't shoot me if I did something wrong. --- mesh_looptools.py | 462 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 420 insertions(+), 42 deletions(-) (limited to 'mesh_looptools.py') diff --git a/mesh_looptools.py b/mesh_looptools.py index 20c9b673..3294c0c8 100644 --- a/mesh_looptools.py +++ b/mesh_looptools.py @@ -19,8 +19,8 @@ bl_info = { "name": "LoopTools", "author": "Bart Crouch", - "version": (4, 2, 0), - "blender": (2, 63, 0), + "version": (4, 5, 0), + "blender": (2, 69, 3), "location": "View3D > Toolbar and View3D > Specials (W-key)", "warning": "", "description": "Mesh modelling toolkit. Several tools to aid modelling", @@ -237,13 +237,19 @@ def calculate_plane(bm_mod, loop, method="best_fit", object=False): # calculating the normal to the plane normal = False try: - mat.invert() + mat = matrix_invert(mat) except: - if sum(mat[0]) == 0.0: + ax = 2 + if math.fabs(sum(mat[0])) < math.fabs(sum(mat[1])): + if math.fabs(sum(mat[0])) < math.fabs(sum(mat[2])): + ax = 0 + elif math.fabs(sum(mat[1])) < math.fabs(sum(mat[2])): + ax = 1 + if ax == 0: normal = mathutils.Vector((1.0, 0.0, 0.0)) - elif sum(mat[1]) == 0.0: + elif ax == 1: normal = mathutils.Vector((0.0, 1.0, 0.0)) - elif sum(mat[2]) == 0.0: + else: normal = mathutils.Vector((0.0, 0.0, 1.0)) if not normal: # warning! this is different from .normalize() @@ -569,6 +575,28 @@ def get_mapping(derived, bm, bm_mod, single_vertices, full_search, loops): return(mapping) +# calculate the determinant of a matrix +def matrix_determinant(m): + determinant = m[0][0] * m[1][1] * m[2][2] + m[0][1] * m[1][2] * m[2][0] \ + + m[0][2] * m[1][0] * m[2][1] - m[0][2] * m[1][1] * m[2][0] \ + - m[0][1] * m[1][0] * m[2][2] - m[0][0] * m[1][2] * m[2][1] + + return(determinant) + + +# custom matrix inversion, to provide higher precision than the built-in one +def matrix_invert(m): + r = mathutils.Matrix(( + (m[1][1]*m[2][2] - m[1][2]*m[2][1], m[0][2]*m[2][1] - m[0][1]*m[2][2], + m[0][1]*m[1][2] - m[0][2]*m[1][1]), + (m[1][2]*m[2][0] - m[1][0]*m[2][2], m[0][0]*m[2][2] - m[0][2]*m[2][0], + m[0][2]*m[1][0] - m[0][0]*m[1][2]), + (m[1][0]*m[2][1] - m[1][1]*m[2][0], m[0][1]*m[2][0] - m[0][0]*m[2][1], + m[0][0]*m[1][1] - m[0][1]*m[1][0]))) + + return (r * (1 / matrix_determinant(m))) + + # returns a list of all loops parallel to the input, input included def get_parallel_loops(bm_mod, loops): # get required dictionaries @@ -751,14 +779,12 @@ def settings_write(self): # clean up and set settings back to original state def terminate(global_undo): - context = bpy.context - # update editmesh cached data - obj = context.active_object + obj = bpy.context.active_object if obj.mode == 'EDIT': bmesh.update_edit_mesh(obj.data, tessface=True, destructive=True) - context.user_preferences.edit.use_global_undo = global_undo + bpy.context.user_preferences.edit.use_global_undo = global_undo ########################################## @@ -1366,11 +1392,14 @@ def bridge_create_faces(object, bm, faces, twist): if faces[i][-1] == faces[i][-2]: faces[i] = faces[i][:-1] + new_faces = [] for i in range(len(faces)): - bm.faces.new([bm.verts[v] for v in faces[i]]) + new_faces.append(bm.faces.new([bm.verts[v] for v in faces[i]])) bm.normal_update() object.data.update(calc_edges=True) # calc_edges prevents memory-corruption + return(new_faces) + # calculate input loops def bridge_get_input(bm): @@ -1566,10 +1595,10 @@ def bridge_save_unused_faces(bm, old_selected_faces, loops): # add the newly created faces to the selection -def bridge_select_new_faces(bm, amount, smooth): - for i in range(amount): - bm.faces[-(i+1)].select_set(True) - bm.faces[-(i+1)].smooth = smooth +def bridge_select_new_faces(new_faces, smooth): + for face in new_faces: + face.select_set(True) + face.smooth = smooth # sort loops, so they are connected in the correct order when lofting @@ -1605,6 +1634,20 @@ def bridge_sort_loops(bm, loops, loft_loop): return(loops) +# remapping old indices to new position in list +def bridge_update_old_selection(bm, old_selected_faces): + #old_indices = old_selected_faces[:] + #old_selected_faces = [] + #for i, face in enumerate(bm.faces): + # if face.index in old_indices: + # old_selected_faces.append(i) + + old_selected_faces = [i for i, face in enumerate(bm.faces) if face.index \ + in old_selected_faces] + + return(old_selected_faces) + + ########################################## ####### Circle functions ################# ########################################## @@ -2432,12 +2475,22 @@ def flatten_project(bm, loop, com, normal): return(verts_projected) - - ########################################## ####### Gstretch functions ############### ########################################## +# fake stroke class, used to create custom strokes if no GP data is found +class gstretch_fake_stroke(): + def __init__(self, points): + self.points = [gstretch_fake_stroke_point(p) for p in points] + + +# fake stroke point class, used in fake strokes +class gstretch_fake_stroke_point(): + def __init__(self, loc): + self.co = loc + + # flips loops, if necessary, to obtain maximum alignment to stroke def gstretch_align_pairs(ls_pairs, object, bm_mod, method): # returns total distance between all verts in loop and corresponding stroke @@ -2535,6 +2588,86 @@ def gstretch_calculate_verts(loop, stroke, object, bm_mod, method): return(move) +# create new vertices, based on GP strokes +def gstretch_create_verts(object, bm_mod, strokes, method, conversion, +conversion_distance, conversion_max, conversion_min, conversion_vertices): + move = [] + stroke_verts = [] + mat_world = object.matrix_world.inverted() + singles = gstretch_match_single_verts(bm_mod, strokes, mat_world) + + for stroke in strokes: + stroke_verts.append([stroke, []]) + min_end_point = 0 + if conversion == 'vertices': + min_end_point = conversion_vertices + end_point = conversion_vertices + elif conversion == 'limit_vertices': + min_end_point = conversion_min + end_point = conversion_max + else: + end_point = len(stroke.points) + # creation of new vertices at fixed user-defined distances + if conversion == 'distance': + method = 'project' + prev_point = stroke.points[0] + stroke_verts[-1][1].append(bm_mod.verts.new(mat_world * \ + prev_point.co)) + distance = 0 + limit = conversion_distance + for point in stroke.points: + new_distance = distance + (point.co - prev_point.co).length + iteration = 0 + while new_distance > limit: + to_cover = limit - distance + (limit * iteration) + new_loc = prev_point.co + to_cover * \ + (point.co - prev_point.co).normalized() + stroke_verts[-1][1].append(bm_mod.verts.new(mat_world * \ + new_loc)) + new_distance -= limit + iteration += 1 + distance = new_distance + prev_point = point + # creation of new vertices for other methods + else: + # add vertices at stroke points + for point in stroke.points[:end_point]: + stroke_verts[-1][1].append(bm_mod.verts.new(\ + mat_world * point.co)) + # add more vertices, beyond the points that are available + if min_end_point > min(len(stroke.points), end_point): + for i in range(min_end_point - + (min(len(stroke.points), end_point))): + stroke_verts[-1][1].append(bm_mod.verts.new(\ + mat_world * point.co)) + # force even spreading of points, so they are placed on stroke + method = 'regular' + bm_mod.verts.index_update() + for stroke, verts_seq in stroke_verts: + if len(verts_seq) < 2: + continue + # spread vertices evenly over the stroke + if method == 'regular': + loop = [[vert.index for vert in verts_seq], False] + move += gstretch_calculate_verts(loop, stroke, object, bm_mod, + method) + # create edges + for i, vert in enumerate(verts_seq): + if i > 0: + bm_mod.edges.new((verts_seq[i-1], verts_seq[i])) + vert.select = True + # connect single vertices to the closest stroke + if singles: + for vert, m_stroke, point in singles: + if m_stroke != stroke: + continue + bm_mod.edges.new((vert, verts_seq[point])) + + bmesh.update_edit_mesh(object.data) + + return(move) + + # erases the grease pencil stroke def gstretch_erase_stroke(stroke, context): # change 3d coordinate into a stroke-point @@ -2549,6 +2682,10 @@ def gstretch_erase_stroke(stroke, context): 'time': 0} return(lib) + if type(stroke) != bpy.types.GPencilStroke: + # fake stroke, there is nothing to delete + return + erase_stroke = [sp(p.co, context) for p in stroke.points] if erase_stroke: erase_stroke[0]['is_start'] = True @@ -2589,6 +2726,17 @@ def gstretch_eval_stroke(stroke, distance, stroke_lengths_cache=False): return(loc, stroke_lengths_cache) +# create fake grease pencil strokes for the active object +def gstretch_get_fake_strokes(object, bm_mod, loops): + strokes = [] + for loop in loops: + p1 = object.matrix_world * bm_mod.verts[loop[0][0]].co + p2 = object.matrix_world * bm_mod.verts[loop[0][-1]].co + strokes.append(gstretch_fake_stroke([p1, p2])) + + return(strokes) + + # get grease pencil strokes for the active object def gstretch_get_strokes(object): gp = object.grease_pencil @@ -2645,6 +2793,53 @@ def gstretch_match_loops_strokes(loops, strokes, object, bm_mod): return(ls_pairs) +# match single selected vertices to the closest stroke endpoint +# returns a list of tuples, constructed as: (vertex, stroke, stroke point index) +def gstretch_match_single_verts(bm_mod, strokes, mat_world): + # calculate stroke endpoints in object space + endpoints = [] + for stroke in strokes: + endpoints.append((mat_world * stroke.points[0].co, stroke, 0)) + endpoints.append((mat_world * stroke.points[-1].co, stroke, -1)) + + distances = [] + # find single vertices (not connected to other selected verts) + for vert in bm_mod.verts: + if not vert.select: + continue + single = True + for edge in vert.link_edges: + if edge.other_vert(vert).select: + single = False + break + if not single: + continue + # calculate distances from vertex to endpoints + distance = [((vert.co - loc).length, vert, stroke, stroke_point, + endpoint_index) for endpoint_index, (loc, stroke, stroke_point) in + enumerate(endpoints)] + distance.sort() + distances.append(distance[0]) + + # create matches, based on shortest distance first + singles = [] + while distances: + distances.sort() + singles.append((distances[0][1], distances[0][2], distances[0][3])) + endpoints.pop(distances[0][4]) + distances.pop(0) + distances_new = [] + for (i, vert, j, k, l) in distances: + distance_new = [((vert.co - loc).length, vert, stroke, stroke_point, + endpoint_index) for endpoint_index, (loc, stroke, + stroke_point) in enumerate(endpoints)] + distance_new.sort() + distances_new.append(distance_new[0]) + distances = distances_new + + return(singles) + + # returns list with a relative distance (0.0 - 1.0) of each vertex on the loop def gstretch_relative_lengths(loop, bm_mod): lengths = [0] @@ -2658,6 +2853,50 @@ def gstretch_relative_lengths(loop, bm_mod): return(relative_lengths) +# convert cache-stored strokes into usable (fake) GP strokes +def gstretch_safe_to_true_strokes(safe_strokes): + strokes = [] + for safe_stroke in safe_strokes: + strokes.append(gstretch_fake_stroke(safe_stroke)) + + return(strokes) + + +# convert a GP stroke into a list of points which can be stored in cache +def gstretch_true_to_safe_strokes(strokes): + safe_strokes = [] + for stroke in strokes: + safe_strokes.append([p.co.copy() for p in stroke.points]) + + return(safe_strokes) + + +# force consistency in GUI, max value can never be lower than min value +def gstretch_update_max(self, context): + # called from operator settings (after execution) + if 'conversion_min' in self.keys(): + if self.conversion_min > self.conversion_max: + self.conversion_max = self.conversion_min + # called from toolbar + else: + lt = context.window_manager.looptools + if lt.gstretch_conversion_min > lt.gstretch_conversion_max: + lt.gstretch_conversion_max = lt.gstretch_conversion_min + + +# force consistency in GUI, min value can never be higher than max value +def gstretch_update_min(self, context): + # called from operator settings (after execution) + if 'conversion_max' in self.keys(): + if self.conversion_max < self.conversion_min: + self.conversion_min = self.conversion_max + # called from toolbar + else: + lt = context.window_manager.looptools + if lt.gstretch_conversion_max < lt.gstretch_conversion_min: + lt.gstretch_conversion_min = lt.gstretch_conversion_max + + ########################################## ####### Relax functions ################## ########################################## @@ -2964,7 +3203,7 @@ class Bridge(bpy.types.Operator): if not cached: cache_write("Bridge", object, bm, input_method, False, False, loops, False, False) - + if loops: # calculate new geometry vertices = [] @@ -2995,8 +3234,10 @@ class Bridge(bpy.types.Operator): bridge_create_vertices(bm, vertices) # create faces if faces: - bridge_create_faces(object, bm, faces, self.twist) - bridge_select_new_faces(bm, len(faces), smooth) + new_faces = bridge_create_faces(object, bm, faces, self.twist) + old_selected_faces = [i for i, face in enumerate(bm.faces) \ + if face.index in old_selected_faces] # updating list + bridge_select_new_faces(new_faces, smooth) # edge-data could have changed, can't use cache next run if faces and not vertices: cache_delete("Bridge") @@ -3004,7 +3245,8 @@ class Bridge(bpy.types.Operator): if self.remove_faces and old_selected_faces: bridge_remove_internal_faces(bm, old_selected_faces) # make sure normals are facing outside - bmesh.update_edit_mesh(object.data, tessface=False, destructive=True) + bmesh.update_edit_mesh(object.data, tessface=False, + destructive=True) bpy.ops.mesh.normals_make_consistent() # cleaning up @@ -3340,6 +3582,47 @@ class GStretch(bpy.types.Operator): bl_description = "Stretch selected vertices to Grease Pencil stroke" bl_options = {'REGISTER', 'UNDO'} + conversion = bpy.props.EnumProperty(name = "Conversion", + items = (("distance", "Distance", "Set the distance between vertices "\ + "of the converted grease pencil stroke"), + ("limit_vertices", "Limit vertices", "Set the minimum and maximum "\ + "number of vertices that converted GP strokes will have"), + ("vertices", "Exact vertices", "Set the exact number of vertices "\ + "that converted grease pencil strokes will have. Short strokes "\ + "with few points may contain less vertices than this number."), + ("none", "No simplification", "Convert each grease pencil point "\ + "to a vertex")), + description = "If grease pencil strokes are converted to geometry, "\ + "use this simplification method", + default = 'limit_vertices') + conversion_distance = bpy.props.FloatProperty(name = "Distance", + description = "Absolute distance between vertices along the converted "\ + "grease pencil stroke", + default = 0.1, + min = 0.000001, + soft_min = 0.01, + soft_max = 100) + conversion_max = bpy.props.IntProperty(name = "Max Vertices", + description = "Maximum number of vertices grease pencil strokes will "\ + "have, when they are converted to geomtery", + default = 32, + min = 3, + soft_max = 500, + update = gstretch_update_min) + conversion_min = bpy.props.IntProperty(name = "Min Vertices", + description = "Minimum number of vertices grease pencil strokes will "\ + "have, when they are converted to geomtery", + default = 8, + min = 3, + soft_max = 500, + update = gstretch_update_max) + conversion_vertices = bpy.props.IntProperty(name = "Vertices", + description = "Number of vertices grease pencil strokes will "\ + "have, when they are converted to geometry. If strokes have less "\ + "points than required, the 'Spread evenly' method is used.", + default = 32, + min = 3, + soft_max = 500) delete_strokes = bpy.props.BoolProperty(name="Delete strokes", description = "Remove Grease Pencil strokes if they have been used "\ "for Gstretch", @@ -3361,23 +3644,38 @@ class GStretch(bpy.types.Operator): description = "Method of distributing the vertices over the Grease "\ "Pencil stroke", default = 'regular') - + @classmethod def poll(cls, context): ob = context.active_object - return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH' - and ob.grease_pencil) + return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH') def draw(self, context): layout = self.layout col = layout.column() - col.prop(self, "delete_strokes") col.prop(self, "method") + col.prop(self, "delete_strokes") + col.separator() + + col_conv = col.column(align=True) + col_conv.prop(self, "conversion", text="") + if self.conversion == 'distance': + col_conv.prop(self, "conversion_distance") + elif self.conversion == 'limit_vertices': + row = col_conv.row(align=True) + row.prop(self, "conversion_min", text="Min") + row.prop(self, "conversion_max", text="Max") + elif self.conversion == 'vertices': + col_conv.prop(self, "conversion_vertices") col.separator() + col.prop(self, "influence") def invoke(self, context, event): + # flush cached strokes + if 'Gstretch' in looptools_cache: + looptools_cache['Gstretch']['single_loops'] = [] # load custom settings settings_load(self) return self.execute(context) @@ -3388,9 +3686,16 @@ class GStretch(bpy.types.Operator): settings_write(self) # check cache to see if we can save time - cached, single_loops, loops, derived, mapping = cache_read("Gstretch", - object, bm, False, False) + cached, safe_strokes, loops, derived, mapping = cache_read("Gstretch", + object, bm, False, self.delete_strokes) if cached: + 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: + strokes = gstretch_get_fake_strokes(object, bm_mod, loops) derived, bm_mod = get_derived_bmesh(object, bm, context.scene) else: # find loops @@ -3398,26 +3703,45 @@ class GStretch(bpy.types.Operator): context.scene, input='selected') mapping = get_mapping(derived, bm, bm_mod, False, False, loops) loops = check_loops(loops, mapping, bm_mod) - strokes = gstretch_get_strokes(object) + # get strokes + if object.grease_pencil: + strokes = gstretch_get_strokes(object) + else: + strokes = gstretch_get_fake_strokes(object, bm_mod, loops) # saving cache for faster execution next time if not cached: - cache_write("Gstretch", object, bm, False, False, False, loops, - derived, mapping) - + if strokes: + safe_strokes = gstretch_true_to_safe_strokes(strokes) + else: + safe_strokes = [] + cache_write("Gstretch", object, bm, False, self.delete_strokes, + safe_strokes, loops, derived, mapping) + # pair 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) move = [] - if ls_pairs: + if not loops: + # no selected geometry, convert GP to verts + if strokes: + move.append(gstretch_create_verts(object, bm, strokes, + self.method, self.conversion, self.conversion_distance, + self.conversion_max, self.conversion_min, + self.conversion_vertices)) + for stroke in strokes: + gstretch_erase_stroke(stroke, context) + elif ls_pairs: for (loop, stroke) in ls_pairs: move.append(gstretch_calculate_verts(loop, stroke, object, bm_mod, self.method)) if self.delete_strokes: 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) # cleaning up @@ -3643,7 +3967,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): lt = context.window_manager.looptools # bridge - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_bridge: split.prop(lt, "display_bridge", text="", icon='DOWNARROW_HLT') else: @@ -3679,7 +4003,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): row.prop(lt, "bridge_reverse") # circle - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_circle: split.prop(lt, "display_circle", text="", icon='DOWNARROW_HLT') else: @@ -3703,7 +4027,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "circle_influence") # curve - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_curve: split.prop(lt, "display_curve", text="", icon='DOWNARROW_HLT') else: @@ -3721,7 +4045,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "curve_influence") # flatten - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_flatten: split.prop(lt, "display_flatten", text="", icon='DOWNARROW_HLT') else: @@ -3737,7 +4061,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "flatten_influence") # gstretch - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_gstretch: split.prop(lt, "display_gstretch", text="", icon='DOWNARROW_HLT') else: @@ -3746,13 +4070,26 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): # gstretch settings if lt.display_gstretch: box = col.column(align=True).box().column() - box.prop(lt, "gstretch_delete_strokes") box.prop(lt, "gstretch_method") + box.prop(lt, "gstretch_delete_strokes") box.separator() + + col_conv = box.column(align=True) + col_conv.prop(lt, "gstretch_conversion", text="") + if lt.gstretch_conversion == 'distance': + col_conv.prop(lt, "gstretch_conversion_distance") + elif lt.gstretch_conversion == 'limit_vertices': + row = col_conv.row(align=True) + row.prop(lt, "gstretch_conversion_min", text="Min") + row.prop(lt, "gstretch_conversion_max", text="Max") + elif lt.gstretch_conversion == 'vertices': + col_conv.prop(lt, "gstretch_conversion_vertices") + box.separator() + box.prop(lt, "gstretch_influence") # loft - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_loft: split.prop(lt, "display_loft", text="", icon='DOWNARROW_HLT') else: @@ -3789,7 +4126,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): row.prop(lt, "bridge_reverse") # relax - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_relax: split.prop(lt, "display_relax", text="", icon='DOWNARROW_HLT') else: @@ -3804,7 +4141,7 @@ class VIEW3D_PT_tools_looptools(bpy.types.Panel): box.prop(lt, "relax_regular") # space - first line - split = col.split(percentage=0.15) + split = col.split(percentage=0.15, align=True) if lt.display_space: split.prop(lt, "display_space", text="", icon='DOWNARROW_HLT') else: @@ -3987,6 +4324,47 @@ class LoopToolsProps(bpy.types.PropertyGroup): default = 'none') # gstretch properties + gstretch_conversion = bpy.props.EnumProperty(name = "Conversion", + items = (("distance", "Distance", "Set the distance between vertices "\ + "of the converted grease pencil stroke"), + ("limit_vertices", "Limit vertices", "Set the minimum and maximum "\ + "number of vertices that converted GP strokes will have"), + ("vertices", "Exact vertices", "Set the exact number of vertices "\ + "that converted grease pencil strokes will have. Short strokes "\ + "with few points may contain less vertices than this number."), + ("none", "No simplification", "Convert each grease pencil point "\ + "to a vertex")), + description = "If grease pencil strokes are converted to geometry, "\ + "use this simplification method", + default = 'limit_vertices') + gstretch_conversion_distance = bpy.props.FloatProperty(name = "Distance", + description = "Absolute distance between vertices along the converted "\ + "grease pencil stroke", + default = 0.1, + min = 0.000001, + soft_min = 0.01, + soft_max = 100) + gstretch_conversion_max = bpy.props.IntProperty(name = "Max Vertices", + description = "Maximum number of vertices grease pencil strokes will "\ + "have, when they are converted to geomtery", + default = 32, + min = 3, + soft_max = 500, + update = gstretch_update_min) + gstretch_conversion_min = bpy.props.IntProperty(name = "Min Vertices", + description = "Minimum number of vertices grease pencil strokes will "\ + "have, when they are converted to geomtery", + default = 8, + min = 3, + soft_max = 500, + update = gstretch_update_max) + gstretch_conversion_vertices = bpy.props.IntProperty(name = "Vertices", + description = "Number of vertices grease pencil strokes will "\ + "have, when they are converted to geometry. If strokes have less "\ + "points than required, the 'Spread evenly' method is used.", + default = 32, + min = 3, + soft_max = 500) gstretch_delete_strokes = bpy.props.BoolProperty(name="Delete strokes", description = "Remove Grease Pencil strokes if they have been used "\ "for Gstretch", -- cgit v1.2.3