# tris_convert_to_quads is not always working

I have a set of objects which initially come as mix of triangles,quads and ngons as you can also see in the image below:

I would like to transform them all to quads. The proposed solution that I've found is to use the bpy.ops.mesh.tris_convert_to_quads operator. However, this doesn't seem to always work, below the code snippet I used:

import bpy

C = bpy.context
scene = C.scene

for ob in C.selected_objects:
if ob.type == 'MESH':
scene.objects.active = ob #set active object
for vert in ob.data.vertices:
vert.select = True #ensure all vertices are selected
bpy.ops.object.mode_set(mode='EDIT') #switch to edit mode
bpy.ops.mesh.remove_doubles() #remove doubles
bpy.ops.object.mode_set(mode='OBJECT') #switch to object mode


I've tried to play a bit with the angle thresholds as suggested but it didn't work as well. Any idea how I can transform all triangles to quads?

Here are some of the links with the proposing solution (but all end up with the same problem that it doesn't always work):

'Tris to Quads' on narrow faces does nothing

How can I decimate a mesh to remove unnecessary triangles?

And here I attach the .blend file:

Update:

Ok, I've tried the quadremesher addon which seems to work quite nicely. I have one issue though, I have the following the script running:

import bpy
import time

bpy.context.scene.qremesher.use_materials = True
bpy.context.scene.qremesher.target_count = 5000
bpy.context.scene.qremesher.autodetect_hard_edges = True

# create list of objects
obs = [o for o in bpy.data.objects
if o.type == 'MESH']

# loop over objects and apply remesh
for ob in obs:
bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
#Set active object to variable
bpy.context.view_layer.objects.active = ob
if ob: ob.select_set(True)

print(ob.name)

# remesh object!!!!!
ret = bpy.ops.qremesher.remesh()
print('Ret: {}'.format(ret))


however the operator returns "{RUNNING MODAL}" this has an affect that only the last object is remeshed because the operator is called multiple times before the first call is finished. Thus, I would like to have the loop going to the next iteration only after the operator is finished a similar issues is described here and here. I am not sure though how to exactly apply this wrapper and callback() calls in my case with the for loop. Thus, any insight will be helpful.

Update 1:

Ok, I've managed to modify my script to work with the wrapper linked above and here is the code snippet:

import bpy

from quad_remesher_1_1 import QREMESHER_OT_remesh as op

def remesh_object(obs):
if obs:
ob = obs.pop(0)
bpy.ops.object.select_all(action='DESELECT') # Deselect all objects
#Set active object to variable
bpy.context.view_layer.objects.active = ob
if ob: ob.select_set(True)

print("Object: {}".format(ob.name))

# remesh object!!!!!
bpy.ops.qremesher.remesh()

# some callback function - here we put what shall be run after the modal is finished
def callback(ret):
print('Callback triggered: {} !!'.format(ret))

remesh_object(obs)

def modal_wrap(modal_func, callback):

def wrap(self, context, event):
ret, = retset = modal_func(self, context, event)
if ret in {'FINISHED'}: # my plugin emits the FINISHED event on finish - yours might do FINISH or CANCELED, you might have to look it up in the source code, __init__.py , there look at the modal() function for things like return {'FINISHED'} or function calls that return things alike.
print(f"{self.bl_idname} returned {ret}")
callback(ret)
return retset
return wrap

op._modal_org = op.modal
op.modal = modal_wrap(op.modal, callback)

bpy.context.scene.qremesher.use_materials = True
bpy.context.scene.qremesher.target_count = 5000