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 #####
"author": "Eclectiel, Spivak Vladimir(cwolf3d)",
"description": "Modeling and retopology tool",
"wiki_url": "https://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.64/Bsurfaces_1.5",
"category": "Mesh",
}
CoDEmanX
committed
from bpy_extras import object_utils
from mathutils import Matrix, Vector
from mathutils.geometry import (
intersect_line_line,
intersect_point_line,
)
from math import (
degrees,
pi,
sqrt,
)
from bpy.props import (
BoolProperty,
FloatProperty,
IntProperty,
StringProperty,
PointerProperty,
)
from bpy.types import (
Operator,
Panel,
PropertyGroup,
AddonPreferences,
)
class VIEW3D_PT_tools_SURFSK_mesh(Panel):
#bl_context = "mesh_edit"
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
row = layout.row()
row.separator()
col.operator("gpencil.surfsk_init", text="Initialize")
col.prop(scn, "SURFSK_object_with_retopology")
col.prop(scn, "SURFSK_object_with_strokes")
col.operator("gpencil.surfsk_add_surface", text="Add Surface")
col.operator("gpencil.surfsk_edit_surface", text="Edit Surface")
col.operator("gpencil.surfsk_add_strokes", text="Add Strokes")
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(Panel):
bl_category = 'Tools'
CoDEmanX
committed
@classmethod
def poll(cls, context):
return context.active_object
CoDEmanX
committed
def draw(self, context):
layout = self.layout
CoDEmanX
committed
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")
CoDEmanX
committed
def get_strokes_type():
strokes_type = ""
strokes_num = 0
CoDEmanX
committed
# Check if they are grease pencil
try:
gpencil = bpy.context.scene.bsurfaces.SURFSK_object_with_strokes
layer = gpencil.data.layers[0]
frame = layer.frames[0]
strokes_num = len(frame.strokes)
if strokes_num > 0:
strokes_type = "GP_STROKES"
# Check if they are mesh
try:
main_object = bpy.context.scene.bsurfaces.SURFSK_object_with_retopology
CoDEmanX
committed
# 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.view_layer.objects.active and ob.type == "CURVE":
strokes_type = "EXTERNAL_CURVE"
strokes_num = len(ob.data.splines)
CoDEmanX
committed
# 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
CoDEmanX
committed
elif ob != bpy.context.view_layer.objects.active and ob.type != "CURVE":
strokes_type = "EXTERNAL_NO_CURVE"
elif len(bpy.context.selected_objects) > 2:
strokes_type = "MORE_THAN_ONE_EXTERNAL"
CoDEmanX
committed
# 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"
CoDEmanX
committed
if strokes_num == 0 and main_object.data.total_vert_sel > 0:
strokes_type = "SELECTION_ALONE"
CoDEmanX
committed
if strokes_type == "":
strokes_type = "NO_STROKES"
CoDEmanX
committed
# Surface generator operator
class GPENCIL_OT_SURFSK_add_surface(Operator):
bl_description = "Generates surfaces from grease pencil strokes, bezier curves or loose edges"
CoDEmanX
committed
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
is_fill_faces: BoolProperty(
default=True
)
selection_U_exists: BoolProperty(
default=False
)
selection_V_exists: BoolProperty(
default=False
)
selection_U2_exists: BoolProperty(
default=False
)
selection_V2_exists: BoolProperty(
default=False
)
selection_V_is_closed: BoolProperty(
default=False
)
selection_U_is_closed: BoolProperty(
default=False
)
selection_V2_is_closed: BoolProperty(
default=False
)
selection_U2_is_closed: BoolProperty(
default=False
)
name="Cross",
description="Number of face-loops crossing the strokes",
default=1,
min=1,
max=200
)
name="Follow",
description="Number of face-loops following the strokes",
default=1,
min=1,
max=200
)
name="Cyclic Cross",
description="Make cyclic the face-loops crossing the strokes",
default=False
)
name="Cyclic Follow",
description="Make cyclic the face-loops following the strokes",
default=False
)
name="Loops on strokes",
description="Make the loops match the paths of the strokes",
default=False
)
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: FloatProperty(
name="Stretch",
description="Amount of stretching or shrinking allowed for "
"edges when joining vertices automatically",
default=1,
min=0,
max=3,
subtype='FACTOR'
)
keep_strokes: BoolProperty(
name="Keep strokes",
description="Keeps the sketched strokes or curves after adding the surface",
default=False
)
strokes_type: StringProperty()
initial_global_undo_state: BoolProperty()
CoDEmanX
committed
def draw(self, context):
layout = self.layout
col = layout.column(align=True)
row = layout.row()
CoDEmanX
committed
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()
CoDEmanX
committed
if not self.selection_V_exists:
col.prop(self, "edges_V")
row.separator()
CoDEmanX
committed
CoDEmanX
committed
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)
):
CoDEmanX
committed
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)
):
CoDEmanX
committed
col.prop(self, "loops_on_strokes")
CoDEmanX
committed
col.prop(self, "automatic_join")
if self.automatic_join:
row.separator()
col.separator()
row.separator()
col.prop(self, "join_stretch_factor")
col.prop(self, "keep_strokes")
CoDEmanX
committed
# 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):
if closing_vert_idx is not None:
verts_ordered.append(ob.data.vertices[closing_vert_idx])
CoDEmanX
committed
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
CoDEmanX
committed
if edges_non_matched == len(all_selected_edges_idx):
finish_while = True
CoDEmanX
committed
CoDEmanX
committed
if closing_vert_idx is not None:
verts_ordered.append(ob.data.vertices[closing_vert_idx])
CoDEmanX
committed
if middle_vertex_idx is not None:
verts_ordered.append(ob.data.vertices[middle_vertex_idx])
CoDEmanX
committed
CoDEmanX
committed
# Calculates length of a chain of points.
CoDEmanX
committed
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
CoDEmanX
committed
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]))
CoDEmanX
committed
edges_lengths.append(edge_length)
edges_lengths_sum += edge_length
CoDEmanX
committed
CoDEmanX
committed
return edges_lengths, edges_lengths_sum
CoDEmanX
committed
# 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
CoDEmanX
committed
CoDEmanX
committed
# 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]
CoDEmanX
committed
CoDEmanX
committed
if angle > 0.5 * pi:
angle = abs(angle - pi)
CoDEmanX
committed
CoDEmanX
committed
# 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
CoDEmanX
committed
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
CoDEmanX
committed
if dist < prev_dist:
prev_dist = dist
nearest_vert_idx = verts_idx[i]
shortest_dist = dist
CoDEmanX
committed
return nearest_vert_idx, shortest_dist
CoDEmanX
committed
# 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]
CoDEmanX
committed
CoDEmanX
committed
# 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)
CoDEmanX
committed
simplified_spline.append(spline_coords[0])
for i in range(1, segments_num):
simplified_spline.append(spline_coords[i * points_between_segments])
CoDEmanX
committed
simplified_spline.append(spline_coords[len(spline_coords) - 1])
CoDEmanX
committed
CoDEmanX
committed
# 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.delete({"selected_objects": [self.original_curve]})
CoDEmanX
committed
bpy.ops.object.delete({"selected_objects": [self.main_object]})
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
self.original_curve.select_set(True)
self.main_object.select_set(True)
bpy.context.view_layer.objects.active = self.main_object
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# 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
CoDEmanX
committed
CoDEmanX
committed
# 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)
CoDEmanX
committed
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]
partial_segment_length = 0
finish_while = False
CoDEmanX
committed
# if not it'll pass the p_idx as an index below and crash
if p_idx < len(surface_splines[sp_idx].bezier_points):
p_co = surface_splines[sp_idx].bezier_points[p_idx].co
new_dist = (prev_p_co - p_co).length
CoDEmanX
committed
# The new distance that could have the partial segment if
# it is still shorter than the target length
potential_segment_length = partial_segment_length + new_dist
CoDEmanX
committed
# If the potential is still shorter, keep adding
if potential_segment_length < target_length:
partial_segment_length = potential_segment_length
CoDEmanX
committed
CoDEmanX
committed
# If the potential is longer than the target, calculate the target
# (a point between the last two points), and assign
elif potential_segment_length > target_length:
remaining_dist = target_length - partial_segment_length
vec = p_co - prev_p_co
vec.normalize()
intermediate_co = prev_p_co + (vec * remaining_dist)
CoDEmanX
committed
surface_splines_parsed[sp_idx].append(intermediate_co)
CoDEmanX
committed
partial_segment_length += remaining_dist
prev_p_co = intermediate_co
CoDEmanX
committed
CoDEmanX
committed
# If the potential is equal to the target, assign
elif potential_segment_length == target_length:
surface_splines_parsed[sp_idx].append(p_co)
prev_p_co = p_co
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
surface_splines_parsed[sp_idx].append(
surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1].co
)
CoDEmanX
committed
CoDEmanX
committed
# Counts the number of faces that belong to each edge
def edge_face_count(self, ob):
ed_keys_count_dict = {}
CoDEmanX
committed
for face in ob.data.polygons:
for ed_keys in face.edge_keys:
if ed_keys not in ed_keys_count_dict:
ed_keys_count_dict[ed_keys] = 1
else:
ed_keys_count_dict[ed_keys] += 1
CoDEmanX
committed
edge_face_count = []
for i in range(len(ob.data.edges)):
edge_face_count.append(0)
CoDEmanX
committed
for i in range(len(ob.data.edges)):
ed = ob.data.edges[i]
CoDEmanX
committed
v1 = ed.vertices[0]
v2 = ed.vertices[1]
CoDEmanX
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)]
CoDEmanX
committed
CoDEmanX
committed
# 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
CoDEmanX
committed
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
CoDEmanX
committed
# 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)
CoDEmanX
committed
CoDEmanX
committed
if not ed.vertices[0] in all_selected_verts:
all_selected_verts.append(ed.vertices[0])
CoDEmanX
committed
if not ed.vertices[1] in all_selected_verts:
all_selected_verts.append(ed.vertices[1])
CoDEmanX
committed
CoDEmanX
committed
# 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
CoDEmanX
committed
# 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
CoDEmanX
committed
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]] = []
CoDEmanX
committed
edges_connected_to_sel_verts[ed.vertices[0]].append(ed_idx)
include_edge = True
CoDEmanX
committed
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]] = []
CoDEmanX
committed
edges_connected_to_sel_verts[ed.vertices[1]].append(ed_idx)
include_edge = True
CoDEmanX
committed
all_edges_around_sel_verts.append(ed_idx)
CoDEmanX
committed
# 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]] = []
CoDEmanX
committed
if not ed.vertices[1] in verts_connected_to_every_vert:
verts_connected_to_every_vert[ed.vertices[1]] = []
CoDEmanX
committed
verts_connected_to_every_vert[ed.vertices[0]].append(ed.vertices[1])
verts_connected_to_every_vert[ed.vertices[1]].append(ed.vertices[0])
CoDEmanX
committed
all_verts_part_of_faces = []
all_edges_faces_count = []
all_edges_faces_count += self.edge_face_count(object)
CoDEmanX
committed
# 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]
CoDEmanX
committed
if all_edges_faces_count[ed_idx] > 0:
ed = object.data.edges[ed_idx]
CoDEmanX
committed
if not ed.vertices[0] in selected_verts_with_faces:
selected_verts_with_faces.append(ed.vertices[0])
CoDEmanX
committed
if not ed.vertices[1] in selected_verts_with_faces:
selected_verts_with_faces.append(ed.vertices[1])
CoDEmanX
committed
all_verts_part_of_faces.append(ed.vertices[0])
all_verts_part_of_faces.append(ed.vertices[1])
CoDEmanX
committed
CoDEmanX
committed
# Discard unneeded verts from calculations
participating_verts = []
movable_verts = []
for v_idx in all_selected_verts:
vert_has_edges_with_one_face = False
CoDEmanX
committed
# Check if the actual vert has at least one edge connected to only one face
for ed_idx in edges_connected_to_sel_verts[v_idx]:
if count_faces_of_edges_around_sel_verts[ed_idx] == 1:
vert_has_edges_with_one_face = True
CoDEmanX
committed
# 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
v_idx not 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)
CoDEmanX
committed
if v_idx not in all_verts_part_of_faces:
CoDEmanX
committed
# 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]
CoDEmanX
committed
for actual_v_idx in all_selected_verts:
count_shared_neighbors = 0
checked_verts = []
CoDEmanX
committed
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 \
mv_conn_v_idx not in checked_verts:
count_shared_neighbors += 1
checked_verts.append(mv_conn_v_idx)
CoDEmanX
committed
if actual_v_idx in mv_connected_verts:
freeze_vert = True
break
CoDEmanX
committed
if count_shared_neighbors == 2:
freeze_vert = True
break
CoDEmanX
committed
CoDEmanX
committed
if freeze_vert:
movable_verts.remove(mv_idx)
CoDEmanX
committed
# 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]]
CoDEmanX
committed
CoDEmanX
committed
if shortest_edge_length is None:
shortest_edge_length = length
else:
if length < shortest_edge_length:
shortest_edge_length = length
CoDEmanX
committed
if shortest_edge_length is not None:
edges_merge_distance = shortest_edge_length * 0.5
else:
edges_merge_distance = 0
CoDEmanX
committed
# 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 = {}
CoDEmanX
committed
CoDEmanX
committed
v1_co = object.data.vertices[v1_idx].co
coords_verts_to_merge[v1_idx] = (v1_co[0], v1_co[1], v1_co[2])
CoDEmanX
committed
for v2_idx in remaining_verts:
if v1_idx != v2_idx:
v2_co = object.data.vertices[v2_idx].co
CoDEmanX
committed
CoDEmanX
committed
if dist <= edges_merge_distance: # Add the verts which are near enough
CoDEmanX
committed
coords_verts_to_merge[v2_idx] = (v2_co[0], v2_co[1], v2_co[2])
CoDEmanX
committed
for vm_idx in verts_to_merge:
remaining_verts.remove(vm_idx)
CoDEmanX
committed
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
CoDEmanX
committed
sum_x_co += v_co[0]
sum_y_co += v_co[1]
sum_z_co += v_co[2]
CoDEmanX
committed
movable_verts_to_merge_count += 1
CoDEmanX
committed
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
]
CoDEmanX
committed
# 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 v_merge_idx not in movable_verts:
are_verts_not_movable = True
verts_not_movable.append(v_merge_idx)
CoDEmanX
committed
# 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 -
Vector(middle_point_co)).length)
CoDEmanX
committed
shortest_dist = dist
nearest_vert_idx = vcf_idx
else:
if dist < shortest_dist:
shortest_dist = dist
nearest_vert_idx = vcf_idx
CoDEmanX
committed
coords = object.data.vertices[nearest_vert_idx].co
CoDEmanX
committed
target_point_co = [coords[0], coords[1], coords[2]]
CoDEmanX
committed
# 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]
CoDEmanX
committed
# 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)
CoDEmanX
committed
bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
CoDEmanX
committed
# Get all the definitive selected edges, after weldding
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)
CoDEmanX
committed
# Save all the edges that belong to each vertex.
if not ed.vertices[0] in edges_per_vert:
edges_per_vert[ed.vertices[0]] = []
CoDEmanX
committed
if not ed.vertices[1] in edges_per_vert:
edges_per_vert[ed.vertices[1]] = []
CoDEmanX
committed
edges_per_vert[ed.vertices[0]].append(ed.index)
edges_per_vert[ed.vertices[1]].append(ed.index)
CoDEmanX
committed
# 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
CoDEmanX
committed
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
CoDEmanX
committed
selected_verts_idx = []
for v in object.data.vertices:
if v.select:
selected_verts_idx.append(v.index)
CoDEmanX
committed
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])
CoDEmanX
committed
if len(face.vertices) == 4:
face_verts.append(face.vertices[3])
CoDEmanX
committed
all_object_faces_verts_idx.append(face_verts)
CoDEmanX
committed
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')
CoDEmanX
committed
# 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]
CoDEmanX
committed
if not verts_surrounded_by_faces[ed.vertices[0]]:
if not ed.vertices[0] in related_key_verts:
related_key_verts[ed.vertices[0]] = []
CoDEmanX
committed
if not ed.vertices[1] in related_key_verts[ed.vertices[0]]:
related_key_verts[ed.vertices[0]].append(ed.vertices[1])
CoDEmanX
committed
if not verts_surrounded_by_faces[ed.vertices[1]]:
if not ed.vertices[1] in related_key_verts:
related_key_verts[ed.vertices[1]] = []
CoDEmanX
committed
if not ed.vertices[0] in related_key_verts[ed.vertices[1]]:
related_key_verts[ed.vertices[1]].append(ed.vertices[0])
CoDEmanX
committed
# Get groups of verts forming each face
CoDEmanX
committed
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]:
# Check if related verts of verts-1 are related verts of verts-2
if rel_v1 in related_key_verts[v2]:
related_verts_in_common.append(rel_v1)
CoDEmanX
committed
if v2 in related_key_verts[v1]:
v2_in_rel_v1 = True
CoDEmanX
committed
if v1 in related_key_verts[v2]:
v1_in_rel_v2 = True
CoDEmanX
committed
# 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
CoDEmanX
committed
for f_verts in all_faces_to_check_idx:
repeated_verts = 0
CoDEmanX
committed
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
CoDEmanX
committed
if repeated_verts == len(f_verts):
repeated_face = True
break
CoDEmanX
committed
faces_verts_idx.append(
[v1, related_verts_in_common[0], v2, related_verts_in_common[1]]
)
CoDEmanX
committed
# If Two verts have one related vert in common and
# they are related to each other, they form a triangle
elif v2_in_rel_v1 and v1_in_rel_v2 and len(related_verts_in_common) == 1:
# Check if the face is already saved.
all_faces_to_check_idx = faces_verts_idx + all_object_faces_verts_idx
CoDEmanX
committed
for f_verts in all_faces_to_check_idx:
repeated_verts = 0
CoDEmanX
committed
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
CoDEmanX
committed
if repeated_verts == len(f_verts):
repeated_face = True
break
CoDEmanX
committed
if not repeated_face:
faces_verts_idx.append([v1, related_verts_in_common[0], v2])
CoDEmanX
committed
# 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
CoDEmanX
committed
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 it doesn't have all it's vertices repeated in the other face
if verts_in_common == 3:
if i not in faces_to_not_include_idx:
faces_to_not_include_idx.append(i)
CoDEmanX
committed
# Build faces discarding the ones in faces_to_not_include