Beispiel #1
0
    def execute(self, context):
        if not maplus_geom.get_active_object():
            self.report(
                {'ERROR'},
                'Cannot complete: no active object.'
            )
            return {'CANCELLED'}
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode

        # Get active (target) transformation matrix components
        active_mat = maplus_geom.get_active_object().matrix_world
        active_trs = [
            mathutils.Matrix.Translation(active_mat.decompose()[0]),
            active_mat.decompose()[1].to_matrix(),
            mathutils.Matrix.Scale(active_mat.decompose()[2][0], 4),
        ]
        active_trs[1].resize_4x4()

        # Copy the transform components from the target to the current object
        selected = [
            item
            for item in bpy.context.scene.objects if maplus_geom.get_select_state(item)
        ]
        for item in selected:
            current_mat = item.matrix_world
            current_trs = [
                mathutils.Matrix.Translation(current_mat.decompose()[0]),
                current_mat.decompose()[1].to_matrix(),
                mathutils.Matrix.Scale(current_mat.decompose()[2][0], 4),
            ]
            current_trs[1].resize_4x4()
            item.matrix_world = (
                active_trs[0] @
                active_trs[1] @
                current_trs[2]
            )

        return {'FINISHED'}
Beispiel #2
0
    def execute(self, context):
        if not (maplus_geom.get_active_object() and
                maplus_geom.get_select_state(maplus_geom.get_active_object())):
            self.report({'ERROR'}, ('Cannot complete: need at least one active'
                                    ' (and selected) object.'))
            return {'CANCELLED'}
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if hasattr(self, "quick_op_target"):
            active_item = addon_data.quick_scale_match_edge_transf
        else:
            active_item = prims[addon_data.active_list_item]

        if (maplus_geom.get_active_object() and type(
                maplus_geom.get_active_object().data) == bpy.types.Mesh):

            if not hasattr(self, "quick_op_target"):
                if (prims[active_item.sme_edge_one].kind != 'LINE'
                        or prims[active_item.sme_edge_two].kind != 'LINE'):
                    self.report({'ERROR'},
                                ('Wrong operands: "Scale Match Edge" can only'
                                 ' operate on two lines'))
                    return {'CANCELLED'}

            if previous_mode != 'EDIT':
                bpy.ops.object.editmode_toggle()
            else:
                # else we could already be in edit mode with some stale
                # updates, exiting and reentering forces an update
                bpy.ops.object.editmode_toggle()
                bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # applicable modifiers applied. Grab either (A) directly from
            # the scene data (for quick ops), (B) from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools), or
            # (C) from the selected verts directly for numeric input mode
            if hasattr(self, "quick_op_target"):
                # Numeric mode is part of this op's quick tools
                if addon_data.quick_sme_numeric_mode:
                    if addon_data.quick_sme_numeric_auto:
                        vert_attribs_to_set = ('line_start', 'line_end')
                        try:
                            vert_data = maplus_geom.return_selected_verts(
                                maplus_geom.get_active_object(),
                                len(vert_attribs_to_set),
                                maplus_geom.get_active_object().matrix_world)
                        except maplus_except.InsufficientSelectionError:
                            self.report({'ERROR'},
                                        'Not enough vertices selected.')
                            return {'CANCELLED'}
                        except maplus_except.NonMeshGrabError:
                            self.report({'ERROR'},
                                        ('Cannot grab coords: non-mesh'
                                         ' or no active object.'))
                            return {'CANCELLED'}

                        maplus_geom.set_item_coords(
                            addon_data.quick_sme_numeric_src,
                            vert_attribs_to_set, vert_data)
                        maplus_geom.set_item_coords(
                            addon_data.quick_sme_numeric_dest,
                            vert_attribs_to_set, vert_data)

                    addon_data.quick_sme_numeric_dest.ln_make_unit_vec = (True)
                    addon_data.quick_sme_numeric_dest.ln_multiplier = (
                        addon_data.quick_sme_numeric_length)

                    src_global_data = maplus_geom.get_modified_global_coords(
                        geometry=addon_data.quick_sme_numeric_src, kind='LINE')
                    dest_global_data = maplus_geom.get_modified_global_coords(
                        geometry=addon_data.quick_sme_numeric_dest,
                        kind='LINE')

                # Non-numeric (normal quick op) mode
                else:
                    if addon_data.quick_scale_match_edge_auto_grab_src:
                        vert_attribs_to_set = ('line_start', 'line_end')
                        try:
                            vert_data = maplus_geom.return_selected_verts(
                                maplus_geom.get_active_object(),
                                len(vert_attribs_to_set),
                                maplus_geom.get_active_object().matrix_world)
                        except maplus_except.InsufficientSelectionError:
                            self.report({'ERROR'},
                                        'Not enough vertices selected.')
                            return {'CANCELLED'}
                        except maplus_except.NonMeshGrabError:
                            self.report({'ERROR'},
                                        ('Cannot grab coords: non-mesh'
                                         ' or no active object.'))
                            return {'CANCELLED'}

                        maplus_geom.set_item_coords(
                            addon_data.quick_scale_match_edge_src,
                            vert_attribs_to_set, vert_data)

                    src_global_data = maplus_geom.get_modified_global_coords(
                        geometry=addon_data.quick_scale_match_edge_src,
                        kind='LINE')
                    dest_global_data = maplus_geom.get_modified_global_coords(
                        geometry=addon_data.quick_scale_match_edge_dest,
                        kind='LINE')

            # Else, operate on data from the advanced tools
            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.sme_edge_one], kind='LINE')
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.sme_edge_two], kind='LINE')

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            src_start = src_global_data[0]
            src_end = src_global_data[1]

            dest_start = dest_global_data[0]
            dest_end = dest_global_data[1]

            # create common vars needed for object and for mesh
            # level transforms
            active_obj_transf = maplus_geom.get_active_object(
            ).matrix_world.copy()
            inverse_active = active_obj_transf.copy()
            inverse_active.invert()

            # Construct vectors for each edge from the global point coord data
            src_edge = src_end - src_start
            dest_edge = dest_end - dest_start

            if dest_edge.length == 0 or src_edge.length == 0:
                self.report(
                    {'ERROR'},
                    'Divide by zero error: zero length edge encountered')
                return {'CANCELLED'}
            scale_factor = dest_edge.length / src_edge.length

            multi_edit_targets = [
                model for model in bpy.context.scene.objects if
                (maplus_geom.get_select_state(model) and model.type == 'MESH')
            ]
            if self.target == 'OBJECT':
                for item in multi_edit_targets:
                    # Get the object world matrix before we modify it here
                    item_matrix_unaltered = item.matrix_world.copy()
                    unaltered_inverse = item_matrix_unaltered.copy()
                    unaltered_inverse.invert()

                    # (Note that there are no transformation modifiers for this
                    # transformation type, so that section is omitted here)
                    item.scale = [scale_factor * num for num in item.scale]
                    bpy.context.view_layer.update()

                    # put the original line starting point (before the object
                    # was transformed) into the local object space
                    src_pivot_location_local = unaltered_inverse @ src_start

                    # get final global position of pivot (source line
                    # start coords) after object rotation
                    new_global_src_pivot_coords = (
                        item.matrix_world @ src_pivot_location_local)

                    # get translation, new to old (original) pivot location
                    new_to_old_pivot = (src_start -
                                        new_global_src_pivot_coords)

                    item.location = (item.location + new_to_old_pivot)
                    bpy.context.view_layer.update()

            else:
                for item in multi_edit_targets:
                    # (Note that there are no transformation modifiers for this
                    # transformation type, so that section is omitted here)
                    self.report({'WARNING'},
                                ('Warning/Experimental: mesh transforms'
                                 ' on objects with non-uniform scaling'
                                 ' are not currently supported.'))

                    # Init source mesh
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    src_start_loc = unaltered_inverse_loc @ src_start
                    src_end_loc = unaltered_inverse_loc @ src_end

                    dest_start_loc = unaltered_inverse_loc @ dest_start
                    dest_end_loc = unaltered_inverse_loc @ dest_end

                    # Construct vectors for each line in local space
                    loc_src_line = src_end_loc - src_start_loc
                    loc_dest_line = dest_end_loc - dest_start_loc

                    # Get the scale match matrix
                    scaling_match = mathutils.Matrix.Scale(scale_factor, 4)

                    # Get the new pivot location
                    new_pivot_location_loc = scaling_match @ src_start_loc

                    # Get the translation, new to old pivot location
                    new_to_old_pivot_vec = (src_start_loc -
                                            new_pivot_location_loc)
                    new_to_old_pivot = mathutils.Matrix.Translation(
                        new_to_old_pivot_vec)

                    # Get combined scale + move
                    match_transf = new_to_old_pivot @ scaling_match

                    if self.target == 'MESHSELECTED':
                        src_mesh.transform(match_transf, filter={'SELECT'})
                    elif self.target == 'WHOLEMESH':
                        src_mesh.transform(match_transf)

                    # write and then release the mesh data
                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)
                    src_mesh.free()

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            self.report({'ERROR'},
                        'Cannot transform: non-mesh or no active object.')
            return {'CANCELLED'}

        return {'FINISHED'}
    def execute(self, context):
        if not (maplus_geom.get_active_object() and maplus_geom.get_select_state(maplus_geom.get_active_object())):
            self.report(
                {'ERROR'},
                ('Cannot complete: need at least'
                 ' one active (and selected) object.')
            )
            return {'CANCELLED'}
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if not hasattr(self, "quick_op_target"):
            active_item = prims[addon_data.active_list_item]
        else:
            active_item = addon_data.quick_directional_slide_transf

        if (maplus_geom.get_active_object() and
                type(maplus_geom.get_active_object().data) == bpy.types.Mesh):

            if not hasattr(self, "quick_op_target"):
                if prims[active_item.ds_direction].kind != 'LINE':
                    self.report(
                        {'ERROR'},
                        'Wrong operand: "Directional Slide" can'
                        ' only operate on a line'
                    )
                    return {'CANCELLED'}

            # a bmesh can only be initialized in edit mode...
            if previous_mode != 'EDIT':
                bpy.ops.object.editmode_toggle()
            else:
                # else we could already be in edit mode with some stale
                # updates, exiting and reentering forces an update
                bpy.ops.object.editmode_toggle()
                bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, 'quick_op_target'):
                if addon_data.quick_directional_slide_auto_grab_src:
                    vert_attribs_to_set = ('line_start', 'line_end')
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world
                        )
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report(
                            {'ERROR'},
                            'Cannot grab coords: non-mesh or no active object.'
                        )
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_directional_slide_src,
                        vert_attribs_to_set,
                        vert_data
                    )

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_directional_slide_src,
                    kind='LINE'
                )

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.ds_direction],
                    kind='LINE'
                )

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            dir_start = src_global_data[0]
            dir_end = src_global_data[1]

            # create common vars needed for object and for mesh level transfs
            active_obj_transf = maplus_geom.get_active_object().matrix_world.copy()
            inverse_active = active_obj_transf.copy()
            inverse_active.invert()

            multi_edit_targets = [
                model for model in bpy.context.scene.objects if (
                    maplus_geom.get_select_state(model) and model.type == 'MESH'
                )
            ]
            if self.target == 'OBJECT':
                for item in multi_edit_targets:
                    # Make the vector specifying the direction and
                    # magnitude to slide in
                    direction = dir_end - dir_start

                    # Take modifiers on the transformation item into account,
                    # in global (object) space
                    if active_item.ds_make_unit_vec:
                        direction.normalize()
                    if active_item.ds_flip_direction:
                        direction.negate()
                    direction *= active_item.ds_multiplier

                    item.location += direction

            else:
                for item in multi_edit_targets:
                    self.report(
                        {'WARNING'},
                        ('Warning/Experimental: mesh transforms'
                         ' on objects with non-uniform scaling'
                         ' are not currently supported.')
                    )
                    # Init source mesh
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    # Get the object world matrix
                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    dir_start_loc = unaltered_inverse_loc @ dir_start
                    dir_end_loc = unaltered_inverse_loc @ dir_end

                    # Get translation vector in local space
                    direction_loc = dir_end_loc - dir_start_loc

                    # Take modifiers on the transformation item into account,
                    # in local (mesh) space
                    if active_item.ds_make_unit_vec:
                        # There are special considerations for this modifier
                        # since we need to achieve a global length of
                        # one, but can only transform it in local space
                        # (NOTE: assumes only uniform scaling on the
                        # active object)
                        scaling_factor = 1.0 / item.scale[0]
                        direction_loc.normalize()
                        direction_loc *= scaling_factor
                    if active_item.ds_flip_direction:
                        direction_loc.negate()
                    direction_loc *= active_item.ds_multiplier
                    dir_slide = mathutils.Matrix.Translation(direction_loc)

                    if self.target == 'MESHSELECTED':
                        src_mesh.transform(
                            dir_slide,
                            filter={'SELECT'}
                        )
                    elif self.target == 'WHOLEMESH':
                        src_mesh.transform(dir_slide)

                    # write and then release the mesh data
                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)
                    src_mesh.free()

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            self.report(
                {'ERROR'},
                'Cannot transform: non-mesh or no active object.'
            )
            return {'CANCELLED'}

        return {'FINISHED'}
