1
$\begingroup$

I'm creating some ways to 'retopologize' CAD assemblies with low-poly objects. I came up with a function that takes all the selected objects and replaces them with the active object, while retaining each original object's dimensions, rotation & location.

However I have a glitch. I can't seem to figure out why half the time one or two of the newly created objects default to (0,0,0) as their location. It doesn't make any sense to me. It only seems to happen on some occasions (not all the time). Any ideas why / how to prevent this?

import bpy

def replaceObjects():
    import bpy
    
    objects = []
    active_object = bpy.context.active_object
    
    for obj in bpy.context.selected_objects:
        # Pass if the object is active object, or is not a mesh (e.g. Camera & Light)
        if(obj.type != 'MESH' or obj == active_object):
            pass
        else:
            
            #This is an important step since objects can be offset from their origin
            bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY',center='BOUNDS')
            world_matrix = obj.matrix_world
            global_location = world_matrix.translation
            rotation = world_matrix.to_3x3().to_euler('XYZ')
            data = obj.data
            
            # Save the object's name, dimensions, location to array
            objects.append({"name": obj.name, 
            "dimensions": obj.dimensions, 
            "location": global_location,
            "rotation": rotation,
            "data": data})
            
            # Delete this object
            bpy.data.objects.remove(obj)

    tag = '.c'
    newID = 0
    for item in objects:
        
        newID += 1
        new = str(newID)
        
        n = bpy.data.objects.new(new, active_object.data)

        # when you create a new object manually this way it's not part of any collection, add it to the active collection so you can actually see it in the viewport
        bpy.context.collection.objects.link(n)
        
        # Name the object
        n.name = item["name"] + tag

        # Size the object
        n.dimensions = item["dimensions"]
        
        # Position the object
        n.location = item["location"]
        
        # Rotate the object
        n.rotation_euler = item["rotation"]
     
replaceObjects()

example_of_viewport_result

$\endgroup$
2
  • 1
    $\begingroup$ You're iterating over selected objects, but also modify selected objects by removing them… "if you mutate something you're iterating over, you're living in a state of sin and deserve whatever happens to you" - Raymond Hettinger $\endgroup$ Oct 18, 2023 at 16:49
  • $\begingroup$ @MarkusvonBroady yeah I ended up solving my problem by saving the object itself as a value within the array objects and then delete them during the second for loop. That fixed it. $\endgroup$
    – Wes Tomer
    Oct 18, 2023 at 17:27

1 Answer 1

2
$\begingroup$

I played around with it and moved the remove() operation to the end which solved the issue. I learned today not to iterate through a list while modifying it. Should have known!

This code works. I think it should be a standard feature in Blender actually - it's really fun & useful:

import bpy

def replaceObjects():
    import bpy
    
    objects = []
    active_object = bpy.context.active_object
    
    for obj in bpy.context.selected_objects:
        # Pass if the object is active object, or is not a mesh (e.g. Camera & Light)
        if(obj.type != 'MESH' or obj == active_object):
            pass
        else:
            
            #This is an important step since objects can be offset from their origin
#            bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY',center='BOUNDS')
            world_matrix = obj.matrix_world
#            global_location = world_matrix.translation
            rotation = world_matrix.to_3x3().to_euler('XYZ')
            data = obj.data
            
            global_location = obj.location
            
            # Save the object's name, dimensions, location to array
            objects.append({"name": obj.name, 
            "dimensions": obj.dimensions, 
            "location": global_location,
            "rotation": rotation,
            "data": data,
            "obj": obj})
            
            # Delete this object
            #bpy.data.objects.remove(obj)

    tag = '.c'
    newID = 0
    for item in objects:
        
        newID += 1
        new = str(newID)
        
        n = bpy.data.objects.new(new, active_object.data)

        # when you create a new object manually this way it's not part of any collection, add it to the active collection so you can actually see it in the viewport
        bpy.context.collection.objects.link(n)
        
        # Name the object
        n.name = item["name"] + tag

        # Size the object
        n.dimensions = item["dimensions"]
        
        # Position the object
        n.location = item["location"]
        
        # Rotate the object
        n.rotation_euler = item["rotation"]
        
        # Remove the original object
        bpy.data.objects.remove(item["obj"])
            
replaceObjects()
```
$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .