MeshLib icon indicating copy to clipboard operation
MeshLib copied to clipboard

Boolean mapping

Open tobias-scheepers opened this issue 1 year ago • 1 comments

Hi, we are working with the python exposed boolean functions and I believe their might still be some issue with the mapping function for mapping the results:

def get_inside(mesh_a, mesh_b):
    bOperation = mm.BooleanOperation.InsideA
    bResMapper = mm.BooleanResultMapper()
    bResult = mm.boolean(mesh_a, mesh_b, bOperation, None, bResMapper)

    inner_faces = bResMapper.map(mesh_a.topology.getValidFaces(), mm.BooleanResMapObj.A)
    inner_verts = bResMapper.map(mesh_a.topology.getValidVerts(), mm.BooleanResMapObj.A)
    
    return mn.getNumpyBitSet(inner_faces), mn.getNumpyBitSet(inner_verts)

I believe the intended way of working is shown in the implementation above. However it seems the Bitsets returned allways contain all True values. And the number of values is equal or smaller then the total number of verts/faces. It seems like only the True values of the Bitset are returned instead of the full Bitset. As such we can't yet map the results on the original mesh.

tobias-scheepers avatar Apr 15 '24 11:04 tobias-scheepers

Hello!

Could you please provide meshes so we can reproduce the issue on our end?

Grantim avatar Apr 15 '24 12:04 Grantim

Sure thing. Please see the example below:

from meshlib import mrmeshpy as mm
from meshlib import mrmeshnumpy as mn

def get_inside(mesh_a, mesh_b):
    bOperation = mm.BooleanOperation.InsideA
    bResMapper = mm.BooleanResultMapper()
    bResult = mm.boolean(mesh_a, mesh_b, bOperation, None, bResMapper)

    inner_faces = bResMapper.map(mesh_a.topology.getValidFaces(), mm.BooleanResMapObj.A)
    inner_verts = bResMapper.map(mesh_a.topology.getValidVerts(), mm.BooleanResMapObj.A)
    
    return mn.getNumpyBitSet(inner_faces), mn.getNumpyBitSet(inner_verts)

torusIntersected = mm.makeTorusWithSelfIntersections(2, 1, 10, 10, None)
mm.fixSelfIntersections(torusIntersected, 0.1)

torus = mm.makeTorus(2, 1, 10, 10, None)

transVector = mm.Vector3f()
transVector.x = 0.5
transVector.y = 1
transVector.z = 1

diffXf = mm.AffineXf3f.translation(transVector)

torus2 = mm.makeTorus(2, 1, 10, 10, None)
torus2.transform(diffXf)

print(f"Valid faces: {torus.topology.getValidFaces().size()}")
print(f"Valid verts: {torus.topology.getValidVerts().size()}")

inside_faces, inside_verts = get_inside(mesh_a=torus, mesh_b=torus2)

print(f"Inside faces: {inside_faces.size}")
print(f"Inside verts: {inside_verts.size}")

print(inside_faces)
print(inside_verts)

Which outputs the following:

Valid faces: 200
Valid verts: 100
Inside faces: 154
Inside verts: 25
[ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True]
[ True  True  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True]

What I'd expected was the that the outputted bitsets would be the same length as that of getValidFaces and getValidVerts but with boolean values indicating if face or vertices comply with the boolean operation.

tobias-scheepers avatar Apr 16 '24 14:04 tobias-scheepers

Thanks!

Now I understand the problem, really BooleanResultMapper.map functions return BitSets of result mesh that correspond to given BitSet. As I understand you need to filter torus.topology.getValidFaces()

There are maps that allow to do it in c++

auto validFaces = torus.topology.getValidFaces();
const auto& cut2originMap = mapper.maps[int(BooleanResultMapper::MapObject::A)].cut2origin;
const auto& cut2newMap = mapper.maps[int(BooleanResultMapper::MapObject::A)].cut2newFaces;
for ( int i = 0; i < cut2originMap.size(); ++i )
{
    auto orgFace = cut2originMap[FaceId(i)];
    if ( !orgFace.valid() )
        continue;
    if ( !cut2newMap[FaceId(i)].valid() )
        validFaces.reset( orgFace );
} 

Unfortunately these maps are not exposed to python yet, we will expose them, an maybe add special functions in Mapper class.

For now there is workaround

def get_inside(mesh_a, mesh_b):
    bOperation = mm.BooleanOperation.InsideA
    bResMapper = mm.BooleanResultMapper()
    bResult = mm.boolean(mesh_a, mesh_b, bOperation, None, bResMapper)

    inner_faces = mesh_a.topology.getValidFaces()
    for f in inner_faces:
        bs = mm.FaceBitSet()
        bs.resize( f.get()+1)
        bs.set(f)
        if (bResMapper.map(bs, mm.BooleanResMapObj.A).count() == 0):
            inner_faces.set(f,False)
    inner_verts = mesh_a.topology.getValidVerts()
    for v in inner_verts:
        bs = mm.VertBitSet()
        bs.resize( v.get()+1)
        bs.set(v)
        if (bResMapper.map(bs, mm.BooleanResMapObj.A).count() == 0):
            inner_verts.set(v,False)
    
    return mn.getNumpyBitSet(inner_faces), mn.getNumpyBitSet(inner_verts)

surely it will do a lot of excessive computations, but it is easiest way to implement it right now

Grantim avatar Apr 16 '24 17:04 Grantim

Got it! Indeed see where this is coming from now!

tobias-scheepers avatar Apr 17 '24 07:04 tobias-scheepers