Newer
Older
CoDEmanX
committed
vec_A = points_original[0] - points_original[1]
vec_B = points_target[0] - points_target[1]
# check for zero angles, not sure if it is a great fix
if vec_A.length != 0 and vec_B.length != 0:
angle = vec_A.angle(vec_B) / pi
edge_new_length = (Vector(verts_middle_position_co) - sp[1][i]).length
else:
angle = 0
edge_new_length = 0
CoDEmanX
committed
# If after moving the verts to the middle point, the segment doesn't stretch too much
if edge_new_length <= loop_segment_dist * 1.5 * \
self.join_stretch_factor and angle < 0.25 * self.join_stretch_factor:
CoDEmanX
committed
# Avoid joining when the actual loop must be merged with the original mesh
if not (self.selection_U_exists and i == 0) and \
not (self.selection_U2_exists and i == len(surface_splines_parsed[0]) - 1):
CoDEmanX
committed
# Change the coords of both verts to the middle position
surface_splines_parsed[0][i] = verts_middle_position_co
surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_middle_position_co
CoDEmanX
committed
# Delete object with control points and object from grease pencil conversion
bpy.ops.object.delete({"selected_objects": [ob_ctrl_pts]})
CoDEmanX
committed
bpy.ops.object.delete({"selected_objects": splines_U_objects})
CoDEmanX
committed
CoDEmanX
committed
all_surface_verts_co = []
for i in range(0, len(surface_splines_parsed)):
# Get coords of all verts and make a list with them
for pt_co in surface_splines_parsed[i]:
all_surface_verts_co.append(pt_co)
CoDEmanX
committed
all_surface_faces = []
for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
all_surface_faces.append(
[i + 1, i, i + len(surface_splines_parsed[0]),
i + len(surface_splines_parsed[0]) + 1]
)
# Build the mesh
surf_me_name = "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.collection.objects.link(ob_surface)
CoDEmanX
committed
# Select all the "unselected but participating" verts, from closed selection
# or double selections with middle-vertex, for later join with remove doubles
for v_idx in single_unselected_verts:
self.main_object.data.vertices[v_idx].select = True
CoDEmanX
committed
# Join the new mesh to the main object
bpy.context.view_layer.objects.active = self.main_object
CoDEmanX
committed
bpy.ops.object.join('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.mesh.remove_doubles('INVOKE_REGION_WIN', threshold=0.0001)
bpy.ops.mesh.normals_make_consistent('INVOKE_REGION_WIN', inside=False)
bpy.ops.mesh.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
CoDEmanX
committed
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
bsurfaces_props = bpy.context.scene.bsurfaces
self.main_object = bsurfaces_props.SURFSK_object_with_retopology
self.main_object.select_set(True)
bpy.context.view_layer.objects.active = self.main_object
CoDEmanX
committed
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode',
value='True, False, False')
CoDEmanX
committed
# Build splines from the "last saved splines".
last_saved_curve = bpy.data.curves.new('SURFSKIO_last_crv', 'CURVE')
self.main_splines = bpy.data.objects.new('SURFSKIO_last_crv', last_saved_curve)
bpy.context.collection.objects.link(self.main_splines)
CoDEmanX
committed
last_saved_curve.dimensions = "3D"
CoDEmanX
committed
for sp in self.last_strokes_splines_coords:
spline = self.main_splines.data.splines.new('BEZIER')
# less one because one point is added when the spline is created
spline.bezier_points.add(len(sp) - 1)
for p in range(0, len(sp)):
spline.bezier_points[p].co = [sp[p][0], sp[p][1], sp[p][2]]
CoDEmanX
committed
#bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.mode_set(mode='OBJECT')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
self.main_splines.select_set(True)
bpy.context.view_layer.objects.active = self.main_splines
CoDEmanX
committed
#bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.mode_set(mode='EDIT')
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
# Important to make it vector first and then automatic, otherwise the
# tips handles get too big and distort the shrinkwrap results later
bpy.ops.curve.handle_type_set(type='VECTOR')
bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type='AUTOMATIC')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
self.main_splines.name = "SURFSKIO_temp_strokes"
CoDEmanX
committed
if self.is_crosshatch:
strokes_for_crosshatch = True
strokes_for_rectangular_surface = False
else:
strokes_for_rectangular_surface = True
strokes_for_crosshatch = False
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.main_object
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
if strokes_for_rectangular_surface:
self.rectangular_surface()
elif strokes_for_crosshatch:
self.crosshatch_surface_execute()
CoDEmanX
committed
#bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.mode_set(mode='OBJECT')
if self.keep_strokes:
self.main_splines.name = "keep_strokes"
self.main_splines.data.bevel_depth = 0.001
if "keep_strokes_material" in bpy.data.materials :
self.main_splines.data.materials.append(bpy.data.materials["keep_strokes_material"])
else:
mat = bpy.data.materials.new("keep_strokes_material")
mat.diffuse_color = (1, 0, 0, 0)
mat.specular_color = (1, 0, 0)
mat.specular_intensity = 0.0
mat.roughness = 0.0
self.main_splines.data.materials.append(mat)
else:
bpy.ops.object.delete({"selected_objects": [self.main_splines]})
# Delete grease pencil strokes
if self.strokes_type == "GP_STROKES" and not self.stopping_errors:
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.data.layers.active.clear()
except:
pass
# Delete annotations
if self.strokes_type == "GP_ANNOTATION" and not self.stopping_errors:
try:
bpy.data.grease_pencils[0].layers.active.clear()
except:
pass
bsurfaces_props.SURFSK_edges_U = self.edges_U
bsurfaces_props.SURFSK_edges_V = self.edges_V
bsurfaces_props.SURFSK_cyclic_cross = self.cyclic_cross
bsurfaces_props.SURFSK_cyclic_follow = self.cyclic_follow
bsurfaces_props.SURFSK_automatic_join = self.automatic_join
bsurfaces_props.SURFSK_loops_on_strokes = self.loops_on_strokes
bsurfaces_props.SURFSK_keep_strokes = self.keep_strokes
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.main_object
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
CoDEmanX
committed
def invoke(self, context, event):
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
bsurfaces_props = bpy.context.scene.bsurfaces
self.cyclic_cross = bsurfaces_props.SURFSK_cyclic_cross
self.cyclic_follow = bsurfaces_props.SURFSK_cyclic_follow
self.automatic_join = bsurfaces_props.SURFSK_automatic_join
self.loops_on_strokes = bsurfaces_props.SURFSK_loops_on_strokes
self.keep_strokes = bsurfaces_props.SURFSK_keep_strokes
self.main_object = bsurfaces_props.SURFSK_object_with_retopology
self.main_object.select_set(True)
except:
self.report({'WARNING'}, "Specify the name of the object with retopology")
return{"CANCELLED"}
bpy.context.view_layer.objects.active = self.main_object
self.main_object_selected_verts_count = len([v for v in self.main_object.data.vertices if v.select])
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode',
value='True, False, False')
CoDEmanX
committed
#if self.loops_on_strokes:
# self.edges_V = 1
#else:
# self.edges_V = bsurfaces_props.SURFSK_edges_V
Spivak Vladimir (cwolf3d)
committed
self.edges_U = bsurfaces_props.SURFSK_edges_U
self.edges_V = bsurfaces_props.SURFSK_edges_V
CoDEmanX
committed
self.is_fill_faces = False
self.stopping_errors = False
self.last_strokes_splines_coords = []
CoDEmanX
committed
# Determine the type of the strokes
self.strokes_type = get_strokes_type(context)
CoDEmanX
committed
# Check if it will be used grease pencil strokes or curves
# If there are strokes to be used
if self.strokes_type == "GP_STROKES" or self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "GP_ANNOTATION":
if self.strokes_type == "GP_STROKES":
# Convert grease pencil strokes to curve
gp = bsurfaces_props.SURFSK_object_with_strokes
#bpy.ops.gpencil.convert(type='CURVE', use_link_strokes=False)
self.original_curve = conver_gpencil_to_curve(self, context, gp, 'GPensil')
# XXX gpencil.convert now keep org object as active/selected, *not* newly created curve!
# XXX This is far from perfect, but should work in most cases...
# self.original_curve = bpy.context.object
gplayer_prefix_translated = bpy.app.translations.pgettext_data('GP_Layer')
for ob in bpy.context.selected_objects:
if ob != bpy.context.view_layer.objects.active and \
ob.name.startswith((gplayer_prefix_translated, 'GP_Layer')):
self.original_curve = ob
self.using_external_curves = False
elif self.strokes_type == "GP_ANNOTATION":
# Convert grease pencil strokes to curve
gp = bpy.data.grease_pencils["Annotations"]
#bpy.ops.gpencil.convert(type='CURVE', use_link_strokes=False)
self.original_curve = conver_gpencil_to_curve(self, context, gp, 'Annotation')
Bastien Montagne
committed
# XXX gpencil.convert now keep org object as active/selected, *not* newly created curve!
# XXX This is far from perfect, but should work in most cases...
# self.original_curve = bpy.context.object
gplayer_prefix_translated = bpy.app.translations.pgettext_data('GP_Layer')
Bastien Montagne
committed
for ob in bpy.context.selected_objects:
if ob != bpy.context.view_layer.objects.active and \
ob.name.startswith((gplayer_prefix_translated, 'GP_Layer')):
Bastien Montagne
committed
self.original_curve = ob
self.using_external_curves = False
elif self.strokes_type == "EXTERNAL_CURVE":
self.original_curve = bsurfaces_props.SURFSK_object_with_strokes
self.using_external_curves = True
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Make sure there are no objects left from erroneous
# executions of this operator, with the reserved names used here
for o in bpy.data.objects:
if o.name.find("SURFSKIO_") != -1:
bpy.ops.object.delete({"selected_objects": [o]})
CoDEmanX
committed
#bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.original_curve
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
CoDEmanX
committed
self.temporary_curve = bpy.context.view_layer.objects.active
CoDEmanX
committed
# Deselect all points of the curve
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
# Delete splines with only a single isolated point
for i in range(len(self.temporary_curve.data.splines)):
sp = self.temporary_curve.data.splines[i]
CoDEmanX
committed
if len(sp.bezier_points) == 1:
sp.bezier_points[0].select_control_point = True
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.temporary_curve
CoDEmanX
committed
# Set a minimum number of points for crosshatch
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
# Check if the number of points of each curve has at least the number of points
# of minimum_points_num, which is a bit more than the face-loops limit.
# If not, subdivide to reach at least that number of points
for i in range(len(self.temporary_curve.data.splines)):
sp = self.temporary_curve.data.splines[i]
CoDEmanX
committed
if len(sp.bezier_points) < minimum_points_num:
for bp in sp.bezier_points:
bp.select_control_point = True
CoDEmanX
committed
if (len(sp.bezier_points) - 1) != 0:
# Formula to get the number of cuts that will make a curve
# of N number of points have near to "minimum_points_num"
# points, when subdividing with this number of cuts
subdivide_cuts = int(
(minimum_points_num - len(sp.bezier_points)) /
(len(sp.bezier_points) - 1)
) + 1
CoDEmanX
committed
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=subdivide_cuts)
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Detect if the strokes are a crosshatch and do it if it is
self.crosshatch_surface_invoke(self.temporary_curve)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.temporary_curve
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Set a minimum number of points for rectangular surfaces
CoDEmanX
committed
# Check if the number of points of each curve has at least the number of points
# of minimum_points_num, which is a bit more than the face-loops limit.
# If not, subdivide to reach at least that number of points
for i in range(len(self.temporary_curve.data.splines)):
sp = self.temporary_curve.data.splines[i]
CoDEmanX
committed
if len(sp.bezier_points) < minimum_points_num:
for bp in sp.bezier_points:
bp.select_control_point = True
CoDEmanX
committed
if (len(sp.bezier_points) - 1) != 0:
# Formula to get the number of cuts that will make a curve of
# N number of points have near to "minimum_points_num" points,
# when subdividing with this number of cuts
subdivide_cuts = int(
(minimum_points_num - len(sp.bezier_points)) /
(len(sp.bezier_points) - 1)
) + 1
CoDEmanX
committed
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=subdivide_cuts)
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Save coordinates of the actual strokes (as the "last saved splines")
for sp_idx in range(len(self.temporary_curve.data.splines)):
self.last_strokes_splines_coords.append([])
for bp_idx in range(len(self.temporary_curve.data.splines[sp_idx].bezier_points)):
coords = self.temporary_curve.matrix_world @ \
self.temporary_curve.data.splines[sp_idx].bezier_points[bp_idx].co
self.last_strokes_splines_coords[sp_idx].append([coords[0], coords[1], coords[2]])
CoDEmanX
committed
# Check for cyclic splines, put the first and last points in the middle of their actual positions
for sp_idx in range(len(self.temporary_curve.data.splines)):
if self.temporary_curve.data.splines[sp_idx].use_cyclic_u is True:
first_p_co = self.last_strokes_splines_coords[sp_idx][0]
last_p_co = self.last_strokes_splines_coords[sp_idx][
len(self.last_strokes_splines_coords[sp_idx]) - 1
]
target_co = [
(first_p_co[0] + last_p_co[0]) / 2,
(first_p_co[1] + last_p_co[1]) / 2,
(first_p_co[2] + last_p_co[2]) / 2
]
CoDEmanX
committed
self.last_strokes_splines_coords[sp_idx][0] = target_co
self.last_strokes_splines_coords[sp_idx][
len(self.last_strokes_splines_coords[sp_idx]) - 1
] = target_co
tuple(self.last_strokes_splines_coords)
CoDEmanX
committed
# Estimation of the average length of the segments between
# each point of the grease pencil strokes.
# Will be useful to determine whether a curve should be made "Cyclic"
segments_lengths_sum = 0
segments_count = 0
random_spline = self.temporary_curve.data.splines[0].bezier_points
for i in range(0, len(random_spline)):
if i != 0 and len(random_spline) - 1 >= i:
segments_lengths_sum += (random_spline[i - 1].co - random_spline[i].co).length
segments_count += 1
CoDEmanX
committed
self.average_gp_segment_length = segments_lengths_sum / segments_count
CoDEmanX
committed
# Delete temporary strokes curve object
bpy.ops.object.delete({"selected_objects": [self.temporary_curve]})
# If "Keep strokes" option is not active, delete original strokes curve object
if not self.stopping_errors or self.is_crosshatch:
bpy.ops.object.delete({"selected_objects": [self.original_curve]})
CoDEmanX
committed
if self.strokes_type == "GP_STROKES" and not self.stopping_errors:
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.data.layers.active.clear()
except:
pass
# Delete grease pencil strokes
if self.strokes_type == "GP_ANNOTATION" and not self.stopping_errors:
try:
bpy.data.grease_pencils[0].layers.active.clear()
except:
pass
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
self.main_object.select_set(True)
bpy.context.view_layer.objects.active = self.main_object
# Set again since "execute()" will turn it again to its initial value
self.execute(context)
CoDEmanX
committed
if not self.stopping_errors:
return {"FINISHED"}
else:
return{"CANCELLED"}
CoDEmanX
committed
elif self.strokes_type == "SELECTION_ALONE":
self.is_fill_faces = True
created_faces_count = self.fill_with_faces(self.main_object)
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.report({'WARNING'}, "There aren't any strokes attached to the object")
return {"CANCELLED"}
else:
return {"FINISHED"}
CoDEmanX
committed
if self.strokes_type == "EXTERNAL_NO_CURVE":
self.report({'WARNING'}, "The secondary object is not a Curve.")
return{"CANCELLED"}
CoDEmanX
committed
elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
return{"CANCELLED"}
CoDEmanX
committed
elif self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION" or \
self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
self.report({'WARNING'}, "It's needed at least one stroke and one selection, or two strokes.")
return{"CANCELLED"}
CoDEmanX
committed
elif self.strokes_type == "NO_STROKES":
self.report({'WARNING'}, "There aren't any strokes attached to the object")
CoDEmanX
committed
elif self.strokes_type == "CURVE_WITH_NON_BEZIER_SPLINES":
self.report({'WARNING'}, "All splines must be Bezier.")
return{"CANCELLED"}
CoDEmanX
committed
else:
return{"CANCELLED"}
# Edit strokes operator
class GPENCIL_OT_SURFSK_init(Operator):
bl_idname = "gpencil.surfsk_init"
bl_label = "Bsurfaces initialize"
bl_description = "Bsurfaces initialize"
active_object: PointerProperty(type=bpy.types.Object)
def execute(self, context):
bs = bpy.context.scene.bsurfaces
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
if bs.SURFSK_object_with_retopology == None:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
mesh = bpy.data.meshes.new('BSurfaceMesh')
mesh_object = object_utils.object_data_add(context, mesh, operator=None)
mesh_object.select_set(True)
mesh_object.show_all_edges = True
mesh_object.show_in_front = True
mesh_object.display_type = 'SOLID'
mesh_object.show_wire = True
bpy.context.view_layer.objects.active = mesh_object
bpy.ops.object.modifier_add(type='SHRINKWRAP')
modifier = mesh_object.modifiers["Shrinkwrap"]
if self.active_object is not None:
modifier.target = self.active_object
modifier.wrap_method = 'TARGET_PROJECT'
modifier.wrap_mode = 'OUTSIDE_SURFACE'
#modifier.offset = 0.05
bpy.context.scene.bsurfaces.SURFSK_object_with_retopology = mesh_object
Spivak Vladimir (cwolf3d)
committed
if context.scene.bsurfaces.SURFSK_guide == 'GPencil' and bs.SURFSK_object_with_strokes == None:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.gpencil_add(radius=1.0, align='WORLD', location=(0.0, 0.0, 0.0), rotation=(0.0, 0.0, 0.0), type='EMPTY')
bpy.context.scene.tool_settings.gpencil_stroke_placement_view3d = 'SURFACE'
gpencil_object = bpy.context.scene.objects[bpy.context.scene.objects[-1].name]
gpencil_object.select_set(True)
bpy.context.view_layer.objects.active = gpencil_object
bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes = gpencil_object
gpencil_object.data.stroke_depth_order = '3D'
Spivak Vladimir (cwolf3d)
committed
if context.scene.bsurfaces.SURFSK_guide == 'Annotation':
bpy.ops.wm.tool_set_by_id(name="builtin.annotate")
bpy.context.scene.tool_settings.annotation_stroke_placement_view3d = 'SURFACE'
return {"FINISHED"}
def invoke(self, context, event):
if bpy.context.active_object:
self.active_object = bpy.context.active_object
else:
self.active_object = None
self.execute(context)
return {"FINISHED"}
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
# Edit strokes operator
class GPENCIL_OT_SURFSK_add_modifiers(Operator):
bl_idname = "gpencil.surfsk_add_modifiers"
bl_label = "Add Mirror and others modifiers"
bl_description = "Add modifiers: Mirror, Shrinkwrap, Subdivision, Solidify "
active_object: PointerProperty(type=bpy.types.Object)
def execute(self, context):
bs = bpy.context.scene.bsurfaces
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT')
if bs.SURFSK_object_with_retopology == None:
self.report({'ERROR_INVALID_INPUT'}, "Please select Mesh of BSurface or click Initialize")
else:
mesh_object = bs.SURFSK_object_with_retopology
try:
mesh_object.select_set(True)
except:
self.report({'ERROR_INVALID_INPUT'}, "Mesh of BSurface does not exist")
return {"CANCEL"}
bpy.context.view_layer.objects.active = mesh_object
try:
shrinkwrap = mesh_object.modifiers["Shrinkwrap"]
if self.active_object is not None and self.active_object != mesh_object:
shrinkwrap.target = self.active_object
shrinkwrap.wrap_method = 'TARGET_PROJECT'
shrinkwrap.wrap_mode = 'OUTSIDE_SURFACE'
except:
bpy.ops.object.modifier_add(type='SHRINKWRAP')
shrinkwrap = mesh_object.modifiers["Shrinkwrap"]
if self.active_object is not None and self.active_object != mesh_object:
shrinkwrap.target = self.active_object
shrinkwrap.wrap_method = 'TARGET_PROJECT'
shrinkwrap.wrap_mode = 'OUTSIDE_SURFACE'
try:
mirror = mesh_object.modifiers["Mirror"]
mirror.use_clip = True
except:
bpy.ops.object.modifier_add(type='MIRROR')
mirror = mesh_object.modifiers["Mirror"]
mirror.use_clip = True
try:
subsurf = mesh_object.modifiers["Subdivision"]
except:
bpy.ops.object.modifier_add(type='SUBSURF')
subsurf = mesh_object.modifiers["Subdivision"]
try:
solidify = mesh_object.modifiers["Solidify"]
solidify.thickness = 0.01
except:
bpy.ops.object.modifier_add(type='SOLIDIFY')
solidify = mesh_object.modifiers["Solidify"]
solidify.thickness = 0.01
return {"FINISHED"}
def invoke(self, context, event):
if bpy.context.active_object:
self.active_object = bpy.context.active_object
else:
self.active_object = None
self.execute(context)
return {"FINISHED"}
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
# Edit surface operator
class GPENCIL_OT_SURFSK_edit_surface(Operator):
bl_idname = "gpencil.surfsk_edit_surface"
bl_label = "Bsurfaces edit surface"
bl_description = "Edit surface mesh"
def execute(self, context):
bpy.context.scene.bsurfaces.SURFSK_object_with_retopology.select_set(True)
bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_object_with_retopology
bpy.ops.object.mode_set(mode='EDIT')
def invoke(self, context, event):
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_retopology.select_set(True)
except:
self.report({'WARNING'}, "Specify the name of the object with retopology")
return{"CANCELLED"}
self.execute(context)
return {"FINISHED"}
# Add strokes operator
class GPENCIL_OT_SURFSK_add_strokes(Operator):
bl_idname = "gpencil.surfsk_add_strokes"
bl_label = "Bsurfaces add strokes"
bl_description = "Add the grease pencil strokes"
def execute(self, context):
# Determine the type of the strokes
self.strokes_type = get_strokes_type(context)
# Check if strokes are grease pencil strokes or a curves object
selected_objs = bpy.context.selected_objects
if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
for ob in selected_objs:
if ob != bpy.context.view_layer.objects.active:
curve_ob = ob
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
curve_ob.select_set(True)
bpy.context.view_layer.objects.active = curve_ob
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.select_set(True)
bpy.context.view_layer.objects.active = bpy.context.scene.bsurfaces.SURFSK_object_with_strokes
bpy.ops.object.mode_set(mode='PAINT_GPENCIL')
def invoke(self, context, event):
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.select_set(True)
except:
self.report({'WARNING'}, "Specify the name of the object with strokes")
return{"CANCELLED"}
self.execute(context)
return {"FINISHED"}
# Edit strokes operator
class GPENCIL_OT_SURFSK_edit_strokes(Operator):
bl_idname = "gpencil.surfsk_edit_strokes"
bl_label = "Bsurfaces edit strokes"
bl_description = "Edit the grease pencil strokes or curves used"
CoDEmanX
committed
# Determine the type of the strokes
self.strokes_type = get_strokes_type(context)
# Check if strokes are grease pencil strokes or a curves object
selected_objs = bpy.context.selected_objects
if self.strokes_type == "EXTERNAL_CURVE" or self.strokes_type == "SINGLE_CURVE_STROKE_NO_SELECTION":
for ob in selected_objs:
if ob != bpy.context.view_layer.objects.active:
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = curve_ob
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
elif self.strokes_type == "GP_STROKES" or self.strokes_type == "SINGLE_GP_STROKE_NO_SELECTION":
# Convert grease pencil strokes to curve
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
#bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
gp = bpy.context.scene.bsurfaces.SURFSK_object_with_strokes
conver_gpencil_to_curve(self, context, gp, 'GPensil')
for ob in bpy.context.selected_objects:
if ob != bpy.context.view_layer.objects.active and ob.name.startswith("GP_Layer"):
ob_gp_strokes = ob
CoDEmanX
committed
ob_gp_strokes = bpy.context.object
CoDEmanX
committed
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.data.layers.active.clear()
except:
pass
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = ob_gp_strokes
CoDEmanX
committed
curve_crv = ob_gp_strokes.data
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.spline_type_set('INVOKE_REGION_WIN', type="BEZIER")
bpy.ops.curve.handle_type_set('INVOKE_REGION_WIN', type="AUTOMATIC")
#curve_crv.show_handles = False
#curve_crv.show_normal_face = False
CoDEmanX
committed
elif self.strokes_type == "EXTERNAL_NO_CURVE":
self.report({'WARNING'}, "The secondary object is not a Curve.")
return{"CANCELLED"}
elif self.strokes_type == "MORE_THAN_ONE_EXTERNAL":
self.report({'WARNING'}, "There shouldn't be more than one secondary object selected.")
return{"CANCELLED"}
elif self.strokes_type == "NO_STROKES" or self.strokes_type == "SELECTION_ALONE":
self.report({'WARNING'}, "There aren't any strokes attached to the object")
CoDEmanX
committed
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.select_set(True)
except:
self.report({'WARNING'}, "Specify the name of the object with strokes")
return{"CANCELLED"}
CoDEmanX
committed
# Add annotation
class GPENCIL_OT_SURFSK_add_annotation(Operator):
bl_idname = "gpencil.surfsk_add_annotation"
bl_label = "Bsurfaces add annotation"
bl_description = "Add annotation"
def execute(self, context):
bpy.ops.wm.tool_set_by_id(name="builtin.annotate")
bpy.context.scene.tool_settings.annotation_stroke_placement_view3d = 'SURFACE'
return{"FINISHED"}
def invoke(self, context, event):
self.execute(context)
return {"FINISHED"}
class CURVE_OT_SURFSK_reorder_splines(Operator):
bl_idname = "curve.surfsk_reorder_splines"
bl_label = "Bsurfaces reorder splines"
bl_description = "Defines the order of the splines by using grease pencil strokes"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
def execute(self, context):
objects_to_delete = []
# Convert grease pencil strokes to curve.
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
for ob in bpy.context.selected_objects:
if ob != bpy.context.view_layer.objects.active and ob.name.startswith("GP_Layer"):
GP_strokes_curve = ob
CoDEmanX
committed
# GP_strokes_curve = bpy.context.object
objects_to_delete.append(GP_strokes_curve)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = GP_strokes_curve
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='SELECT')
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=100)
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
GP_strokes_mesh = bpy.context.object
objects_to_delete.append(GP_strokes_mesh)
CoDEmanX
committed
GP_strokes_mesh.data.resolution_u = 1
bpy.ops.object.convert(target='MESH', keep_original=False)
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.main_curve
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
curves_duplicate_1 = bpy.context.object
objects_to_delete.append(curves_duplicate_1)
CoDEmanX
committed
CoDEmanX
committed
# Some iterations since the subdivision operator
# has a limit of 100 subdivisions per iteration
for x in range(round(minimum_points_num / 100)):
# Check if the number of points of each curve has at least the number of points
# of minimum_points_num. If not, subdivide to reach at least that number of points
for i in range(len(curves_duplicate_1.data.splines)):
sp = curves_duplicate_1.data.splines[i]
CoDEmanX
committed
if len(sp.bezier_points) < minimum_points_num:
for bp in sp.bezier_points:
bp.select_control_point = True
CoDEmanX
committed
if (len(sp.bezier_points) - 1) != 0:
# Formula to get the number of cuts that will make a curve of N
# number of points have near to "minimum_points_num" points,
# when subdividing with this number of cuts
subdivide_cuts = int(
(minimum_points_num - len(sp.bezier_points)) /
(len(sp.bezier_points) - 1)
) + 1
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.subdivide('INVOKE_REGION_WIN', number_cuts=subdivide_cuts)
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
curves_duplicate_2 = bpy.context.object
objects_to_delete.append(curves_duplicate_2)
CoDEmanX
committed
# Duplicate the duplicate and add Shrinkwrap to it, with the grease pencil strokes curve as target
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = curves_duplicate_2
CoDEmanX
committed
bpy.ops.object.modifier_add('INVOKE_REGION_WIN', type='SHRINKWRAP')
curves_duplicate_2.modifiers["Shrinkwrap"].wrap_method = "NEAREST_VERTEX"
curves_duplicate_2.modifiers["Shrinkwrap"].target = GP_strokes_mesh
bpy.ops.object.modifier_apply('INVOKE_REGION_WIN', apply_as='DATA', modifier='Shrinkwrap')
CoDEmanX
committed
# Get the distance of each vert from its original position to its position with Shrinkwrap
nearest_points_coords = {}
for st_idx in range(len(curves_duplicate_1.data.splines)):
for bp_idx in range(len(curves_duplicate_1.data.splines[st_idx].bezier_points)):
bp_1_co = curves_duplicate_1.matrix_world @ \
curves_duplicate_1.data.splines[st_idx].bezier_points[bp_idx].co
bp_2_co = curves_duplicate_2.matrix_world @ \
curves_duplicate_2.data.splines[st_idx].bezier_points[bp_idx].co
CoDEmanX
committed
if bp_idx == 0:
shortest_dist = (bp_1_co - bp_2_co).length
nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0],
"%.4f" % bp_2_co[1],
"%.4f" % bp_2_co[2])
CoDEmanX
committed
dist = (bp_1_co - bp_2_co).length
CoDEmanX
committed
nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0],
"%.4f" % bp_2_co[1],
"%.4f" % bp_2_co[2])
CoDEmanX
committed
# Get all coords of GP strokes points, for comparison
GP_strokes_coords = []
for st_idx in range(len(GP_strokes_curve.data.splines)):
GP_strokes_coords.append(
[("%.4f" % x if "%.4f" % x != "-0.00" else "0.00",
"%.4f" % y if "%.4f" % y != "-0.00" else "0.00",
"%.4f" % z if "%.4f" % z != "-0.00" else "0.00") for
x, y, z in [bp.co for bp in GP_strokes_curve.data.splines[st_idx].bezier_points]]
)
# Check the point of the GP strokes with the same coords as
# the nearest points of the curves (with shrinkwrap)
# Dictionary with GP stroke index as index, and a list as value.
# The list has as index the point index of the GP stroke
# nearest to the spline, and as value the spline index
GP_connection_points = {}
for gp_st_idx in range(len(GP_strokes_coords)):
GPvert_spline_relationship = {}
CoDEmanX
committed
for splines_st_idx in range(len(nearest_points_coords)):
if nearest_points_coords[splines_st_idx] in GP_strokes_coords[gp_st_idx]:
GPvert_spline_relationship[
GP_strokes_coords[gp_st_idx].index(nearest_points_coords[splines_st_idx])
] = splines_st_idx
CoDEmanX
committed
GP_connection_points[gp_st_idx] = GPvert_spline_relationship
CoDEmanX
committed
splines_new_order = []
for i in GP_connection_points:
dict_keys = sorted(GP_connection_points[i].keys()) # Sort dictionaries by key
CoDEmanX
committed
for k in dict_keys:
splines_new_order.append(GP_connection_points[i][k])
CoDEmanX
committed
curve_original_name = self.main_curve.name
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.context.view_layer.objects.active = self.main_curve
CoDEmanX
committed
self.main_curve.name = "SURFSKIO_CRV_ORD"
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
for sp_idx in range(len(self.main_curve.data.splines)):
self.main_curve.data.splines[0].bezier_points[0].select_control_point = True
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.curve.separate('EXEC_REGION_WIN')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Get the names of the separated splines objects in the original order
splines_unordered = {}
for o in bpy.data.objects:
if o.name.find("SURFSKIO_CRV_ORD") != -1:
spline_order_string = o.name.partition(".")[2]
CoDEmanX
committed
if spline_order_string != "" and int(spline_order_string) > 0:
spline_order_index = int(spline_order_string) - 1
splines_unordered[spline_order_index] = o.name
CoDEmanX
committed
# Join all splines objects in final order
for order_idx in splines_new_order:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[splines_unordered[order_idx]].select_set(True)
bpy.data.objects["SURFSKIO_CRV_ORD"].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects["SURFSKIO_CRV_ORD"]
CoDEmanX
committed
bpy.ops.object.join('INVOKE_REGION_WIN')
CoDEmanX
committed
# Go back to the original name of the curves object.
bpy.context.object.name = curve_original_name
CoDEmanX
committed
bpy.ops.object.delete({"selected_objects": objects_to_delete})
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[curve_original_name].select_set(True)
bpy.context.view_layer.objects.active = bpy.data.objects[curve_original_name]
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
try:
bpy.context.scene.bsurfaces.SURFSK_object_with_strokes.data.layers.active.clear()
except:
pass
CoDEmanX
committed
CoDEmanX
committed
self.main_curve = bpy.context.object
there_are_GP_strokes = False
# Get the active grease pencil layer
strokes_num = len(self.main_curve.grease_pencil.layers.active.active_frame.strokes)
CoDEmanX
committed
if strokes_num > 0:
there_are_GP_strokes = True
except:
pass
CoDEmanX
committed
if there_are_GP_strokes:
self.execute(context)
self.report({'INFO'}, "Splines have been reordered")