# ##### 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # ##### END GPL LICENSE BLOCK ##### bl_info = { "name": "Bsurfaces GPL Edition", "author": "Eclectiel", "version": (0,9), "blender": (2, 5, 7), "api": 35733, "location": "View3D > EditMode > ToolShelf", "description": "Draw meshes and re-topologies with Grease Pencil", "warning": "Beta", "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=26642&group_id=153&atid=469", "category": "Mesh"} import bpy import math from math import * class VIEW3D_PT_tools_SURF_SKETCH(bpy.types.Panel): bl_space_type = 'VIEW_3D' bl_region_type = 'TOOLS' bl_context = "mesh_edit" bl_label = "Bsurfaces" @classmethod def poll(cls, 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.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.surfsk_strokes_to_curves", text="Strokes to curves") class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator): bl_idname = "gpencil.surfsk_add_surface" bl_label = "Bsurfaces add surface" bl_description = "Generates a surface from grease pencil strokes or from curves." bl_options = {'REGISTER', 'UNDO'} ##### 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.vertices[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].vertices[0] == prev_v and ob.data.edges[i].vertices[1] in all_selected_verts_idx: verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[1]]) prev_v = ob.data.edges[i].vertices[1] prev_ed = ob.data.edges[i] elif ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[1] == prev_v and ob.data.edges[i].vertices[0] in all_selected_verts_idx: verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[0]]) prev_v = ob.data.edges[i].vertices[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.vertices[middle_vertex_idx]) verts_ordered.reverse() return verts_ordered #### Calculates length of a chain of points. def get_chain_length(self, object, verts_ordered): matrix = object.matrix_world edges_lengths = [] edges_lengths_sum = 0 for i in range(0, len(verts_ordered)): if i == 0: prev_v_co = verts_ordered[i].co * matrix else: v_co = verts_ordered[i].co * matrix 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_co = v_co 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.select: all_selected_edges_idx.append(ed.index) # Selected vertexes. if not ed.vertices[0] in all_selected_verts: all_selected_verts.append(self.main_object.data.vertices[ed.vertices[0]]) if not ed.vertices[1] in all_selected_verts: all_selected_verts.append(self.main_object.data.vertices[ed.vertices[1]]) # All verts (both from each edge) to determine later which are at the tips (those not repeated twice). all_verts_idx.append(ed.vertices[0]) all_verts_idx.append(ed.vertices[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.vertices[0] in all_chains_tips_idx or ed.vertices[1] in all_chains_tips_idx) and not (ed.vertices[0] in all_verts_idx and ed.vertices[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.vertices[0] in all_verts_idx and (((ed_tips.vertices[1] == ed_tips_b.vertices[0]) or ed_tips.vertices[1] == ed_tips_b.vertices[1])): middle_vertex_idx = ed_tips.vertices[1] tips_to_discard_idx.append(ed_tips.vertices[0]) elif ed_tips.vertices[1] in all_verts_idx and (((ed_tips.vertices[0] == ed_tips_b.vertices[0]) or ed_tips.vertices[0] == ed_tips_b.vertices[1])): middle_vertex_idx = ed_tips.vertices[0] tips_to_discard_idx.append(ed_tips.vertices[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('INVOKE_REGION_WIN') else: #### Vheck if there is a grease pencil layer. If not, quit. try: x = self.main_object.grease_pencil.layers.active.active_frame.strokes except: return{'CANCELLED'} #### Convert grease pencil strokes to curve. bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE') ob_gp_strokes = bpy.context.object using_external_curves = False ob_gp_strokes.name = "SURFSK_temp_strokes" bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', 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('INVOKE_REGION_WIN') bpy.context.object.name = "SURFSK_used_strokes" bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', 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('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') 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.vertices[verts_tips_parsed_idx[0]].co * self.main_object.matrix_world) points_A.append(self.main_object.data.vertices[middle_vertex_idx].co * self.main_object.matrix_world) points_B.append(self.main_object.data.vertices[verts_tips_parsed_idx[1]].co * self.main_object.matrix_world) points_B.append(self.main_object.data.vertices[middle_vertex_idx].co * self.main_object.matrix_world) 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.vertices[verts_tips_same_chain_idx[i][v_idx]].co * self.main_object.matrix_world) 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.vertices[verts_tips_same_chain_idx[i][v_idx]].co * self.main_object.matrix_world) 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.vertices[verts_tips_same_chain_idx[i][v_idx]].co * self.main_object.matrix_world) 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.vertices[self.main_object.data.edges[i].vertices[0]].co * self.main_object.matrix_world, self.main_object.data.vertices[self.main_object.data.edges[i].vertices[1]].co * self.main_object.matrix_world) 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.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co * self.main_object.matrix_world) points_tips.append(self.main_object.data.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]].co * self.main_object.matrix_world) 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(self.main_object, 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(self.main_object, verts_ordered_V) bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') for i in range(0, int(bpy.context.scene.SURFSK_precision)): bpy.ops.curve.subdivide('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') # 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 * self.main_object.matrix_world #### 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.vertices.add(1) last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1] last_v.co = verts_ordered_U[i].co * self.main_object.matrix_world vert_num_in_spline += 1 for sp in sketched_splines_parsed: ob_ctrl_pts.data.vertices.add(1) v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1] v.co = sp[i] if vert_num_in_spline > 1: ob_ctrl_pts.data.edges.add(1) ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = len(ob_ctrl_pts.data.vertices) - 2 ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = len(ob_ctrl_pts.data.vertices) - 1 last_v = v vert_num_in_spline += 1 bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', 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('INVOKE_REGION_WIN', target='CURVE', keep_original=False) ob_curves_surf = bpy.context.scene.objects.active bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type='BEZIER') bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC') for i in range(0, int(bpy.context.scene.SURFSK_precision)): bpy.ops.curve.subdivide('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') # 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('INVOKE_REGION_WIN') for i in range(0, int(bpy.context.scene.SURFSK_precision)): bpy.ops.curve.subdivide('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') 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 * self.main_object.matrix_world 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 * self.main_object.matrix_world #### Delete object with control points and object from grease pencil convertion. bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', 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_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', 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.select = True self.main_object.select = True bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name] bpy.ops.object.join('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', limit=0.0001) bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False) bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') #### Delete grease pencil strokes try: bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN') except: pass bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') return {"FINISHED"} def invoke (self, context, event): bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') 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.surfsk_strokes_to_curves" bl_label = "Bsurfaces strokes to curves" bl_description = "Convert grease pencil strokes into curves and enter edit mode" def execute(self, context): #### Convert grease pencil strokes to curve. bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE') ob_gp_strokes = bpy.context.object ob_gp_strokes.name = "SURFSK_strokes" #### Delete grease pencil strokes. bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', name = self.main_object.name) bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name] bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN') bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT') bpy.ops.object.select_name('INVOKE_REGION_WIN', name = ob_gp_strokes.name) bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name] #bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') bpy.ops.curve.smooth('INVOKE_REGION_WIN') curve_crv = ob_gp_strokes.data bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type="BEZIER") bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type="AUTOMATIC") bpy.data.curves[curve_crv.name].show_handles = False bpy.data.curves[curve_crv.name].show_normal_face = False def invoke (self, context, event): self.main_object = bpy.context.object self.execute(context) return {"FINISHED"} def register(): bpy.utils.register_class(GPENCIL_OT_SURFSK_add_surface) bpy.utils.register_class(VIEW3D_PT_tools_SURF_SKETCH) bpy.utils.register_class(GPENCIL_OT_SURFSK_strokes_to_curves) bpy.types.Scene.SURFSK_edges_U = bpy.props.IntProperty(name="Cross", description="Number of edge rings crossing the strokes (perpendicular to strokes direction)", default=10, min=0, max=100000) bpy.types.Scene.SURFSK_edges_V = bpy.props.IntProperty(name="Follow", description="Number of edge rings following the strokes (parallel to strokes direction)", default=10, min=0, max=100000) bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(name="Precision", description="Precision level of the surface calculation", default=4, min=0, max=100000) bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(name="Keep strokes", description="Keeps the sketched strokes after adding the surface", default=False) keymap_item_add_surf = bpy.data.window_managers[0].keyconfigs.active.keymaps["3D View"].keymap_items.new("gpencil.surfsk_add_surface","E","PRESS", key_modifier="D") keymap_item_stroke_to_curve = bpy.data.window_managers[0].keyconfigs.active.keymaps["3D View"].keymap_items.new("gpencil.surfsk_strokes_to_curves","C","PRESS", key_modifier="D") def unregister(): bpy.utils.unregister_class(GPENCIL_OT_SURFSK_add_surface) bpy.utils.unregister_class(VIEW3D_PT_tools_SURF_SKETCH) bpy.utils.unregister_class(GPENCIL_OT_SURFSK_strokes_to_curves) del bpy.types.Scene.SURFSK_edges_U del bpy.types.Scene.SURFSK_edges_V del bpy.types.Scene.SURFSK_precision del bpy.types.Scene.SURFSK_keep_strokes km = bpy.data.window_managers[0].keyconfigs.active.keymaps["3D View"] for kmi in km.keymap_items: if kmi.idname == 'wm.call_menu': if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface": km.keymap_items.remove(kmi) elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves": km.keymap_items.remove(kmi) else: continue if __name__ == "__main__": register()