Beispiel #4
0
    def execute(self, context):
        if not (maplus_geom.get_active_object() and maplus_geom.get_select_state(maplus_geom.get_active_object())):
            self.report(
                {'ERROR'},
                ('Cannot complete: need at least'
                 ' one active (and selected) object.')
            )
            return {'CANCELLED'}
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if not hasattr(self, "quick_op_target"):
            active_item = prims[addon_data.active_list_item]
        else:
            active_item = addon_data.quick_align_planes_transf

        if (maplus_geom.get_active_object() and
                type(maplus_geom.get_active_object().data) == bpy.types.Mesh):

            if not hasattr(self, "quick_op_target"):
                if (prims[active_item.apl_src_plane].kind != 'PLANE' or
                        prims[active_item.apl_dest_plane].kind != 'PLANE'):
                    self.report(
                        {'ERROR'},
                        ('Wrong operands: "Align Planes" can only operate on '
                         'two planes')
                    )
                    return {'CANCELLED'}

            # a bmesh can only be initialized in edit mode...
            if previous_mode != 'EDIT':
                bpy.ops.object.editmode_toggle()
            else:
                # else we could already be in edit mode with some stale
                # updates, exiting and reentering forces an update
                bpy.ops.object.editmode_toggle()
                bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, "quick_op_target"):
                if addon_data.quick_align_planes_auto_grab_src:
                    vert_attribs_to_set = (
                        'plane_pt_a',
                        'plane_pt_b',
                        'plane_pt_c'
                    )
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world
                        )
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report(
                            {'ERROR'},
                            'Cannot grab coords: non-mesh or no active object.'
                        )
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_align_planes_src,
                        vert_attribs_to_set,
                        vert_data
                    )

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_planes_src,
                    kind='PLANE'
                )
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_planes_dest,
                    kind='PLANE'
                )

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apl_src_plane],
                    kind='PLANE'
                )
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apl_dest_plane],
                    kind='PLANE'
                )

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            if active_item.apl_alternate_pivot:
                src_pt_a = src_global_data[1]
                src_pt_b = src_global_data[0]
            else:
                src_pt_a = src_global_data[0]
                src_pt_b = src_global_data[1]
            src_pt_c = src_global_data[2]

            if active_item.apl_alternate_pivot:
                dest_pt_a = dest_global_data[1]
                dest_pt_b = dest_global_data[0]
            else:
                dest_pt_a = dest_global_data[0]
                dest_pt_b = dest_global_data[1]
            dest_pt_c = dest_global_data[2]

            # create common vars needed for object and for mesh level transfs
            active_obj_transf = maplus_geom.get_active_object().matrix_world.copy()
            inverse_active = active_obj_transf.copy()
            inverse_active.invert()

            # We need global data for the object operation and for creation
            # of a custom transform orientation if the user enables it.
            # construct normal vector for first (source) plane
            src_pln_ln_BA = src_pt_a - src_pt_b
            src_pln_ln_BC = src_pt_c - src_pt_b
            src_normal = src_pln_ln_BA.cross(src_pln_ln_BC)

            # Take modifiers on the transformation item into account,
            # in global (object) space
            if active_item.apl_flip_normal:
                src_normal.negate()

            # construct normal vector for second (destination) plane
            dest_pln_ln_BA = dest_pt_a - dest_pt_b
            dest_pln_ln_BC = dest_pt_c - dest_pt_b
            dest_normal = dest_pln_ln_BA.cross(dest_pln_ln_BC)

            # find rotational difference between source and dest planes
            rotational_diff = src_normal.rotation_difference(dest_normal)

            # Set up edge alignment (BA plane1 to BA plane2)
            new_lead_edge_orientation = src_pln_ln_BA.copy()
            new_lead_edge_orientation.rotate(rotational_diff)
            parallelize_edges = new_lead_edge_orientation.rotation_difference(
                dest_pln_ln_BA
            )

            # TODO: Disabled until Blender 2.8 custom transform
            #       orientations status is known
            # # Create custom transform orientation, for sliding the user's
            # # target along the destination face after it has been aligned.
            # # We do this by making a basis matrix out of the dest plane
            # # leading edge vector, the dest normal vector, and the cross
            # # of those two (each vector is normalized first)
            # vdest = dest_pln_ln_BA.copy()
            # vdest.normalize()
            # vnorm = dest_normal.copy()
            # vnorm.normalize()
            # # vnorm.negate()
            # vcross = vdest.cross(vnorm)
            # vcross.normalize()
            # vcross.negate()
            # custom_orientation = mathutils.Matrix(
            #     [
            #         [vcross[0], vnorm[0], vdest[0]],
            #         [vcross[1], vnorm[1], vdest[1]],
            #         [vcross[2], vnorm[2], vdest[2]]
            #     ]
            # )
            # bpy.ops.transform.create_orientation(
            #     name='MAPlus',
            #     use=active_item.apl_use_custom_orientation,
            #     overwrite=True
            # )
            # bpy.context.scene.orientations['MAPlus'].matrix = (
            #     custom_orientation
            # )

            multi_edit_targets = [
                model for model in bpy.context.scene.objects if (
                    maplus_geom.get_select_state(model) and model.type == 'MESH'
                )
            ]
            if self.target == 'OBJECT':
                for item in multi_edit_targets:
                    # Get the object world matrix before we modify it here
                    item_matrix_unaltered = item.matrix_world.copy()
                    unaltered_inverse = item_matrix_unaltered.copy()
                    unaltered_inverse.invert()

                    # Try to rotate the object by the rotational_diff
                    item.rotation_euler.rotate(
                        rotational_diff
                    )
                    bpy.context.view_layer.update()

                    # Parallelize the leading edges
                    item.rotation_euler.rotate(
                        parallelize_edges
                    )
                    bpy.context.view_layer.update()

                    # get local coords using active object as basis, in
                    # other words, determine coords of the source pivot
                    # relative to the active object's origin by reversing
                    # the active object's transf from the pivot's coords
                    local_src_pivot_coords = (
                        unaltered_inverse @ src_pt_b
                    )

                    # find the new global location of the pivot (we access
                    # the item's matrix_world directly here since we
                    # changed/updated it earlier)
                    new_global_src_pivot_coords = (
                        item.matrix_world @ local_src_pivot_coords
                    )
                    # figure out how to translate the object (the translation
                    # vector) so that the source pivot sits on the destination
                    # pivot's location
                    # first vector is the global/absolute distance
                    # between the two pivots
                    pivot_to_dest = (
                        dest_pt_b -
                        new_global_src_pivot_coords
                    )
                    item.location = (
                        item.location +
                        pivot_to_dest
                    )
                    bpy.context.view_layer.update()

            else:
                for item in multi_edit_targets:
                    self.report(
                        {'WARNING'},
                        ('Warning/Experimental: mesh transforms'
                         ' on objects with non-uniform scaling'
                         ' are not currently supported.')
                    )
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    src_a_loc = unaltered_inverse_loc @ src_pt_a
                    src_b_loc = unaltered_inverse_loc @ src_pt_b
                    src_c_loc = unaltered_inverse_loc @ src_pt_c

                    dest_a_loc = unaltered_inverse_loc @ dest_pt_a
                    dest_b_loc = unaltered_inverse_loc @ dest_pt_b
                    dest_c_loc = unaltered_inverse_loc @ dest_pt_c

                    src_ba_loc = src_a_loc - src_b_loc
                    src_bc_loc = src_c_loc - src_b_loc
                    src_normal_loc = src_ba_loc.cross(src_bc_loc)

                    # Take modifiers on the transformation item into account,
                    # in local (mesh) space
                    if active_item.apl_flip_normal:
                        src_normal_loc.negate()

                    dest_ba_loc = dest_a_loc - dest_b_loc
                    dest_bc_loc = dest_c_loc - dest_b_loc
                    dest_normal_loc = dest_ba_loc.cross(dest_bc_loc)

                    # Get translation, move source pivot to local origin
                    src_b_inv = src_b_loc.copy()
                    src_b_inv.negate()
                    src_pivot_to_loc_origin = mathutils.Matrix.Translation(
                        src_b_inv
                    )

                    # Get rotational diff between planes
                    loc_rot_diff = src_normal_loc.rotation_difference(
                        dest_normal_loc
                    )
                    parallelize_planes_loc = loc_rot_diff.to_matrix()
                    parallelize_planes_loc.resize_4x4()

                    # Get edge alignment rotation (align leading plane edges)
                    new_lead_edge_ornt_loc = (
                        parallelize_planes_loc @ src_ba_loc
                    )
                    edge_align_loc = (
                        new_lead_edge_ornt_loc.rotation_difference(
                            dest_ba_loc
                        )
                    )
                    parallelize_edges_loc = edge_align_loc.to_matrix()
                    parallelize_edges_loc.resize_4x4()

                    # Get translation, move pivot to destination
                    pivot_to_dest_loc = mathutils.Matrix.Translation(
                        dest_b_loc
                    )

                    mesh_coplanar = (
                        pivot_to_dest_loc @
                        parallelize_edges_loc @
                        parallelize_planes_loc @
                        src_pivot_to_loc_origin
                    )

                    if self.target == 'MESHSELECTED':
                        src_mesh.transform(
                            mesh_coplanar,
                            filter={'SELECT'}
                        )
                    elif self.target == 'WHOLEMESH':
                        src_mesh.transform(mesh_coplanar)

                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            self.report(
                {'ERROR'},
                "\nCannot transform: non-mesh or no active object."
            )
            return {'CANCELLED'}

        return {'FINISHED'}
    def execute(self, context):
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if not hasattr(self, "quick_op_target"):
            active_item = prims[addon_data.active_list_item]
        else:
            active_item = addon_data.quick_align_pts_transf
        # Gather selected Blender object(s) to apply the transform to
        multi_edit_targets = [
            item for item in bpy.context.scene.objects if (
                maplus_geom.get_select_state(item)
            )
        ]
        # Check prerequisites for mesh level transforms, need an active/selected object
        if (self.target != 'OBJECT' and not (maplus_geom.get_active_object()
                and maplus_geom.get_select_state(maplus_geom.get_active_object()))):
            self.report(
                {'ERROR'},
                ('Cannot complete: cannot perform mesh-level transform'
                 ' without an active (and selected) object.')
            )
            return {'CANCELLED'}
        # Check auto grab prerequisites
        if addon_data.quick_align_pts_auto_grab_src:
            if not (maplus_geom.get_active_object()
                    and maplus_geom.get_select_state(maplus_geom.get_active_object())):
                self.report(
                    {'ERROR'},
                    ('Cannot complete: cannot auto-grab source verts '
                     ' without an active (and selected) object.')
                )
                return {'CANCELLED'}
            if maplus_geom.get_active_object().type != 'MESH':
                self.report(
                    {'ERROR'},
                    ('Cannot complete: cannot auto-grab source verts '
                     ' from a non-mesh object.')
                )
                return {'CANCELLED'}

        # Proceed only if selected Blender objects are compatible with the transform target
        # (Do not allow mesh-level transforms when there are non-mesh objects selected)
        if not (self.target in {'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'}
                and [item for item in multi_edit_targets if item.type != 'MESH']):

            # todo: use a bool check and put on all derived classes
            # instead of hasattr
            if not hasattr(self, 'quick_op_target'):
                if (prims[active_item.apt_pt_one].kind != 'POINT' or
                        prims[active_item.apt_pt_two].kind != 'POINT'):
                    self.report(
                        {'ERROR'},
                        ('Wrong operands: "Align Points" can only operate on '
                         'two points')
                    )
                    return {'CANCELLED'}

            if maplus_geom.get_active_object().type == 'MESH':
                # a bmesh can only be initialized in edit mode...todo/better way?
                if previous_mode != 'EDIT':
                    bpy.ops.object.editmode_toggle()
                else:
                    # else we could already be in edit mode with some stale
                    # updates, exiting and reentering forces an update
                    bpy.ops.object.editmode_toggle()
                    bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, 'quick_op_target'):
                if addon_data.quick_align_pts_auto_grab_src:
                    vert_attribs_to_set = ('point',)
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world
                        )
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report(
                            {'ERROR'},
                            'Cannot grab coords: non-mesh or no active object.'
                        )
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_align_pts_src,
                        vert_attribs_to_set,
                        vert_data
                    )

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_pts_src,
                    kind='POINT'
                )
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_pts_dest,
                    kind='POINT'
                )

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apt_pt_one],
                    kind='POINT'
                )
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apt_pt_two],
                    kind='POINT'
                )

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            src_pt = src_global_data[0]
            dest_pt = dest_global_data[0]

            if self.target in {'OBJECT', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    align_points = dest_pt - src_pt

                    # Take modifiers on the transformation item into account,
                    # in global (object) space
                    if active_item.apt_make_unit_vector:
                        align_points.normalize()
                    if active_item.apt_flip_direction:
                        align_points.negate()
                    align_points *= active_item.apt_multiplier

                    item.location += align_points

            if self.target in {'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    self.report(
                        {'WARNING'},
                        ('Warning/Experimental: mesh transforms'
                         ' on objects with non-uniform scaling'
                         ' are not currently supported.')
                    )
                    # Init source mesh
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    active_obj_transf = maplus_geom.get_active_object().matrix_world.copy()
                    inverse_active = active_obj_transf.copy()
                    inverse_active.invert()

                    # Stored geom data in local coords
                    src_pt_loc = inverse_active @ src_pt
                    dest_pt_loc = inverse_active @ dest_pt

                    # Get translation vector (in local space), src to dest
                    align_points_vec = dest_pt_loc - src_pt_loc

                    # Take modifiers on the transformation item into account,
                    # in local (mesh) space
                    if active_item.apt_make_unit_vector:
                        # There are special considerations for this modifier
                        # since we need to achieve a global length of
                        # one, but can only transform it in local space
                        # (NOTE: assumes only uniform scaling on the
                        # active object)
                        scaling_factor = 1.0 / item.scale[0]
                        align_points_vec.normalize()
                        align_points_vec *= scaling_factor
                    if active_item.apt_flip_direction:
                        align_points_vec.negate()
                    align_points_vec *= active_item.apt_multiplier

                    align_points_loc = mathutils.Matrix.Translation(
                        align_points_vec
                    )

                    if self.target == 'MESH_SELECTED':
                        src_mesh.transform(
                            align_points_loc,
                            filter={'SELECT'}
                        )
                    elif self.target == 'WHOLE_MESH':
                        src_mesh.transform(align_points_loc)
                    elif self.target == 'OBJECT_ORIGIN':
                        # Note: a target of 'OBJECT_ORIGIN' is equivalent
                        # to performing an object transf. + an inverse
                        # whole mesh level transf. To the user,
                        # the object appears to stay in the same place,
                        # while only the object's origin moves.
                        src_mesh.transform(align_points_loc.inverted())

                    # write and then release the mesh data
                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)
                    src_mesh.free()

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            # The selected Blender objects are not compatible with the
            # requested transformation type (we can't apply a transform
            # to mesh data when there are non-mesh objects selected)
            self.report(
                {'ERROR'},
                ('Cannot complete: Cannot apply mesh-level'
                 ' transformations to selected non-mesh objects.')
            )
            return {'CANCELLED'}

        return {'FINISHED'}
