Skip to content
Snippets Groups Projects
  • Damien Picard's avatar
    8db31f5c
    Assign modifiers to variables instead of getting them by name · 8db31f5c
    Damien Picard authored
    Using hardcoded names to retrieve modifiers can result in errors if
    the user has enabled UI translation, because the new modifiers may not
    have the expected English names if the UI is translated.
    
    So, use the data API to create the modifiers instead of the ops API,
    and assign nodes to variables instead of getting them by name.
    8db31f5c
    History
    Assign modifiers to variables instead of getting them by name
    Damien Picard authored
    Using hardcoded names to retrieve modifiers can result in errors if
    the user has enabled UI translation, because the new modifiers may not
    have the expected English names if the UI is translated.
    
    So, use the data API to create the modifiers instead of the ops API,
    and assign nodes to variables instead of getting them by name.
mesh_auto_mirror.py 11.25 KiB
# SPDX-License-Identifier: GPL-3.0-or-later

######################################################################################################
# A simple add-on to auto cut in two and mirror an object                                            #
# Actually partially uncommented (see further version)                                               #
# Author: Lapineige, Bookyakuno                                                                      #
######################################################################################################
# 2.8 update by Bookyakuno, meta-androcto

bl_info = {
    "name": "Auto Mirror",
    "description": "Super fast cutting and mirroring for mesh",
    "author": "Lapineige",
    "version": (2, 5, 4),
    "blender": (2, 80, 0),
    "location": "View 3D > Sidebar > Edit Tab > AutoMirror (panel)",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/mesh/auto_mirror.html",
    "category": "Mesh",
}


import bpy
from mathutils import Vector

import bmesh
import bpy
import collections
import mathutils
import math
from bpy_extras import view3d_utils
from bpy.types import (
        Operator,
        Menu,
        Panel,
        AddonPreferences,
        PropertyGroup,
        )
from bpy.props import (
        BoolProperty,
        EnumProperty,
        FloatProperty,
        IntProperty,
        PointerProperty,
        StringProperty,
        )


# Operator

class AlignVertices(Operator):

    """ Automatically cut an object along an axis """

    bl_idname = "object.align_vertices"
    bl_label = "Align Vertices on 1 Axis"

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj and obj.type == "MESH"

    def execute(self, context):
        automirror = context.scene.automirror

        bpy.ops.object.mode_set(mode = 'OBJECT')

        x1,y1,z1 = bpy.context.scene.cursor.location
        bpy.ops.view3d.snap_cursor_to_selected()

        x2,y2,z2 = bpy.context.scene.cursor.location

        bpy.context.scene.cursor.location[0], \
        bpy.context.scene.cursor.location[1], \
        bpy.context.scene.cursor.location[2]  = 0, 0, 0

        #Vertices coordinate to 0 (local coordinate, so on the origin)
        for vert in bpy.context.object.data.vertices:
            if vert.select:
                if automirror.axis == 'x':
                    axis = 0
                elif automirror.axis == 'y':
                    axis = 1
                elif automirror.axis == 'z':
                    axis = 2
                vert.co[axis] = 0

        bpy.context.scene.cursor.location = x2,y2,z2

        bpy.ops.object.origin_set(type='ORIGIN_CURSOR')

        bpy.context.scene.cursor.location = x1,y1,z1

        bpy.ops.object.mode_set(mode = 'EDIT')
        return {'FINISHED'}


