Various fixes to matrix calculations.
authorDima Glib <dima.glib@gmail.com>
Fri, 13 Apr 2012 09:06:09 +0000 (09:06 +0000)
committerDima Glib <dima.glib@gmail.com>
Fri, 13 Apr 2012 09:06:09 +0000 (09:06 +0000)
Added "Compensate Orientation" operator.

space_view3d_enhanced_3d_cursor.py

index 31dc5955f98b391dfc8122cc786d21cba37fda6b..282811878d20f0f88cb028d035aae7875d2cddea 100644 (file)
@@ -40,8 +40,11 @@ operator cycle, object.ray_cast() crashes if object's tessfaces were
 update()d earlier in the code. However, not update()ing the meshes
 seems to work fine -- ray_cast() does its job, and it's possible to
 access tessfaces afterwards.
-Seems like mesh.calc_tessface() is the proper way now (not reflected
-in the docs, though)
+mesh.calc_tessface() -- ? crashes too
+
+Seems like now axes are stored in columns instead of rows.
+Perhaps it's better to write utility functions to create/decompose
+matrices from/to 3D-vector axes and a translation component
 
 Breakdown:
     Addon registration
@@ -1402,14 +1405,10 @@ class EnhancedSetCursor(bpy.types.Operator):
             
             snapshot = bpy.data.objects.new("normal_snapshot", None)
             
-            m = Matrix()
             if tangential:
-                #m = Matrix((x, y, _z))
-                m = Matrix((_z, y, x))
+                m = MatrixCompose(_z, y, x, p0)
             else:
-                m = Matrix((_x, y, z))
-            m.resize_4x4()
-            m.translation[:3] = p0
+                m = MatrixCompose(_x, y, z, p0)
             snapshot.matrix_world = m
             
             snapshot.empty_draw_type = 'SINGLE_ARROW'
@@ -1616,7 +1615,8 @@ def gather_particles(**kwargs):
             elif context_mode == 'POSE':
                 active_bone = active_object.data.bones.active
                 if active_bone:
-                    active_element = active_bone.matrix_local.translation.to_3d()
+                    active_element = active_bone.\
+                        matrix_local.translation.to_3d()
                     active_element = active_object.\
                         matrix_world * active_element
                 
@@ -1680,7 +1680,7 @@ def gather_particles(**kwargs):
                 else:
                     t1 = Vector((0, 0, 1)).cross(normal)
                 t2 = t1.cross(normal)
-                normal_system = Matrix((t1, t2, normal))
+                normal_system = MatrixCompose(t1, t2, normal)
                 
                 median, bbox_center = calc_median_bbox_pivots(positions)
                 median = m * median
@@ -1702,9 +1702,9 @@ def gather_particles(**kwargs):
                 bbox_center = active_element
                 
                 normal_system = active_object.matrix_world.to_3x3()
-                normal_system[0].normalize()
-                normal_system[1].normalize()
-                normal_system[2].normalize()
+                normal_system.col[0].normalize()
+                normal_system.col[1].normalize()
+                normal_system.col[2].normalize()
             
             if context_mode not in {'EDIT_ARMATURE', 'POSE'}:
                 bpy.ops.object.mode_set(mode=prev_mode)
@@ -2150,18 +2150,19 @@ class View3DUtility:
         
         if rv3d.view_perspective == 'CAMERA':
             d = self.get_direction()
-            v3d.camera.matrix_world.translation[:3] = pos - d * rv3d.view_distance
+            v3d.camera.matrix_world.translation = pos - d * rv3d.view_distance
         else:
             if v3d.lock_object:
                 obj, bone = self._get_lock_obj_bone()
                 if bone:
                     try:
-                        bone.matrix.translation[:3] = obj.matrix_world.inverted() * pos
+                        bone.matrix.translation = \
+                            obj.matrix_world.inverted() * pos
                     except:
                         # this is some degenerate object
-                        bone.matrix.translation[:3] = pos
+                        bone.matrix.translation = pos
                 else:
-                    obj.matrix_world.translation[:3] = pos
+                    obj.matrix_world.translation = pos
             elif v3d.lock_cursor:
                 set_cursor_location(pos, v3d=v3d)
             else:
@@ -2197,7 +2198,7 @@ class View3DUtility:
     def get_matrix(self):
         m = self.get_rotation().to_matrix()
         m.resize_4x4()
-        m.translation[:3] = self.get_viewpoint()
+        m.translation = self.get_viewpoint()
         return m
     
     def get_point(self, xy, pos):
@@ -2332,7 +2333,8 @@ class SnapUtilityBase:
                     ]
                 
                 if use_object_centers:
-                    self.extra_snap_points = [obj.matrix_world.to_translation()]
+                    self.extra_snap_points = \
+                        [obj.matrix_world.to_translation()]
                 elif alt_snap:
                     pse = self.potential_snap_elements
                     n = len(pse)
@@ -2395,13 +2397,16 @@ class SnapUtilityBase:
                     direction.rotate(sys_matrix)
                     
                     if axes_of_freedom == 2:
