-
Luca Bonavita authored
- every script is now (2,5,3) - added warning field in bl_addon_info so every dev knows about this: when "warning" field is not empty, a warning icon appears and text alerts users about a buggy script (this is used during development, released scripts should must have this empty of course) - formatted scripts info like this - GPL - docstring - bl_addon_info - imports so that the actual script starts after the bl_addon_info dict - removed old 2.4x cruft like __url__, __bpydoc__, __author__ etc, not needed anymore (when the case integrated info with bl_addon_info and in case of one email meta just put it in the GPL) - next commits I plan to do contrib scripts and conform io scripts naming
Luca Bonavita authored- every script is now (2,5,3) - added warning field in bl_addon_info so every dev knows about this: when "warning" field is not empty, a warning icon appears and text alerts users about a buggy script (this is used during development, released scripts should must have this empty of course) - formatted scripts info like this - GPL - docstring - bl_addon_info - imports so that the actual script starts after the bl_addon_info dict - removed old 2.4x cruft like __url__, __bpydoc__, __author__ etc, not needed anymore (when the case integrated info with bl_addon_info and in case of one email meta just put it in the GPL) - next commits I plan to do contrib scripts and conform io scripts naming
mesh_surface_sketch.py 37.13 KiB
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_addon_info = {
'name': 'Mesh: Surface Sketch',
'author': 'Eclectiel',
'version': '0.8 Beta',
'blender': (2, 5, 3),
'location': 'View3D > EditMode > ToolShelf',
'description': 'Draw meshes and re-topologies with Grease Pencil',
'warning': '', # used for warning icon and text in addons panel
'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
'Scripts/Mesh/Surface_Sketch',
'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
'func=detail&aid=22062&group_id=153&atid=469',
'category': 'Mesh'}
import bpy
import math
from math import *
bpy.types.Scene.IntProperty(attr = "SURFSK_edges_U", name = "Cross", description = "Number of edge rings crossing the strokes (perpendicular to strokes direction)", default = 10, min = 0, max = 100000)
bpy.types.Scene.IntProperty(attr = "SURFSK_edges_V", name = "Follow", description = "Number of edge rings following the strokes (parallel to strokes direction)", default = 10, min = 0, max = 100000)
bpy.types.Scene.IntProperty(attr = "SURFSK_precision", name = "Precision", description = "Precision level of the surface calculation", default = 4, min = 0, max = 100000)
bpy.types.Scene.BoolProperty(attr = "SURFSK_keep_strokes", name = "Keep strokes", description = "Keeps the sketched strokes after adding the surface", default = False)
class View3DPanel(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
class VIEW3D_PT_tools_SURF_SKETCH(View3DPanel):
bl_context = "mesh_edit"
bl_label = "Surface Sketching"
def poll(self, context):
return context.active_object
def draw(self, context):
layout = self.layout
scn = context.scene
ob = context.object
col = layout.column(align=True)
row = layout.row()
row.separator()
col.operator("GPENCIL_OT_SURFSK_add_surface", text="Add Surface")
col.prop(scn, "SURFSK_edges_U")
col.prop(scn, "SURFSK_edges_V")
row.separator()
col.prop(scn, "SURFSK_keep_strokes")
col.separator()
row.separator()
col.operator("GPENCIL_OT_SURFSK_strokes_to_curves", text="Strokes to curves")
class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
bl_idname = "GPENCIL_OT_SURFSK_add_surface"
bl_label = "Surface generation from grease pencil strokes"
bl_description = "Surface generation from grease pencil strokes"
#### Get an ordered list of a chain of vertices.
def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx):
# Order selected vertexes.
verts_ordered = []
verts_ordered.append(self.main_object.data.verts[first_vert_idx])
prev_v = first_vert_idx
prev_ed = None
finish_while = False
while True:
edges_non_matched = 0
for i in all_selected_edges_idx:
if ob.data.edges[i] != prev_ed and ob.data.edges[i].verts[0] == prev_v and ob.data.edges[i].verts[1] in all_selected_verts_idx:
verts_ordered.append(self.main_object.data.verts[ob.data.edges[i].verts[1]])
prev_v = ob.data.edges[i].verts[1]
prev_ed = ob.data.edges[i]
elif ob.data.edges[i] != prev_ed and ob.data.edges[i].verts[1] == prev_v and ob.data.edges[i].verts[0] in all_selected_verts_idx:
verts_ordered.append(self.main_object.data.verts[ob.data.edges[i].verts[0]])
prev_v = ob.data.edges[i].verts[0]
prev_ed = ob.data.edges[i]
else:
edges_non_matched += 1
if edges_non_matched == len(all_selected_edges_idx):
finish_while = True
if finish_while:
break
if middle_vertex_idx != None:
verts_ordered.append(self.main_object.data.verts[middle_vertex_idx])
verts_ordered.reverse()
return verts_ordered
#### Calculates length of a chain of points.
def get_chain_length(self, verts_ordered):
edges_lengths = []
edges_lengths_sum = 0
for i in range(0, len(verts_ordered)):
if i == 0:
prev_v = verts_ordered[i]
else:
v = verts_ordered[i]
v_difs = [prev_v.co[0] - v.co[0], prev_v.co[1] - v.co[1], prev_v.co[2] - v.co[2]]
edge_length = abs(sqrt(v_difs[0] * v_difs[0] + v_difs[1] * v_difs[1] + v_difs[2] * v_difs[2]))
edges_lengths.append(edge_length)
edges_lengths_sum += edge_length
prev_v = v
return edges_lengths, edges_lengths_sum
#### Calculates the proportion of the edges of a chain of edges, relative to the full chain length.
def get_edges_proportions(self, edges_lengths, edges_lengths_sum, use_boundaries, fixed_edges_num):
edges_proportions = []
if use_boundaries:
verts_count = 1
for l in edges_lengths:
edges_proportions.append(l / edges_lengths_sum)
verts_count += 1
else:
verts_count = 1
for n in range(0, fixed_edges_num):
edges_proportions.append(1 / fixed_edges_num)
verts_count += 1
return edges_proportions
#### Calculates the angle between two pairs of points in space.
def orientation_difference(self, points_A_co, points_B_co): # each parameter should be a list with two elements, and each element should be a x,y,z coordinate.
vec_A = points_A_co[0] - points_A_co[1]
vec_B = points_B_co[0] - points_B_co[1]
angle = vec_A.angle(vec_B)
if angle > 0.5 * math.pi:
angle = abs(angle - math.pi)
return angle
#### Calculate distance between two points
def pts_distance(self, p1_co, p2_co):
p_difs = [p1_co[0] - p2_co[0], p1_co[1] - p2_co[1], p1_co[2] - p2_co[2]]
distance = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
return distance
def execute(self, context):
#### Selected edges.
all_selected_edges_idx = []
all_selected_verts = []
all_verts_idx = []
for ed in self.main_object.data.edges:
if ed.selected:
all_selected_edges_idx.append(ed.index)
# Selected vertexes.
if not ed.verts[0] in all_selected_verts:
all_selected_verts.append(self.main_object.data.verts[ed.verts[0]])
if not ed.verts[1] in all_selected_verts:
all_selected_verts.append(self.main_object.data.verts[ed.verts[1]])
# All verts (both from each edge) to determine later which are at the tips (those not repeated twice).
all_verts_idx.append(ed.verts[0])
all_verts_idx.append(ed.verts[1])
#### Identify the tips and "middle-vertex" that separates U from V, if there is one.
all_chains_tips_idx = []
for v_idx in all_verts_idx:
if all_verts_idx.count(v_idx) < 2:
all_chains_tips_idx.append(v_idx)
edges_connected_to_tips = []
for ed in self.main_object.data.edges:
if (ed.verts[0] in all_chains_tips_idx or ed.verts[1] in all_chains_tips_idx) and not (ed.verts[0] in all_verts_idx and ed.verts[1] in all_verts_idx):
edges_connected_to_tips.append(ed)
middle_vertex_idx = None
tips_to_discard_idx = []
for ed_tips in edges_connected_to_tips:
for ed_tips_b in edges_connected_to_tips:
if (ed_tips != ed_tips_b):
if ed_tips.verts[0] in all_verts_idx and (((ed_tips.verts[1] == ed_tips_b.verts[0]) or ed_tips.verts[1] == ed_tips_b.verts[1])):
middle_vertex_idx = ed_tips.verts[1]
tips_to_discard_idx.append(ed_tips.verts[0])
elif ed_tips.verts[1] in all_verts_idx and (((ed_tips.verts[0] == ed_tips_b.verts[0]) or ed_tips.verts[0] == ed_tips_b.verts[1])):
middle_vertex_idx = ed_tips.verts[0]
tips_to_discard_idx.append(ed_tips.verts[1])
#### List with pairs of verts that belong to the tips of each selection chain (row).
verts_tips_same_chain_idx = []
if len(all_chains_tips_idx) >= 2:
checked_v = []
for i in range(0, len(all_chains_tips_idx)):
if all_chains_tips_idx[i] not in checked_v:
v_chain = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, all_chains_tips_idx[i], middle_vertex_idx)
verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
checked_v.append(v_chain[0].index)
checked_v.append(v_chain[len(v_chain) - 1].index)
#### Selection tips (vertices)
verts_tips_parsed_idx = []
if len(all_chains_tips_idx) >= 2:
for spec_v_idx in all_chains_tips_idx:
if (spec_v_idx not in tips_to_discard_idx):
verts_tips_parsed_idx.append(spec_v_idx)
#### Identify the type of selection made by the user.
if middle_vertex_idx != None:
if len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
selection_type = "TWO_CONNECTED"
else:
# The type of the selection was not identified, so the script stops.
return
else:
if len(all_chains_tips_idx) == 2: # If there are 2 tips (one selection chain)
selection_type = "SINGLE"
elif len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
selection_type = "TWO_NOT_CONNECTED"
elif len(all_chains_tips_idx) == 0:
selection_type = "NO_SELECTION"
else:
# The type of the selection was not identified, so the script stops.
return
#### Check if it will be used grease pencil strokes or curves.
selected_objs = bpy.context.selected_objects
if len(selected_objs) > 1:
for ob in selected_objs:
if ob != bpy.context.scene.objects.active:
ob_gp_strokes = ob
using_external_curves = True
bpy.ops.object.editmode_toggle()
else:
#### Convert grease pencil strokes to curve.
bpy.ops.gpencil.convert(type='CURVE')
ob_gp_strokes = bpy.context.object
using_external_curves = False
bpy.ops.object.editmode_toggle()
ob_gp_strokes.name = "SURFSK_temp_strokes"
bpy.ops.object.select_name(name = ob_gp_strokes.name)
bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
#### If "Keep strokes" is active make a duplicate of the original strokes, which will be intact
if bpy.context.scene.SURFSK_keep_strokes:
bpy.ops.object.duplicate_move()
bpy.context.object.name = "SURFSK_used_strokes"
bpy.ops.object.editmode_toggle()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.object.editmode_toggle()
bpy.ops.object.select_name(name = ob_gp_strokes.name)
bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
#### Enter editmode for the new curve (converted from grease pencil strokes).
bpy.ops.object.editmode_toggle()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.object.editmode_toggle()
selection_U_exists = False
selection_U2_exists = False
selection_V_exists = False
selection_V2_exists = False
#### Define what vertexes are at the tips of each selection and are not the middle-vertex.
if selection_type == "TWO_CONNECTED":
selection_U_exists = True
selection_V_exists = True
# Determine which selection is Selection-U and which is Selection-V.
points_A = []
points_B = []
points_first_stroke_tips = []
points_A.append(self.main_object.data.verts[verts_tips_parsed_idx[0]].co)
points_A.append(self.main_object.data.verts[middle_vertex_idx].co)
points_B.append(self.main_object.data.verts[verts_tips_parsed_idx[1]].co)
points_B.append(self.main_object.data.verts[middle_vertex_idx].co)
points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
if angle_A < angle_B:
first_vert_U_idx = verts_tips_parsed_idx[0]
first_vert_V_idx = verts_tips_parsed_idx[1]
else:
first_vert_U_idx = verts_tips_parsed_idx[1]
first_vert_V_idx = verts_tips_parsed_idx[0]
elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
first_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[0].co
last_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co
first_sketched_point_last_stroke_co = ob_gp_strokes.data.splines[len(ob_gp_strokes.data.splines) - 1].bezier_points[0].co
# The tip of the selected vertices nearest to the first point of the first sketched stroke.
prev_dist = 999999999999
for i in range(0, len(verts_tips_same_chain_idx)):
for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
dist = self.pts_distance(first_sketched_point_first_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
if dist < prev_dist:
prev_dist = dist
nearest_tip_first_st_first_pt_idx = i
nearest_tip_first_pair_first_pt_idx = v_idx
# Shortest distance to the first point of the first stroke
shortest_distance_to_first_stroke = dist
# The tip of the selected vertices nearest to the last point of the first sketched stroke.
prev_dist = 999999999999
for i in range(0, len(verts_tips_same_chain_idx)):
for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
dist = self.pts_distance(last_sketched_point_first_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
if dist < prev_dist:
prev_dist = dist
nearest_tip_first_st_last_pt_pair_idx = i
nearest_tip_first_st_last_pt_point_idx = v_idx
# The tip of the selected vertices nearest to the first point of the last sketched stroke.
prev_dist = 999999999999
for i in range(0, len(verts_tips_same_chain_idx)):
for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
dist = self.pts_distance(first_sketched_point_last_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
if dist < prev_dist:
prev_dist = dist
nearest_tip_last_st_first_pt_pair_idx = i
nearest_tip_last_st_first_pt_point_idx = v_idx
points_tips = []
points_first_stroke_tips = []
# Determine if the single selection will be treated as U or as V.
edges_sum = 0
for i in all_selected_edges_idx:
edges_sum += self.pts_distance(self.main_object.data.verts[self.main_object.data.edges[i].verts[0]].co, self.main_object.data.verts[self.main_object.data.edges[i].verts[1]].co)
average_edge_length = edges_sum / len(all_selected_edges_idx)
# If the beginning of the first stroke is near enough to interpret things as an "extrude along strokes" instead of "extrude through strokes"
if shortest_distance_to_first_stroke < average_edge_length / 3:
selection_U_exists = False
selection_V_exists = True
first_vert_V_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][nearest_tip_first_pair_first_pt_idx]
if selection_type == "TWO_NOT_CONNECTED":
selection_V2_exists = True
first_vert_V2_idx = verts_tips_same_chain_idx[nearest_tip_first_st_last_pt_pair_idx][nearest_tip_first_st_last_pt_point_idx]
else:
selection_V2_exists = False
else:
selection_U_exists = True
selection_V_exists = False
points_tips.append(self.main_object.data.verts[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co)
points_tips.append(self.main_object.data.verts[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]].co)
points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
vec_A = points_tips[0] - points_tips[1]
vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
# Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
if vec_A.dot(vec_B) < 0:
first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]
else:
first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]
if selection_type == "TWO_NOT_CONNECTED":
selection_U2_exists = True
first_vert_U2_idx = verts_tips_same_chain_idx[nearest_tip_last_st_first_pt_pair_idx][nearest_tip_last_st_first_pt_point_idx]
else:
selection_U2_exists = False
elif selection_type == "NO_SELECTION":
selection_U_exists = False
selection_V_exists = False
#### Get an ordered list of the vertices of Selection-U.
if selection_U_exists:
verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx)
#### Get an ordered list of the vertices of Selection-U.
if selection_U2_exists:
verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx)
#### Get an ordered list of the vertices of Selection-V.
if selection_V_exists:
verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx)
#### Get an ordered list of the vertices of Selection-U.
if selection_V2_exists:
verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx)
#### Calculate edges U proportions.
# Sum selected edges U lengths.
edges_lengths_U = []
edges_lengths_sum_U = 0
if selection_U_exists:
edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(verts_ordered_U)
# Sum selected edges V lengths.
edges_lengths_V = []
edges_lengths_sum_V = 0
if selection_V_exists:
edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(verts_ordered_V)
bpy.ops.object.editmode_toggle()
for i in range(0, int(bpy.context.scene.SURFSK_precision)):
bpy.ops.curve.subdivide()
bpy.ops.object.editmode_toggle()
# Proportions U.
edges_proportions_U = []
edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, selection_U_exists, bpy.context.scene.SURFSK_edges_U)
verts_count_U = len(edges_proportions_U) + 1
# Proportions V.
edges_proportions_V = []
edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, selection_V_exists, bpy.context.scene.SURFSK_edges_V)
verts_count_V = len(edges_proportions_V) + 1
#### Get ordered lists of points on each sketched curve that mimics the proportions of the edges in the vertex selection.
sketched_splines = ob_gp_strokes.data.splines
sketched_splines_lengths = []
sketched_splines_parsed = []
for sp_idx in range(0, len(sketched_splines)):
# Calculate spline length
sketched_splines_lengths.append(0)
for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
if i == 0:
prev_p = sketched_splines[sp_idx].bezier_points[i]
else:
p = sketched_splines[sp_idx].bezier_points[i]
p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
sketched_splines_lengths[sp_idx] += edge_length
prev_p = p
# Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
sketched_splines_parsed.append([])
partial_spline_length = 0
related_edge_U = 0
edges_proportions_sum_U = 0
edges_lengths_sum_U = 0
for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
if i == 0:
prev_p = sketched_splines[sp_idx].bezier_points[i]
sketched_splines_parsed[sp_idx].append(prev_p.co)
elif i != len(sketched_splines[sp_idx].bezier_points) - 1:
p = sketched_splines[sp_idx].bezier_points[i]
p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
if edges_proportions_sum_U + edges_proportions_U[related_edge_U] - ((edges_lengths_sum_U + partial_spline_length + edge_length) / sketched_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
partial_spline_length += edge_length
elif related_edge_U < len(edges_proportions_U) - 1:
sketched_splines_parsed[sp_idx].append(prev_p.co)
edges_proportions_sum_U += edges_proportions_U[related_edge_U]
related_edge_U += 1
edges_lengths_sum_U += partial_spline_length
partial_spline_length = edge_length
prev_p = p
else: # last point of the spline for the last edge
p = sketched_splines[sp_idx].bezier_points[len(sketched_splines[sp_idx].bezier_points) - 1]
sketched_splines_parsed[sp_idx].append(p.co)
#### If the selection type is "TWO_NOT_CONNECTED" replace the last point of each spline with the points in the "target" selection.
if selection_type == "TWO_NOT_CONNECTED":
if selection_U2_exists:
for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = verts_ordered_U2[i].co
#### Create temporary curves along the "control-points" found on the sketched curves and the mesh selection.
mesh_ctrl_pts_name = "SURFSK_ctrl_pts"
me = bpy.data.meshes.new(mesh_ctrl_pts_name)
ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
ob_ctrl_pts.data = me
bpy.context.scene.objects.link(ob_ctrl_pts)
for i in range(0, verts_count_U):
vert_num_in_spline = 1
if selection_U_exists:
ob_ctrl_pts.data.add_geometry(1,0,0)
last_v = ob_ctrl_pts.data.verts[len(ob_ctrl_pts.data.verts) - 1]
last_v.co = verts_ordered_U[i].co
vert_num_in_spline += 1
for sp in sketched_splines_parsed:
ob_ctrl_pts.data.add_geometry(1,0,0)
v = ob_ctrl_pts.data.verts[len(ob_ctrl_pts.data.verts) - 1]
v.co = sp[i]
if vert_num_in_spline > 1:
ob_ctrl_pts.data.add_geometry(0,1,0)
ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].verts[0] = len(ob_ctrl_pts.data.verts) - 2
ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].verts[1] = len(ob_ctrl_pts.data.verts) - 1
last_v = v
vert_num_in_spline += 1
bpy.ops.object.select_name(name = ob_ctrl_pts.name)
bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
# Create curves from control points.
bpy.ops.object.convert(target='CURVE', keep_original=False)
ob_curves_surf = bpy.context.scene.objects.active
bpy.ops.object.editmode_toggle()
bpy.ops.curve.spline_type_set(type='BEZIER')
bpy.ops.curve.handle_type_set(type='AUTOMATIC')
for i in range(0, int(bpy.context.scene.SURFSK_precision)):
bpy.ops.curve.subdivide()
bpy.ops.object.editmode_toggle()
# Calculate the length of each final surface spline.
surface_splines = ob_curves_surf.data.splines
surface_splines_lengths = []
surface_splines_parsed = []
for sp_idx in range(0, len(surface_splines)):
# Calculate spline length
surface_splines_lengths.append(0)
for i in range(0, len(surface_splines[sp_idx].bezier_points)):
if i == 0:
prev_p = surface_splines[sp_idx].bezier_points[i]
else:
p = surface_splines[sp_idx].bezier_points[i]
edge_length = self.pts_distance(prev_p.co, p.co)
surface_splines_lengths[sp_idx] += edge_length
prev_p = p
bpy.ops.object.editmode_toggle()
for i in range(0, int(bpy.context.scene.SURFSK_precision)):
bpy.ops.curve.subdivide()
bpy.ops.object.editmode_toggle()
for sp_idx in range(0, len(surface_splines)):
# Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
surface_splines_parsed.append([])
partial_spline_length = 0
related_edge_V = 0
edges_proportions_sum_V = 0
edges_lengths_sum_V = 0
for i in range(0, len(surface_splines[sp_idx].bezier_points)):
if i == 0:
prev_p = surface_splines[sp_idx].bezier_points[i]
surface_splines_parsed[sp_idx].append(prev_p.co)
elif i != len(surface_splines[sp_idx].bezier_points) - 1:
p = surface_splines[sp_idx].bezier_points[i]
edge_length = self.pts_distance(prev_p.co, p.co)
if edges_proportions_sum_V + edges_proportions_V[related_edge_V] - ((edges_lengths_sum_V + partial_spline_length + edge_length) / surface_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
partial_spline_length += edge_length
elif related_edge_V < len(edges_proportions_V) - 1:
surface_splines_parsed[sp_idx].append(prev_p.co)
edges_proportions_sum_V += edges_proportions_V[related_edge_V]
related_edge_V += 1
edges_lengths_sum_V += partial_spline_length
partial_spline_length = edge_length
prev_p = p
else: # last point of the spline for the last edge
p = surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1]
surface_splines_parsed[sp_idx].append(p.co)
# Set the first and last verts of each spline to the locations of the respective verts in the selections.
if selection_V_exists:
for i in range(0, len(surface_splines_parsed[0])):
surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_ordered_V[i].co
if selection_type == "TWO_NOT_CONNECTED":
if selection_V2_exists:
for i in range(0, len(surface_splines_parsed[0])):
surface_splines_parsed[0][i] = verts_ordered_V2[i].co
#### Delete object with control points and object from grease pencil convertion.
bpy.ops.object.select_name(name = ob_ctrl_pts.name)
bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
bpy.ops.object.delete()
bpy.ops.object.select_name(name = ob_gp_strokes.name)
bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
bpy.ops.object.delete()
#### Generate surface.
# Get all verts coords.
all_surface_verts_co = []
for i in range(0, len(surface_splines_parsed)):
# Get coords of all verts and make a list with them
for pt_co in surface_splines_parsed[i]:
all_surface_verts_co.append(pt_co)
# Define verts for each face.
all_surface_faces = []
for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
all_surface_faces.append([i+1, i , i + len(surface_splines_parsed[0]), i + len(surface_splines_parsed[0]) + 1])
# Build the mesh.
surf_me_name = "SURFSK_surface"
me_surf = bpy.data.meshes.new(surf_me_name)
me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
me_surf.update()
ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
bpy.context.scene.objects.link(ob_surface)
#### Join the new mesh to the main object.
ob_surface.selected = True
self.main_object.selected = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
bpy.ops.object.join()
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(limit=0.0001)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.mesh.select_all(action='DESELECT')
#### Delete grease pencil strokes
bpy.ops.gpencil.active_frame_delete()
def invoke (self, context, event):
bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
self.main_object = bpy.context.scene.objects.active
self.execute(context)
return {"FINISHED"}
class GPENCIL_OT_SURFSK_strokes_to_curves(bpy.types.Operator):
bl_idname = "GPENCIL_OT_SURFSK_strokes_to_curves"
bl_label = "Convert grease pencil strokes into curves and enter edit mode"
bl_description = "Convert grease pencil strokes into curves and enter edit mode"
def execute(self, context):
#### Convert grease pencil strokes to curve.
bpy.ops.gpencil.convert(type='CURVE')
ob_gp_strokes = bpy.context.object
ob_gp_strokes.name = "SURFSK_strokes"
#### Delete grease pencil strokes.
bpy.ops.object.select_name(name = self.main_object.name)
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
bpy.ops.gpencil.active_frame_delete()
bpy.ops.object.select_name(name = ob_gp_strokes.name)
bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
bpy.ops.object.editmode_toggle()
bpy.ops.object.editmode_toggle()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
bpy.ops.curve.smooth()
curve_crv = ob_gp_strokes.data
bpy.ops.curve.spline_type_set(type="BEZIER")
bpy.ops.curve.handle_type_set(type="AUTOMATIC")
bpy.data.curves[curve_crv.name].draw_handles = False
bpy.data.curves[curve_crv.name].draw_normals = False
def invoke (self, context, event):
self.main_object = bpy.context.object
self.execute(context)
return {"FINISHED"}
def register():
bpy.types.register(GPENCIL_OT_SURFSK_add_surface)
bpy.types.register(GPENCIL_OT_SURFSK_strokes_to_curves)
bpy.types.register(VIEW3D_PT_tools_SURF_SKETCH)
keymap_item_add_surf = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"].items.add("GPENCIL_OT_SURFSK_add_surface","E","PRESS", key_modifier="D")
keymap_item_stroke_to_curve = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"].items.add("GPENCIL_OT_SURFSK_strokes_to_curves","C","PRESS", key_modifier="D")
def unregister():
bpy.types.unregister(GPENCIL_OT_SURFSK_add_surface)
bpy.types.unregister(GPENCIL_OT_SURFSK_strokes_to_curves)
bpy.types.unregister(VIEW3D_PT_tools_SURF_SKETCH)
km = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"]
for kmi in km.items:
if kmi.idname == 'wm.call_menu':
if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface":
km.remove_item(kmi)
elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves":
km.remove_item(kmi)
else:
continue
if __name__ == "__main__":
register()