Beispiel #6
0
    def execute(self, context):
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if hasattr(self, "quick_op_target"):
            active_item = addon_data.quick_align_lines_transf
        else:
            active_item = prims[addon_data.active_list_item]
        # Gather selected Blender object(s) to apply the transform to
        multi_edit_targets = [
            item for item in bpy.context.scene.objects
            if (maplus_geom.get_select_state(item))
        ]
        # Check prerequisites for mesh level transforms, need an active/selected object
        if (self.target != 'OBJECT'
                and not (maplus_geom.get_active_object()
                         and maplus_geom.get_select_state(
                             maplus_geom.get_active_object()))):
            self.report({'ERROR'},
                        ('Cannot complete: cannot perform mesh-level transform'
                         ' without an active (and selected) object.'))
            return {'CANCELLED'}
        # Check auto grab prerequisites
        if addon_data.quick_align_lines_auto_grab_src:
            if not (maplus_geom.get_active_object()
                    and maplus_geom.get_select_state(
                        maplus_geom.get_active_object())):
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' without an active (and selected) object.'))
                return {'CANCELLED'}
            if maplus_geom.get_active_object().type != 'MESH':
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' from a non-mesh object.'))
                return {'CANCELLED'}

        # Proceed only if selected Blender objects are compatible with the transform target
        # (Do not allow mesh-level transforms when there are non-mesh objects selected)
        if not (self.target in {
                'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'
        } and [item for item in multi_edit_targets if item.type != 'MESH']):

            if not hasattr(self, "quick_op_target"):
                if (prims[active_item.aln_src_line].kind != 'LINE'
                        or prims[active_item.aln_dest_line].kind != 'LINE'):
                    self.report(
                        {'ERROR'},
                        ('Wrong operands: "Align Lines" can only operate on '
                         'two lines'))
                    return {'CANCELLED'}

            if maplus_geom.get_active_object().type == 'MESH':
                # a bmesh can only be initialized in edit mode...
                if previous_mode != 'EDIT':
                    bpy.ops.object.editmode_toggle()
                else:
                    # else we could already be in edit mode with some stale
                    # updates, exiting and reentering forces an update
                    bpy.ops.object.editmode_toggle()
                    bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, 'quick_op_target'):
                if addon_data.quick_align_lines_auto_grab_src:
                    vert_attribs_to_set = ('line_start', 'line_end')
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world)
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report({
                            'ERROR'
                        }, 'Cannot grab coords: non-mesh or no active object.')
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_align_lines_src, vert_attribs_to_set,
                        vert_data)

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_lines_src, kind='LINE')
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_lines_dest, kind='LINE')

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.aln_src_line], kind='LINE')
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.aln_dest_line], kind='LINE')

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            src_start = src_global_data[0]
            src_end = src_global_data[1]

            dest_start = dest_global_data[0]
            dest_end = dest_global_data[1]

            if self.target in {'OBJECT', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    # Get the object world matrix before we modify it here
                    item_matrix_unaltered = item.matrix_world.copy()
                    unaltered_inverse = item_matrix_unaltered.copy()
                    unaltered_inverse.invert()

                    # construct lines from the stored geometry
                    src_line = src_end - src_start
                    dest_line = dest_end - dest_start

                    # Take modifiers on the transformation item into account,
                    # in global (object) space
                    if active_item.aln_flip_direction:
                        src_line.negate()

                    # find rotational difference between source and dest lines
                    rotational_diff = src_line.rotation_difference(dest_line)
                    parallelize_lines = rotational_diff.to_matrix()
                    parallelize_lines.resize_4x4()

                    # rotate active object so line one is parallel linear,
                    # position will be corrected after this
                    item.rotation_euler.rotate(rotational_diff)
                    bpy.context.view_layer.update()

                    # put the original line starting point (before the object
                    # was rotated) into the local object space
                    src_pivot_location_local = unaltered_inverse @ src_start

                    # get final global position of pivot (source line
                    # start coords) after object rotation
                    new_global_src_pivot_coords = (
                        item.matrix_world @ src_pivot_location_local)
                    # get translation, pivot to dest
                    pivot_to_dest = (dest_start - new_global_src_pivot_coords)

                    item.location = (item.location + pivot_to_dest)
                    bpy.context.view_layer.update()

            if self.target in {'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    self.report({'WARNING'},
                                ('Warning/Experimental: mesh transforms'
                                 ' on objects with non-uniform scaling'
                                 ' are not currently supported.'))
                    # Init source mesh
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    # Get the object world matrix
                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    src_start_loc = unaltered_inverse_loc @ src_start
                    src_end_loc = unaltered_inverse_loc @ src_end

                    dest_start_loc = unaltered_inverse_loc @ dest_start
                    dest_end_loc = unaltered_inverse_loc @ dest_end

                    # Construct vectors for each line in local space
                    loc_src_line = src_end_loc - src_start_loc
                    loc_dest_line = dest_end_loc - dest_start_loc

                    # Take modifiers on the transformation item into account,
                    # in local (mesh) space
                    if active_item.aln_flip_direction:
                        loc_src_line.negate()

                    # Get translation, move source pivot to local origin
                    src_start_inv = src_start_loc.copy()
                    src_start_inv.negate()
                    src_pivot_to_loc_origin = mathutils.Matrix.Translation(
                        src_start_inv)

                    # Get edge alignment rotation (align src to dest)
                    loc_rdiff = loc_src_line.rotation_difference(loc_dest_line)
                    parallelize_lines_loc = loc_rdiff.to_matrix()
                    parallelize_lines_loc.resize_4x4()

                    # Get translation, move pivot to destination
                    pivot_to_dest_loc = mathutils.Matrix.Translation(
                        dest_start_loc)

                    loc_make_collinear = (
                        pivot_to_dest_loc @ parallelize_lines_loc
                        @ src_pivot_to_loc_origin)

                    if self.target == 'MESH_SELECTED':
                        src_mesh.transform(loc_make_collinear,
                                           filter={'SELECT'})
                    elif self.target == 'WHOLE_MESH':
                        src_mesh.transform(loc_make_collinear)
                    elif self.target == 'OBJECT_ORIGIN':
                        # Note: a target of 'OBJECT_ORIGIN' is equivalent
                        # to performing an object transf. + an inverse
                        # whole mesh level transf. To the user,
                        # the object appears to stay in the same place,
                        # while only the object's origin moves.
                        src_mesh.transform(loc_make_collinear.inverted())

                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)
                    src_mesh.free()

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            # The selected Blender objects are not compatible with the
            # requested transformation type (we can't apply a transform
            # to mesh data when there are non-mesh objects selected)
            self.report({'ERROR'},
                        ('Cannot complete: Cannot apply mesh-level'
                         ' transformations to selected non-mesh objects.'))
            return {'CANCELLED'}

        return {'FINISHED'}