-                        # Constrained in one axis. Find intersection with plane.
+                        # Constrained in one axis.
+                        # Find intersection with plane.
                         i_p = intersect_line_plane(a, b, start, direction)
                         if i_p is not None:
                             pos = i_p
                     elif axes_of_freedom == 1:
-                        # Constrained in two axes. Find nearest point to line.
-                        i_p = intersect_line_line(a, b, start, start + direction)
+                        # Constrained in two axes.
+                        # Find nearest point to line.
+                        i_p = intersect_line_line(a, b, start,
+                                                  start + direction)
                         if i_p is not None:
                             pos = i_p[1]
         #end if do_raycast
@@ -2454,6 +2459,7 @@ class Snap3DUtility(SnapUtilityBase):
         mesh = bpy.data.meshes.new(tmp_name)
         bm.to_mesh(mesh)
         mesh.update(calc_tessface=True)
+        #mesh.calc_tessface()
         
         self.bbox_obj = self.cache.create_temporary_mesh_obj(mesh, Matrix())
         self.bbox_obj.hide = True
@@ -2543,10 +2549,10 @@ class Snap3DUtility(SnapUtilityBase):
         
         half = (bbox[1] - bbox[0]) * 0.5
         
-        m = Matrix(((half[0], 0, 0), (0, half[1], 0), (0, 0, half[2])))
+        m = MatrixCompose(half[0], half[1], half[2])
         m = sys_matrix.to_3x3() * m
         m.resize_4x4()
-        m.translation[:3] = sys_matrix * (bbox[0] + half)
+        m.translation = sys_matrix * (bbox[0] + half)
         self.bbox_obj.matrix_world = m
         
         return self.bbox_obj
@@ -2713,11 +2719,12 @@ class Snap3DUtility(SnapUtilityBase):
         
         if self.snap_type == 'VERTEX' or self.snap_type == 'VOLUME':
             for v0 in face.vertices:
-                p0 = obj.data.vertices[v0].co
+                v = obj.data.vertices[v0]
+                p0 = v.co
                 l = (lp - p0).length_squared
                 if (L is None) or (l < L):
                     p = p0
-                    ln = obj.data.vertices[v0].normal.copy()
+                    ln = v.normal.copy()
                     #t1 = ln.cross(_ln)
                     L = l
             
