Newer
Older
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.scene.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
ob_surface.select = True
self.main_object.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')
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
bpy.context.user_preferences.edit.use_global_undo = False
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.scene.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')
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.data.objects[self.main_splines.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
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.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.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')
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.data.objects[self.main_splines.name]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
CoDEmanX
committed
CoDEmanX
committed
def invoke(self, context, event):
self.initial_global_undo_state = bpy.context.user_preferences.edit.use_global_undo
CoDEmanX
committed
self.main_object = bpy.context.scene.objects.active
self.main_object_selected_verts_count = int(self.main_object.data.total_vert_sel)
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = False
bpy.ops.wm.context_set_value(data_path='tool_settings.mesh_select_mode',
value='True, False, False')
CoDEmanX
committed
# Out Edit mode and In again to make sure the actual mesh selections are being taken
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
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
CoDEmanX
committed
CoDEmanX
committed
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(self.main_object)
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":
if self.strokes_type == "GP_STROKES":
# Convert grease pencil strokes to curve
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
Bastien Montagne
committed
bpy.ops.gpencil.convert('INVOKE_REGION_WIN', type='CURVE', use_link_strokes=False)
# 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
Bastien Montagne
committed
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
self.original_curve = ob
self.using_external_curves = False
elif self.strokes_type == "EXTERNAL_CURVE":
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active:
self.original_curve = ob
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.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[o.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[o.name]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.original_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
CoDEmanX
committed
bpy.ops.object.duplicate('INVOKE_REGION_WIN')
CoDEmanX
committed
self.temporary_curve = bpy.context.scene.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.data.objects[self.temporary_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
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 ponts
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
if not self.is_crosshatch:
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.temporary_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
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 ponts
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.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.temporary_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.temporary_curve.name]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
# Set again since "execute()" will turn it again to its initial value
bpy.context.user_preferences.edit.use_global_undo = False
CoDEmanX
committed
# If "Keep strokes" option is not active, delete original strokes curve object
if (not self.stopping_errors and not self.keep_strokes) or self.is_crosshatch:
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.original_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.original_curve.name]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
if self.strokes_type == "GP_STROKES" and not self.stopping_errors:
bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
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')
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
self.report({'WARNING'}, "There aren't any strokes attatched to the object")
return {"CANCELLED"}
else:
return {"FINISHED"}
CoDEmanX
committed
bpy.context.user_preferences.edit.use_global_undo = self.initial_global_undo_state
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 attatched 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_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(self.main_object)
# 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.scene.objects.active:
curve_ob = ob
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[curve_ob.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[curve_ob.name]
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)
for ob in bpy.context.selected_objects:
if ob != bpy.context.scene.objects.active and ob.name.startswith("GP_Layer"):
ob_gp_strokes = ob
CoDEmanX
committed
# ob_gp_strokes = bpy.context.object
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[self.main_object.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
CoDEmanX
committed
bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[ob_gp_strokes.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
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")
bpy.data.curves[curve_crv.name].show_handles = False
bpy.data.curves[curve_crv.name].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 attatched to the object")
CoDEmanX
committed
self.main_object = bpy.context.object
self.execute(context)
CoDEmanX
committed
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.scene.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.data.objects[GP_strokes_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[GP_strokes_curve.name]
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.data.objects[self.main_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
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 ponts
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.data.objects[curves_duplicate_2.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[curves_duplicate_2.name]
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.data.objects[self.main_curve.name].select = True
bpy.context.scene.objects.active = bpy.data.objects[self.main_curve.name]
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 = True
bpy.data.objects["SURFSKIO_CRV_ORD"].select = True
bpy.context.scene.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
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]
CoDEmanX
committed
CoDEmanX
committed
bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
bpy.data.objects[curve_original_name].select = True
bpy.context.scene.objects.active = bpy.data.objects[curve_original_name]
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
bpy.ops.gpencil.active_frame_delete('INVOKE_REGION_WIN')
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")
self.report({'WARNING'}, "Draw grease pencil strokes to connect splines")
CoDEmanX
committed
CoDEmanX
committed
class CURVE_OT_SURFSK_first_points(Operator):
bl_idname = "curve.surfsk_first_points"
bl_label = "Bsurfaces set first points"
bl_description = "Set the selected points as the first point of each spline"
bl_options = {'REGISTER', 'UNDO'}
CoDEmanX
committed
CoDEmanX
committed
# Check non-cyclic splines to invert
for i in range(len(self.main_curve.data.splines)):
b_points = self.main_curve.data.splines[i].bezier_points
CoDEmanX
committed
if i not in self.cyclic_splines: # Only for non-cyclic splines
if b_points[len(b_points) - 1].select_control_point:
splines_to_invert.append(i)
CoDEmanX
committed
# Reorder points of cyclic splines, and set all handles to "Automatic"
CoDEmanX
committed
cyclic_splines_new_first_pt = {}
for i in self.cyclic_splines:
sp = self.main_curve.data.splines[i]
CoDEmanX
committed
for t in range(len(sp.bezier_points)):
bp = sp.bezier_points[t]
if bp.select_control_point or bp.select_right_handle or bp.select_left_handle:
cyclic_splines_new_first_pt[i] = t
break # To take only one if there are more
CoDEmanX
committed
for spline_idx in cyclic_splines_new_first_pt:
sp = self.main_curve.data.splines[spline_idx]
CoDEmanX
committed
spline_old_coords = []
for bp_old in sp.bezier_points:
coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
CoDEmanX
committed
left_handle_type = str(bp_old.handle_left_type)
left_handle_length = float(bp_old.handle_left.length)
left_handle_xyz = (
float(bp_old.handle_left.x),
float(bp_old.handle_left.y),
float(bp_old.handle_left.z)
)
right_handle_type = str(bp_old.handle_right_type)
right_handle_length = float(bp_old.handle_right.length)
right_handle_xyz = (
float(bp_old.handle_right.x),
float(bp_old.handle_right.y),
float(bp_old.handle_right.z)
)
spline_old_coords.append(
[coords, left_handle_type,
right_handle_type, left_handle_length,
right_handle_length, left_handle_xyz,
right_handle_xyz]
)
CoDEmanX
committed
for t in range(len(sp.bezier_points)):
bp = sp.bezier_points
CoDEmanX
committed
if t + cyclic_splines_new_first_pt[spline_idx] + 1 <= len(bp) - 1:
new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1
else:
new_index = t + cyclic_splines_new_first_pt[spline_idx] + 1 - len(bp)
CoDEmanX
committed
bp[t].co = Vector(spline_old_coords[new_index][0])
CoDEmanX
committed
bp[t].handle_left.length = spline_old_coords[new_index][3]
bp[t].handle_right.length = spline_old_coords[new_index][4]
CoDEmanX
committed
bp[t].handle_left_type = "FREE"
bp[t].handle_right_type = "FREE"
CoDEmanX
committed
bp[t].handle_left.x = spline_old_coords[new_index][5][0]
bp[t].handle_left.y = spline_old_coords[new_index][5][1]
bp[t].handle_left.z = spline_old_coords[new_index][5][2]
CoDEmanX
committed
bp[t].handle_right.x = spline_old_coords[new_index][6][0]
bp[t].handle_right.y = spline_old_coords[new_index][6][1]
bp[t].handle_right.z = spline_old_coords[new_index][6][2]
CoDEmanX
committed
bp[t].handle_left_type = spline_old_coords[new_index][1]
bp[t].handle_right_type = spline_old_coords[new_index][2]
CoDEmanX
committed
# Invert the non-cyclic splines designated above
for i in range(len(splines_to_invert)):
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
self.main_curve.data.splines[splines_to_invert[i]].bezier_points[0].select_control_point = True
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
bpy.ops.curve.switch_direction()
CoDEmanX
committed
bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
CoDEmanX
committed
# Keep selected the first vert of each spline
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
for i in range(len(self.main_curve.data.splines)):
if not self.main_curve.data.splines[i].use_cyclic_u:
bp = self.main_curve.data.splines[i].bezier_points[0]
else:
bp = self.main_curve.data.splines[i].bezier_points[
len(self.main_curve.data.splines[i].bezier_points) - 1
]
CoDEmanX
committed
bp.select_control_point = True
bp.select_right_handle = True
bp.select_left_handle = True
CoDEmanX
committed
bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
CoDEmanX
committed
CoDEmanX
committed
self.main_curve = bpy.context.object
CoDEmanX
committed
# Check if all curves are Bezier, and detect which ones are cyclic
self.cyclic_splines = []
for i in range(len(self.main_curve.data.splines)):
if self.main_curve.data.splines[i].type != "BEZIER":
self.report({'WARNING'}, "All splines must be Bezier type")
CoDEmanX
committed
return {'CANCELLED'}
else:
if self.main_curve.data.splines[i].use_cyclic_u:
self.cyclic_splines.append(i)
CoDEmanX
committed
self.report({'INFO'}, "First points have been set")
CoDEmanX
committed
Campbell Barton
committed
return {'FINISHED'}
CoDEmanX
committed
# Add-ons Preferences Update Panel
# Define Panel classes for updating
panels = (
VIEW3D_PT_tools_SURFSK_mesh,
VIEW3D_PT_tools_SURFSK_curve,
)
message = "Bsurfaces GPL Edition: Updating Panel locations has failed"
for panel in panels:
if "bl_rna" in panel.__dict__:
bpy.utils.unregister_class(panel)
for panel in panels:
panel.bl_category = context.user_preferences.addons[__name__].preferences.category
bpy.utils.register_class(panel)
except Exception as e:
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
class BsurfPreferences(AddonPreferences):
# this must match the addon name, use '__package__'
# when defining this in a submodule of a python package.
bl_idname = __name__
CoDEmanX
committed
name="Tab Category",
description="Choose a name for the category of the panel",
default="Tools",
def draw(self, context):
layout = self.layout
row = layout.row()
col = row.column()
col.label(text="Tab Category:")
col.prop(self, "category", text="")
CoDEmanX
committed
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
# Properties
class BsurfacesProps(PropertyGroup):
SURFSK_cyclic_cross = BoolProperty(
name="Cyclic Cross",
description="Make cyclic the face-loops crossing the strokes",
default=False
)
SURFSK_cyclic_follow = BoolProperty(
name="Cyclic Follow",
description="Make cyclic the face-loops following the strokes",
default=False
)
SURFSK_keep_strokes = BoolProperty(
name="Keep strokes",
description="Keeps the sketched strokes or curves after adding the surface",
default=False
)
SURFSK_automatic_join = BoolProperty(
name="Automatic join",
description="Join automatically vertices of either surfaces "
"generated by crosshatching, or from the borders of closed shapes",
default=True
)
SURFSK_loops_on_strokes = BoolProperty(
name="Loops on strokes",
description="Make the loops match the paths of the strokes",
default=True
)
SURFSK_precision = IntProperty(
name="Precision",
description="Precision level of the surface calculation",
default=2,
min=1,
max=100
)
classes = (
VIEW3D_PT_tools_SURFSK_mesh,
VIEW3D_PT_tools_SURFSK_curve,
GPENCIL_OT_SURFSK_add_surface,
GPENCIL_OT_SURFSK_edit_strokes,
CURVE_OT_SURFSK_reorder_splines,
CURVE_OT_SURFSK_first_points,
BsurfPreferences,
BsurfacesProps,
)
CoDEmanX
committed
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.bsurfaces = PointerProperty(type=BsurfacesProps)
update_panel(None, bpy.context)
CoDEmanX
committed
for cls in classes:
bpy.utils.unregister_class(cls)
CoDEmanX
committed