Beispiel #7
0
    def execute(self, context):
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if not hasattr(self, "quick_op_target"):
            active_item = prims[addon_data.active_list_item]
        else:
            active_item = addon_data.quick_align_planes_transf
        # Gather selected Blender object(s) to apply the transform to
        multi_edit_targets = [
            item for item in bpy.context.scene.objects
            if (maplus_geom.get_select_state(item))
        ]
        # Check prerequisites for mesh level transforms, need an active/selected object
        if (self.target != 'OBJECT'
                and not (maplus_geom.get_active_object()
                         and maplus_geom.get_select_state(
                             maplus_geom.get_active_object()))):
            self.report({'ERROR'},
                        ('Cannot complete: cannot perform mesh-level transform'
                         ' without an active (and selected) object.'))
            return {'CANCELLED'}
        # Check auto grab prerequisites
        if addon_data.quick_align_planes_auto_grab_src:
            if not (maplus_geom.get_active_object()
                    and maplus_geom.get_select_state(
                        maplus_geom.get_active_object())):
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' without an active (and selected) object.'))
                return {'CANCELLED'}
            if maplus_geom.get_active_object().type != 'MESH':
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' from a non-mesh object.'))
                return {'CANCELLED'}

        # Proceed only if selected Blender objects are compatible with the transform target
        # (Do not allow mesh-level transforms when there are non-mesh objects selected)
        if not (self.target in {
                'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'
        } and [item for item in multi_edit_targets if item.type != 'MESH']):

            if not hasattr(self, "quick_op_target"):
                if (prims[active_item.apl_src_plane].kind != 'PLANE'
                        or prims[active_item.apl_dest_plane].kind != 'PLANE'):
                    self.report(
                        {'ERROR'},
                        ('Wrong operands: "Align Planes" can only operate on '
                         'two planes'))
                    return {'CANCELLED'}

            if maplus_geom.get_active_object().type == 'MESH':
                # a bmesh can only be initialized in edit mode...
                if previous_mode != 'EDIT':
                    bpy.ops.object.editmode_toggle()
                else:
                    # else we could already be in edit mode with some stale
                    # updates, exiting and reentering forces an update
                    bpy.ops.object.editmode_toggle()
                    bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, "quick_op_target"):
                if (addon_data.quick_align_planes_auto_grab_src
                        and not addon_data.quick_align_planes_set_origin_mode):
                    vert_attribs_to_set = ('plane_pt_a', 'plane_pt_b',
                                           'plane_pt_c')
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world)
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report({
                            'ERROR'
                        }, 'Cannot grab coords: non-mesh or no active object.')
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_align_planes_src, vert_attribs_to_set,
                        vert_data)

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_planes_src, kind='PLANE')
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_align_planes_dest, kind='PLANE')

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apl_src_plane], kind='PLANE')
                dest_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.apl_dest_plane], kind='PLANE')

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            if active_item.apl_alternate_pivot:
                src_pt_a = src_global_data[1]
                src_pt_b = src_global_data[0]
            else:
                src_pt_a = src_global_data[0]
                src_pt_b = src_global_data[1]
            src_pt_c = src_global_data[2]

            if active_item.apl_alternate_pivot:
                dest_pt_a = dest_global_data[1]
                dest_pt_b = dest_global_data[0]
            else:
                dest_pt_a = dest_global_data[0]
                dest_pt_b = dest_global_data[1]
            dest_pt_c = dest_global_data[2]

            # We need global data for the object operation and for creation
            # of a custom transform orientation if the user enables it.
            # construct normal vector for first (source) plane
            src_pln_ln_BA = src_pt_a - src_pt_b
            src_pln_ln_BC = src_pt_c - src_pt_b
            src_normal = src_pln_ln_BA.cross(src_pln_ln_BC)

            # Take modifiers on the transformation item into account,
            # in global (object) space
            if active_item.apl_flip_normal:
                src_normal.negate()

            # construct normal vector for second (destination) plane
            dest_pln_ln_BA = dest_pt_a - dest_pt_b
            dest_pln_ln_BC = dest_pt_c - dest_pt_b
            dest_normal = dest_pln_ln_BA.cross(dest_pln_ln_BC)

            # find rotational difference between source and dest planes
            rotational_diff = src_normal.rotation_difference(dest_normal)

            # Set up edge alignment (BA plane1 to BA plane2)
            new_lead_edge_orientation = src_pln_ln_BA.copy()
            new_lead_edge_orientation.rotate(rotational_diff)
            parallelize_edges = new_lead_edge_orientation.rotation_difference(
                dest_pln_ln_BA)

            # Create custom transform orientation, for sliding the user's
            # target along the destination face after it has been aligned.
            # We do this by making a basis matrix out of the dest plane
            # leading edge vector, the dest normal vector, and the cross
            # of those two (each vector is normalized first)
            vdest = dest_pln_ln_BA.copy()
            vdest.normalize()
            vnorm = dest_normal.copy()
            vnorm.normalize()
            # vnorm.negate()
            vcross = vdest.cross(vnorm)
            vcross.normalize()
            vcross.negate()
            orthonormal_basis_matrix = mathutils.Matrix(
                [[vcross[0], vnorm[0], vdest[0]],
                 [vcross[1], vnorm[1], vdest[1]],
                 [vcross[2], vnorm[2], vdest[2]]])
            bpy.ops.transform.create_orientation(
                name='MAPlus',
                use=active_item.apl_use_custom_orientation,
                overwrite=True)
            bpy.context.view_layer.update()
            orient_slot = [
                slot for slot in bpy.context.scene.transform_orientation_slots
                if slot.custom_orientation
                and slot.custom_orientation.name == 'MAPlus'
            ]
            if orient_slot:
                orient_slot[
                    0].custom_orientation.matrix = orthonormal_basis_matrix
            else:
                print('Error: Could not find MAPlus transform orientation...')

            if hasattr(self, 'quick_op_target'
                       ) and addon_data.quick_align_planes_set_origin_mode:
                # TODO: Refactor this feature or possibly make it a new full operator

                # This entire block is for *Set Origin* mode. It is equivalent to an
                # OBJECT_ORIGIN transformation (both OBJECT and WHOLE_MESH transforms,
                # with the mesh level transf. inverted), with a special set of SOURCE
                # verts (a triangle at the current object's origin per object)

                for item in multi_edit_targets:

                    ######## COMMON DATA ########

                    # *Set Origin* mode uses a set of 3 pts at the object's origin
                    src_pt_a = (item.matrix_world @ mathutils.Vector(
                        (1, 0.0, 0.0)))
                    src_pt_b = (item.matrix_world @ mathutils.Vector(
                        (0.0, 0.0, 0.0)))
                    src_pt_c = (item.matrix_world @ mathutils.Vector(
                        (0.0, 1, 0.0)))

                    # We have a separate/alternate storage plane for this data
                    dest_data_set_origin_mode = maplus_geom.get_modified_global_coords(
                        geometry=addon_data.
                        quick_align_planes_set_origin_mode_dest,
                        kind='PLANE')
                    dest_pt_a = dest_data_set_origin_mode[0]
                    dest_pt_b = dest_data_set_origin_mode[1]
                    dest_pt_c = dest_data_set_origin_mode[2]

                    # Set the pivot point here (co-located points on src/dest after alignment)
                    src_pivot = src_pt_b
                    dest_pivot = dest_pt_b
                    if addon_data.quick_align_planes_set_origin_mode_alt_pivot:
                        print('AAA')
                        # *Set Origin* mode uses a set of 3 pts at the object's origin
                        src_pt_a, src_pt_b = src_pt_b, src_pt_a

                        src_pivot = src_pt_a
                        dest_pivot = dest_pt_a

                    # We need global data for the object operation and for creation
                    # of a custom transform orientation if the user enables it.
                    # construct normal vector for first (source) plane
                    src_pln_ln_BA = src_pt_a - src_pt_b
                    src_pln_ln_BC = src_pt_c - src_pt_b
                    src_normal = src_pln_ln_BA.cross(src_pln_ln_BC)

                    # construct normal vector for second (destination) plane
                    dest_pln_ln_BA = dest_pt_a - dest_pt_b
                    dest_pln_ln_BC = dest_pt_c - dest_pt_b
                    dest_normal = dest_pln_ln_BA.cross(dest_pln_ln_BC)

                    # find rotational difference between source and dest planes
                    rotational_diff = src_normal.rotation_difference(
                        dest_normal)

                    # Set up edge alignment (BA plane1 to BA plane2)
                    new_lead_edge_orientation = src_pln_ln_BA.copy()
                    new_lead_edge_orientation.rotate(rotational_diff)
                    parallelize_edges = new_lead_edge_orientation.rotation_difference(
                        dest_pln_ln_BA)

                    ######## OBJECT ########

                    # Get the object world matrix before we modify it here
                    item_matrix_unaltered = item.matrix_world.copy()
                    unaltered_inverse = item_matrix_unaltered.copy()
                    unaltered_inverse.invert()

                    # Try to rotate the object by the rotational_diff
                    item.rotation_euler.rotate(rotational_diff)
                    bpy.context.view_layer.update()

                    # Parallelize the leading edges
                    item.rotation_euler.rotate(parallelize_edges)
                    bpy.context.view_layer.update()

                    # get local coords using active object as basis, in
                    # other words, determine coords of the source pivot
                    # relative to the active object's origin by reversing
                    # the active object's transf from the pivot's coords
                    local_src_pivot_coords = (unaltered_inverse @ src_pivot)

                    # find the new global location of the pivot (we access
                    # the item's matrix_world directly here since we
                    # changed/updated it earlier)
                    new_global_src_pivot_coords = (
                        item.matrix_world @ local_src_pivot_coords)
                    # figure out how to translate the object (the translation
                    # vector) so that the source pivot sits on the destination
                    # pivot's location
                    # first vector is the global/absolute distance
                    # between the two pivots
                    pivot_to_dest = (dest_pivot - new_global_src_pivot_coords)
                    item.location = (item.location + pivot_to_dest)
                    bpy.context.view_layer.update()

                    ######## MESH ########
                    self.report({'WARNING'},
                                ('Warning/Experimental: mesh transforms'
                                 ' on objects with non-uniform scaling'
                                 ' are not currently supported.'))
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    src_a_loc = unaltered_inverse_loc @ src_pt_a
                    src_b_loc = unaltered_inverse_loc @ src_pt_b
                    src_c_loc = unaltered_inverse_loc @ src_pt_c

                    dest_a_loc = unaltered_inverse_loc @ dest_pt_a
                    dest_b_loc = unaltered_inverse_loc @ dest_pt_b
                    dest_c_loc = unaltered_inverse_loc @ dest_pt_c

                    src_ba_loc = src_a_loc - src_b_loc
                    src_bc_loc = src_c_loc - src_b_loc
                    src_normal_loc = src_ba_loc.cross(src_bc_loc)

                    dest_ba_loc = dest_a_loc - dest_b_loc
                    dest_bc_loc = dest_c_loc - dest_b_loc
                    dest_normal_loc = dest_ba_loc.cross(dest_bc_loc)

                    # Set the pivot point here (co-located points on src/dest after alignment)
                    src_pivot_loc = src_b_loc
                    dest_pivot_loc = dest_b_loc
                    if addon_data.quick_align_planes_set_origin_mode_alt_pivot:
                        # *Set Origin* mode uses a set of 3 pts at the object's origin
                        print('BBB')
                        src_pivot_loc = src_a_loc
                        dest_pivot_loc = dest_a_loc

                    # Get translation, move source pivot to local origin
                    src_pivot_inv = src_pivot_loc.copy()
                    src_pivot_inv.negate()
                    src_pivot_to_loc_origin = mathutils.Matrix.Translation(
                        src_pivot_inv)

                    # Get rotational diff between planes
                    loc_rot_diff = src_normal_loc.rotation_difference(
                        dest_normal_loc)
                    parallelize_planes_loc = loc_rot_diff.to_matrix()
                    parallelize_planes_loc.resize_4x4()

                    # Get edge alignment rotation (align leading plane edges)
                    new_lead_edge_ornt_loc = (
                        parallelize_planes_loc @ src_ba_loc)
                    edge_align_loc = (new_lead_edge_ornt_loc.
                                      rotation_difference(dest_ba_loc))
                    parallelize_edges_loc = edge_align_loc.to_matrix()
                    parallelize_edges_loc.resize_4x4()

                    # Get translation, move pivot to destination
                    pivot_to_dest_loc = mathutils.Matrix.Translation(
                        dest_pivot_loc)

                    mesh_coplanar = (
                        pivot_to_dest_loc @ parallelize_edges_loc
                        @ parallelize_planes_loc @ src_pivot_to_loc_origin)

                    # Special *Set Origin* mode needs only a
                    # mesh level OBJECT_ORIGIN transform only
                    src_mesh.transform(mesh_coplanar.inverted())

                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)

            else:
                if self.target in {'OBJECT', 'OBJECT_ORIGIN'}:
                    for item in multi_edit_targets:
                        # Get the object world matrix before we modify it here
                        item_matrix_unaltered = item.matrix_world.copy()
                        unaltered_inverse = item_matrix_unaltered.copy()
                        unaltered_inverse.invert()

                        # Try to rotate the object by the rotational_diff
                        item.rotation_euler.rotate(rotational_diff)
                        bpy.context.view_layer.update()

                        # Parallelize the leading edges
                        item.rotation_euler.rotate(parallelize_edges)
                        bpy.context.view_layer.update()

                        # get local coords using active object as basis, in
                        # other words, determine coords of the source pivot
                        # relative to the active object's origin by reversing
                        # the active object's transf from the pivot's coords
                        local_src_pivot_coords = (unaltered_inverse @ src_pt_b)

                        # find the new global location of the pivot (we access
                        # the item's matrix_world directly here since we
                        # changed/updated it earlier)
                        new_global_src_pivot_coords = (
                            item.matrix_world @ local_src_pivot_coords)
                        # figure out how to translate the object (the translation
                        # vector) so that the source pivot sits on the destination
                        # pivot's location
                        # first vector is the global/absolute distance
                        # between the two pivots
                        pivot_to_dest = (dest_pt_b -
                                         new_global_src_pivot_coords)
                        item.location = (item.location + pivot_to_dest)
                        bpy.context.view_layer.update()

                if self.target in {
                        'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'
                }:
                    for item in multi_edit_targets:
                        self.report({'WARNING'},
                                    ('Warning/Experimental: mesh transforms'
                                     ' on objects with non-uniform scaling'
                                     ' are not currently supported.'))
                        src_mesh = bmesh.new()
                        src_mesh.from_mesh(item.data)

                        item_matrix_unaltered_loc = item.matrix_world.copy()
                        unaltered_inverse_loc = item_matrix_unaltered_loc.copy(
                        )
                        unaltered_inverse_loc.invert()

                        # Stored geom data in local coords
                        src_a_loc = unaltered_inverse_loc @ src_pt_a
                        src_b_loc = unaltered_inverse_loc @ src_pt_b
                        src_c_loc = unaltered_inverse_loc @ src_pt_c

                        dest_a_loc = unaltered_inverse_loc @ dest_pt_a
                        dest_b_loc = unaltered_inverse_loc @ dest_pt_b
                        dest_c_loc = unaltered_inverse_loc @ dest_pt_c

                        src_ba_loc = src_a_loc - src_b_loc
                        src_bc_loc = src_c_loc - src_b_loc
                        src_normal_loc = src_ba_loc.cross(src_bc_loc)

                        # Take modifiers on the transformation item into account,
                        # in local (mesh) space
                        if active_item.apl_flip_normal:
                            src_normal_loc.negate()

                        dest_ba_loc = dest_a_loc - dest_b_loc
                        dest_bc_loc = dest_c_loc - dest_b_loc
                        dest_normal_loc = dest_ba_loc.cross(dest_bc_loc)

                        # Get translation, move source pivot to local origin
                        src_b_inv = src_b_loc.copy()
                        src_b_inv.negate()
                        src_pivot_to_loc_origin = mathutils.Matrix.Translation(
                            src_b_inv)

                        # Get rotational diff between planes
                        loc_rot_diff = src_normal_loc.rotation_difference(
                            dest_normal_loc)
                        parallelize_planes_loc = loc_rot_diff.to_matrix()
                        parallelize_planes_loc.resize_4x4()

                        # Get edge alignment rotation (align leading plane edges)
                        new_lead_edge_ornt_loc = (
                            parallelize_planes_loc @ src_ba_loc)
                        edge_align_loc = (new_lead_edge_ornt_loc.
                                          rotation_difference(dest_ba_loc))
                        parallelize_edges_loc = edge_align_loc.to_matrix()
                        parallelize_edges_loc.resize_4x4()

                        # Get translation, move pivot to destination
                        pivot_to_dest_loc = mathutils.Matrix.Translation(
                            dest_b_loc)

                        mesh_coplanar = (
                            pivot_to_dest_loc @ parallelize_edges_loc
                            @ parallelize_planes_loc @ src_pivot_to_loc_origin)

                        if self.target == 'MESH_SELECTED':
                            src_mesh.transform(mesh_coplanar,
                                               filter={'SELECT'})
                        elif self.target == 'WHOLE_MESH':
                            src_mesh.transform(mesh_coplanar)
                        elif self.target == 'OBJECT_ORIGIN':
                            # Note: a target of 'OBJECT_ORIGIN' is equivalent
                            # to performing an object transf. + an inverse
                            # whole mesh level transf. To the user,
                            # the object appears to stay in the same place,
                            # while only the object's origin moves.
                            src_mesh.transform(mesh_coplanar.inverted())

                        bpy.ops.object.mode_set(mode='OBJECT')
                        src_mesh.to_mesh(item.data)

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            # The selected Blender objects are not compatible with the
            # requested transformation type (we can't apply a transform
            # to mesh data when there are non-mesh objects selected)
            self.report({'ERROR'},
                        ('Cannot complete: Cannot apply mesh-level'
                         ' transformations to selected non-mesh objects.'))
            return {'CANCELLED'}

        return {'FINISHED'}