@@ -2786,7 +2793,7 @@ class Snap3DUtility(SnapUtilityBase):
                         L = l
             '''
         
-        n = ln#.copy()
+        n = ln.copy()
         n.rotate(m)
         n.normalize()
         
@@ -2805,11 +2812,7 @@ class Snap3DUtility(SnapUtilityBase):
         t2 = t1.cross(n)
         t2.normalize()
         
-        matrix = Matrix()
-        matrix[0][:3] = t1
-        matrix[1][:3] = t2
-        matrix[2][:3] = n
-        matrix.translation[:3] = p
+        matrix = MatrixCompose(t1, t2, n, p)
         
         return (matrix, face_id, obj, orig_obj)
     
@@ -2934,6 +2937,8 @@ class MeshCache:
                     self.edit_object = self.__convert(
                                 obj, True, False, False)
                     #self.edit_object.data.update(calc_tessface=True)
+                    #self.edit_object.data.calc_tessface()
+                    self.edit_object.data.calc_normals()
                 return self.edit_object
         
         # A usual object. Cached data will suffice.
@@ -2953,7 +2958,10 @@ class MeshCache:
         
         self.object_cache[obj] = rco
         if rco:
-            pass#rco.data.update(calc_tessface=True)
+            #rco.data.update(calc_tessface=True)
+            #rco.data.calc_tessface()
+            rco.data.calc_normals()
+            pass
         
         return rco
     
@@ -4057,6 +4065,103 @@ class SetCursorDialog(bpy.types.Operator):
         row.prop(tfm_opts, "use_relative_coords", text="Relative")
         row.prop(v3d, "transform_orientation", text="")
 
+class CompensateOrientation(bpy.types.Operator):
+    bl_idname = "view3d.compensate_orientation"
+    bl_label = "Compensate Orientation"
+    bl_description = "Rotates active object to match axis of current "\
+        "orientation to axis of another orientation"
+    
+    axes_items = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        ('-X', '-X', '-X axis'),
+        ('-Y', '-Y', '-Y axis'),
+        ('-Z', '-Z', '-Z axis'),
+    ]
+    
+    axes_items_ = [
+        ('X', 'X', 'X axis'),
+        ('Y', 'Y', 'Y axis'),
+        ('Z', 'Z', 'Z axis'),
+        (' ', ' ', 'Same as source axis'),
+    ]
+    
+    axes_ids = {'X':0, 'Y':1, 'Z':2}
+    
+    def get_orients(self, context):
+        orients = []
+        orients.append(('GLOBAL', "Global", ""))
+        orients.append(('LOCAL', "Local", ""))
+        orients.append(('GIMBAL', "Gimbal", ""))
+        orients.append(('NORMAL', "Normal", ""))
+        orients.append(('VIEW', "View", ""))
+        
+        for orientation in context.scene.orientations:
+            name = orientation.name
+            orients.append((name, name, ""))
+        
+        return orients
+    
+    src_axis = bpy.props.EnumProperty(default='Z', items=axes_items,
+                                      name="Initial axis")
+    #src_orient = bpy.props.EnumProperty(default='GLOBAL', items=get_orients)
+    
+    dest_axis = bpy.props.EnumProperty(default=' ', items=axes_items_,
+                                       name="Final axis")
+    dest_orient = bpy.props.EnumProperty(items=get_orients,
+                                         name="Final orientation")
+    
+    @classmethod
+    def poll(cls, context):
+        return (context.area.type == 'VIEW_3D') and context.object
+    
+    def execute(self, context):
+        obj = context.object
+        scene = context.scene
+        v3d = context.space_data
+        rv3d = context.region_data
+        
+        tou = TransformOrientationUtility(scene, v3d, rv3d)
+        
+        src_matrix = tou.get_matrix(v3d.transform_orientation)
+        src_axes = MatrixDecompose(src_matrix)
+        src_axis_name = self.src_axis
+        if src_axis_name.startswith("-"):
+            src_axis_name = src_axis_name[1:]
+            src_axis = -src_axes[self.axes_ids[src_axis_name]]
+        else:
+            src_axis = src_axes[self.axes_ids[src_axis_name]]
+        
+        dest_matrix = tou.get_matrix(self.dest_orient)
+        dest_axes = MatrixDecompose(dest_matrix)
+        if self.dest_axis != ' ':
+            dest_axis_name = self.dest_axis
+        else:
+            dest_axis_name = src_axis_name
+        dest_axis = dest_axes[self.axes_ids[dest_axis_name]]
+        
+        q = src_axis.rotation_difference(dest_axis)
+        
+        m = obj.matrix_world.to_3x3()
+        m.rotate(q)
+        m.resize_4x4()
+        m.translation = obj.matrix_world.translation.copy()
+        
+        obj.matrix_world = m
+        
+        bpy.ops.ed.undo_push(message="Compensate Orientation")
+        
+        return {'FINISHED'}
+    
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self)
+    
+    @staticmethod
+    def panel_draw(self, context):
+        self.layout.operator("view3d.compensate_orientation")
+
 # ===== CURSOR MONITOR ===== #
 class CursorMonitor(bpy.types.Operator):
     '''Monitor changes in cursor location and write to history'''
@@ -4302,6 +4407,58 @@ def to_matrix4x4(orient, pos):
     m.translation = pos.to_3d()
     return m
 
+def MatrixCompose(*args):
+    size = len(args)
+    m = Matrix.Identity(size)
+    axes = m.col # m.row
+    
+    if size == 2:
+        for i in (0, 1):
+            c = args[i]
+            if isinstance(c, Vector):
+                axes[i] = c.to_2d()
+            elif hasattr(c, "__iter__"):
+                axes[i] = Vector(c).to_2d()
+            else:
+                axes[i][i] = c
+    else:
+        for i in (0, 1, 2):
+            c = args[i]
+            if isinstance(c, Vector):
+                axes[i][:3] = c.to_3d()
+            elif hasattr(c, "__iter__"):
+                axes[i][:3] = Vector(c).to_3d()
+            else:
+                axes[i][i] = c
+        
+        if size == 4:
+            c = args[3]
+            if isinstance(c, Vector):
+                m.translation = c.to_3d()
+            elif hasattr(c, "__iter__"):
+                m.translation = Vector(c).to_3d()
+    
+    return m
+
+def MatrixDecompose(m, res_size=None):
+    size = len(m)
+    axes = m.col # m.row
+    if res_size is None:
+        res_size = size
+    
+    if res_size == 2:
+        return (axes[0].to_2d(), axes[1].to_2d())
+    else:
+        x = axes[0].to_3d()
+        y = axes[1].to_3d()
+        z = (axes[2].to_3d() if size > 2 else Vector())
+        if res_size == 3:
+            return (x, y, z)
+        
+        t = (m.translation.to_3d() if size == 4 else Vector())
+        if res_size == 4:
+            return (x, y, z, t)
+
 def angle_axis_to_quat(angle, axis):
     w = math.cos(angle / 2.0)
     xyz = axis.normalized() * math.sin(angle / 2.0)
@@ -4899,6 +5056,10 @@ def update_keymap(activate):
         kmi.active = not activate
 
 def register():
+    bpy.utils.register_class(CompensateOrientation)
+    bpy.types.VIEW3D_PT_transform_orientations.append(
+        CompensateOrientation.panel_draw)
+    
     bpy.utils.register_class(SetCursorDialog)
     
     bpy.utils.register_class(NewCursor3DBookmarkLibrary)
@@ -5004,6 +5165,10 @@ def unregister():
     bpy.utils.unregister_class(AddEmptyAtCursor3DBookmark)
     
     bpy.utils.unregister_class(SetCursorDialog)
+    
+    bpy.types.VIEW3D_PT_transform_orientations.remove(
+        CompensateOrientation.panel_draw)
+    bpy.utils.unregister_class(CompensateOrientation)
 
 class DelayRegistrationOperator(bpy.types.Operator):
     bl_idname = "wm.enhanced_3d_cursor_registration"