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, r) cy = round(current_location, r) cz = round(current_location, r) for lo in lg_objs: lx = round(lo.location, r) ly = round(lo.location, r) lz = round(lo.location, 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$