Although I've read a Q&A for an issue very similar to mine (same error), I can't figure out what I need to change in my script to resolve it. I hate to post such a similar question, but I couldn't really take away from that what the "trick" of troubleshooting this type of error is.
I understand the cause in principle is that somewhere in the code, an attempt is being made to access data that no longer exists. What would be the steps for tracking down the offending code? I know from what the console messages that it occurs in at least one place: line 87, in check_for_matches
and I have tried changing that function in lots of different ways, but without being able to pin down the data that is expected to exist but doesn't, I'm stuck.
What my script is supposed to do is cycle through all objects having names beginning with 'B' and join to them all objects having names beginning with 'L' that are close enough. "Close enough" is defined as having origins in the same locations as the circular geometry of the meshes, but I don't think it's necessary to know how that part of the code works to understand the problem. This error occurs in the process of joining the objects together.
Here is my code:
import bpy
import bmesh
import statistics
from mathutils import Matrix
from mathutils import Vector
import sys
import logging
logging.basicConfig(level=logging.DEBUG)
context = bpy.context
active_obj = context.view_layer.objects.active
bk_objs = []
lg_objs = []
def select_only(source_obj):
bpy.ops.object.select_all(action='DESELECT') # deselect all
source_obj.select_set(True) # select just the mesh source object
bpy.context.view_layer.objects.active = source_obj # make this the active object
def select_starting_with(beginning_of_name):
beg = beginning_of_name
if type(beg) is str:
all_objs = bpy.data.objects # all objects
for obj in all_objs: # select all mesh objects with mesh data starting with a specific name
if obj.type == 'MESH': # first make sure it's a mesh
if obj.data.name.startswith(beg):
obj.select_set(True)
else:
print('Error: Expected string.')
def shift_origin(ob, x, y, z):
mw = ob.matrix_world
imw = mw.inverted()
me = ob.data
offset = Vector((x, y, z)) # offset to move origin in object's local space
me.transform(Matrix.Translation( -offset )) # move mesh negatively by the offset
ob.matrix_world.translation = mw @ offset # move object positively by the offset
def group_all_verts(object_name, group_name='Group', weight=1.0, type='REPLACE'): # type can be 'REPLACE' to set weight, 'ADD', or 'SUBSTRACT'
o = object_name
group = o.vertex_groups.new(name = group_name)
vertices_to_add = []
bpy.ops.object.mode_set(mode='OBJECT') # group.add(): cannot be called in Edit Mode
for v in o.data.vertices:
vertices_to_add.append(v.index)
try:
group.add(vertices_to_add, weight, type)
logging.debug("\t\tvertex group added")
except:
logging.error(sys.exc_info())
print('Object:', o.name, '| Group:', group.name, '| Vertices:', vertices_to_add, '| Object vertices could not be added to group.')
def assign_vertex_color(obj, vcol_map_name, rgba):
bpy.ops.object.mode_set(mode='OBJECT')
mesh = obj.data # mesh data
mesh.vertex_colors.new(name = vcol_map_name) # make a vertex color map with this name
color_layer = mesh.vertex_colors[vcol_map_name]
i = 0
for poly in mesh.polygons:
for idx in poly.loop_indices:
color_layer.data[i].color = rgba
i += 1
def prepare_bks ():
bpy.ops.object.select_all(action='DESELECT') # deselect all
select_starting_with('B')
sel_objs = bpy.context.selected_objects
for obj in sel_objs:
bk_objs.append(obj)
def check_for_matches(current_location):
global lg_objs
r = 4 # decimal places to round to
cx = round(current_location[0], r)
cy = round(current_location[1], r)
cz = round(current_location[2], r)
for lo in lg_objs:
lx = round(lo.location[0], r)
ly = round(lo.location[1], r)
lz = round(lo.location[2], r)
if cx == lx and cy == ly and cz == lz:
joinable_lgs.append(lo)
def join_lgs_to_bk(current_bk):
bpy.ops.object.select_all(action='DESELECT') # deselect all
bpy.context.view_layer.objects.active = current_bk # make this the active object
current_bk.select_set(True) # select the current BK object
for jl in joinable_lgs: # select the joinable LG objects
jl.select_set(True)
bpy.ops.object.join() # join operation using active and selected objects
def check_geom(ob, facecount, match_area, decplaces=3):
if ob.type == 'MESH': # make sure it's a mesh
context.view_layer.objects.active = ob # must be active to enter Edit Mode
bpy.ops.object.mode_set(mode='EDIT')
me = ob.data
bm = bmesh.from_edit_mesh(me).copy() # bmesh as a COPY
bm.transform(ob.matrix_world) # that copy gets converted to world coords
poles = [
v for v in bm.verts
if len(v.link_faces) == len(v.link_edges) == facecount
and all(len(f.edges) == 3 for f in v.link_faces)
]
print("Potential Poles", [p.index for p in poles])
for p in poles:
def check(f):
return (
bool(
e for e in f.edges
if p not in e.verts
and e.is_boundary
)
and
all(
len(e.other_vert(p).link_edges) == 3
for e in p.link_edges
)
)
area = sum(f.calc_area() for f in p.link_faces)
if all(check(f) for f in p.link_faces):
print('\n' + str(p.index) + " is a " + str(facecount) + " tri fan pole, with an area of " + str(area) )
if round(area, decplaces) == round(match_area, decplaces): # things to do if the area matches
bpy.ops.object.mode_set(mode='OBJECT')
v_x = []
v_y = []
v_z = []
linked_verts = [p] + [e.other_vert(p) for e in p.link_edges] # verts of this island
for v in linked_verts:
# populate lists with coordinates for each axis
v_x.append(v.co.x)
v_y.append(v.co.y)
v_z.append(v.co.z)
# average each list's values to find the center
c_x = statistics.mean(v_x)
c_y = statistics.mean(v_y)
c_z = statistics.mean(v_z)
center = [c_x, c_y, c_z] # mean of island vertices
print('Center location: ' + str(center) )
# actions to take upon finding the circle centers
check_for_matches(center)
else:
print('Cannot proceed. Active object is not a mesh.')
def prepare_lgs():
'''
=== This function will: ===
・Find LG objects by name
・Shift their object origins
・Group their vertices
・Assign a vertex color
'''
print('\n====== Attaching lgs ======')
bpy.ops.object.select_all(action='DESELECT') # deselect all
select_starting_with('L') # select objects with names starting with...
sel_objs = bpy.context.selected_objects # all selected objects
if len(sel_objs) > 0: # if any objects were selected (more than 0)
for obj in sel_objs: # do the following for all objects
select_only(obj) # select only this current object
shift_origin(obj, 0, 0, 0.074783) # shift their object origins by (distances in local XYZ)
group_all_verts(obj, 'lg')
assign_vertex_color(obj, 'lg Geometry', (1.0, 0.0, 0.0, 1.0) )
lg_objs.append(obj)
prepare_lgs()
prepare_bks()
for bk in bk_objs:
joinable_lgs = []
check_geom(bk, 48, 0.045) # params: the object, the face count, the total face area (object must be a triangle fan mesh)
join_lgs_to_bk(bk)
bpy.ops.object.mode_set(mode='OBJECT')
try: # make active again the object the user had initially had as active
context.view_layer.objects.active = active_obj
except: # unless it no longer exists because it was joined to another object
print('Could not find previously active object.')
If someone could please explain how to track down which data causes ReferenceError: StructRNA of type Object has been removed
, and how to construct a workaround, that would be much appreciated.
Since the script does need to detect some specific geometry, here's the .blend file:
(739KB)
bm.from_mesh(me)
used again in a bmesh is the (non modified) equivalent of joining a mesh. Henceforth feel this could be done very differently. The error is most likely an issue in the object lists via reference. Adding print methods is a way to debug. (Find run script in console addon (codemanx, sambler and pinkvertex example here) is another way to debug. $\endgroup$