1
$\begingroup$

I have a python script to generate a table out of simple cubes. To create the legs I've saved the coordinates of the bottom corners of the tabletop, and I'm then creating a cube at each location to begin the legs.

The issue I've run into is that setting the location of the newly created leg cube objects while still in edit mode works, but gives the wrong values when setting the location in object mode. Further, the values in object mode change intermittently if you run the script a few times.

Here's the key part of the code, while staying fully in edit mode:

    def create_legs(self):
        
        log(bpy.context.mode)
        log(self.tabletop_bottom_corners_co)
        #set_mode('OBJECT')
        log(bpy.context.mode)
        log(self.tabletop_bottom_corners_co)
        
        self.legs = []
        for co in self.tabletop_bottom_corners_co:
            leg = self.create_cube()
            self.legs.append(leg)
            leg.location = co
        
        for leg in self.legs:
            log(leg.location)

which logs

create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: <Vector (-1.0000, 0.5000, 0.0000)>
create_legs: <Vector (1.0000, 0.5000, 0.0000)>
create_legs: <Vector (1.0000, -0.5000, 0.0000)>
create_legs: <Vector (-1.0000, -0.5000, 0.0000)>

as expected. Uncommenting the #set_mode('OBJECT') so it goes into object mode first gives:

create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: OBJECT
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: <Vector (-0.5000, 0.5000, -0.5000)>
create_legs: <Vector (0.5000, 0.5000, -0.5000)>
create_legs: <Vector (0.5000, -0.5000, -0.5000)>
create_legs: <Vector (-0.5000, -0.5000, -0.5000)>

The coordinate values haven't changed but the locations of the objects are no longer set to them.

Running it a few more times I was even able to get the following with no changes:

create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: OBJECT
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: <Vector (158682362545562451968.0000, 0.0000, 0.0000)>
create_legs: <Vector (0.5000, 0.5000, -0.5000)>
create_legs: <Vector (158692495644724035584.0000, 0.0000, 0.0000)>
create_legs: <Vector (-0.5000, -0.5000, -0.5000)>

The create_cube() function is just a simple mesh creation via bmesh:

    def create_cube(self):
        '''
        creates a cube object using bmesh and returns it.
        '''
        
        # unselect all others
        for obj in bpy.context.selected_objects:
            obj.select_set(False)
        
        # old bpy.ops method
        # bpy.ops.mesh.primitive_cube_add(size=initial_size)
        
        # use the bmesh method: https://blender.stackexchange.com/questions/63546/create-a-cube-in-blender-from-python
        scene = bpy.context.scene
        # create a new mesh
        cube_mesh = bpy.data.meshes.new('Cube')
        cube_object = bpy.data.objects.new('Cube', cube_mesh)
        # add the object into the scene. to do this we now link to a collection
        scene.collection.objects.link(cube_object)

        # make it the only active and selected

        bpy.context.view_layer.objects.active = cube_object
        cube_object.select_set(True)

        # create the cube in the mesh via bmesh
        cube_bm = bmesh.new()
        bmesh.ops.create_cube(cube_bm, size=1)
        cube_bm.to_mesh(cube_mesh)
        # the bmesh is freed at the end of the script anyway
        #cube_bm.free()

        return cube_object

What's happening differently in object mode, and how can I fix this?

Update: I've narrowed it down to: changing to Object mode changes the value of co, but only in the loop.

        log(bpy.context.mode)
        log(self.tabletop_bottom_corners_co)
        #set_mode('OBJECT')
        log(bpy.context.mode)
        log(self.tabletop_bottom_corners_co)
        
        log()
        
        self.legs = []
        for co in self.tabletop_bottom_corners_co:
            leg = self.create_cube()
            self.legs.append(leg)
            leg.location = 1,1,1
            #leg.scale = self.leg_size,self.leg_size,self.leg_length
            log('co:')
            log(co)

prints

