Skip to content
Snippets Groups Projects
mesh_bsurfaces.py 204 KiB
Newer Older
  • Learn to ignore specific revisions
  •         bpy.ops.object.duplicate('INVOKE_REGION_WIN')
            curves_duplicate_2 = bpy.context.object
            objects_to_delete.append(curves_duplicate_2)
    
            # 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')
    
            curves_duplicate_2.select_set(True)
    
            bpy.context.view_layer.objects.active = curves_duplicate_2
    
            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', modifier='Shrinkwrap')
    
            # 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
    
                    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])
    
                    dist = (bp_1_co - bp_2_co).length
    
                    if dist < shortest_dist:
    
                        nearest_points_coords[st_idx] = ("%.4f" % bp_2_co[0],
                                                         "%.4f" % bp_2_co[1],
                                                         "%.4f" % bp_2_co[2])
    
                        shortest_dist = dist
    
            # 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 = {}
    
                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
    
                GP_connection_points[gp_st_idx] = GPvert_spline_relationship
    
            # Get the splines new order
    
            splines_new_order = []
            for i in GP_connection_points:
    
                dict_keys = sorted(GP_connection_points[i].keys())  # Sort dictionaries by key
    
                for k in dict_keys:
                    splines_new_order.append(GP_connection_points[i][k])
    
            curve_original_name = self.main_curve.name
    
            bpy.ops.object.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            self.main_curve.select_set(True)
    
            bpy.context.view_layer.objects.active = self.main_curve
    
            self.main_curve.name = "SURFSKIO_CRV_ORD"
    
            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')
    
            for _sp_idx in range(len(self.main_curve.data.splines)):
    
                self.main_curve.data.splines[0].bezier_points[0].select_control_point = True
    
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
                bpy.ops.curve.separate('EXEC_REGION_WIN')
    
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            # 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]
    
                    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
    
            # 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"]
    
                bpy.ops.object.join('INVOKE_REGION_WIN')
    
            # Go back to the original name of the curves object.
    
            bpy.context.object.name = curve_original_name
    
            # Delete all unused objects
    
            bpy.ops.object.delete({"selected_objects": objects_to_delete})
    
            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]
    
    Eclectiel L's avatar
    Eclectiel L committed
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
                bpy.context.scene.bsurfaces.SURFSK_gpencil.data.layers.active.clear()
    
            return {"FINISHED"}
    
        def invoke(self, context, event):
    
            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)
    
                if strokes_num > 0:
                    there_are_GP_strokes = True
            except:
                pass
    
            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")
    
            return {"FINISHED"}
    
    # ----------------------------
    # Set first points operator
    
    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'}
    
        def execute(self, context):
    
            splines_to_invert = []
    
            # 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
    
                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)
    
            # Reorder points of cyclic splines, and set all handles to "Automatic"
    
            # Check first selected point
    
            cyclic_splines_new_first_pt = {}
            for i in self.cyclic_splines:
                sp = self.main_curve.data.splines[i]
    
                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
    
            for spline_idx in cyclic_splines_new_first_pt:
                sp = self.main_curve.data.splines[spline_idx]
    
                spline_old_coords = []
                for bp_old in sp.bezier_points:
                    coords = (bp_old.co[0], bp_old.co[1], bp_old.co[2])
    
                    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]
                            )
    
                for t in range(len(sp.bezier_points)):
                    bp = sp.bezier_points
    
                    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)
    
                    bp[t].co = Vector(spline_old_coords[new_index][0])
    
                    bp[t].handle_left.length = spline_old_coords[new_index][3]
                    bp[t].handle_right.length = spline_old_coords[new_index][4]
    
                    bp[t].handle_left_type = "FREE"
                    bp[t].handle_right_type = "FREE"
    
                    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]
    
                    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]
    
                    bp[t].handle_left_type = spline_old_coords[new_index][1]
                    bp[t].handle_right_type = spline_old_coords[new_index][2]
    
            # 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')
    
                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')
    
                bpy.ops.curve.switch_direction()
    
            bpy.ops.curve.select_all('INVOKE_REGION_WIN', action='DESELECT')
    
            # 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
                                                            ]
    
                bp.select_control_point = True
                bp.select_right_handle = True
                bp.select_left_handle = True
    
            bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
            return {'FINISHED'}
    
        def invoke(self, context, event):
    
            self.main_curve = bpy.context.object
    
            # 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")
    
                    return {'CANCELLED'}
                else:
                    if self.main_curve.data.splines[i].use_cyclic_u:
                        self.cyclic_splines.append(i)
    
            self.execute(context)
    
            self.report({'INFO'}, "First points have been set")
    
    
    # Add-ons Preferences Update Panel
    
    # Define Panel classes for updating
    panels = (
            VIEW3D_PT_tools_SURFSK_mesh,
    
            VIEW3D_PT_tools_SURFSK_curve
    
    def conver_gpencil_to_curve(self, context, pencil, type):
    
        newCurve = bpy.data.curves.new(type + '_curve', type='CURVE')
    
        newCurve.dimensions = '3D'
    
        CurveObject = object_utils.object_data_add(context, newCurve)
    
            try:
                strokes = pencil.data.layers.active.active_frame.strokes
            except:
                error = True
    
            CurveObject.location = pencil.location
            CurveObject.rotation_euler = pencil.rotation_euler
            CurveObject.scale = pencil.scale
    
                strokes = bpy.context.annotation_data.layers.active.active_frame.strokes
    
            CurveObject.location = (0.0, 0.0, 0.0)
            CurveObject.rotation_euler = (0.0, 0.0, 0.0)
            CurveObject.scale = (1.0, 1.0, 1.0)
    
            for i, _stroke in enumerate(strokes):
    
                data_list = [ (point.co.x, point.co.y, point.co.z)
    
                                for point in stroke_points ]
                points_to_add = len(data_list)-1
    
                flat_list = []
                for point in data_list:
                    flat_list.extend(point)
    
                spline = newCurve.splines.new(type='BEZIER')
                spline.bezier_points.add(points_to_add)
                spline.bezier_points.foreach_set("co", flat_list)
    
                for point in spline.bezier_points:
                    point.handle_left_type="AUTO"
                    point.handle_right_type="AUTO"
    
    
    def update_panel(self, context):
        message = "Bsurfaces GPL Edition: Updating Panel locations has failed"
        try:
            for panel in panels:
                if "bl_rna" in panel.__dict__:
                    bpy.utils.unregister_class(panel)
    
            for panel in panels:
                category = context.preferences.addons[__name__].preferences.category
                if category != 'Tool':
                    panel.bl_category = context.preferences.addons[__name__].preferences.category
                else:
                    context.preferences.addons[__name__].preferences.category = 'Edit'
                    panel.bl_category = 'Edit'
                    raise ValueError("You can not install add-ons in the Tool panel")
                bpy.utils.register_class(panel)
    
        except Exception as e:
            print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
            pass
    
    def makeMaterial(name, diffuse):
    
        if name in bpy.data.materials:
            material = bpy.data.materials[name]
            material.diffuse_color = diffuse
        else:
            material = bpy.data.materials.new(name)
            material.diffuse_color = diffuse
    
        return material
    
    
    def update_mesh(self, context):
        try:
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            bpy.context.view_layer.update()
            global global_mesh_object
            global_mesh_object = bpy.context.scene.bsurfaces.SURFSK_mesh.name
            bpy.data.objects[global_mesh_object].select_set(True)
            bpy.context.view_layer.objects.active = bpy.data.objects[global_mesh_object]
    
            print("Select mesh object")
    
    def update_gpencil(self, context):
    
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            bpy.context.view_layer.update()
            global global_gpencil_object
            global_gpencil_object = bpy.context.scene.bsurfaces.SURFSK_gpencil.name
            bpy.data.objects[global_gpencil_object].select_set(True)
            bpy.context.view_layer.objects.active = bpy.data.objects[global_gpencil_object]
    
            print("Select gpencil object")
    
    def update_curve(self, context):
    
            bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
            bpy.ops.object.select_all(action='DESELECT')
            bpy.context.view_layer.update()
            global global_curve_object
            global_curve_object = bpy.context.scene.bsurfaces.SURFSK_curve.name
            bpy.data.objects[global_curve_object].select_set(True)
            bpy.context.view_layer.objects.active = bpy.data.objects[global_curve_object]
    
            material = makeMaterial("BSurfaceMesh", bpy.context.scene.bsurfaces.SURFSK_mesh_color)
    
            if bpy.data.objects[global_mesh_object].data.materials:
                bpy.data.objects[global_mesh_object].data.materials[0] = material
    
                bpy.data.objects[global_mesh_object].data.materials.append(material)
    
            diffuse_color = material.diffuse_color
            global_color = (diffuse_color[0], diffuse_color[1], diffuse_color[2], diffuse_color[3])
    
            print("Select mesh object")
    
            global global_offset
            global_offset = bpy.context.scene.bsurfaces.SURFSK_Shrinkwrap_offset
    
            global global_mesh_object
            modifier = bpy.data.objects[global_mesh_object].modifiers["Shrinkwrap"]
    
            print("Shrinkwrap modifier not found")
    
            global global_in_front
            global_in_front = bpy.context.scene.bsurfaces.SURFSK_in_front
    
            global global_mesh_object
            bpy.data.objects[global_mesh_object].show_in_front = global_in_front
    
            print("Select mesh object")
    
    
    def update_show_wire(self, context):
        try:
            global global_show_wire
            global_show_wire = bpy.context.scene.bsurfaces.SURFSK_show_wire
            global global_mesh_object
            bpy.data.objects[global_mesh_object].show_wire = global_show_wire
    
    def update_shade_smooth(self, context):
        try:
            global global_shade_smooth
            global_shade_smooth = bpy.context.scene.bsurfaces.SURFSK_shade_smooth
    
            if bpy.ops.object.mode_set.poll():
                bpy.ops.object.mode_set('INVOKE_REGION_WIN', mode='OBJECT')
    
            bpy.ops.object.select_all(action='DESELECT')
            global global_mesh_object
            global_mesh_object = bpy.context.scene.bsurfaces.SURFSK_mesh.name
            bpy.data.objects[global_mesh_object].select_set(True)
    
            if global_shade_smooth:
                bpy.ops.object.shade_smooth()
            else:
                bpy.ops.object.shade_flat()
    
            if contex_mode == "EDIT_MESH":
                bpy.ops.object.editmode_toggle('INVOKE_REGION_WIN')
    
    class BsurfPreferences(AddonPreferences):
    
        # this must match the addon name, use '__package__'
        # when defining this in a submodule of a python package.
        bl_idname = __name__
    
        category: StringProperty(
    
                name="Tab Category",
                description="Choose a name for the category of the panel",
    
                update=update_panel
                )
    
    
        def draw(self, context):
            layout = self.layout
    
            row = layout.row()
            col = row.column()
            col.label(text="Tab Category:")
            col.prop(self, "category", text="")
    
    # Properties
    class BsurfacesProps(PropertyGroup):
    
        SURFSK_guide: EnumProperty(
            name="Guide:",
            items=[
                    ('Annotation', 'Annotation', 'Annotation'),
                    ('GPencil', 'GPencil', 'GPencil'),
                    ('Curve', 'Curve', 'Curve')
                  ],
            default="Annotation"
            )
    
        SURFSK_edges_U: IntProperty(
                        name="Cross",
                        description="Number of face-loops crossing the strokes",
                        default=5,
                        min=1,
                        max=200
                        )
        SURFSK_edges_V: IntProperty(
                        name="Follow",
                        description="Number of face-loops following the strokes",
                        default=1,
                        min=1,
                        max=200
                        )
    
        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
                    )
    
                    type=bpy.types.Object,
                    description="Mesh of BSurface",
    
                    update=update_mesh,
                    )
        SURFSK_gpencil: PointerProperty(
                    name="GreasePencil object",
                    type=bpy.types.Object,
                    description="GreasePencil object",
                    update=update_gpencil,
    
        SURFSK_curve: PointerProperty(
                    name="Curve object",
    
                    description="Curve object",
                    update=update_curve,
    
                    size=4,
                    subtype="COLOR",
                    min=0,
                    max=1,
                    update=update_color,
                    description="Mesh color",
                    )
    
                    name="Shrinkwrap offset",
                    default=0.01,
                    precision=3,
                    description="Distance to keep from the target",
                    update=update_Shrinkwrap_offset,
                    )
    
        SURFSK_in_front: BoolProperty(
                    name="In Front",
                    description="Make the object draw in front of others",
                    default=False,
                    update=update_in_front,
    
        SURFSK_show_wire: BoolProperty(
                    name="Show wire",
                    description="Add the object’s wireframe over solid drawing",
                    default=False,
                    update=update_show_wire,
                    )
        SURFSK_shade_smooth: BoolProperty(
                    name="Shade smooth",
                    description="Render and display faces smooth, using interpolated Vertex Normals",
                    default=False,
                    update=update_shade_smooth,
                    )
    
        MESH_OT_SURFSK_init,
        MESH_OT_SURFSK_add_modifiers,
        MESH_OT_SURFSK_add_surface,
        MESH_OT_SURFSK_edit_surface,
    
        GPENCIL_OT_SURFSK_add_strokes,
    
        GPENCIL_OT_SURFSK_edit_strokes,
    
        GPENCIL_OT_SURFSK_strokes_to_curves,
    
        GPENCIL_OT_SURFSK_add_annotation,
    
        CURVE_OT_SURFSK_reorder_splines,
        CURVE_OT_SURFSK_first_points,
        BsurfPreferences,
    
    def register():
        for cls in classes:
            bpy.utils.register_class(cls)
    
        for panel in panels:
            bpy.utils.register_class(panel)
    
        bpy.types.Scene.bsurfaces = PointerProperty(type=BsurfacesProps)
        update_panel(None, bpy.context)
    
    def unregister():
    
        for panel in panels:
            bpy.utils.unregister_class(panel)
    
        for cls in classes:
            bpy.utils.unregister_class(cls)
    
        del bpy.types.Scene.bsurfaces
    
    
    if __name__ == "__main__":
    
    Guillermo S. Romero's avatar
    Guillermo S. Romero committed
        register()