Newer
Older
# ########################################
# ##### Relax functions ##################
# ########################################
# create lists with knots and points, all correctly sorted
def relax_calculate_knots(loops):
all_knots = []
all_points = []
for loop, circular in loops:
knots = [[], []]
points = [[], []]
if circular:
extend = [False, True, 0, 1, 0, 1]
extend = [True, False, 0, 1, 1, 2]
else:
extend = [False, False, 0, 1, 1, 2]
extend = [False, False, 0, 1, 1, 2]
for j in range(2):
if extend[j]:
loop = [loop[-1]] + loop + [loop[0]]
for i in range(extend[2 + 2 * j], len(loop), 2):
knots[j].append(loop[i])
for i in range(extend[3 + 2 * j], len(loop), 2):
if loop[i] == loop[-1] and not circular:
continue
if len(points[j]) == 0:
points[j].append(loop[i])
elif loop[i] != points[j][0]:
points[j].append(loop[i])
if circular:
if knots[j][0] != knots[j][-1]:
knots[j].append(knots[j][0])
if len(points[1]) == 0:
knots.pop(1)
points.pop(1)
for k in knots:
all_knots.append(k)
for p in points:
all_points.append(p)
CoDEmanX
committed
return(all_knots, all_points)
# calculate relative positions compared to first knot
def relax_calculate_t(bm_mod, knots, points, regular):
all_tknots = []
all_tpoints = []
for i in range(len(knots)):
amount = len(knots[i]) + len(points[i])
for j in range(amount):
if j % 2 == 0:
mix.append([True, knots[i][round(j / 2)]])
elif j == amount - 1:
mix.append([True, knots[i][-1]])
else:
mix.append([False, points[i][int(j / 2)]])
len_total = 0
loc_prev = False
tknots = []
tpoints = []
for m in mix:
loc = mathutils.Vector(bm_mod.verts[m[1]].co[:])
if not loc_prev:
loc_prev = loc
len_total += (loc - loc_prev).length
if m[0]:
tknots.append(len_total)
else:
tpoints.append(len_total)
loc_prev = loc
if regular:
tpoints = []
for p in range(len(points[i])):
tpoints.append((tknots[p] + tknots[p + 1]) / 2)
all_tknots.append(tknots)
all_tpoints.append(tpoints)
CoDEmanX
committed
return(all_tknots, all_tpoints)
# change the location of the points to their place on the spline
def relax_calculate_verts(bm_mod, interpolation, tknots, knots, tpoints,
points, splines):
change = []
move = []
for i in range(len(knots)):
for p in points[i]:
m = tpoints[i][points[i].index(p)]
if m in tknots[i]:
n = tknots[i].index(m)
else:
t = tknots[i][:]
t.append(m)
t.sort()
if n > len(splines[i]) - 1:
n = len(splines[i]) - 1
elif n < 0:
n = 0
CoDEmanX
committed
if interpolation == 'cubic':
ax, bx, cx, dx, tx = splines[i][n][0]
x = ax + bx * (m - tx) + cx * (m - tx) ** 2 + dx * (m - tx) ** 3
ay, by, cy, dy, ty = splines[i][n][1]
y = ay + by * (m - ty) + cy * (m - ty) ** 2 + dy * (m - ty) ** 3
az, bz, cz, dz, tz = splines[i][n][2]
z = az + bz * (m - tz) + cz * (m - tz) ** 2 + dz * (m - tz) ** 3
change.append([p, mathutils.Vector([x, y, z])])
else: # interpolation == 'linear'
a, d, t, u = splines[i][n]
if u == 0:
u = 1e-8
change.append([p, ((m - t) / u) * d + a])
for c in change:
move.append([c[0], (bm_mod.verts[c[0]].co + c[1]) / 2])
CoDEmanX
committed
return(move)
# ########################################
# ##### Space functions ##################
# ########################################
# calculate relative positions compared to first knot
def space_calculate_t(bm_mod, knots):
tknots = []
loc_prev = False
len_total = 0
for k in knots:
loc = mathutils.Vector(bm_mod.verts[k].co[:])
if not loc_prev:
loc_prev = loc
len_total += (loc - loc_prev).length
tknots.append(len_total)
loc_prev = loc
amount = len(knots)
t_per_segment = len_total / (amount - 1)
tpoints = [i * t_per_segment for i in range(amount)]
CoDEmanX
committed
return(tknots, tpoints)
# change the location of the points to their place on the spline
def space_calculate_verts(bm_mod, interpolation, tknots, tpoints, points,
splines):
move = []
for p in points:
m = tpoints[points.index(p)]
if m in tknots:
n = tknots.index(m)
else:
t = tknots[:]
t.append(m)
t.sort()
n = t.index(m) - 1
if n > len(splines) - 1:
n = len(splines) - 1
elif n < 0:
n = 0
CoDEmanX
committed
if interpolation == 'cubic':
ax, bx, cx, dx, tx = splines[n][0]
x = ax + bx * (m - tx) + cx * (m - tx) ** 2 + dx * (m - tx) ** 3
ay, by, cy, dy, ty = splines[n][1]
y = ay + by * (m - ty) + cy * (m - ty) ** 2 + dy * (m - ty) ** 3
az, bz, cz, dz, tz = splines[n][2]
z = az + bz * (m - tz) + cz * (m - tz) ** 2 + dz * (m - tz) ** 3
move.append([p, mathutils.Vector([x, y, z])])
else: # interpolation == 'linear'
a, d, t, u = splines[n]
move.append([p, ((m - t) / u) * d + a])
CoDEmanX
committed
return(move)
# ########################################
# ##### Operators ########################
# ########################################
# bridge operator
bl_idname = 'mesh.looptools_bridge'
bl_label = "Bridge / Loft"
bl_description = "Bridge two, or loft several, loops of vertices"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
name="Strength",
description="Higher strength results in more fluid curves",
default=1.0,
soft_min=-3.0,
soft_max=3.0
)
name="Interpolation mode",
items=(('cubic', "Cubic", "Gives curved results"),
('linear', "Linear", "Basic, fast, straight interpolation")),
description="Interpolation mode: algorithm used when creating "
"segments",
default='cubic'
)
name="Loft",
description="Loft multiple loops, instead of considering them as "
"a multi-input for bridging",
default=False
)
name="Loop",
description="Connect the first and the last loop with each other",
default=False
)
name="Minimum width",
description="Segments with an edge smaller than this are merged "
"(compared to base edge)",
default=0,
min=0,
max=100,
subtype='PERCENTAGE'
)
name="Mode",
items=(('basic', "Basic", "Fast algorithm"),
('shortest', "Shortest edge", "Slower algorithm with better vertex matching")),
description="Algorithm used for bridging",
default='shortest'
)
name="Remove faces",
description="Remove faces that are internal after bridging",
default=True
)
name="Reverse",
description="Manually override the direction in which the loops "
"are bridged. Only use if the tool gives the wrong result",
default=False
)
name="Segments",
description="Number of segments used to bridge the gap (0=automatic)",
default=1,
min=0,
soft_max=20
)
name="Twist",
description="Twist what vertices are connected to each other",
default=0
)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return (ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
# layout.prop(self, "mode") # no cases yet where 'basic' mode is needed
CoDEmanX
committed
# top row
col_top = layout.column(align=True)
row = col_top.row(align=True)
col_left = row.column(align=True)
col_right = row.column(align=True)
col_right.active = self.segments != 1
col_left.prop(self, "segments")
col_right.prop(self, "min_width", text="")
# bottom row
bottom_left = col_left.row()
bottom_left.active = self.segments != 1
bottom_left.prop(self, "interpolation", text="")
bottom_right = col_right.row()
bottom_right.active = self.interpolation == 'cubic'
bottom_right.prop(self, "cubic_strength")
# boolean properties
col_top.prop(self, "remove_faces")
if self.loft:
col_top.prop(self, "loft_loop")
CoDEmanX
committed
# override properties
col_top.separator()
row.prop(self, "twist")
row.prop(self, "reverse")
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
context.window_manager.looptools.bridge_loft = self.loft
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
object, bm = initialise()
edge_faces, edgekey_to_edge, old_selected_faces, smooth = \
bridge_initialise(bm, self.interpolation)
settings_write(self)
CoDEmanX
committed
# check cache to see if we can save time
input_method = bridge_input_method(self.loft, self.loft_loop)
cached, single_loops, loops, derived, mapping = cache_read("Bridge",
object, bm, input_method, False)
if not cached:
# get loops
loops = bridge_get_input(bm)
if loops:
# reorder loops if there are more than 2
if len(loops) > 2:
if self.loft:
loops = bridge_sort_loops(bm, loops, self.loft_loop)
else:
loops = bridge_match_loops(bm, loops)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Bridge", object, bm, input_method, False, False,
loops, False, False)
if loops:
# calculate new geometry
vertices = []
faces = []
max_vert_index = len(bm.verts) - 1
for i in range(1, len(loops)):
lines = bridge_calculate_lines(bm, loops[i - 1:i + 1],
self.mode, self.twist, self.reverse)
vertex_normals = bridge_calculate_virtual_vertex_normals(bm,
lines, loops[i - 1:i + 1], edge_faces, edgekey_to_edge)
segments = bridge_calculate_segments(bm, lines,
loops[i - 1:i + 1], self.segments)
new_verts, new_faces, max_vert_index = \
bridge_calculate_geometry(
bm, lines, vertex_normals,
segments, self.interpolation, self.cubic_strength,
self.min_width, max_vert_index
)
if new_verts:
vertices += new_verts
if new_faces:
faces += new_faces
# make sure faces in loops that aren't used, aren't removed
if self.remove_faces and old_selected_faces:
bridge_save_unused_faces(bm, old_selected_faces, loops)
# create vertices
if vertices:
bridge_create_vertices(bm, vertices)
# create faces
if faces:
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")
# delete internal faces
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, loop_triangles=False,
bpy.ops.mesh.normals_make_consistent()
CoDEmanX
committed
CoDEmanX
committed
return{'FINISHED'}
# circle operator
bl_idname = "mesh.looptools_circle"
bl_label = "Circle"
bl_description = "Move selected vertices into a circle shape"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
name="Radius",
description="Force a custom radius",
default=False
)
name="Method",
items=(("best", "Best fit", "Non-linear least squares"),
("inside", "Fit inside", "Only move vertices towards the center")),
description="Method used for fitting a circle to the vertices",
default='best'
)
name="Flatten",
description="Flatten the circle, instead of projecting it on the mesh",
default=True
)
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
description="Lock editing of the z-coordinate",
default=False
)
name="Radius",
description="Custom radius for circle",
default=1.0,
min=0.0,
soft_max=1000.0
)
name="Regular",
description="Distribute vertices at constant distances along the circle",
default=True
)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "fit")
col.separator()
CoDEmanX
committed
col.prop(self, "flatten")
row = col.row(align=True)
row.prop(self, "custom_radius")
row_right = row.row(align=True)
row_right.active = self.custom_radius
row_right.prop(self, "radius", text="")
col.prop(self, "regular")
col.separator()
CoDEmanX
committed
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Circle",
object, bm, False, False)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
derived, bm_mod, single_vertices, single_loops, loops = \
circle_get_input(object, bm)
mapping = get_mapping(derived, bm, bm_mod, single_vertices,
False, loops)
single_loops, loops = circle_check_loops(single_loops, loops,
mapping, bm_mod)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Circle", object, bm, False, False, single_loops,
loops, derived, mapping)
CoDEmanX
committed
move = []
for i, loop in enumerate(loops):
# best fitting flat plane
com, normal = calculate_plane(bm_mod, loop)
# if circular, shift loop so we get a good starting vertex
if loop[1]:
loop = circle_shift_loop(bm_mod, loop, com)
# flatten vertices on plane
locs_2d, p, q = circle_3d_to_2d(bm_mod, loop, com, normal)
# calculate circle
if self.fit == 'best':
x0, y0, r = circle_calculate_best_fit(locs_2d)
x0, y0, r = circle_calculate_min_fit(locs_2d)
# radius override
if self.custom_radius:
r = self.radius / p.length
# calculate positions on circle
if self.regular:
new_locs_2d = circle_project_regular(locs_2d[:], x0, y0, r)
else:
new_locs_2d = circle_project_non_regular(locs_2d[:], x0, y0, r)
# take influence into account
locs_2d = circle_influence_locs(locs_2d, new_locs_2d,
self.influence)
# calculate 3d positions of the created 2d input
move.append(circle_calculate_verts(self.flatten, bm_mod,
locs_2d, com, p, q, normal))
# flatten single input vertices on plane defined by loop
if self.flatten and single_loops:
move.append(circle_flatten_singles(bm_mod, com, p, q,
normal, single_loops[i]))
CoDEmanX
committed
# 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, mapping, move, lock, -1)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
CoDEmanX
committed
return{'FINISHED'}
# curve operator
bl_idname = "mesh.looptools_curve"
bl_label = "Curve"
bl_description = "Turn a loop into a smooth curve"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
name="Boundaries",
description="Limit the tool to work within the boundaries of the selected vertices",
default=False
)
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
name="Interpolation",
items=(("cubic", "Cubic", "Natural cubic spline, smooth results"),
("linear", "Linear", "Simple and fast linear algorithm")),
description="Algorithm used for interpolation",
default='cubic'
)
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
name="Regular",
description="Distribute vertices at constant distances along the curve",
default=True
)
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("extrude", "Extrude only", "Only allow extrusions (no indentations)"),
("indent", "Indent only", "Only allow indentation (no extrusions)")),
description="Restrictions on how the vertices can be moved",
default='none'
)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "interpolation")
col.prop(self, "restriction")
col.prop(self, "boundaries")
col.prop(self, "regular")
col.separator()
CoDEmanX
committed
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Curve",
object, bm, False, self.boundaries)
if cached:
derived, bm_mod = get_derived_bmesh(object, bm)
else:
# find loops
derived, bm_mod, loops = curve_get_input(object, bm, self.boundaries)
mapping = get_mapping(derived, bm, bm_mod, False, True, loops)
loops = check_loops(loops, mapping, bm_mod)
verts_selected = [
v.index for v in bm_mod.verts if v.select and not v.hide
]
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Curve", object, bm, False, self.boundaries, False,
loops, derived, mapping)
CoDEmanX
committed
move = []
for loop in loops:
knots, points = curve_calculate_knots(loop, verts_selected)
pknots = curve_project_knots(bm_mod, verts_selected, knots,
points, loop[1])
tknots, tpoints = curve_calculate_t(bm_mod, knots, points,
pknots, self.regular, loop[1])
splines = calculate_splines(self.interpolation, bm_mod,
tknots, knots)
move.append(curve_calculate_vertices(bm_mod, knots, tknots,
points, tpoints, splines, self.interpolation,
self.restriction))
CoDEmanX
committed
# 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, mapping, move, lock, self.influence)
CoDEmanX
committed
# cleaning up
if derived:
bm_mod.free()
return{'FINISHED'}
# flatten operator
bl_idname = "mesh.looptools_flatten"
bl_label = "Flatten"
bl_description = "Flatten vertices on a best-fitting plane"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
description="Lock editing of the z-coordinate",
default=False
)
name="Plane",
items=(("best_fit", "Best fit", "Calculate a best fitting plane"),
("normal", "Normal", "Derive plane from averaging vertex normals"),
("view", "View", "Flatten on a plane perpendicular to the viewing angle")),
description="Plane on which vertices are flattened",
default='best_fit'
)
name="Restriction",
items=(("none", "None", "No restrictions on vertex movement"),
("bounding_box", "Bounding box", "Vertices are restricted to "
"movement inside the bounding box of the selection")),
description="Restrictions on how the vertices can be moved",
default='none'
)
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
col.prop(self, "plane")
CoDEmanX
committed
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
CoDEmanX
committed
def invoke(self, context, event):
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
def execute(self, context):
# initialise
object, bm = initialise()
settings_write(self)
# check cache to see if we can save time
cached, single_loops, loops, derived, mapping = cache_read("Flatten",
object, bm, False, False)
if not cached:
# order input into virtual loops
loops = flatten_get_input(bm)
loops = check_loops(loops, mapping, bm)
CoDEmanX
committed
# saving cache for faster execution next time
if not cached:
cache_write("Flatten", object, bm, False, False, False, loops,
False, False)
CoDEmanX
committed
move = []
for loop in loops:
# calculate plane and position of vertices on them
com, normal = calculate_plane(bm, loop, method=self.plane,
object=object)
to_move = flatten_project(bm, loop, com, normal)
if self.restriction == 'none':
move.append(to_move)
else:
move.append(to_move)
# 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)
CoDEmanX
committed
CoDEmanX
committed
return{'FINISHED'}
bl_label = "Remove Annotation"
bl_description = "Remove all Annotation Strokes"
try:
bpy.data.grease_pencils[0].layers.active.clear()
except:
self.report({'INFO'}, "No Annotation data to Unlink")
bl_idname = "mesh.looptools_gstretch"
bl_label = "Gstretch"
bl_description = "Stretch selected vertices to active Annotation stroke"
CoDEmanX
committed
name="Conversion",
items=(("distance", "Distance", "Set the distance between vertices "
"of the converted annotation stroke"),
("limit_vertices", "Limit vertices", "Set the minimum and maximum "
"number of vertices that converted annotation strokes will have"),
("vertices", "Exact vertices", "Set the exact number of vertices "
"that converted annotation strokes will have. Short strokes "
"with few points may contain less vertices than this number."),
("none", "No simplification", "Convert each annotation point "
description="If annotation strokes are converted to geometry, "
"use this simplification method",
default='limit_vertices'
)
name="Distance",
description="Absolute distance between vertices along the converted "
"annotation stroke",
default=0.1,
min=0.000001,
soft_min=0.01,
soft_max=100
)
description="Maximum number of vertices annotation strokes will "
"have, when they are converted to geomtery",
default=32,
min=3,
soft_max=500,
update=gstretch_update_min
)
description="Minimum number of vertices annotation strokes will "
"have, when they are converted to geomtery",
default=8,
min=3,
soft_max=500,
update=gstretch_update_max
)
description="Number of vertices annotation 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
)
description="Remove annotation strokes if they have been used "
"for Annotation. WARNING: DOES NOT SUPPORT UNDO",
name="Influence",
description="Force of the tool",
default=100.0,
min=0.0,
max=100.0,
precision=1,
subtype='PERCENTAGE'
)
name="Lock X",
description="Lock editing of the x-coordinate",
default=False
)
name="Lock Y",
description="Lock editing of the y-coordinate",
default=False
)
name="Lock Z",
description="Lock editing of the z-coordinate",
default=False
)
name="Method",
items=(("project", "Project", "Project vertices onto the stroke, "
"using vertex normals and connected edges"),
("irregular", "Spread", "Distribute vertices along the full "
"stroke, retaining relative distances between the vertices"),
("regular", "Spread evenly", "Distribute vertices at regular "
"distances along the full stroke")),
description="Method of distributing the vertices over the annotation "
"stroke",
CoDEmanX
committed
@classmethod
def poll(cls, context):
ob = context.active_object
return(ob and ob.type == 'MESH' and context.mode == 'EDIT_MESH')
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column()
CoDEmanX
committed
CoDEmanX
committed
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")
CoDEmanX
committed
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')
row.prop(self, "lock_x", text="X", icon='UNLOCKED')
row.prop(self, "lock_y", text="Y", icon='LOCKED')
row.prop(self, "lock_y", text="Y", icon='UNLOCKED')
row.prop(self, "lock_z", text="Z", icon='LOCKED')
row.prop(self, "lock_z", text="Z", icon='UNLOCKED')
col.operator("remove.gp", text="Cancel and delete annotation strokes")
CoDEmanX
committed
# flush cached strokes
if 'Gstretch' in looptools_cache:
looptools_cache['Gstretch']['single_loops'] = []
# load custom settings
settings_load(self)
return self.execute(context)
CoDEmanX
committed
object, bm = initialise()
CoDEmanX
committed
cached, safe_strokes, loops, derived, mapping = cache_read("Gstretch",
if safe_strokes:
strokes = gstretch_safe_to_true_strokes(safe_strokes)
# cached strokes were flushed (see operator's invoke function)
elif get_annotation(self, context):