0
$\begingroup$

I've an object with pivot point in the centre (0,0,0). I want to mirror it by python on the x-axis and do it for all objects in my current scene.

I tried this, but the second line seems not to be applied:

def mirror_object_along_x(obj):
  bpy.context.scene.objects.active = obj
  bpy.ops.transform.mirror(constraint_axis=(True, False, False), constraint_orientation='GLOBAL', proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1)

Or I tried also this but then the indices of the generated triangles are in the wrong order:

def mirror_object_along_x(obj):
  obj.matrix_world[0][0] *= -1

I found this answer here:

How to use a pivot point to mirror mesh with script

But it also doesn't work. I'm using Blender 2.79a.


UPDATE

To be more specific. I want to:

Loop all objects in my current scene and mirror them on the x-axis. The original object shall not be there anymore but only the mirrored one.

I try to use the approach form @batFINGER and it looks like that right now:

import bpy
import bmesh
from mathutils import Matrix

def mirror_object_along_x(obj, axis):
  '''Mirrors the x-coordinates of the object.'''
  '''obj:  the object to be mirrored'''
  '''axis: the axis to mirror the object on => x, y, z = 0, 1, 2'''

  transformationMatrix = Matrix.Scale(-1, 4, Matrix.Identity(3)[axis])
  pivotPointGlocalCoordinates = bpy.context.scene.cursor_location

  objectData = obj.data
  newBmesh = bmesh.new()
  newBmesh.from_mesh(objectData)

  bmesh.ops.transform(newBmesh, matrix=transformationMatrix, space=Matrix.Translation(-pivotPointGlocalCoordinates), verts=newBmesh.verts)

  # recalculate the normals
  bmesh.ops.recalc_face_normals(newBmesh, faces=newBmesh.faces)
  # write to mesh
  newBmesh.to_mesh(objectData)
  objectData.update()
  newBmesh.clear() # clear for the next one
  newBmesh.free() # free the memory used by bmesh


def main():
  for obj in bpy.data.objects:
    if obj.type == 'MESH':
      mirror_object_along_x(obj, 0)

main()
$\endgroup$
  • $\begingroup$ The pivot point needs to be in local coordinates. $\endgroup$ – batFINGER Nov 21 '18 at 0:44
  • $\begingroup$ You mean this line form your code: lpp = ob.matrix_world.inverted() * pp # pivot point local coords is missing? I didn't include it because I haven't seen the variable lpp being used later. Does this line of code need to be executed anyway? $\endgroup$ – Bruno Bieri Nov 21 '18 at 6:48
  • $\begingroup$ Oops cut n paste error. Edited answer. The mesh is in local coordinates, the scene cursor in global. Global = Local only when the object matrix_world is Identity. $\endgroup$ – batFINGER Nov 21 '18 at 6:56
2
$\begingroup$

Simple mirror in place about origin

To mirror about the X axis (the ZY plane) at the origin is a simple matter of negating the x vert coordinate (v.co.x = -v.co.x or v.co.x *= -1) Sample script run in edit mode, mirrors all verts.

import bpy
import bmesh

context = bpy.context
ob = context.edit_object
me = ob.data

bm = bmesh.from_edit_mesh(me)
for v in bm.verts:
    v.co.x *= -1

bmesh.update_edit_mesh(me)

Matrices

Check out the layout of a transform matrix an example of which it the 4x4 matrix ob.matrix_world . Just setting the matrix value to -1 as shown in question will not work as expected in all cases.

A transform matrix T to mirror on x axis would be identity with T[0][0] = -1 Or a scale matrix T = Matrix.Scale(-1, 3, (1, 0, 0))

T = Matrix.Identity(3)
T[0][0] = -1
bmesh.ops.transform(bm, 
        verts=bm.verts, 
        matrix=T, 
        space=Matrix.Translation(-pp))

