"resources-allocation-policy" did not exist on "3f87d10013516d306a53e18b74c949f0b22d202f"
Newer
Older
# ##### 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; version 2
# of the License.
#
# 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 #####
"version": (1,5),
"blender": (2, 6, 3),
"api": 45996,
"description": "Modeling and retopology tool.",
"wiki_url": "http://www.bsurfaces.info",
"tracker_url": "http://projects.blender.org/tracker/index.php?"\
"func=detail&aid=26642",
import mathutils
import operator
class VIEW3D_PT_tools_SURFSK_mesh(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_context = "mesh_edit"
def draw(self, context):
layout = self.layout
scn = context.scene
col = layout.column(align=True)
row = layout.row()
row.separator()
col.operator("gpencil.surfsk_add_surface", text="Add Surface")
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
col.operator("gpencil.surfsk_edit_strokes", text="Edit Strokes")
col.prop(scn, "SURFSK_cyclic_cross")
col.prop(scn, "SURFSK_cyclic_follow")
col.prop(scn, "SURFSK_loops_on_strokes")
col.prop(scn, "SURFSK_automatic_join")
col.prop(scn, "SURFSK_keep_strokes")
class VIEW3D_PT_tools_SURFSK_curve(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_context = "curve_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("curve.surfsk_first_points", text="Set First Points")
col.operator("curve.switch_direction", text="Switch Direction")
col.operator("curve.surfsk_reorder_splines", text="Reorder Splines")
#### Returns the type of strokes used.
def get_strokes_type(main_object):
strokes_type = ""
strokes_num = 0
# Check if they are grease pencil
try:
#### Get the active grease pencil layer.
strokes_num = len(main_object.grease_pencil.layers.active.active_frame.strokes)
if strokes_num > 0:
strokes_type = "GP_STROKES"
except:
pass
# Check if they are curves, if there aren't grease pencil strokes.
if strokes_type == "":
if len(bpy.context.selected_objects) == 2:
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active and ob.type == "CURVE":
strokes_type = "EXTERNAL_CURVE"
strokes_num = len(ob.data.splines)
# Check if there is any non-bezier spline.
for i in range(len(ob.data.splines)):
if ob.data.splines[i].type != "BEZIER":
strokes_type = "CURVE_WITH_NON_BEZIER_SPLINES"
break
elif ob != bpy.context.scene.objects.active and ob.type != "CURVE":
strokes_type = "EXTERNAL_NO_CURVE"
elif len(bpy.context.selected_objects) > 2:
strokes_type = "MORE_THAN_ONE_EXTERNAL"
# Check if there is a single stroke without any selection in the object.
if strokes_num == 1 and main_object.data.total_vert_sel == 0:
if strokes_type == "EXTERNAL_CURVE":
strokes_type = "SINGLE_CURVE_STROKE_NO_SELECTION"
elif strokes_type == "GP_STROKES":
strokes_type = "SINGLE_GP_STROKE_NO_SELECTION"
if strokes_num == 0 and main_object.data.total_vert_sel > 0:
strokes_type = "SELECTION_ALONE"
if strokes_type == "":
strokes_type = "NO_STROKES"
return strokes_type
# Surface generator operator.
class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
bl_idname = "gpencil.surfsk_add_surface"
bl_description = "Generates surfaces from grease pencil strokes, bezier curves or loose edges."
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
edges_U = bpy.props.IntProperty(name = "Cross",
description = "Number of face-loops crossing the strokes.",
default = 1,
min = 1,
max = 200)
edges_V = bpy.props.IntProperty(name = "Follow",
description = "Number of face-loops following the strokes.",
default = 1,
min = 1,
max = 200)
cyclic_cross = bpy.props.BoolProperty(name = "Cyclic Cross",
description = "Make cyclic the face-loops crossing the strokes.",
default = False)
cyclic_follow = bpy.props.BoolProperty(name = "Cyclic Follow",
description = "Make cyclic the face-loops following the strokes.",
default = False)
loops_on_strokes = bpy.props.BoolProperty(name = "Loops on strokes",
description = "Make the loops match the paths of the strokes.",
default = False)
automatic_join = bpy.props.BoolProperty(name = "Automatic join",
description = "Join automatically vertices of either surfaces generated by crosshatching, or from the borders of closed shapes.",
default = False)
join_stretch_factor = bpy.props.FloatProperty(name = "Stretch",
description = "Amount of stretching or shrinking allowed for edges when joining vertices automatically.",
default = 1,
min = 0,
max = 3,
subtype = 'FACTOR')
def draw(self, context):
layout = self.layout
scn = context.scene
ob = context.object
col = layout.column(align=True)
row = layout.row()
if not self.is_fill_faces:
row.separator()
if not self.is_crosshatch:
if not self.selection_U_exists:
col.prop(self, "edges_U")
row.separator()
if not self.selection_V_exists:
col.prop(self, "edges_V")
row.separator()
row.separator()
if not self.selection_U_exists:
if not ((self.selection_V_exists and not self.selection_V_is_closed) or (self.selection_V2_exists and not self.selection_V2_is_closed)):
col.prop(self, "cyclic_cross")
if not self.selection_V_exists:
if not ((self.selection_U_exists and not self.selection_U_is_closed) or (self.selection_U2_exists and not self.selection_U2_is_closed)):
col.prop(self, "cyclic_follow")
col.prop(self, "loops_on_strokes")
col.prop(self, "automatic_join")
if self.automatic_join:
row.separator()
col.separator()
row.separator()
col.prop(self, "join_stretch_factor")
#### 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, closing_vert_idx):
# Order selected vertices.
if closing_vert_idx != None:
verts_ordered.append(ob.data.vertices[closing_vert_idx])
verts_ordered.append(ob.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(ob.data.vertices[ob.data.edges[i].vertices[1]])
prev_v = ob.data.edges[i].vertices[1]
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(ob.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 closing_vert_idx != None:
verts_ordered.append(ob.data.vertices[closing_vert_idx])
verts_ordered.append(ob.data.vertices[middle_vertex_idx])
#### Calculates length of a chain of points.
edges_lengths = []
edges_lengths_sum = 0
for i in range(0, len(verts_ordered)):
if i == 0:
prev_v_co = matrix * verts_ordered[i].co
v_co = matrix * verts_ordered[i].co
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
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
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 the which vert of verts_idx list is the nearest one to the point_co coordinates, and the distance.
def shortest_distance(self, object, point_co, verts_idx):
matrix = object.matrix_world
for i in range(0, len(verts_idx)):
dist = (point_co - matrix * object.data.vertices[verts_idx[i]].co).length
if i == 0:
prev_dist = dist
nearest_vert_idx = verts_idx[i]
shortest_dist = dist
if dist < prev_dist:
prev_dist = dist
nearest_vert_idx = verts_idx[i]
shortest_dist = dist
return nearest_vert_idx, shortest_dist
#### Returns the index of the opposite vert tip in a chain, given a vert tip index as parameter, and a multidimentional list with all pairs of tips.
def opposite_tip(self, vert_tip_idx, all_chains_tips_idx):
opposite_vert_tip_idx = None
for i in range(0, len(all_chains_tips_idx)):
if vert_tip_idx == all_chains_tips_idx[i][0]:
opposite_vert_tip_idx = all_chains_tips_idx[i][1]
if vert_tip_idx == all_chains_tips_idx[i][1]:
opposite_vert_tip_idx = all_chains_tips_idx[i][0]
#### Simplifies a spline and returns the new points coordinates.
def simplify_spline(self, spline_coords, segments_num):
simplified_spline = []
points_between_segments = round(len(spline_coords) / segments_num)
simplified_spline.append(spline_coords[0])
for i in range(1, segments_num):
simplified_spline.append(spline_coords[i * points_between_segments])
simplified_spline.append(spline_coords[len(spline_coords) - 1])
return simplified_spline
#### Cleans up the scene and gets it the same it was at the beginning, in case the script is interrupted in the middle of the execution.
def cleanup_on_interruption(self):
# If the original strokes curve comes from conversion from grease pencil and wasn't made by hand, delete it.
if not self.using_external_curves:
try:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.original_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
bpy.ops.object.delete()
except:
pass
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.original_curve.name].select = True
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
#### Returns a list with the coords of the points distributed over the splines passed to this method according to the proportions parameter.
def distribute_pts(self, surface_splines, proportions):
# Calculate the length of each final surface spline.
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 = (prev_p.co - p.co).length
surface_splines_lengths[sp_idx] += edge_length
prev_p = p
# Calculate vertex positions with appropriate edge proportions, and ordered, for each spline.
for sp_idx in range(0, len(surface_splines)):
surface_splines_parsed.append([])
surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[0].co)
prev_p_co = surface_splines[sp_idx].bezier_points[0].co
p_idx = 0
for prop_idx in range(len(proportions) - 1):
target_length = surface_splines_lengths[sp_idx] * proportions[prop_idx]
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
partial_segment_length = 0
finish_while = False
while True:
p_co = surface_splines[sp_idx].bezier_points[p_idx].co
new_dist = (prev_p_co - p_co).length
potential_segment_length = partial_segment_length + new_dist # The new distance that could have the partial segment if it is still shorter than the target length.
if potential_segment_length < target_length: # If the potential is still shorter, keep adding.
partial_segment_length = potential_segment_length
p_idx += 1
prev_p_co = p_co
elif potential_segment_length > target_length: # If the potential is longer than the target, calculate the target (a point between the last two points), and assign.
remaining_dist = target_length - partial_segment_length
vec = p_co - prev_p_co
vec.normalize()
intermediate_co = prev_p_co + (vec * remaining_dist)
surface_splines_parsed[sp_idx].append(intermediate_co)
partial_segment_length += remaining_dist
prev_p_co = intermediate_co
finish_while = True
elif potential_segment_length == target_length: # If the potential is equal to the target, assign.
surface_splines_parsed[sp_idx].append(p_co)
prev_p_co = p_co
finish_while = True
if finish_while:
break
Eclectiel L
committed
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# last point of the spline
surface_splines_parsed[sp_idx].append(surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1].co)
return surface_splines_parsed
#### Counts the number of faces that belong to each edge.
def edge_face_count(self, ob):
ed_keys_count_dict = {}
for face in ob.data.polygons:
for ed_keys in face.edge_keys:
if not ed_keys in ed_keys_count_dict:
ed_keys_count_dict[ed_keys] = 1
else:
ed_keys_count_dict[ed_keys] += 1
edge_face_count = []
for i in range(len(ob.data.edges)):
edge_face_count.append(0)
for i in range(len(ob.data.edges)):
ed = ob.data.edges[i]
v1 = ed.vertices[0]
v2 = ed.vertices[1]
Eclectiel L
committed
if (v1, v2) in ed_keys_count_dict:
edge_face_count[i] = ed_keys_count_dict[(v1, v2)]
elif (v2, v1) in ed_keys_count_dict:
edge_face_count[i] = ed_keys_count_dict[(v2, v1)]
return edge_face_count
#### Fills with faces all the selected vertices which form empty triangles or quads.
def fill_with_faces(self, object):
all_selected_verts_count = self.main_object_selected_verts_count
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
#### Calculate average length of selected edges.
all_selected_verts = []
original_sel_edges_count = 0
for ed in object.data.edges:
if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
coords = []
coords.append(object.data.vertices[ed.vertices[0]].co)
coords.append(object.data.vertices[ed.vertices[1]].co)
original_sel_edges_count += 1
if not ed.vertices[0] in all_selected_verts:
all_selected_verts.append(ed.vertices[0])
if not ed.vertices[1] in all_selected_verts:
all_selected_verts.append(ed.vertices[1])
#### Check if there is any edge selected. If not, interrupt the script.
if original_sel_edges_count == 0 and all_selected_verts_count > 0:
return 0
#### Get all edges connected to selected verts.
all_edges_around_sel_verts = []
edges_connected_to_sel_verts = {}
verts_connected_to_every_vert = {}
for ed_idx in range(len(object.data.edges)):
ed = object.data.edges[ed_idx]
include_edge = False
if ed.vertices[0] in all_selected_verts:
if not ed.vertices[0] in edges_connected_to_sel_verts:
edges_connected_to_sel_verts[ed.vertices[0]] = []
edges_connected_to_sel_verts[ed.vertices[0]].append(ed_idx)
include_edge = True
if ed.vertices[1] in all_selected_verts:
if not ed.vertices[1] in edges_connected_to_sel_verts:
edges_connected_to_sel_verts[ed.vertices[1]] = []
edges_connected_to_sel_verts[ed.vertices[1]].append(ed_idx)
include_edge = True
if include_edge == True:
all_edges_around_sel_verts.append(ed_idx)
# Get all connected verts to each vert.
if not ed.vertices[0] in verts_connected_to_every_vert:
verts_connected_to_every_vert[ed.vertices[0]] = []
if not ed.vertices[1] in verts_connected_to_every_vert:
verts_connected_to_every_vert[ed.vertices[1]] = []
verts_connected_to_every_vert[ed.vertices[0]].append(ed.vertices[1])
verts_connected_to_every_vert[ed.vertices[1]].append(ed.vertices[0])
#### Get all verts connected to faces.
all_verts_part_of_faces = []
all_edges_faces_count = []
all_edges_faces_count += self.edge_face_count(object)
# Get only the selected edges that have faces attached.
count_faces_of_edges_around_sel_verts = {}
selected_verts_with_faces = []
for ed_idx in all_edges_around_sel_verts:
count_faces_of_edges_around_sel_verts[ed_idx] = all_edges_faces_count[ed_idx]
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
if all_edges_faces_count[ed_idx] > 0:
ed = object.data.edges[ed_idx]
if not ed.vertices[0] in selected_verts_with_faces:
selected_verts_with_faces.append(ed.vertices[0])
if not ed.vertices[1] in selected_verts_with_faces:
selected_verts_with_faces.append(ed.vertices[1])
all_verts_part_of_faces.append(ed.vertices[0])
all_verts_part_of_faces.append(ed.vertices[1])
tuple(selected_verts_with_faces)
#### Discard unneeded verts from calculations.
participating_verts = []
movable_verts = []
for v_idx in all_selected_verts:
vert_has_edges_with_one_face = False
for ed_idx in edges_connected_to_sel_verts[v_idx]: # Check if the actual vert has at least one edge connected to only one face.
if count_faces_of_edges_around_sel_verts[ed_idx] == 1:
vert_has_edges_with_one_face = True
# If the vert has two or less edges connected and the vert is not part of any face. Or the vert is part of any face and at least one of the connected edges has only one face attached to it.
if (len(edges_connected_to_sel_verts[v_idx]) == 2 and not v_idx in all_verts_part_of_faces) or len(edges_connected_to_sel_verts[v_idx]) == 1 or (v_idx in all_verts_part_of_faces and vert_has_edges_with_one_face):
participating_verts.append(v_idx)
if not v_idx in all_verts_part_of_faces:
movable_verts.append(v_idx)
#### Remove from movable verts list those that are part of closed geometry (ie: triangles, quads)
for mv_idx in movable_verts:
freeze_vert = False
mv_connected_verts = verts_connected_to_every_vert[mv_idx]
for actual_v_idx in all_selected_verts:
count_shared_neighbors = 0
checked_verts = []
for mv_conn_v_idx in mv_connected_verts:
if mv_idx != actual_v_idx:
if mv_conn_v_idx in verts_connected_to_every_vert[actual_v_idx] and not mv_conn_v_idx in checked_verts:
count_shared_neighbors += 1
checked_verts.append(mv_conn_v_idx)
if actual_v_idx in mv_connected_verts:
freeze_vert = True
break
if count_shared_neighbors == 2:
freeze_vert = True
break
if freeze_vert:
break
if freeze_vert:
movable_verts.remove(mv_idx)
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
#### Calculate merge distance for participating verts.
shortest_edge_length = None
for ed in object.data.edges:
if ed.vertices[0] in movable_verts and ed.vertices[1] in movable_verts:
v1 = object.data.vertices[ed.vertices[0]]
v2 = object.data.vertices[ed.vertices[1]]
length = (v1.co - v2.co).length
if shortest_edge_length == None:
shortest_edge_length = length
else:
if length < shortest_edge_length:
shortest_edge_length = length
if shortest_edge_length != None:
edges_merge_distance = shortest_edge_length * 0.5
else:
edges_merge_distance = 0
#### Get together the verts near enough. They will be merged later.
remaining_verts = []
remaining_verts += participating_verts
for v1_idx in participating_verts:
if v1_idx in remaining_verts and v1_idx in movable_verts:
verts_to_merge = []
coords_verts_to_merge = {}
verts_to_merge.append(v1_idx)
v1_co = object.data.vertices[v1_idx].co
coords_verts_to_merge[v1_idx] = (v1_co[0], v1_co[1], v1_co[2])
for v2_idx in remaining_verts:
if v1_idx != v2_idx:
v2_co = object.data.vertices[v2_idx].co
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
if dist <= edges_merge_distance: # Add the verts which are near enough.
verts_to_merge.append(v2_idx)
coords_verts_to_merge[v2_idx] = (v2_co[0], v2_co[1], v2_co[2])
for vm_idx in verts_to_merge:
remaining_verts.remove(vm_idx)
if len(verts_to_merge) > 1:
# Calculate middle point of the verts to merge.
sum_x_co = 0
sum_y_co = 0
sum_z_co = 0
movable_verts_to_merge_count = 0
for i in range(len(verts_to_merge)):
if verts_to_merge[i] in movable_verts:
v_co = object.data.vertices[verts_to_merge[i]].co
sum_x_co += v_co[0]
sum_y_co += v_co[1]
sum_z_co += v_co[2]
movable_verts_to_merge_count += 1
middle_point_co = [sum_x_co / movable_verts_to_merge_count, sum_y_co / movable_verts_to_merge_count, sum_z_co / movable_verts_to_merge_count]
# Check if any vert to be merged is not movable.
shortest_dist = None
are_verts_not_movable = False
verts_not_movable = []
for v_merge_idx in verts_to_merge:
if v_merge_idx in participating_verts and not v_merge_idx in movable_verts:
are_verts_not_movable = True
verts_not_movable.append(v_merge_idx)
if are_verts_not_movable:
# Get the vert connected to faces, that is nearest to the middle point of the movable verts.
shortest_dist = None
for vcf_idx in verts_not_movable:
dist = abs((object.data.vertices[vcf_idx].co - mathutils.Vector(middle_point_co)).length)
if shortest_dist == None:
shortest_dist = dist
nearest_vert_idx = vcf_idx
else:
if dist < shortest_dist:
shortest_dist = dist
nearest_vert_idx = vcf_idx
coords = object.data.vertices[nearest_vert_idx].co
target_point_co = [coords[0], coords[1], coords[2]]
else:
target_point_co = middle_point_co
# Move verts to merge to the middle position.
for v_merge_idx in verts_to_merge:
if v_merge_idx in movable_verts: # Only move the verts that are not part of faces.
object.data.vertices[v_merge_idx].co[0] = target_point_co[0]
object.data.vertices[v_merge_idx].co[1] = target_point_co[1]
object.data.vertices[v_merge_idx].co[2] = target_point_co[2]
#### Perform "Remove Doubles" to weld all the disconnected verts
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
#### Get all the definitive selected edges, after weldding.
selected_edges = []
edges_per_vert = {} # Number of faces of each selected edge.
for ed in object.data.edges:
if object.data.vertices[ed.vertices[0]].select and object.data.vertices[ed.vertices[1]].select:
selected_edges.append(ed.index)
# Save all the edges that belong to each vertex.
if not ed.vertices[0] in edges_per_vert:
edges_per_vert[ed.vertices[0]] = []
if not ed.vertices[1] in edges_per_vert:
edges_per_vert[ed.vertices[1]] = []
edges_per_vert[ed.vertices[0]].append(ed.index)
edges_per_vert[ed.vertices[1]].append(ed.index)
# Check if all the edges connected to each vert have two faces attached to them. To discard them later and make calculations faster.
a = []
a += self.edge_face_count(object)
tuple(a)
verts_surrounded_by_faces = {}
for v_idx in edges_per_vert:
edges = edges_per_vert[v_idx]
edges_with_two_faces_count = 0
for ed_idx in edges_per_vert[v_idx]:
if a[ed_idx] == 2:
edges_with_two_faces_count += 1
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
if edges_with_two_faces_count == len(edges_per_vert[v_idx]):
verts_surrounded_by_faces[v_idx] = True
else:
verts_surrounded_by_faces[v_idx] = False
#### Get all the selected vertices.
selected_verts_idx = []
for v in object.data.vertices:
if v.select:
selected_verts_idx.append(v.index)
#### Get all the faces of the object.
all_object_faces_verts_idx = []
for face in object.data.polygons:
face_verts = []
face_verts.append(face.vertices[0])
face_verts.append(face.vertices[1])
face_verts.append(face.vertices[2])
if len(face.vertices) == 4:
face_verts.append(face.vertices[3])
all_object_faces_verts_idx.append(face_verts)
#### Deselect all vertices.
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
#### Make a dictionary with the verts related to each vert.
related_key_verts = {}
for ed_idx in selected_edges:
ed = object.data.edges[ed_idx]
if not verts_surrounded_by_faces[ed.vertices[0]]:
if not ed.vertices[0] in related_key_verts:
related_key_verts[ed.vertices[0]] = []
if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
related_key_verts[ed.vertices[0]].append(ed.vertices[1])
if not verts_surrounded_by_faces[ed.vertices[1]]:
if not ed.vertices[1] in related_key_verts:
related_key_verts[ed.vertices[1]] = []
if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
related_key_verts[ed.vertices[1]].append(ed.vertices[0])
#### Get groups of verts forming each face.
faces_verts_idx = []
for v1 in related_key_verts: # verts-1 ....
for v2 in related_key_verts: # verts-2
if v1 != v2:
related_verts_in_common = []
v2_in_rel_v1 = False
v1_in_rel_v2 = False
for rel_v1 in related_key_verts[v1]:
if rel_v1 in related_key_verts[v2]: # Check if related verts of verts-1 are related verts of verts-2.
related_verts_in_common.append(rel_v1)
if v2 in related_key_verts[v1]:
v2_in_rel_v1 = True
if v1 in related_key_verts[v2]:
v1_in_rel_v2 = True
repeated_face = False
# If two verts have two related verts in common, they form a quad.
if len(related_verts_in_common) == 2:
# Check if the face is already saved.
all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
for f_verts in all_faces_to_check_idx:
repeated_verts = 0
if len(f_verts) == 4:
if v1 in f_verts: repeated_verts += 1
if v2 in f_verts: repeated_verts += 1
if related_verts_in_common[0] in f_verts: repeated_verts += 1
if related_verts_in_common[1] in f_verts: repeated_verts += 1
if repeated_verts == len(f_verts):
repeated_face = True
break
if not repeated_face:
faces_verts_idx.append([v1, related_verts_in_common[0], v2, related_verts_in_common[1]])
elif v2_in_rel_v1 and v1_in_rel_v2 and len(related_verts_in_common) == 1: # If Two verts have one related vert in common and they are related to each other, they form a triangle.
# Check if the face is already saved.
all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
for f_verts in all_faces_to_check_idx:
repeated_verts = 0
if len(f_verts) == 3:
if v1 in f_verts: repeated_verts += 1
if v2 in f_verts: repeated_verts += 1
if related_verts_in_common[0] in f_verts: repeated_verts += 1
if repeated_verts == len(f_verts):
repeated_face = True
break
if not repeated_face:
faces_verts_idx.append([v1, related_verts_in_common[0], v2])
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
#### Keep only the faces that don't overlap by ignoring quads that overlap with two adjacent triangles.
faces_to_not_include_idx = [] # Indices of faces_verts_idx to eliminate.
all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
for i in range(len(faces_verts_idx)):
for t in range(len(all_faces_to_check_idx)):
if i != t:
verts_in_common = 0
if len(faces_verts_idx[i]) == 4 and len(all_faces_to_check_idx[t]) == 3:
for v_idx in all_faces_to_check_idx[t]:
if v_idx in faces_verts_idx[i]:
verts_in_common += 1
if verts_in_common == 3: # If it doesn't have all it's vertices repeated in the other face.
if not i in faces_to_not_include_idx:
faces_to_not_include_idx.append(i)
#### Build faces discarding the ones in faces_to_not_include.
me = object.data
bm = bmesh.new()
bm.from_mesh(me)
num_faces_created = 0
for i in range(len(faces_verts_idx)):
if not i in faces_to_not_include_idx:
bm.faces.new([ bm.verts[v] for v in faces_verts_idx[i] ])
num_faces_created += 1
bm.to_mesh(me)
bm.free()
for v_idx in selected_verts_idx:
self.main_object.data.vertices[v_idx].select = True
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='EDIT')
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
return num_faces_created
#### Crosshatch skinning.
def crosshatch_surface_invoke(self, ob_original_splines):
self.is_crosshatch = False
self.crosshatch_merge_distance = 0
objects_to_delete = [] # duplicated strokes to be deleted.
# If the main object uses modifiers deactivate them temporarily until the surface is joined. (without this the surface verts merging with the main object doesn't work well)