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.mesh.tris_convert_to_quads() #tris to quads
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
https://blenderartists.org/t/help-converting-tris-to-quads/518437
https://blenderartists.org/t/remove-doubles-and-tris-to-quads-for-all-imported-meshes-at-once/646911
https://blenderartists.org/t/converting-triangles-to-quads-on-multiple-meshes-objects/548173/7
https://blenderartists.org/t/why-doesnt-convert-triangle-to-quads-work/376707/9
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
# set quadremesher parameters
bpy.context.scene.qremesher.use_materials = True
bpy.context.scene.qremesher.adaptive_size = 100
bpy.context.scene.qremesher.target_count = 5000
bpy.context.scene.qremesher.adapt_quad_count = True
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)
# set quadremesher parameters
bpy.context.scene.qremesher.use_materials = True
bpy.context.scene.qremesher.adaptive_size = 100
bpy.context.scene.qremesher.target_count = 5000
bpy.context.scene.qremesher.adapt_quad_count = True
bpy.context.scene.qremesher.autodetect_hard_edges = True
obs = [o for o in bpy.data.objects
if o.type == 'MESH']
remesh_object(obs)
and the output:
Considering that the plugin is not open source, my provided solution might not be the ideal. Thus, if someone can come up with another solution I would be glad if he can share it.
cancel called!!!
andError: Python: RuntimeError: expected class QREMESHER_OT_remesh, function cancel to return None, not set
$\endgroup$asyncio
to just make a coroutine wait and check if the selected object has "Retopo_" in its name yet -- which means QR finished :). But I couldn't run QR from python! I talked to the dev and he said it doesn't work but I can make it work in the scripts. After debugging I got it to work by replacing theself.timer = wm.event_timer_add(0.3, window=context.window)
in thedef execute(
function in theclass QREMESHER_OT_remesh(bpy.types.Operator):
inqr_operators.py
with my own recursive timer function version of thedef modal(self, context, event):
$\endgroup$context.window_manager
wm.event_timer
&wm.modal_handler
somehow don't work correctly / at all from python. So I edited thedef modal(self, context, event):
func intodef no_modal(self, context):
-- which doesn't use modals, doesn't use events, is just a recursive func that polls with atime.sleep(0.3)
. This works because 1. QR runs an internal exe in a separate thread so it doesn't block, and 2. time.sleep does block our py script which means we can wait for the result so no need for my initial asyncio plan. $\endgroup$