3

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:

enter image description here

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:

enter image description here

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.

ttsesm
  • 409
  • 3
  • 10
  • I am trying to do the same thing, and I think I know how to wait for the output. But my problem is I can't even run quad remesher from blender python. Are you sure your code works? When I try to run my code, or your code, I get: cancel called!!! and Error: Python: RuntimeError: expected class QREMESHER_OT_remesh, function cancel to return None, not set – Spectraljump Sep 15 '21 at 12:37
  • @Spectraljump I do not have such an issue. Btw, how did you manage to get the script to wait for the output? – ttsesm Sep 21 '21 at 09:02
  • First I was going to use 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 the self.timer = wm.event_timer_add(0.3, window=context.window) in the def execute( function in the class QREMESHER_OT_remesh(bpy.types.Operator): in qr_operators.py with my own recursive timer function version of the def modal(self, context, event): – Spectraljump Sep 21 '21 at 09:21
  • 1
    It was failing for the same reason you can't wait for results: blender context.window_manager wm.event_timer & wm.modal_handler somehow don't work correctly / at all from python.

    So I edited the def modal(self, context, event): func into def no_modal(self, context): -- which doesn't use modals, doesn't use events, is just a recursive func that polls with a time.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.

    – Spectraljump Sep 21 '21 at 09:29
  • Cool, possibly you could add your solution as an answer here. Thus, if anyone else stumbles upon this thread can have both alternatives. – ttsesm Sep 21 '21 at 09:46

0 Answers0