import bpy, sys, math, gpu
gpu.init()
bpy.ops.preferences.addon_enable(module='bl_ext.user_default.hardops')
from bl_ext.user_default.hardops.utility import object as hops_object
from bl_ext.user_default.hardops.utility import addon
from bl_ext.user_default.hardops.utility import geometry_nodes
from bl_ext.user_default.hardops.ui_framework.operator_ui import Master
# headless has no context.area, so the operator's viewport HUD can't draw
Master.draw = lambda self, *args, **kwargs: None
print("\nRNA_INPUTS:", geometry_nodes.compat.RNA_INPUTS)
# --- the reported crash path: get_auto_smooth ---
bpy.ops.mesh.primitive_cube_add(size=2)
target = bpy.context.active_object
target.name = 'target'
bpy.ops.object.shade_auto_smooth(angle=math.radians(37))
print("get_auto_smooth:", hops_object.get_auto_smooth(target))
assert hops_object.get_auto_smooth(target)[0] is True
assert abs(math.degrees(hops_object.get_auto_smooth(target)[1]) - 37) < 0.01
hops_object.set_auto_smooth(target, True, angle=math.radians(50))
assert abs(math.degrees(hops_object.get_auto_smooth(target)[1]) - 50) < 0.01
print("set_auto_smooth OK")
# --- the operator that crashed, on both workflows ---
def run_difference(workflow_modifier):
addon.preference().property.workflow_modifier = workflow_modifier
bpy.ops.mesh.primitive_cube_add(size=1, location=(0.5, 0, 0))
cutter = bpy.context.active_object
cutter.name = 'cutter_' + workflow_modifier
bpy.ops.object.select_all(action='DESELECT')
cutter.select_set(True)
target.select_set(True)
bpy.context.view_layer.objects.active = target
# execute() skips invoke(), which is what normally sets self.operator
result = bpy.ops.hops.bool_difference(operator='DIFFERENCE')
print(f"\nbool_difference [{workflow_modifier}] ->", result)
assert result == {'FINISHED'}, result
return cutter
run_difference('CLASSIC')
cutter = run_difference('NODES')
# --- the cut actually evaluates, before any round-trip mutation ---
dg = bpy.context.evaluated_depsgraph_get()
me = target.evaluated_get(dg).to_mesh()
print("evaluated verts:", len(me.vertices), "polys:", len(me.polygons))
assert len(me.polygons) > 6, 'boolean did not cut'
# --- inspect the geometry nodes boolean wrapper ---
for mod in target.modifiers:
wrapper = geometry_nodes.NodeBoolean.new(mod)
if not wrapper:
continue
print("\nHOPS node boolean:", mod.name)
print(" operation:", wrapper.operation)
print(" object :", wrapper.object)
print(" solver :", wrapper.solver)
print(" inset_thickness:", wrapper.inset_thickness)
print(" weld:", wrapper.weld, "merge_threshold:", wrapper.merge_threshold)
assert wrapper.operation == 'DIFFERENCE', wrapper.operation
assert wrapper.object is cutter, wrapper.object
for op in ('INTERSECT', 'UNION', 'DIFFERENCE', 'INSET', 'KNIFE'):
wrapper.operation = op
assert wrapper.operation == op, (op, wrapper.operation)
wrapper.operation = 'DIFFERENCE'
for solver in ('FLOAT', 'EXACT', 'MANIFOLD'):
wrapper.solver = solver
assert wrapper.solver == solver, (solver, wrapper.solver)
wrapper.inset_thickness = 0.25
assert abs(wrapper.inset_thickness - 0.25) < 1e-6
wrapper.weld = True
assert wrapper.weld is True
wrapper.tag_faces_name = 'hops_tag'
assert wrapper.tag_faces_name == 'hops_tag'
wrapper.material = bpy.data.materials.new('probe_mat')
assert wrapper.material.name == 'probe_mat'
print(" round trips OK")
break
else:
raise AssertionError('no HOPS node boolean modifier found on target')
# --- prop_draw_data, used by the bevel helper panel ---
data, prop = geometry_nodes.NodeBoolean.prop_draw_data(mod, 'solver')
print("\nprop_draw_data(solver):", data, prop, '->', getattr(data, prop))
assert data.bl_rna.properties[prop] is not None
data, prop = geometry_nodes.NodeBoolean.prop_draw_data(mod, 'name')
assert (data, prop) == (mod, 'name'), (data, prop)
print("\nALL CHECKS PASSED")