class AutoMirror(bpy.types.Operator):
    """ Automatically cut an object along an axis """
    bl_idname = "object.automirror"
    bl_label = "AutoMirror"
    bl_options = {'REGISTER'} # 'UNDO' ?

    @classmethod
    def poll(cls, context):
        obj = context.active_object
        return obj and obj.type == "MESH"

    def draw(self, context):
        automirror = context.scene.automirror

        layout = self.layout
        if bpy.context.object and bpy.context.object.type == 'MESH':
            layout.prop(automirror, "axis", text = "Mirror axis")
            layout.prop(automirror, "orientation", text = "Orientation")
            layout.prop(automirror, "threshold", text = "Threshold")
            layout.prop(automirror, "toggle_edit", text = "Toggle edit")
            layout.prop(automirror, "cut", text = "Cut and mirror")
            if automirror.cut:
                layout.prop(automirror, "clipping", text = "Clipping")
                layout.prop(automirror, "mirror", text = "Apply mirror")

        else:
            layout.label(icon = "ERROR", text = "No mesh selected")

    def get_local_axis_vector(self, context, X, Y, Z, orientation):
        loc = context.object.location
        bpy.ops.object.mode_set(mode = "OBJECT") # Needed to avoid to translate vertices

        v1 = Vector((loc[0],loc[1],loc[2]))
        bpy.ops.transform.translate(value = (X*orientation, Y*orientation, Z*orientation),
                                        constraint_axis = ((X==1), (Y==1), (Z==1)),
                                        orient_type = 'LOCAL')
        v2 = Vector((loc[0],loc[1],loc[2]))
        bpy.ops.transform.translate(value = (-X*orientation, -Y*orientation, -Z*orientation),
                                        constraint_axis = ((X==1), (Y==1), (Z==1)),
                                        orient_type = 'LOCAL')

        bpy.ops.object.mode_set(mode="EDIT")
        return v2-v1

    def execute(self, context):
        context.active_object.select_set(True)

        automirror = context.scene.automirror

        X,Y,Z = 0,0,0

        if automirror.axis == 'x':
            X = 1
        elif automirror.axis == 'y':
            Y = 1
        elif automirror.axis == 'z':
            Z = 1

        current_mode = bpy.context.object.mode # Save the current mode

        if bpy.context.object.mode != "EDIT":
            bpy.ops.object.mode_set(mode = "EDIT") # Go to edit mode

        bpy.ops.mesh.select_all(action = 'SELECT') # Select all the vertices

        if automirror.orientation == 'positive':
            orientation = 1
        else:
            orientation = -1

        cut_normal = self.get_local_axis_vector(context, X, Y, Z, orientation)

        # Cut the mesh
        bpy.ops.mesh.bisect(
                plane_co = (
                bpy.context.object.location[0],
                bpy.context.object.location[1],
                bpy.context.object.location[2]
                ),
                plane_no = cut_normal,
                use_fill = False,
                clear_inner = automirror.cut,
                clear_outer = 0,
                threshold = automirror.threshold
                )

        bpy.ops.object.align_vertices() # Use to align the vertices on the origin, needed by the "threshold"

        if not automirror.toggle_edit:
            bpy.ops.object.mode_set(mode = current_mode) # Reload previous mode

        if automirror.cut:
            # Add a mirror modifier
            mirror_modifier = bpy.context.object.modifiers.new("", 'MIRROR')
            mirror_modifier.use_axis[0] = X # Choose the axis to use, based on the cut's axis
            mirror_modifier.use_axis[1] = Y
            mirror_modifier.use_axis[2] = Z
            mirror_modifier.use_clip = automirror.Use_Matcap
            mirror_modifier.show_on_cage = automirror.show_on_cage
            if automirror.apply_mirror:
                bpy.ops.object.mode_set(mode = 'OBJECT')
                bpy.ops.object.modifier_apply(modifier = bpy.context.object.modifiers[-1].name)
                if automirror.toggle_edit:
                    bpy.ops.object.mode_set(mode = 'EDIT')
                else:
                    bpy.ops.object.mode_set(mode = current_mode)

        return {'FINISHED'}


# Panel

class VIEW3D_PT_BisectMirror(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_label = "Auto Mirror"
    bl_category = 'Edit'
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        automirror = context.scene.automirror

        layout = self.layout
        col = layout.column(align=True)

        layout = self.layout

        if bpy.context.object and bpy.context.object.type == 'MESH':
            layout.operator("object.automirror")
            layout.prop(automirror, "axis", text = "Mirror Axis", expand=True)
            layout.prop(automirror, "orientation", text = "Orientation")
            layout.prop(automirror, "threshold", text = "Threshold")
            layout.prop(automirror, "toggle_edit", text = "Toggle Edit")
            layout.prop(automirror, "cut", text = "Cut and Mirror")
            if bpy.context.scene.automirror.cut:
                layout.prop(automirror, "Use_Matcap", text = "Use Clip")
                layout.prop(automirror, "show_on_cage", text = "Editable")
                layout.prop(automirror, "apply_mirror", text = "Apply Mirror")

        else:
            layout.label(icon="ERROR", text = "No mesh selected")

# Properties
class AutoMirrorProps(PropertyGroup):
    axis : EnumProperty(
        items = [("x", "X", "", 1),
                 ("y", "Y", "", 2),
                 ("z", "Z", "", 3)],
        description="Axis used by the mirror modifier",
        )

    orientation : EnumProperty(
        items = [("positive", "Positive", "", 1),("negative", "Negative", "", 2)],
        description="Choose the side along the axis of the editable part (+/- coordinates)",
        )

    threshold : FloatProperty(
        default= 0.001, min= 0.001,
        description="Vertices closer than this distance are merged on the loopcut",
        )

    toggle_edit : BoolProperty(
        default= False,
        description="If not in edit mode, change mode to edit",
        )

    cut : BoolProperty(
        default= True,
        description="If enabled, cut the mesh in two parts and mirror it. If not, just make a loopcut",
        )

    clipping : BoolProperty(
        default=True,
        )

    Use_Matcap : BoolProperty(
        default=True,
        description="Use clipping for the mirror modifier",
        )

    show_on_cage : BoolProperty(
        default=False,
        description="Enable to edit the cage (it's the classical modifier's option)",
        )

    apply_mirror : BoolProperty(
        description="Apply the mirror modifier (useful to symmetrise the mesh)",
        )


# Add-ons Preferences Update Panel

# Define Panel classes for updating
panels = (
        VIEW3D_PT_BisectMirror,
        )


def update_panel(self, context):
    message = ": 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:
            panel.bl_category = context.preferences.addons[__name__].preferences.category
            bpy.utils.register_class(panel)

    except Exception as e:
        print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
        pass


class AutoMirrorAddonPreferences(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",
            default = "Edit",
            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 = "")

# define classes for registration
classes = (
    VIEW3D_PT_BisectMirror,
    AutoMirror,
    AlignVertices,
    AutoMirrorAddonPreferences,
    AutoMirrorProps,
    )


# registering and menu integration
def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.Scene.automirror = PointerProperty(type = AutoMirrorProps)
    update_panel(None, bpy.context)

# unregistering and removing menus
def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)

    del bpy.types.Scene.automirror

if __name__ == "__main__":
    register()