2
$\begingroup$

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.

$\endgroup$
5
  • $\begingroup$ 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 $\endgroup$ Sep 15, 2021 at 12:37
  • $\begingroup$ @Spectraljump I do not have such an issue. Btw, how did you manage to get the script to wait for the output? $\endgroup$
    – ttsesm
    Sep 21, 2021 at 9:02
  • $\begingroup$ 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): $\endgroup$ Sep 21, 2021 at 9:21
  • 1
    $\begingroup$ 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. $\endgroup$ Sep 21, 2021 at 9:29
  • $\begingroup$ Cool, possibly you could add your solution as an answer here. Thus, if anyone else stumbles upon this thread can have both alternatives. $\endgroup$
    – ttsesm
    Sep 21, 2021 at 9:46

0

You must log in to answer this question.

Browse other questions tagged .