Where pp is the point on x axis about which to mirror, Can exclude (as it's default) when mirroring about the origin pp = Vector((0, 0, 0)) or since it's default pp = Vector().

Another example from py console run in object mode, will mirror mesh about axis by transforming vertices of the mesh.vertices collection

>>> T = Matrix.Scale(-1, 4, (1, 0, 0))
>>> C.object.data.transform(T)
>>> C.object.data.update()

On an object level

>>> C.object.matrix_world *= T.to_4x4()

Finally to mirror about some arbitrary pivot point, will need to translate such that the pivot point is the origin, mirror, then translate back. This is the equivalent object level result of changing space in a bmesh operator with the translation matrix.

>>> pp = Vector((3, 0, 0))
>>> A = Matrix.Translation(-pp) * T * Matrix.Translation(pp)
>>> C.object.matrix_world *= A

this may quite likely invert some normals that will need to be fixed later.

Putting it together to mirror each mesh about a designated axis using the scene scene cursor location as pivot point, and correcting the normals. Run this script in object mode.

import bpy
from mathutils import Matrix 
import bmesh

context = bpy.context
scene = context.scene
pp = scene.cursor_location # in global coords
mesh_obs = (o for o in scene.objects if o.type == 'MESH')
bm = bmesh.new()
axis = 0 # x, y, z = 0, 1, 2
T = Matrix.Scale(-1, 4, Matrix.Identity(3)[axis])
for ob in mesh_obs:
    me = ob.data
    bm.from_mesh(me)
    lpp = ob.matrix_world.inverted() * pp # pivot point local coords
    bmesh.ops.transform(bm, matrix=T, space=Matrix.Translation(-lpp), verts=bm.verts)
    # recalculate the normals
    bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
    bm.to_mesh(me) # write to mesh
    me.update()
    bm.clear() # clear for the next one.
bm.free() # free the mem used by bmesh

Bmesh Mirror operator

bmesh.ops.mirror(...)

Test script run in edit mode. Will mirror the whole mesh on x to the scene cursor location's local x location. Creates new geometry as the mirror. Remove the original to only leave the mirrored result.

enter image description here

import bpy
import bmesh
from mathutils import Vector, Matrix
from math import radians, degrees
context = bpy.context
scene = context.scene
ob = context.edit_object
me = ob.data

bm = bmesh.from_edit_mesh(me)

pp = ob.matrix_world.inverted() * scene.cursor_location
bmesh.ops.mirror(bm, 
        geom=bm.faces[:] + bm.verts[:] + bm.edges[:],
        axis=0,  # x, y, z <==> 0, 1, 2
        matrix=Matrix.Translation(-pp), 
        merge_dist=-1 # disable so center verts dont merge.
        )
bmesh.update_edit_mesh(me)

Note the operator returns the new geometry.. ret = bmesh.ops.mirror(...) in a dictionary. new_geom = ret["geom"].

$\endgroup$
  • $\begingroup$ Thanks for your answer. The script mirror's on the x-axis as I need it. I need to have the original vertices mirrored on the x-axis. The whole "object" shall be mirrored. $\endgroup$ – Bruno Bieri Nov 11 '18 at 20:10
  • $\begingroup$ Edited for trivial case about origin. The mirror op creates new mirrored geom. The original can be removed. $\endgroup$ – batFINGER Nov 12 '18 at 7:29
  • $\begingroup$ Thanks for the update. When I try to run the script above Blender tells me AttributeError: 'NoneType' object has no attribute 'data'. It seems the data property is not known on the ob object. But I can clearly find it in the API documentation: docs.blender.org/api/current/… Do I need to switch on something? $\endgroup$ – Bruno Bieri Nov 18 '18 at 17:06
  • $\begingroup$ Test script run in edit mode. context.edit_object is chosen specifically to give that error if not run in edit mode as loading via bmesh.from_edit_mesh only works in edit mode context.edit_object is None in object mode. None is the NoneType object and has no attributes. To use in object mode use bm = bmesh.new() load in mesh with bm.from_mesh(me) and then write back with bm.to_mesh(me) $\endgroup$ – batFINGER Nov 18 '18 at 17:25
  • $\begingroup$ Thank you. I just don't understand what you mean. I tried to use bpy.ops.object.editmode_toggle() -> see updated question, to enter edit mode. But still get the same error. $\endgroup$ – Bruno Bieri Nov 19 '18 at 19:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.