1

I am trying to subdivide the following mesh which is split in 3 objects and each object is structured from quads:

enter image description here

I tried both the following two functions:

def subdivide_objects():    
    obs = [o for o in bpy.data.objects
        if o.type == 'MESH' and (o.name == "Wall" or o.name == "Ceiling" or o.name == "Floor")]
for obj in obs:
    me = obj.data
    # New bmesh
    bm = bmesh.new()
    # load the mesh
    bm.from_mesh(me)            

    bmesh.ops.subdivide_edges(bm, edges=bm.edges, cuts=12, use_grid_fill=True)

    # Write back to the mesh
    bm.to_mesh(me)
    me.update()

def post_process_objects(): bpy.ops.object.select_all(action='DESELECT') # Deselect all objects

for o in ("Wall", "Ceiling", "Floor"):
   obj = bpy.context.scene.objects.get(o)
   bpy.context.view_layer.objects.active = obj
   if obj: 
       obj.select_set(True)

       # Get into edit mode
       bpy.ops.object.mode_set(mode="EDIT")

       # Subdivide faces
       bpy.ops.mesh.subdivide(number_cuts=12)

       # Return back to object mode
       bpy.ops.object.mode_set(mode="OBJECT")

       bpy.ops.object.select_all(action='DESELECT') # Deselect all objects 

but for some reason both are not giving the desired result, but as you can see a faulty one:

enter image description here

Any idea what I am missing here (attached the .blend file)?


Update:

After applying @Lucca's solution, for some reason I am still facing the same problem on some specific parts of the plane as you can see in the following images:

enter image description here

enter image description here

It doesn't seem that I have duplicate vertices or something now thus ny idea why still the subdivide operator does not work properly.


Example for @batFINGER 's solution:

Still problematic:

enter image description here

ttsesm
  • 409
  • 3
  • 10

2 Answers2

4

It's not a problem with your code, but rather with your base objects. You have several overlapping vertices and edges in there. Take a look at this screenshot: A screenshot of two overlapping vertices

See how there is no orange line indicating connected edges to the left? If I pull the vertex point to the right, this happens: A screenshot of the overlapping vertices separated

This kind of 'malformed' model will play havoc with expected outputs.

There are quite a few vertices like that in your model. Fixing them by hand can be tedious, so it's a good idea to start with using Blender's 'Merge vertices by distance' functionality. In newer versions of Blender (I'm using 2.91, don't have a 2.8 install to check right now), this can be accessed by selecting two ore more vertices (using A to select all will do in your case), then pressing M -> By Distance: A screenshot of the 'Merge Vertices' functionality

Just repeat this for each object, then run your script again: A screenshot of the subdivided object

I assume that looks like what you expected.

I've attached a .blend file with the fix I did:

  • Thanks a lot, indeed the issue was the duplicate vertices. For completeness, in python if someone wants to achieve the same result he can use bpy.ops.mesh.remove_doubles(threshold = 0.05) in edit mode. – ttsesm Jan 18 '21 at 10:57
  • The problem with the subdivision is still chasing me finally. Check on my update in the initial post. It doesn't seem that I have any duplicate vertices or something now but for some reason parts of the plane are not subdivided. – ttsesm Feb 09 '21 at 10:42
3

Using same code from Add Cuboid.

enter image description here Test Run, first linked mesh in question, bisecting (2, 2, 2) 3x be same as running once with (8, 8, 8)

Another take on this is to use the same method as outlined in https://blender.stackexchange.com/a/102239/15543

Since all the edges are aligned in X, Y and Z can select on each and cut.

Note this is not joining the meshes, rather subdividing the axis aligned edges of each.

import bpy
import bmesh

from mathutils import Matrix

def subdivide(mesh, subs=(2, 2, 2)): bm = bmesh.new() bm.from_mesh(mesh)

axes = [axis for axis in Matrix().to_3x3()]

for cuts, axis in zip(subs, axes):
    def aligned(e):
        dir = (e.verts[1].co - e.verts[0].co).normalized()
        return abs(dir.dot(axis)) > 0.5
    if cuts == 1:
        continue
    bmesh.ops.subdivide_edges(bm,
        edges=[e for e in bm.edges if aligned(e)],
        use_grid_fill=True,
        cuts=cuts - 1)    