Beispiel #8
0
    def execute(self, context):
        addon_data = bpy.context.scene.maplus_data
        prims = addon_data.prim_list
        previous_mode = maplus_geom.get_active_object().mode
        if not hasattr(self, "quick_op_target"):
            active_item = prims[addon_data.active_list_item]
        else:
            active_item = addon_data.quick_axis_rotate_transf
        # Gather selected Blender object(s) to apply the transform to
        multi_edit_targets = [
            item for item in bpy.context.scene.objects
            if (maplus_geom.get_select_state(item))
        ]
        # Check prerequisites for mesh level transforms, need an active/selected object
        if (self.target != 'OBJECT'
                and not (maplus_geom.get_active_object()
                         and maplus_geom.get_select_state(
                             maplus_geom.get_active_object()))):
            self.report({'ERROR'},
                        ('Cannot complete: cannot perform mesh-level transform'
                         ' without an active (and selected) object.'))
            return {'CANCELLED'}
        # Check auto grab prerequisites
        if addon_data.quick_axis_rotate_auto_grab_src:
            if not (maplus_geom.get_active_object()
                    and maplus_geom.get_select_state(
                        maplus_geom.get_active_object())):
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' without an active (and selected) object.'))
                return {'CANCELLED'}
            if maplus_geom.get_active_object().type != 'MESH':
                self.report({'ERROR'},
                            ('Cannot complete: cannot auto-grab source verts '
                             ' from a non-mesh object.'))
                return {'CANCELLED'}

        # Proceed only if selected Blender objects are compatible with the transform target
        # (Do not allow mesh-level transforms when there are non-mesh objects selected)
        if not (self.target in {
                'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'
        } and [item for item in multi_edit_targets if item.type != 'MESH']):

            if not hasattr(self, "quick_op_target"):
                if prims[active_item.axr_axis].kind != 'LINE':
                    self.report(
                        {'ERROR'},
                        ('Wrong operands: "Axis Rotate" can only operate on '
                         'a line'))
                    return {'CANCELLED'}

            if maplus_geom.get_active_object().type == 'MESH':
                # a bmesh can only be initialized in edit mode...
                if previous_mode != 'EDIT':
                    bpy.ops.object.editmode_toggle()
                else:
                    # else we could already be in edit mode with some stale
                    # updates, exiting and reentering forces an update
                    bpy.ops.object.editmode_toggle()
                    bpy.ops.object.editmode_toggle()

            # Get global coordinate data for each geometry item, with
            # modifiers applied. Grab either directly from the scene data
            # (for quick ops), or from the MAPlus primitives
            # CollectionProperty on the scene data (for advanced tools)
            if hasattr(self, 'quick_op_target'):
                if addon_data.quick_axis_rotate_auto_grab_src:
                    vert_attribs_to_set = ('line_start', 'line_end')
                    try:
                        vert_data = maplus_geom.return_selected_verts(
                            maplus_geom.get_active_object(),
                            len(vert_attribs_to_set),
                            maplus_geom.get_active_object().matrix_world)
                    except maplus_except.InsufficientSelectionError:
                        self.report({'ERROR'}, 'Not enough vertices selected.')
                        return {'CANCELLED'}
                    except maplus_except.NonMeshGrabError:
                        self.report({
                            'ERROR'
                        }, 'Cannot grab coords: non-mesh or no active object.')
                        return {'CANCELLED'}

                    maplus_geom.set_item_coords(
                        addon_data.quick_axis_rotate_src, vert_attribs_to_set,
                        vert_data)

                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=addon_data.quick_axis_rotate_src, kind='LINE')

            else:
                src_global_data = maplus_geom.get_modified_global_coords(
                    geometry=prims[active_item.axr_axis], kind='LINE')

            # These global point coordinate vectors will be used to construct
            # geometry and transformations in both object (global) space
            # and mesh (local) space
            axis_start = src_global_data[0]
            axis_end = src_global_data[1]

            # Get rotation in proper units (radians)
            if (bpy.context.scene.unit_settings.system_rotation == 'RADIANS'):
                converted_rot_amount = active_item.axr_amount
            else:
                converted_rot_amount = math.radians(active_item.axr_amount)

            if self.target in {'OBJECT', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    # (Note that there are no transformation modifiers for this
                    # transformation type, so that section is omitted here)

                    # Get the object world matrix before we modify it here
                    item_matrix_unaltered = item.matrix_world.copy()
                    unaltered_inverse = item_matrix_unaltered.copy()
                    unaltered_inverse.invert()

                    # Construct the axis vector and corresponding matrix
                    axis = axis_end - axis_start
                    axis_rot = mathutils.Matrix.Rotation(
                        converted_rot_amount, 4, axis)

                    # Perform the rotation (axis will be realigned later)
                    item.rotation_euler.rotate(axis_rot)
                    bpy.context.view_layer.update()

                    # put the original line starting point (before the object
                    # was rotated) into the local object space
                    src_pivot_location_local = unaltered_inverse @ axis_start

                    # Calculate the new pivot location (after the
                    # first rotation), so that the axis can be moved
                    # back into place
                    new_pivot_loc_global = (
                        item.matrix_world @ src_pivot_location_local)
                    pivot_to_dest = axis_start - new_pivot_loc_global

                    item.location += pivot_to_dest
                    bpy.context.view_layer.update()

            if self.target in {'MESH_SELECTED', 'WHOLE_MESH', 'OBJECT_ORIGIN'}:
                for item in multi_edit_targets:
                    self.report({'WARNING'},
                                ('Warning/Experimental: mesh transforms'
                                 ' on objects with non-uniform scaling'
                                 ' are not currently supported.'))
                    # (Note that there are no transformation modifiers for this
                    # transformation type, so that section is omitted here)

                    # Init source mesh
                    src_mesh = bmesh.new()
                    src_mesh.from_mesh(item.data)

                    # Get the object world matrix
                    item_matrix_unaltered_loc = item.matrix_world.copy()
                    unaltered_inverse_loc = item_matrix_unaltered_loc.copy()
                    unaltered_inverse_loc.invert()

                    # Stored geom data in local coords
                    axis_start_loc = unaltered_inverse_loc @ axis_start
                    axis_end_loc = unaltered_inverse_loc @ axis_end

                    # Get axis vector in local space
                    axis_loc = axis_end_loc - axis_start_loc

                    # Get translation, pivot to local origin
                    axis_start_inv = axis_start_loc.copy()
                    axis_start_inv.negate()
                    src_pivot_to_loc_origin = mathutils.Matrix.Translation(
                        axis_start_inv)
                    src_pivot_to_loc_origin.resize_4x4()

                    # Get local axis rotation
                    axis_rot_at_loc_origin = mathutils.Matrix.Rotation(
                        converted_rot_amount, 4, axis_loc)

                    # Get translation, pivot to dest
                    pivot_to_dest = mathutils.Matrix.Translation(
                        axis_start_loc)
                    pivot_to_dest.resize_4x4()

                    axis_rotate_loc = (pivot_to_dest @ axis_rot_at_loc_origin
                                       @ src_pivot_to_loc_origin)

                    if self.target == 'MESH_SELECTED':
                        src_mesh.transform(axis_rotate_loc, filter={'SELECT'})
                    elif self.target == 'WHOLE_MESH':
                        src_mesh.transform(axis_rotate_loc)
                    elif self.target == 'OBJECT_ORIGIN':
                        # Note: a target of 'OBJECT_ORIGIN' is equivalent
                        # to performing an object transf. + an inverse
                        # whole mesh level transf. To the user,
                        # the object appears to stay in the same place,
                        # while only the object's origin moves.
                        src_mesh.transform(axis_rotate_loc.inverted())

                    bpy.ops.object.mode_set(mode='OBJECT')
                    src_mesh.to_mesh(item.data)
                    src_mesh.free()

            # Go back to whatever mode we were in before doing this
            bpy.ops.object.mode_set(mode=previous_mode)

        else:
            # The selected Blender objects are not compatible with the
            # requested transformation type (we can't apply a transform
            # to mesh data when there are non-mesh objects selected)
            self.report({'ERROR'},
                        ('Cannot complete: Cannot apply mesh-level'
                         ' transformations to selected non-mesh objects.'))
            return {'CANCELLED'}

        return {'FINISHED'}