Simplifying material check thanks to the teachings of Master Campbell
[blender-addons.git] / mesh_surface_sketch.py
blob80cd6c04eeb5220813fdc58b5fa9c7a84fe4c32d
1 # ##### BEGIN GPL LICENSE BLOCK #####
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software Foundation,
15 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 # ##### END GPL LICENSE BLOCK #####
19 bl_addon_info = {
20 "name": "Surface Sketch",
21 "author": "Eclectiel",
22 "version": (0,8),
23 "blender": (2, 5, 3),
24 "api": 31847,
25 "location": "View3D > EditMode > ToolShelf",
26 "description": "Draw meshes and re-topologies with Grease Pencil",
27 "warning": "Beta",
28 "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
29 "Scripts/Mesh/Surface_Sketch",
30 "tracker_url": "https://projects.blender.org/tracker/index.php?"\
31 "func=detail&aid=22062&group_id=153&atid=469",
32 "category": "Mesh"}
35 import bpy
36 import math
38 from math import *
41 class VIEW3D_PT_tools_SURF_SKETCH(bpy.types.Panel):
42 bl_space_type = 'VIEW_3D'
43 bl_region_type = 'TOOLS'
45 bl_context = "mesh_edit"
46 bl_label = "Surface Sketching"
48 @classmethod
49 def poll(cls, context):
50 return context.active_object
52 def draw(self, context):
53 layout = self.layout
55 scn = context.scene
56 ob = context.object
58 col = layout.column(align=True)
59 row = layout.row()
60 row.separator()
61 col.operator("GPENCIL_OT_SURFSK_add_surface", text="Add Surface")
62 col.prop(scn, "SURFSK_edges_U")
63 col.prop(scn, "SURFSK_edges_V")
64 row.separator()
65 col.prop(scn, "SURFSK_keep_strokes")
66 col.separator()
67 row.separator()
68 col.operator("GPENCIL_OT_SURFSK_strokes_to_curves", text="Strokes to curves")
72 class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
73 bl_idname = "GPENCIL_OT_SURFSK_add_surface"
74 bl_label = "Surface generation from grease pencil strokes"
75 bl_description = "Surface generation from grease pencil strokes"
78 #### Get an ordered list of a chain of vertices.
79 def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx):
80 # Order selected vertexes.
81 verts_ordered = []
82 verts_ordered.append(self.main_object.data.vertices[first_vert_idx])
83 prev_v = first_vert_idx
84 prev_ed = None
85 finish_while = False
86 while True:
87 edges_non_matched = 0
88 for i in all_selected_edges_idx:
89 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:
90 verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[1]])
91 prev_v = ob.data.edges[i].vertices[1]
92 prev_ed = ob.data.edges[i]
93 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:
94 verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[0]])
95 prev_v = ob.data.edges[i].vertices[0]
96 prev_ed = ob.data.edges[i]
97 else:
98 edges_non_matched += 1
100 if edges_non_matched == len(all_selected_edges_idx):
101 finish_while = True
103 if finish_while:
104 break
106 if middle_vertex_idx != None:
107 verts_ordered.append(self.main_object.data.vertices[middle_vertex_idx])
108 verts_ordered.reverse()
110 return verts_ordered
113 #### Calculates length of a chain of points.
114 def get_chain_length(self, verts_ordered):
115 edges_lengths = []
116 edges_lengths_sum = 0
117 for i in range(0, len(verts_ordered)):
118 if i == 0:
119 prev_v = verts_ordered[i]
120 else:
121 v = verts_ordered[i]
123 v_difs = [prev_v.co[0] - v.co[0], prev_v.co[1] - v.co[1], prev_v.co[2] - v.co[2]]
124 edge_length = abs(sqrt(v_difs[0] * v_difs[0] + v_difs[1] * v_difs[1] + v_difs[2] * v_difs[2]))
126 edges_lengths.append(edge_length)
127 edges_lengths_sum += edge_length
129 prev_v = v
131 return edges_lengths, edges_lengths_sum
134 #### Calculates the proportion of the edges of a chain of edges, relative to the full chain length.
135 def get_edges_proportions(self, edges_lengths, edges_lengths_sum, use_boundaries, fixed_edges_num):
136 edges_proportions = []
137 if use_boundaries:
138 verts_count = 1
139 for l in edges_lengths:
140 edges_proportions.append(l / edges_lengths_sum)
141 verts_count += 1
142 else:
143 verts_count = 1
144 for n in range(0, fixed_edges_num):
145 edges_proportions.append(1 / fixed_edges_num)
146 verts_count += 1
148 return edges_proportions
151 #### Calculates the angle between two pairs of points in space.
152 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.
153 vec_A = points_A_co[0] - points_A_co[1]
154 vec_B = points_B_co[0] - points_B_co[1]
156 angle = vec_A.angle(vec_B)
158 if angle > 0.5 * math.pi:
159 angle = abs(angle - math.pi)
161 return angle
164 #### Calculate distance between two points
165 def pts_distance(self, p1_co, p2_co):
166 p_difs = [p1_co[0] - p2_co[0], p1_co[1] - p2_co[1], p1_co[2] - p2_co[2]]
167 distance = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
169 return distance
172 def execute(self, context):
173 #### Selected edges.
174 all_selected_edges_idx = []
175 all_selected_verts = []
176 all_verts_idx = []
177 for ed in self.main_object.data.edges:
178 if ed.select:
179 all_selected_edges_idx.append(ed.index)
181 # Selected vertexes.
182 if not ed.vertices[0] in all_selected_verts:
183 all_selected_verts.append(self.main_object.data.vertices[ed.vertices[0]])
184 if not ed.vertices[1] in all_selected_verts:
185 all_selected_verts.append(self.main_object.data.vertices[ed.vertices[1]])
187 # All verts (both from each edge) to determine later which are at the tips (those not repeated twice).
188 all_verts_idx.append(ed.vertices[0])
189 all_verts_idx.append(ed.vertices[1])
192 #### Identify the tips and "middle-vertex" that separates U from V, if there is one.
193 all_chains_tips_idx = []
194 for v_idx in all_verts_idx:
195 if all_verts_idx.count(v_idx) < 2:
196 all_chains_tips_idx.append(v_idx)
198 edges_connected_to_tips = []
199 for ed in self.main_object.data.edges:
200 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):
201 edges_connected_to_tips.append(ed)
203 middle_vertex_idx = None
204 tips_to_discard_idx = []
205 for ed_tips in edges_connected_to_tips:
206 for ed_tips_b in edges_connected_to_tips:
207 if (ed_tips != ed_tips_b):
208 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])):
209 middle_vertex_idx = ed_tips.vertices[1]
210 tips_to_discard_idx.append(ed_tips.vertices[0])
211 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])):
212 middle_vertex_idx = ed_tips.vertices[0]
213 tips_to_discard_idx.append(ed_tips.vertices[1])
216 #### List with pairs of verts that belong to the tips of each selection chain (row).
217 verts_tips_same_chain_idx = []
218 if len(all_chains_tips_idx) >= 2:
219 checked_v = []
220 for i in range(0, len(all_chains_tips_idx)):
221 if all_chains_tips_idx[i] not in checked_v:
222 v_chain = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, all_chains_tips_idx[i], middle_vertex_idx)
224 verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
226 checked_v.append(v_chain[0].index)
227 checked_v.append(v_chain[len(v_chain) - 1].index)
230 #### Selection tips (vertices)
231 verts_tips_parsed_idx = []
232 if len(all_chains_tips_idx) >= 2:
233 for spec_v_idx in all_chains_tips_idx:
234 if (spec_v_idx not in tips_to_discard_idx):
235 verts_tips_parsed_idx.append(spec_v_idx)
238 #### Identify the type of selection made by the user.
239 if middle_vertex_idx != None:
240 if len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
241 selection_type = "TWO_CONNECTED"
242 else:
243 # The type of the selection was not identified, so the script stops.
244 return
245 else:
246 if len(all_chains_tips_idx) == 2: # If there are 2 tips (one selection chain)
247 selection_type = "SINGLE"
248 elif len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
249 selection_type = "TWO_NOT_CONNECTED"
250 elif len(all_chains_tips_idx) == 0:
251 selection_type = "NO_SELECTION"
252 else:
253 # The type of the selection was not identified, so the script stops.
254 return
257 #### Check if it will be used grease pencil strokes or curves.
258 selected_objs = bpy.context.selected_objects
259 if len(selected_objs) > 1:
260 for ob in selected_objs:
261 if ob != bpy.context.scene.objects.active:
262 ob_gp_strokes = ob
263 using_external_curves = True
265 bpy.ops.object.editmode_toggle()
266 else:
267 #### Convert grease pencil strokes to curve.
268 bpy.ops.gpencil.convert(type='CURVE')
269 ob_gp_strokes = bpy.context.object
270 using_external_curves = False
272 bpy.ops.object.editmode_toggle()
274 ob_gp_strokes.name = "SURFSK_temp_strokes"
276 bpy.ops.object.select_name(name = ob_gp_strokes.name)
277 bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
280 #### If "Keep strokes" is active make a duplicate of the original strokes, which will be intact
281 if bpy.context.scene.SURFSK_keep_strokes:
282 bpy.ops.object.duplicate_move()
283 bpy.context.object.name = "SURFSK_used_strokes"
284 bpy.ops.object.editmode_toggle()
285 bpy.ops.curve.smooth()
286 bpy.ops.curve.smooth()
287 bpy.ops.curve.smooth()
288 bpy.ops.curve.smooth()
289 bpy.ops.curve.smooth()
290 bpy.ops.curve.smooth()
291 bpy.ops.object.editmode_toggle()
293 bpy.ops.object.select_name(name = ob_gp_strokes.name)
294 bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
297 #### Enter editmode for the new curve (converted from grease pencil strokes).
298 bpy.ops.object.editmode_toggle()
299 bpy.ops.curve.smooth()
300 bpy.ops.curve.smooth()
301 bpy.ops.curve.smooth()
302 bpy.ops.curve.smooth()
303 bpy.ops.curve.smooth()
304 bpy.ops.curve.smooth()
305 bpy.ops.object.editmode_toggle()
308 selection_U_exists = False
309 selection_U2_exists = False
310 selection_V_exists = False
311 selection_V2_exists = False
312 #### Define what vertexes are at the tips of each selection and are not the middle-vertex.
313 if selection_type == "TWO_CONNECTED":
314 selection_U_exists = True
315 selection_V_exists = True
317 # Determine which selection is Selection-U and which is Selection-V.
318 points_A = []
319 points_B = []
320 points_first_stroke_tips = []
322 points_A.append(self.main_object.data.vertices[verts_tips_parsed_idx[0]].co)
323 points_A.append(self.main_object.data.vertices[middle_vertex_idx].co)
325 points_B.append(self.main_object.data.vertices[verts_tips_parsed_idx[1]].co)
326 points_B.append(self.main_object.data.vertices[middle_vertex_idx].co)
328 points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
329 points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
331 angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
332 angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
334 if angle_A < angle_B:
335 first_vert_U_idx = verts_tips_parsed_idx[0]
336 first_vert_V_idx = verts_tips_parsed_idx[1]
337 else:
338 first_vert_U_idx = verts_tips_parsed_idx[1]
339 first_vert_V_idx = verts_tips_parsed_idx[0]
341 elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
342 first_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[0].co
343 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
345 first_sketched_point_last_stroke_co = ob_gp_strokes.data.splines[len(ob_gp_strokes.data.splines) - 1].bezier_points[0].co
347 # The tip of the selected vertices nearest to the first point of the first sketched stroke.
348 prev_dist = 999999999999
349 for i in range(0, len(verts_tips_same_chain_idx)):
350 for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
351 dist = self.pts_distance(first_sketched_point_first_stroke_co, self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
352 if dist < prev_dist:
353 prev_dist = dist
355 nearest_tip_first_st_first_pt_idx = i
357 nearest_tip_first_pair_first_pt_idx = v_idx
359 # Shortest distance to the first point of the first stroke
360 shortest_distance_to_first_stroke = dist
363 # The tip of the selected vertices nearest to the last point of the first sketched stroke.
364 prev_dist = 999999999999
365 for i in range(0, len(verts_tips_same_chain_idx)):
366 for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
367 dist = self.pts_distance(last_sketched_point_first_stroke_co, self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
368 if dist < prev_dist:
369 prev_dist = dist
371 nearest_tip_first_st_last_pt_pair_idx = i
372 nearest_tip_first_st_last_pt_point_idx = v_idx
375 # The tip of the selected vertices nearest to the first point of the last sketched stroke.
376 prev_dist = 999999999999
377 for i in range(0, len(verts_tips_same_chain_idx)):
378 for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
379 dist = self.pts_distance(first_sketched_point_last_stroke_co, self.main_object.data.vertices[verts_tips_same_chain_idx[i][v_idx]].co)
380 if dist < prev_dist:
381 prev_dist = dist
383 nearest_tip_last_st_first_pt_pair_idx = i
384 nearest_tip_last_st_first_pt_point_idx = v_idx
387 points_tips = []
388 points_first_stroke_tips = []
390 # Determine if the single selection will be treated as U or as V.
391 edges_sum = 0
392 for i in all_selected_edges_idx:
393 edges_sum += self.pts_distance(self.main_object.data.vertices[self.main_object.data.edges[i].vertices[0]].co, self.main_object.data.vertices[self.main_object.data.edges[i].vertices[1]].co)
395 average_edge_length = edges_sum / len(all_selected_edges_idx)
399 # If the beginning of the first stroke is near enough to interpret things as an "extrude along strokes" instead of "extrude through strokes"
400 if shortest_distance_to_first_stroke < average_edge_length / 3:
401 selection_U_exists = False
402 selection_V_exists = True
404 first_vert_V_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][nearest_tip_first_pair_first_pt_idx]
406 if selection_type == "TWO_NOT_CONNECTED":
407 selection_V2_exists = True
409 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]
411 else:
412 selection_V2_exists = False
414 else:
415 selection_U_exists = True
416 selection_V_exists = False
418 points_tips.append(self.main_object.data.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co)
419 points_tips.append(self.main_object.data.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]].co)
421 points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
422 points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
424 vec_A = points_tips[0] - points_tips[1]
425 vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
427 # Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
428 if vec_A.dot(vec_B) < 0:
429 first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]
430 else:
431 first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]
433 if selection_type == "TWO_NOT_CONNECTED":
434 selection_U2_exists = True
436 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]
437 else:
438 selection_U2_exists = False
440 elif selection_type == "NO_SELECTION":
441 selection_U_exists = False
442 selection_V_exists = False
445 #### Get an ordered list of the vertices of Selection-U.
446 if selection_U_exists:
447 verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx)
449 #### Get an ordered list of the vertices of Selection-U.
450 if selection_U2_exists:
451 verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx)
453 #### Get an ordered list of the vertices of Selection-V.
454 if selection_V_exists:
455 verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx)
457 #### Get an ordered list of the vertices of Selection-U.
458 if selection_V2_exists:
459 verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx)
462 #### Calculate edges U proportions.
464 # Sum selected edges U lengths.
465 edges_lengths_U = []
466 edges_lengths_sum_U = 0
468 if selection_U_exists:
469 edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(verts_ordered_U)
471 # Sum selected edges V lengths.
472 edges_lengths_V = []
473 edges_lengths_sum_V = 0
475 if selection_V_exists:
476 edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(verts_ordered_V)
478 bpy.ops.object.editmode_toggle()
479 for i in range(0, int(bpy.context.scene.SURFSK_precision)):
480 bpy.ops.curve.subdivide()
481 bpy.ops.object.editmode_toggle()
483 # Proportions U.
484 edges_proportions_U = []
485 edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, selection_U_exists, bpy.context.scene.SURFSK_edges_U)
486 verts_count_U = len(edges_proportions_U) + 1
488 # Proportions V.
489 edges_proportions_V = []
490 edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, selection_V_exists, bpy.context.scene.SURFSK_edges_V)
491 verts_count_V = len(edges_proportions_V) + 1
495 #### Get ordered lists of points on each sketched curve that mimics the proportions of the edges in the vertex selection.
496 sketched_splines = ob_gp_strokes.data.splines
497 sketched_splines_lengths = []
498 sketched_splines_parsed = []
499 for sp_idx in range(0, len(sketched_splines)):
500 # Calculate spline length
501 sketched_splines_lengths.append(0)
502 for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
503 if i == 0:
504 prev_p = sketched_splines[sp_idx].bezier_points[i]
505 else:
506 p = sketched_splines[sp_idx].bezier_points[i]
508 p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
509 edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
511 sketched_splines_lengths[sp_idx] += edge_length
513 prev_p = p
515 # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
516 sketched_splines_parsed.append([])
517 partial_spline_length = 0
518 related_edge_U = 0
519 edges_proportions_sum_U = 0
520 edges_lengths_sum_U = 0
521 for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
522 if i == 0:
523 prev_p = sketched_splines[sp_idx].bezier_points[i]
524 sketched_splines_parsed[sp_idx].append(prev_p.co)
525 elif i != len(sketched_splines[sp_idx].bezier_points) - 1:
526 p = sketched_splines[sp_idx].bezier_points[i]
528 p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
529 edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
532 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.
533 partial_spline_length += edge_length
534 elif related_edge_U < len(edges_proportions_U) - 1:
535 sketched_splines_parsed[sp_idx].append(prev_p.co)
537 edges_proportions_sum_U += edges_proportions_U[related_edge_U]
538 related_edge_U += 1
540 edges_lengths_sum_U += partial_spline_length
541 partial_spline_length = edge_length
543 prev_p = p
544 else: # last point of the spline for the last edge
545 p = sketched_splines[sp_idx].bezier_points[len(sketched_splines[sp_idx].bezier_points) - 1]
546 sketched_splines_parsed[sp_idx].append(p.co)
549 #### If the selection type is "TWO_NOT_CONNECTED" replace the last point of each spline with the points in the "target" selection.
550 if selection_type == "TWO_NOT_CONNECTED":
551 if selection_U2_exists:
552 for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
553 sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = verts_ordered_U2[i].co
556 #### Create temporary curves along the "control-points" found on the sketched curves and the mesh selection.
557 mesh_ctrl_pts_name = "SURFSK_ctrl_pts"
558 me = bpy.data.meshes.new(mesh_ctrl_pts_name)
559 ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
560 ob_ctrl_pts.data = me
561 bpy.context.scene.objects.link(ob_ctrl_pts)
564 for i in range(0, verts_count_U):
565 vert_num_in_spline = 1
567 if selection_U_exists:
568 ob_ctrl_pts.data.vertices.add(1)
569 last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
570 last_v.co = verts_ordered_U[i].co
572 vert_num_in_spline += 1
574 for sp in sketched_splines_parsed:
575 ob_ctrl_pts.data.vertices.add(1)
576 v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
577 v.co = sp[i]
579 if vert_num_in_spline > 1:
580 ob_ctrl_pts.data.edges.add(1)
581 ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = len(ob_ctrl_pts.data.vertices) - 2
582 ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = len(ob_ctrl_pts.data.vertices) - 1
584 last_v = v
586 vert_num_in_spline += 1
588 bpy.ops.object.select_name(name = ob_ctrl_pts.name)
589 bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
592 # Create curves from control points.
593 bpy.ops.object.convert(target='CURVE', keep_original=False)
594 ob_curves_surf = bpy.context.scene.objects.active
595 bpy.ops.object.editmode_toggle()
596 bpy.ops.curve.spline_type_set(type='BEZIER')
597 bpy.ops.curve.handle_type_set(type='AUTOMATIC')
598 for i in range(0, int(bpy.context.scene.SURFSK_precision)):
599 bpy.ops.curve.subdivide()
600 bpy.ops.object.editmode_toggle()
603 # Calculate the length of each final surface spline.
604 surface_splines = ob_curves_surf.data.splines
605 surface_splines_lengths = []
606 surface_splines_parsed = []
607 for sp_idx in range(0, len(surface_splines)):
608 # Calculate spline length
609 surface_splines_lengths.append(0)
610 for i in range(0, len(surface_splines[sp_idx].bezier_points)):
611 if i == 0:
612 prev_p = surface_splines[sp_idx].bezier_points[i]
613 else:
614 p = surface_splines[sp_idx].bezier_points[i]
616 edge_length = self.pts_distance(prev_p.co, p.co)
618 surface_splines_lengths[sp_idx] += edge_length
620 prev_p = p
622 bpy.ops.object.editmode_toggle()
623 for i in range(0, int(bpy.context.scene.SURFSK_precision)):
624 bpy.ops.curve.subdivide()
625 bpy.ops.object.editmode_toggle()
627 for sp_idx in range(0, len(surface_splines)):
628 # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
629 surface_splines_parsed.append([])
630 partial_spline_length = 0
631 related_edge_V = 0
632 edges_proportions_sum_V = 0
633 edges_lengths_sum_V = 0
634 for i in range(0, len(surface_splines[sp_idx].bezier_points)):
635 if i == 0:
636 prev_p = surface_splines[sp_idx].bezier_points[i]
637 surface_splines_parsed[sp_idx].append(prev_p.co)
638 elif i != len(surface_splines[sp_idx].bezier_points) - 1:
639 p = surface_splines[sp_idx].bezier_points[i]
641 edge_length = self.pts_distance(prev_p.co, p.co)
643 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.
644 partial_spline_length += edge_length
645 elif related_edge_V < len(edges_proportions_V) - 1:
646 surface_splines_parsed[sp_idx].append(prev_p.co)
648 edges_proportions_sum_V += edges_proportions_V[related_edge_V]
649 related_edge_V += 1
651 edges_lengths_sum_V += partial_spline_length
652 partial_spline_length = edge_length
654 prev_p = p
655 else: # last point of the spline for the last edge
656 p = surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1]
657 surface_splines_parsed[sp_idx].append(p.co)
659 # Set the first and last verts of each spline to the locations of the respective verts in the selections.
660 if selection_V_exists:
661 for i in range(0, len(surface_splines_parsed[0])):
662 surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_ordered_V[i].co
664 if selection_type == "TWO_NOT_CONNECTED":
665 if selection_V2_exists:
666 for i in range(0, len(surface_splines_parsed[0])):
667 surface_splines_parsed[0][i] = verts_ordered_V2[i].co
670 #### Delete object with control points and object from grease pencil convertion.
671 bpy.ops.object.select_name(name = ob_ctrl_pts.name)
672 bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
673 bpy.ops.object.delete()
675 bpy.ops.object.select_name(name = ob_gp_strokes.name)
676 bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
677 bpy.ops.object.delete()
681 #### Generate surface.
683 # Get all verts coords.
684 all_surface_verts_co = []
685 for i in range(0, len(surface_splines_parsed)):
686 # Get coords of all verts and make a list with them
687 for pt_co in surface_splines_parsed[i]:
688 all_surface_verts_co.append(pt_co)
691 # Define verts for each face.
692 all_surface_faces = []
693 for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
694 if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
695 all_surface_faces.append([i+1, i , i + len(surface_splines_parsed[0]), i + len(surface_splines_parsed[0]) + 1])
698 # Build the mesh.
699 surf_me_name = "SURFSK_surface"
700 me_surf = bpy.data.meshes.new(surf_me_name)
702 me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
704 me_surf.update()
706 ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
707 bpy.context.scene.objects.link(ob_surface)
710 #### Join the new mesh to the main object.
711 ob_surface.select = True
712 self.main_object.select = True
713 bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
714 bpy.ops.object.join()
715 bpy.ops.object.editmode_toggle()
716 bpy.ops.mesh.select_all(action='SELECT')
717 bpy.ops.mesh.remove_doubles(limit=0.0001)
718 bpy.ops.mesh.normals_make_consistent(inside=False)
719 bpy.ops.mesh.select_all(action='DESELECT')
721 #### Delete grease pencil strokes
722 bpy.ops.gpencil.active_frame_delete()
725 def invoke (self, context, event):
726 bpy.ops.object.editmode_toggle()
727 bpy.ops.object.editmode_toggle()
728 self.main_object = bpy.context.scene.objects.active
730 self.execute(context)
732 return {"FINISHED"}
737 class GPENCIL_OT_SURFSK_strokes_to_curves(bpy.types.Operator):
738 bl_idname = "GPENCIL_OT_SURFSK_strokes_to_curves"
739 bl_label = "Convert grease pencil strokes into curves and enter edit mode"
740 bl_description = "Convert grease pencil strokes into curves and enter edit mode"
743 def execute(self, context):
744 #### Convert grease pencil strokes to curve.
745 bpy.ops.gpencil.convert(type='CURVE')
746 ob_gp_strokes = bpy.context.object
747 ob_gp_strokes.name = "SURFSK_strokes"
749 #### Delete grease pencil strokes.
750 bpy.ops.object.select_name(name = self.main_object.name)
751 bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
752 bpy.ops.gpencil.active_frame_delete()
755 bpy.ops.object.select_name(name = ob_gp_strokes.name)
756 bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
759 bpy.ops.object.editmode_toggle()
760 bpy.ops.object.editmode_toggle()
761 bpy.ops.curve.smooth()
762 bpy.ops.curve.smooth()
763 bpy.ops.curve.smooth()
764 bpy.ops.curve.smooth()
765 bpy.ops.curve.smooth()
766 bpy.ops.curve.smooth()
767 bpy.ops.curve.smooth()
768 bpy.ops.curve.smooth()
769 bpy.ops.curve.smooth()
770 bpy.ops.curve.smooth()
771 bpy.ops.curve.smooth()
772 bpy.ops.curve.smooth()
774 curve_crv = ob_gp_strokes.data
775 bpy.ops.curve.spline_type_set(type="BEZIER")
776 bpy.ops.curve.handle_type_set(type="AUTOMATIC")
777 bpy.data.curves[curve_crv.name].show_handles = False
778 bpy.data.curves[curve_crv.name].show_normal_face = False
781 def invoke (self, context, event):
782 self.main_object = bpy.context.object
785 self.execute(context)
787 return {"FINISHED"}
790 def register():
791 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)
792 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)
793 bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(name="Precision", description="Precision level of the surface calculation", default=4, min=0, max=100000)
794 bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(name="Keep strokes", description="Keeps the sketched strokes after adding the surface", default=False)
796 kc = bpy.data.window_managers[0].keyconfigs.default
797 km = kc.keymaps.get("3D View")
798 if km is None:
799 km = kc.keymaps.new(name="3D View")
800 keymap_item_add_surf = km.items.new("GPENCIL_OT_SURFSK_add_surface","E","PRESS", key_modifier="D")
801 keymap_item_stroke_to_curve = km.items.new("GPENCIL_OT_SURFSK_strokes_to_curves","C","PRESS", key_modifier="D")
804 def unregister():
805 del bpy.types.Scene.SURFSK_edges_U
806 del bpy.types.Scene.SURFSK_edges_V
807 del bpy.types.Scene.SURFSK_precision
808 del bpy.types.Scene.SURFSK_keep_strokes
810 kc = bpy.data.window_managers[0].keyconfigs.default
811 km = kc.keymaps["3D View"]
812 for kmi in km.items:
813 if kmi.idname == 'wm.call_menu':
814 if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface":
815 km.items.remove(kmi)
816 elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves":
817 km.items.remove(kmi)
818 else:
819 continue
822 if __name__ == "__main__":
823 register()