create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs:
create_legs: co:
create_legs: <Vector (-1.0000, 0.5000, 0.0000)>
create_legs: co:
create_legs: <Vector (1.0000, 0.5000, 0.0000)>
create_legs: co:
create_legs: <Vector (1.0000, -0.5000, 0.0000)>
create_legs: co:
create_legs: <Vector (-1.0000, -0.5000, 0.0000)>

as expected. But changing to object mode first (set_mode('OBJECT')) gives

create_legs: EDIT_MESH
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((-1.0, -0.5, 0.0))]
create_legs: OBJECT
create_legs: [Vector((-1.0, 0.5, 0.0)), Vector((1.0, 0.5, 0.0)), Vector((1.0, -0.5, 0.0)), Vector((0.0, 0.0, -2.3268253990238463e-10))]
create_legs:
create_legs: co:
create_legs: <Vector (-0.5000, 0.5000, -0.5000)>
create_legs: co:
create_legs: <Vector (0.5000, 0.5000, -0.5000)>
create_legs: co:
create_legs: <Vector (0.5000, -0.5000, -0.5000)>
create_legs: co:
create_legs: <Vector (-0.5000, -0.5000, -0.5000)>
create_legs:

Running log(self.tabletop_bottom_corners_co) again afterwards shows that the array contents has been updated:

create_legs: [Vector((-0.5, 0.5, -0.5)), Vector((0.5, 0.5, -0.5)), Vector((0.5, -0.5, -0.5)), Vector((-0.5, -0.5, -0.5))]

Why does iterating over the array of co change them, but only in object mode? Saving them was done as

        self.tabletop_bottom_corners_co = []
        for vert in bm.faces[4].verts:
            self.tabletop_bottom_corners_co.append(vert.co)
$\endgroup$
8
  • 1
    $\begingroup$ If you get a value like 158682362545562451968.0000 chances are something is wrong. Recommend looking up the bmesh docs on using bmesh.from_edit_mesh when in edit mode. $\endgroup$ – batFINGER Aug 29 '20 at 0:12
  • $\begingroup$ It looks like bmesh.ops.create_cube takes a 'matrix' argument. It could be the default value for this matrix is changing. Try explicitly setting it? $\endgroup$ – Ron Jensen Aug 29 '20 at 1:47
  • $\begingroup$ Thanks guys. @batFINGER, I use that elsewhere, but only after creating a cube in object mode beforehand. Do you mean creating the cube mesh in edit mode then switching back to object mode to adjust the object location? $\endgroup$ – GSGBen Aug 29 '20 at 1:58
  • $\begingroup$ @RonJensen, I tried with a mathutils.Matrix() and a mathutils.Matrix.Translation((0,0,0)), no luck. Any other ideas? $\endgroup$ – GSGBen Aug 29 '20 at 1:58
  • 1
    $\begingroup$ Fixed it! Vector(), which is what vert.co is, is an object, so variables are references to it. My list of saved coordinates was a list of references to the actual coordinates of the bottom four vertices on the bmesh, a bmesh that was removed when I left edit mode. The intermittent behaviour was the nature of accessing freed memory. I'll update the answer with the fix. Thanks heaps for taking the time to look into this, much appreciated. $\endgroup$ – GSGBen Aug 29 '20 at 2:30
0
$\begingroup$

Fixed it! Vector(), which is what vert.co is, is an object, so variables are references to it. My list of saved coordinates was a list of references to the actual coordinates of the bottom four vertices on the bmesh, a bmesh that was removed when I left edit mode. The intermittent behaviour was the nature of accessing freed memory.

I changed

        # save the bottom face corner locations too
        self.tabletop_bottom_corners_co = []
        for vert in bm.faces[4].verts:
            self.tabletop_bottom_corners_co.append(vert.co)

to

        # save the bottom face corner locations too
        self.tabletop_bottom_corners_co = []
        for vert in bm.faces[4].verts:
            # Vector variables are references, so we need to copy them, otherwise they can change/disappear,
            # *especially* when changing modes
            co_copy = vert.co.copy()
            self.tabletop_bottom_corners_co.append(co_copy)
$\endgroup$

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.