Newer
Older
if len(self.main_object.modifiers) > 0:
for m_idx in range(len(self.main_object.modifiers)):
self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
CoDEmanX
committed
self.main_object.modifiers[m_idx].show_viewport = False
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_original_splines.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_original_splines.name]
CoDEmanX
committed
if len(ob_original_splines.data.splines) >= 2:
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
ob_splines = bpy.context.object
ob_splines.name = "SURFSKIO_NE_STR"
CoDEmanX
committed
#### Get estimative merge distance (sum up the distances from the first point to all other points, then average them and then divide them).
first_point_dist_sum = 0
first_dist = 0
second_dist = 0
coords_first_pt = ob_splines.data.splines[0].bezier_points[0].co
for i in range(len(ob_splines.data.splines)):
sp = ob_splines.data.splines[i]
CoDEmanX
committed
if coords_first_pt != sp.bezier_points[0].co:
first_dist = (coords_first_pt - sp.bezier_points[0].co).length
CoDEmanX
committed
if coords_first_pt != sp.bezier_points[len(sp.bezier_points) - 1].co:
second_dist = (coords_first_pt - sp.bezier_points[len(sp.bezier_points) - 1].co).length
CoDEmanX
committed
first_point_dist_sum += first_dist + second_dist
CoDEmanX
committed
if i == 0:
if first_dist != 0:
shortest_dist = first_dist
elif second_dist != 0:
shortest_dist = second_dist
CoDEmanX
committed
if shortest_dist > first_dist and first_dist != 0:
shortest_dist = first_dist
CoDEmanX
committed
if shortest_dist > second_dist and second_dist != 0:
shortest_dist = second_dist
CoDEmanX
committed
self.crosshatch_merge_distance = shortest_dist / 20
CoDEmanX
committed
#### Recalculation of merge distance.
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
CoDEmanX
committed
ob_calc_merge_dist = bpy.context.object
ob_calc_merge_dist.name = "SURFSKIO_CALC_TMP"
CoDEmanX
committed
objects_to_delete.append(ob_calc_merge_dist)
CoDEmanX
committed
#### Smooth out strokes a little to improve crosshatch detection.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
CoDEmanX
committed
for i in range(4):
bpy.ops.curve.smooth('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
#### Convert curves into mesh.
ob_calc_merge_dist.data.resolution_u = 12
bpy.ops.object.convert(target='MESH', keep_original=False)
CoDEmanX
committed
# Find "intersection-nodes".
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance)
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Remove verts with less than three edges.
verts_edges_count = {}
for ed in ob_calc_merge_dist.data.edges:
v = ed.vertices
CoDEmanX
committed
if v[0] not in verts_edges_count:
verts_edges_count[v[0]] = 0
CoDEmanX
committed
if v[1] not in verts_edges_count:
verts_edges_count[v[1]] = 0
CoDEmanX
committed
verts_edges_count[v[0]] += 1
verts_edges_count[v[1]] += 1
CoDEmanX
committed
nodes_verts_coords = []
for v_idx in verts_edges_count:
v = ob_calc_merge_dist.data.vertices[v_idx]
CoDEmanX
committed
if verts_edges_count[v_idx] < 3:
v.select = True
CoDEmanX
committed
# Remove them.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.delete('INVOKE_REGION_WIN', type='VERT')
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
CoDEmanX
committed
# Remove doubles to discard very near verts from calculations of distance.
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=self.crosshatch_merge_distance * 4.0)
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Get all coords of the resulting nodes.
nodes_verts_coords = [(v.co[0], v.co[1], v.co[2]) for v in ob_calc_merge_dist.data.vertices]
CoDEmanX
committed
#### Check if the strokes are a crosshatch.
if len(nodes_verts_coords) >= 3:
self.is_crosshatch = True
CoDEmanX
committed
shortest_dist = None
for co_1 in nodes_verts_coords:
for co_2 in nodes_verts_coords:
if co_1 != co_2:
dist = (mathutils.Vector(co_1) - mathutils.Vector(co_2)).length
CoDEmanX
committed
if shortest_dist != None:
if dist < shortest_dist:
shortest_dist = dist
else:
shortest_dist = dist
CoDEmanX
committed
self.crosshatch_merge_distance = shortest_dist / 3
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_splines.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_splines.name]
CoDEmanX
committed
#### Deselect all points.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
#### Smooth splines in a localized way, to eliminate "saw-teeth" like shapes when there are many points.
for sp in ob_splines.data.splines:
angle_sum = 0
CoDEmanX
committed
angle_limit = 2 # Degrees
for t in range(len(sp.bezier_points)):
if t <= len(sp.bezier_points) - 3: # Because on each iteration it checks the "next two points" of the actual. This way it doesn't go out of range.
p1 = sp.bezier_points[t]
p2 = sp.bezier_points[t + 1]
p3 = sp.bezier_points[t + 2]
CoDEmanX
committed
vec_1 = p1.co - p2.co
vec_2 = p2.co - p3.co
CoDEmanX
committed
if p2.co != p1.co and p2.co != p3.co:
angle = vec_1.angle(vec_2)
angle_sum += degrees(angle)
CoDEmanX
committed
if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
CoDEmanX
committed
if (p1.co - p2.co).length <= self.crosshatch_merge_distance:
p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
CoDEmanX
committed
CoDEmanX
committed
sp.bezier_points[0].select_control_point = False
sp.bezier_points[0].select_left_handle = False
sp.bezier_points[0].select_right_handle = False
CoDEmanX
committed
sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = False
sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = False
sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle = False
CoDEmanX
committed
#### Smooth out strokes a little to improve crosshatch detection.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
for i in range(15):
bpy.ops.curve.smooth('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
#### Simplify the splines.
for sp in ob_splines.data.splines:
angle_sum = 0
CoDEmanX
committed
sp.bezier_points[0].select_control_point = True
sp.bezier_points[0].select_left_handle = True
sp.bezier_points[0].select_right_handle = True
CoDEmanX
committed
sp.bezier_points[len(sp.bezier_points) - 1].select_control_point = True
sp.bezier_points[len(sp.bezier_points) - 1].select_left_handle = True
sp.bezier_points[len(sp.bezier_points) - 1].select_right_handle = True
CoDEmanX
committed
angle_limit = 15 # Degrees
for t in range(len(sp.bezier_points)):
if t <= len(sp.bezier_points) - 3: # Because on each iteration it checks the "next two points" of the actual. This way it doesn't go out of range.
p1 = sp.bezier_points[t]
p2 = sp.bezier_points[t + 1]
p3 = sp.bezier_points[t + 2]
CoDEmanX
committed
vec_1 = p1.co - p2.co
vec_2 = p2.co - p3.co
CoDEmanX
committed
if p2.co != p1.co and p2.co != p3.co:
angle = vec_1.angle(vec_2)
angle_sum += degrees(angle)
CoDEmanX
committed
if angle_sum >= angle_limit: # If sum of angles is grater than the limit.
p1.select_control_point = True; p1.select_left_handle = True; p1.select_right_handle = True
p2.select_control_point = True; p2.select_left_handle = True; p2.select_right_handle = True
p3.select_control_point = True; p3.select_left_handle = True; p3.select_right_handle = True
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.curve.select_all(action = 'INVERT')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
objects_to_delete.append(ob_splines)
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
#### Check if the strokes are a crosshatch.
if self.is_crosshatch:
all_points_coords = []
for i in range(len(ob_splines.data.splines)):
all_points_coords.append([])
CoDEmanX
committed
all_points_coords[i] = [mathutils.Vector((x, y, z)) for x, y, z in [bp.co for bp in ob_splines.data.splines[i].bezier_points]]
CoDEmanX
committed
all_intersections = []
checked_splines = []
for i in range(len(all_points_coords)):
CoDEmanX
committed
for t in range(len(all_points_coords[i]) - 1):
bp1_co = all_points_coords[i][t]
bp2_co = all_points_coords[i][t + 1]
CoDEmanX
committed
for i2 in range(len(all_points_coords)):
if i != i2 and not i2 in checked_splines:
for t2 in range(len(all_points_coords[i2]) - 1):
bp3_co = all_points_coords[i2][t2]
bp4_co = all_points_coords[i2][t2 + 1]
CoDEmanX
committed
intersec_coords = mathutils.geometry.intersect_line_line(bp1_co, bp2_co, bp3_co, bp4_co)
CoDEmanX
committed
if intersec_coords != None:
dist = (intersec_coords[0] - intersec_coords[1]).length
CoDEmanX
committed
if dist <= self.crosshatch_merge_distance * 1.5:
temp_co, percent1 = mathutils.geometry.intersect_point_line(intersec_coords[0], bp1_co, bp2_co)
CoDEmanX
committed
if (percent1 >= -0.02 and percent1 <= 1.02):
temp_co, percent2 = mathutils.geometry.intersect_point_line(intersec_coords[1], bp3_co, bp4_co)
if (percent2 >= -0.02 and percent2 <= 1.02):
all_intersections.append((i, t, percent1, ob_splines.matrix_world * intersec_coords[0])) # Format: spline index, first point index from corresponding segment, percentage from first point of actual segment, coords of intersection point.
all_intersections.append((i2, t2, percent2, ob_splines.matrix_world * intersec_coords[1]))
CoDEmanX
committed
checked_splines.append(i)
all_intersections.sort(key = operator.itemgetter(0,1,2)) # Sort list by spline, then by corresponding first point index of segment, and then by percentage from first point of segment: elements 0 and 1 respectively.
CoDEmanX
committed
self.crosshatch_strokes_coords = {}
for i in range(len(all_intersections)):
if not all_intersections[i][0] in self.crosshatch_strokes_coords:
self.crosshatch_strokes_coords[all_intersections[i][0]] = []
CoDEmanX
committed
self.crosshatch_strokes_coords[all_intersections[i][0]].append(all_intersections[i][3]) # Save intersection coords.
CoDEmanX
committed
else:
self.is_crosshatch = False
CoDEmanX
committed
#### Delete all duplicates.
for o in objects_to_delete:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[o.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[o.name]
bpy.ops.object.delete()
CoDEmanX
committed
#### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
if len(self.main_object.modifiers) > 0:
for m_idx in range(len(self.main_object.modifiers)):
self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
CoDEmanX
committed
CoDEmanX
committed
#### Part of the Crosshatch process that is repeated when the operator is tweaked.
def crosshatch_surface_execute(self):
# 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)
self.modifiers_prev_viewport_state = []
if len(self.main_object.modifiers) > 0:
for m_idx in range(len(self.main_object.modifiers)):
self.modifiers_prev_viewport_state.append(self.main_object.modifiers[m_idx].show_viewport)
CoDEmanX
committed
self.main_object.modifiers[m_idx].show_viewport = False
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
me_name = "SURFSKIO_STK_TMP"
me = bpy.data.meshes.new(me_name)
CoDEmanX
committed
all_verts_coords = []
all_edges = []
for st_idx in self.crosshatch_strokes_coords:
for co_idx in range(len(self.crosshatch_strokes_coords[st_idx])):
coords = self.crosshatch_strokes_coords[st_idx][co_idx]
CoDEmanX
committed
CoDEmanX
committed
if co_idx > 0:
all_edges.append((len(all_verts_coords) - 2, len(all_verts_coords) - 1))
CoDEmanX
committed
me.from_pydata(all_verts_coords, all_edges, [])
CoDEmanX
committed
CoDEmanX
committed
ob = bpy.data.objects.new(me_name, me)
ob.data = me
bpy.context.scene.objects.link(ob)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob.name]
CoDEmanX
committed
#### Get together each vert and its nearest, to the middle position.
verts = ob.data.vertices
checked_verts = []
for i in range(len(verts)):
shortest_dist = None
CoDEmanX
committed
if not i in checked_verts:
for t in range(len(verts)):
if i != t and not t in checked_verts:
dist = (verts[i].co - verts[t].co).length
CoDEmanX
committed
if shortest_dist != None:
if dist < shortest_dist:
shortest_dist = dist
nearest_vert = t
else:
shortest_dist = dist
nearest_vert = t
CoDEmanX
committed
middle_location = (verts[i].co + verts[nearest_vert].co) / 2
CoDEmanX
committed
verts[i].co = middle_location
verts[nearest_vert].co = middle_location
CoDEmanX
committed
checked_verts.append(i)
checked_verts.append(nearest_vert)
CoDEmanX
committed
#### Calculate average length between all the generated edges.
ob = bpy.context.object
lengths_sum = 0
for ed in ob.data.edges:
v1 = ob.data.vertices[ed.vertices[0]]
v2 = ob.data.vertices[ed.vertices[1]]
CoDEmanX
committed
lengths_sum += (v1.co - v2.co).length
CoDEmanX
committed
edges_count = len(ob.data.edges)
CoDEmanX
committed
average_edge_length = lengths_sum / edges_count
CoDEmanX
committed
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='SELECT')
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=average_edge_length / 15.0)
CoDEmanX
committed
final_points_ob = bpy.context.scene.objects.active
CoDEmanX
committed
#### Make a dictionary with the verts related to each vert.
related_key_verts = {}
for ed in final_points_ob.data.edges:
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:
related_key_verts[ed.vertices[1]] = []
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 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]:
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)
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
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.
for f_verts in faces_verts_idx:
repeated_verts = 0
CoDEmanX
committed
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
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, related_verts_in_common[1]])
CoDEmanX
committed
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.
for f_verts in faces_verts_idx:
repeated_verts = 0
CoDEmanX
committed
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
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.
for i in range(len(faces_verts_idx)):
for t in range(len(faces_verts_idx)):
if i != t:
verts_in_common = 0
CoDEmanX
committed
if len(faces_verts_idx[i]) == 4 and len(faces_verts_idx[t]) == 3:
for v_idx in faces_verts_idx[t]:
if v_idx in faces_verts_idx[i]:
verts_in_common += 1
CoDEmanX
committed
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)
CoDEmanX
committed
#### Build surface.
all_surface_verts_co = []
verts_idx_translation = {}
for i in range(len(final_points_ob.data.vertices)):
coords = final_points_ob.data.vertices[i].co
all_surface_verts_co.append([coords[0], coords[1], coords[2]])
CoDEmanX
committed
# Verts of each face.
all_surface_faces = []
for i in range(len(faces_verts_idx)):
if not i in faces_to_not_include_idx:
face = []
for v_idx in faces_verts_idx[i]:
face.append(v_idx)
CoDEmanX
committed
CoDEmanX
committed
# Build the mesh.
surf_me_name = "SURFSKIO_surface"
me_surf = bpy.data.meshes.new(surf_me_name)
CoDEmanX
committed
me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
CoDEmanX
committed
CoDEmanX
committed
ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
bpy.context.scene.objects.link(ob_surface)
CoDEmanX
committed
# Delete final points temporal object
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[final_points_ob.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[final_points_ob.name]
CoDEmanX
committed
CoDEmanX
committed
# Delete isolated verts if there are any.
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_surface.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_surface.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.select_all(action='DESELECT')
bpy.ops.mesh.select_face_by_sides(type='NOTEQUAL')
bpy.ops.mesh.delete()
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
#### Join crosshatch results with original mesh.
CoDEmanX
committed
# Calculate a distance to merge the verts of the crosshatch surface to the main object.
edges_length_sum = 0
for ed in ob_surface.data.edges:
edges_length_sum += (ob_surface.data.vertices[ed.vertices[0]].co - ob_surface.data.vertices[ed.vertices[1]].co).length
CoDEmanX
committed
if len(ob_surface.data.edges) > 0:
average_surface_edges_length = edges_length_sum / len(ob_surface.data.edges)
else:
average_surface_edges_length = 0.0001
CoDEmanX
committed
# Make dictionary with all the verts connected to each vert, on the new surface object.
surface_connected_verts = {}
for ed in ob_surface.data.edges:
if not ed.vertices[0] in surface_connected_verts:
surface_connected_verts[ed.vertices[0]] = []
CoDEmanX
committed
surface_connected_verts[ed.vertices[0]].append(ed.vertices[1])
CoDEmanX
committed
if not ed.vertices[1] in surface_connected_verts:
surface_connected_verts[ed.vertices[1]] = []
CoDEmanX
committed
surface_connected_verts[ed.vertices[1]].append(ed.vertices[0])
CoDEmanX
committed
# Duplicate the new surface object, and use shrinkwrap to calculate later the nearest verts to the main object.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
CoDEmanX
committed
final_ob_duplicate = bpy.context.scene.objects.active
CoDEmanX
committed
bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
final_ob_duplicate.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
final_ob_duplicate.modifiers["Shrinkwrap"].target = self.main_object
CoDEmanX
committed
bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
CoDEmanX
committed
# Make list with verts of original mesh as index and coords as value.
main_object_verts_coords = []
for v in self.main_object.data.vertices:
coords = self.main_object.matrix_world * v.co
CoDEmanX
committed
for c in range(len(coords)): # To avoid problems when taking "-0.00" as a different value as "0.00".
if "%.3f" % coords[c] == "-0.00":
coords[c] = 0
CoDEmanX
committed
main_object_verts_coords.append(["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]])
CoDEmanX
committed
CoDEmanX
committed
# Determine which verts will be merged, snap them to the nearest verts on the original verts, and get them selected.
crosshatch_verts_to_merge = []
if self.automatic_join:
for i in range(len(ob_surface.data.vertices)):
# Calculate the distance from each of the connected verts to the actual vert, and compare it with the distance they would have if joined. If they don't change much, that vert can be joined.
merge_actual_vert = True
if len(surface_connected_verts[i]) < 4:
for c_v_idx in surface_connected_verts[i]:
points_original = []
points_original.append(ob_surface.data.vertices[c_v_idx].co)
points_original.append(ob_surface.data.vertices[i].co)
CoDEmanX
committed
points_target = []
points_target.append(ob_surface.data.vertices[c_v_idx].co)
points_target.append(final_ob_duplicate.data.vertices[i].co)
CoDEmanX
committed
vec_A = points_original[0] - points_original[1]
vec_B = points_target[0] - points_target[1]
CoDEmanX
committed
dist_A = (points_original[0] - points_original[1]).length
dist_B = (points_target[0] - points_target[1]).length
CoDEmanX
committed
if not (points_original[0] == points_original[1] or points_target[0] == points_target[1]): # If any vector's length is zero.
angle = vec_A.angle(vec_B) / math.pi
else:
angle= 0
CoDEmanX
committed
if dist_B > dist_A * 1.7 * self.join_stretch_factor or dist_B < dist_A / 2 / self.join_stretch_factor or angle >= 0.15 * self.join_stretch_factor: # Set a range of acceptable variation in the connected edges.
merge_actual_vert = False
break
else:
merge_actual_vert = False
CoDEmanX
committed
if merge_actual_vert:
coords = final_ob_duplicate.data.vertices[i].co
CoDEmanX
committed
for c in range(len(coords)): # To avoid problems when taking "-0.000" as a different value as "0.00".
if "%.3f" % coords[c] == "-0.00":
coords[c] = 0
CoDEmanX
committed
comparison_coords = ["%.3f" % coords[0], "%.3f" % coords[1], "%.3f" % coords[2]]
CoDEmanX
committed
if comparison_coords in main_object_verts_coords:
main_object_related_vert_idx = main_object_verts_coords.index(comparison_coords) # Get the index of the vert with those coords in the main object.
CoDEmanX
committed
if self.main_object.data.vertices[main_object_related_vert_idx].select == True or self.main_object_selected_verts_count == 0:
ob_surface.data.vertices[i].co = final_ob_duplicate.data.vertices[i].co
ob_surface.data.vertices[i].select = True
crosshatch_verts_to_merge.append(i)
CoDEmanX
committed
# Make sure the vert in the main object is selected, in case it wasn't selected and the "join crosshatch" option is active.
self.main_object.data.vertices[main_object_related_vert_idx].select = True
CoDEmanX
committed
# Delete duplicated object.
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[final_ob_duplicate.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[final_ob_duplicate.name]
bpy.ops.object.delete()
CoDEmanX
committed
# Join crosshatched surface and main object.
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_surface.name].select = True
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.join('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
# Perform Remove doubles to merge verts.
if not (self.automatic_join == False and self.main_object_selected_verts_count == 0):
bpy.ops.mesh.remove_doubles(threshold=0.0001)
CoDEmanX
committed
bpy.ops.mesh.select_all(action='DESELECT')
CoDEmanX
committed
#### If the main object has modifiers, turn their "viewport view status" to what it was before the forced deactivation above.
if len(self.main_object.modifiers) > 0:
for m_idx in range(len(self.main_object.modifiers)):
self.main_object.modifiers[m_idx].show_viewport = self.modifiers_prev_viewport_state[m_idx]
CoDEmanX
committed
CoDEmanX
committed
def rectangular_surface(self):
#### 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)
CoDEmanX
committed
# Selected vertices.
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]])
CoDEmanX
committed
# 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])
CoDEmanX
committed
#### 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)
CoDEmanX
committed
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)
CoDEmanX
committed
#### Check closed selections.
single_unselected_verts_and_neighbors = [] # List with groups of three verts, where the first element of the pair is the unselected vert of a closed selection and the other two elements are the selected neighbor verts (it will be useful to determine which selection chain the unselected vert belongs to, and determine the "middle-vertex")
CoDEmanX
committed
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
# To identify a "closed" selection (a selection that is a closed chain except for one vertex) find the vertex in common that have the edges connected to tips. If there is a vertex in common, that one is the unselected vert that closes the selection or is a "middle-vertex".
single_unselected_verts = []
for ed in edges_connected_to_tips:
for ed_b in edges_connected_to_tips:
if ed != ed_b:
if ed.vertices[0] == ed_b.vertices[0] and not self.main_object.data.vertices[ed.vertices[0]].select and ed.vertices[0] not in single_unselected_verts:
single_unselected_verts_and_neighbors.append([ed.vertices[0], ed.vertices[1], ed_b.vertices[1]]) # The second element is one of the tips of the selected vertices of the closed selection.
single_unselected_verts.append(ed.vertices[0])
break
elif ed.vertices[0] == ed_b.vertices[1] and not self.main_object.data.vertices[ed.vertices[0]].select and ed.vertices[0] not in single_unselected_verts:
single_unselected_verts_and_neighbors.append([ed.vertices[0], ed.vertices[1], ed_b.vertices[0]])
single_unselected_verts.append(ed.vertices[0])
break
elif ed.vertices[1] == ed_b.vertices[0] and not self.main_object.data.vertices[ed.vertices[1]].select and ed.vertices[1] not in single_unselected_verts:
single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[1]])
single_unselected_verts.append(ed.vertices[1])
break
elif ed.vertices[1] == ed_b.vertices[1] and not self.main_object.data.vertices[ed.vertices[1]].select and ed.vertices[1] not in single_unselected_verts:
single_unselected_verts_and_neighbors.append([ed.vertices[1], ed.vertices[0], ed_b.vertices[0]])
single_unselected_verts.append(ed.vertices[1])
break
CoDEmanX
committed
middle_vertex_idx = None
tips_to_discard_idx = []
# Check if there is a "middle-vertex", and get its index.
for i in range(0, len(single_unselected_verts_and_neighbors)):
actual_chain_verts = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, single_unselected_verts_and_neighbors[i][1], None, None)
CoDEmanX
committed
if single_unselected_verts_and_neighbors[i][2] != actual_chain_verts[len(actual_chain_verts) - 1].index:
middle_vertex_idx = single_unselected_verts_and_neighbors[i][0]
tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][1])
tips_to_discard_idx.append(single_unselected_verts_and_neighbors[i][2])
CoDEmanX
committed
#### 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, None)
CoDEmanX
committed
verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
CoDEmanX
committed
checked_v.append(v_chain[0].index)
checked_v.append(v_chain[len(v_chain) - 1].index)
CoDEmanX
committed
#### 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)
CoDEmanX
committed
#### Identify the type of selection made by the user.
if middle_vertex_idx != None:
if len(all_chains_tips_idx) == 4 and len(single_unselected_verts_and_neighbors) == 1: # If there are 4 tips (two selection chains), and there is only one single unselected vert (the middle vert).
selection_type = "TWO_CONNECTED"
else:
# The type of the selection was not identified, the script stops.
self.report({'WARNING'}, "The selection isn't valid.")
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.cleanup_on_interruption()
self.stopping_errors = True
CoDEmanX
committed
return{'CANCELLED'}
else:
if len(all_chains_tips_idx) == 2: # If there are 2 tips
selection_type = "SINGLE"
elif len(all_chains_tips_idx) == 4: # If there are 4 tips
selection_type = "TWO_NOT_CONNECTED"
elif len(all_chains_tips_idx) == 0:
if len(self.main_splines.data.splines) > 1:
selection_type = "NO_SELECTION"
else:
# If the selection was not identified and there is only one stroke, there's no possibility to build a surface, so the script is interrupted.
self.report({'WARNING'}, "The selection isn't valid.")
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.cleanup_on_interruption()
self.stopping_errors = True
CoDEmanX
committed
return{'CANCELLED'}
else:
# The type of the selection was not identified, the script stops.
self.report({'WARNING'}, "The selection isn't valid.")
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.cleanup_on_interruption()
CoDEmanX
committed
CoDEmanX
committed
CoDEmanX
committed
#### If the selection type is TWO_NOT_CONNECTED and there is only one stroke, stop the script.
if selection_type == "TWO_NOT_CONNECTED" and len(self.main_splines.data.splines) == 1:
self.report({'WARNING'}, "At least two strokes are needed when there are two not connected selections.")
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.cleanup_on_interruption()
self.stopping_errors = True
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_splines.name].select = True
bpy.context.scene.objects.active = bpy.context.scene.objects[self.main_splines.name]
CoDEmanX
committed
#### Enter editmode for the new curve (converted from grease pencil strokes), to smooth it out.
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')
CoDEmanX
committed
self.selection_U_exists = False
self.selection_U2_exists = False
self.selection_V_exists = False
self.selection_V2_exists = False
CoDEmanX
committed
self.selection_U_is_closed = False
self.selection_U2_is_closed = False
self.selection_V_is_closed = False
self.selection_V2_is_closed = False
CoDEmanX
committed
#### Define what vertices are at the tips of each selection and are not the middle-vertex.
if selection_type == "TWO_CONNECTED":
self.selection_U_exists = True
self.selection_V_exists = True
CoDEmanX
committed
closing_vert_U_idx = None
closing_vert_V_idx = None
closing_vert_U2_idx = None
closing_vert_V2_idx = None
CoDEmanX
committed
# Determine which selection is Selection-U and which is Selection-V.
points_A = []
points_B = []
points_first_stroke_tips = []
CoDEmanX
committed
points_A.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_parsed_idx[0]].co)
points_A.append(self.main_object.matrix_world * self.main_object.data.vertices[middle_vertex_idx].co)
CoDEmanX
committed
points_B.append(self.main_object.matrix_world * self.main_object.data.vertices[verts_tips_parsed_idx[1]].co)
points_B.append(self.main_object.matrix_world * self.main_object.data.vertices[middle_vertex_idx].co)
CoDEmanX
committed
points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[0].co)
points_first_stroke_tips.append(self.main_splines.data.splines[0].bezier_points[len(self.main_splines.data.splines[0].bezier_points) - 1].co)
CoDEmanX
committed
angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
CoDEmanX
committed
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]
CoDEmanX
committed
elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
first_sketched_point_first_stroke_co = self.main_splines.data.splines[0].bezier_points[0].co
last_sketched_point_first_stroke_co = self.main_splines.data.splines[0].bezier_points[len(self.main_splines.data.splines[0].bezier_points) - 1].co
first_sketched_point_last_stroke_co = self.main_splines.data.splines[len(self.main_splines.data.splines) - 1].bezier_points[0].co
if len(self.main_splines.data.splines) > 1:
first_sketched_point_second_stroke_co = self.main_splines.data.splines[1].bezier_points[0].co
last_sketched_point_second_stroke_co = self.main_splines.data.splines[1].bezier_points[len(self.main_splines.data.splines[1].bezier_points) - 1].co
CoDEmanX
committed
single_unselected_neighbors = [] # Only the neighbors of the single unselected verts.
for verts_neig_idx in single_unselected_verts_and_neighbors:
single_unselected_neighbors.append(verts_neig_idx[1])
single_unselected_neighbors.append(verts_neig_idx[2])
CoDEmanX
committed
all_chains_tips_and_middle_vert = []
for v_idx in all_chains_tips_idx:
if v_idx not in single_unselected_neighbors:
all_chains_tips_and_middle_vert.append(v_idx)
CoDEmanX
committed
all_chains_tips_and_middle_vert += single_unselected_verts
CoDEmanX
committed
all_participating_verts = all_chains_tips_and_middle_vert + all_verts_idx
CoDEmanX
committed
# The tip of the selected vertices nearest to the first point of the first sketched stroke.
nearest_tip_to_first_st_first_pt_idx, shortest_distance_to_first_stroke = self.shortest_distance(self.main_object, first_sketched_point_first_stroke_co, all_chains_tips_and_middle_vert)
# If the nearest tip is not from a closed selection, get the opposite tip vertex index.
if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx:
nearest_tip_to_first_st_first_pt_opposite_idx = self.opposite_tip(nearest_tip_to_first_st_first_pt_idx, verts_tips_same_chain_idx)
CoDEmanX
committed
# The tip of the selected vertices nearest to the last point of the first sketched stroke.
nearest_tip_to_first_st_last_pt_idx, temp_dist = self.shortest_distance(self.main_object, last_sketched_point_first_stroke_co, all_chains_tips_and_middle_vert)
CoDEmanX
committed
# The tip of the selected vertices nearest to the first point of the last sketched stroke.
nearest_tip_to_last_st_first_pt_idx, shortest_distance_to_last_stroke = self.shortest_distance(self.main_object, first_sketched_point_last_stroke_co, all_chains_tips_and_middle_vert)
CoDEmanX
committed
if len(self.main_splines.data.splines) > 1:
# The selected vertex nearest to the first point of the second sketched stroke. (This will be useful to determine the direction of the closed selection V when extruding along strokes)
nearest_vert_to_second_st_first_pt_idx, temp_dist = self.shortest_distance(self.main_object, first_sketched_point_second_stroke_co, all_verts_idx)
CoDEmanX
committed
# The selected vertex nearest to the first point of the second sketched stroke. (This will be useful to determine the direction of the closed selection V2 when extruding along strokes)
nearest_vert_to_second_st_last_pt_idx, temp_dist = self.shortest_distance(self.main_object, last_sketched_point_second_stroke_co, all_verts_idx)
CoDEmanX
committed
# 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.main_object.matrix_world * 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)).length
CoDEmanX
committed
average_edge_length = edges_sum / len(all_selected_edges_idx)
CoDEmanX
committed
# Get shortest distance from the first point of the last stroke to any participating vertex.
temp_idx, shortest_distance_to_last_stroke = self.shortest_distance(self.main_object, first_sketched_point_last_stroke_co, all_participating_verts)
CoDEmanX
committed
if shortest_distance_to_first_stroke < average_edge_length / 4 and shortest_distance_to_last_stroke < average_edge_length and len(self.main_splines.data.splines) > 1: # If the beginning of the first stroke is near enough, and its orientation difference with the first edge of the nearest selection chain is not too high, interpret things as an "extrude along strokes" instead of "extrude through strokes"
self.selection_U_exists = False
self.selection_V_exists = True
if nearest_tip_to_first_st_first_pt_idx not in single_unselected_verts or nearest_tip_to_first_st_first_pt_idx == middle_vertex_idx: # If the first selection is not closed.
self.selection_V_is_closed = False
first_neighbor_V_idx = None
closing_vert_U_idx = None
closing_vert_U2_idx = None
closing_vert_V_idx = None
closing_vert_V2_idx = None
CoDEmanX
committed
first_vert_V_idx = nearest_tip_to_first_st_first_pt_idx