bm.to_mesh(mesh)

for ob in bpy.context.selected_objects: if ob.type == 'MESH': subdivide(ob.data, (2, 2, 2))

By Edge Length, rather than cuts.

enter image description here Result on first linked mesh in question using method below

enter image description here Result on problematic mesh, converted to all quads. Notice thought that the walls are cut to not match ceiling so grids do not line up. They will (as shown above) if the wall edges match the corresponding roof / ceiling edge lengths.

Instead of cutting all edges the same, can instead keep to a minimal edge length, ie the longer the edge the more the cuts.

import bpy
import bmesh
from mathutils import Matrix, Vector
from collections import defaultdict
from math import ceil

def subdivide(mesh, subs=(1, 1, 1)): bm = bmesh.new() bm.from_mesh(mesh)

axes = [axis for axis in Matrix().to_3x3()]

for min_length, axis in zip(subs, axes):
    def aligned(e):
        dir = (e.verts[1].co - e.verts[0].co).normalized()
        return abs(dir.dot(axis)) > 0.5
    edges=[e for e in bm.edges if aligned(e)]
    pool_edges = defaultdict(list)
    for e in edges:
        l = e.calc_length()
        cuts = ceil(l / min_length)
        pool_edges[cuts].append(e) 
    for cuts, edges in pool_edges.items():
        if cuts == 1:
            continue
        bmesh.ops.subdivide_edges(bm,
            edges=edges,
            use_grid_fill=True,
            cuts=cuts - 1)    
bm.to_mesh(mesh)

for ob in bpy.context.selected_objects: if ob.type == 'MESH': subdivide(ob.data, (0.5, 0.5, 0.5))

EDIT.

Re problematic mesh in question

enter image description here The face from ceiling not subdividing is a 7 edge ngon.

Having followed the progression of this there is an expectation that there are no doubles, and no ngons in the mesh. The "problematic" roof mesh shown in question has both. The floor has similar issues.

could clean by removing doubles, and removing inline verts, however this should be done prior when generating the initial floor-plan.

    bmesh.ops.remove_doubles(
            bm,
            verts=bm.verts,
            dist=1e-4,
            )
    bmesh.ops.dissolve_verts(
            bm,
            verts=[
                v for v in bm.verts
                if len(v.link_edges) == 2 and
                    abs((v.link_edges[0].other_vert(v).co - v.co).angle(
                     v.co - v.link_edges[1].other_vert(v).co)) <= 1e-4
                     ]
            )
batFINGER
  • 84,216
  • 10
  • 108
  • 233
  • thanks for the response. As usual a nice answer, however testing your code sometimes I am still getting not a correct subdivision. Check on the updated example I've added based on your solution in my initial post. – ttsesm Feb 09 '21 at 13:01
  • 1
    Edited re it being a problematic mesh. – batFINGER Feb 09 '21 at 14:12
  • Thanks a lot :-). – ttsesm Feb 09 '21 at 14:16
  • 1
    @ttsesm cheers. Have added re sub'd longer edges more than shorter. – batFINGER Feb 09 '21 at 15:31
  • That's neat, many thanks for your time. – ttsesm Feb 10 '21 at 21:28
  • could the subdivide(ob.data, (0.5, 0.5, 0.5)) function that you provide above be modified to work with a triangular mesh as well. I've tested it on a triangular mesh sample and it didn't work that well. – ttsesm Apr 21 '21 at 13:00
  • The cuts need to be integers. What is the expectation of (0.5, 0.5, 0.5) ??? .. something like https://blender.stackexchange.com/a/118497/15543 – batFINGER Apr 21 '21 at 13:05
  • If we subdivide by edge length as you do in the second version of subdivide() above, floats can be used. So the idea is the same to subdivide the big triangles by length (small edged triangles can be ingored), the longer the edge the more the cuts. The example in the link cuts the triangles by cuts rather by length. – ttsesm Apr 21 '21 at 13:34