Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

python filling hole(and stitching two holes) with same result as MeshInspector #1402

Closed
sysuyl opened this issue Jul 10, 2023 · 7 comments
Closed
Assignees

Comments

@sysuyl
Copy link

sysuyl commented Jul 10, 2023

Hello,

I'm testing stitching_two_holes function using python code which is similar to here, but got bad result in some cases. For example, (1) very narrow triangle directly linking the two hole border even after subdivision. (2) or the result mesh is exploded. I modified some filing hole configurations but cannot solve those problems. Besides, because we cannot modify the mesh's existing region when filing holes, I need to set SubdivideSettings.subdivideBorder=False(but this cannot avoid problems).

I tested the meshInspector's stitching_two_holes function, and it works correctly on my test meshes. I wonder if you can provide some sample code which can get same result as MeshInspector. With that, we can compare hole filling result with meshinspector directly when we encountered bad result. And it will be much convenient to report problems to you.

Thanks.

@sysuyl
Copy link
Author

sysuyl commented Jul 10, 2023

I have adjusted some subdivision configurations to avoid narrow triangles successfully.

But there is still exploded result on some cases. I found it occurs after function buildCylinderBetweenTwoHoles. I tried the available metrics(the default one and the getEdgeLengthStitchMetric), both failed. Besides, the MeshInspector can stitching two holes(triangulate only) with different metrics.

Here is my test data.
test_data.zip

@Grantim Grantim self-assigned this Jul 10, 2023
@Grantim
Copy link
Contributor

Grantim commented Jul 10, 2023

Hello, can you please send code sample that you tried, it result looks very strange
image

@sysuyl
Copy link
Author

sysuyl commented Jul 10, 2023

hello, the core code is like this,

def stitch_mesh_two_holes(mesh, he1, he2):
    # Create parameters for filling hole
    params = mrmesh.StitchHolesParams()
    newFacesBitSet = mrmesh.FaceBitSet()
    params.outNewFaces = newFacesBitSet
    params.metric = mrmesh.getEdgeLengthStitchMetric(mesh)

    mrmesh.buildCylinderBetweenTwoHoles(mesh, he1, he2, params) 

    return mesh, newFacesBitSet

Yes, the result looks very strange. Let me try debugging again, I have just discovered some new details.

@Grantim
Copy link
Contributor

Grantim commented Jul 10, 2023

I have checked

from meshlib import mrmeshpy as mm

mesh = mm.loadMesh("test_input.ply")

holes = mesh.topology.findHoleRepresentiveEdges()

newFacesBitSet= mm.FaceBitSet()
params = mm.StitchHolesParams()
params.outNewFaces = newFacesBitSet
params.metric = mm.getEdgeLengthStitchMetric(mesh)
mm.buildCylinderBetweenTwoHoles(mesh,holes[0],holes[1], params)

# subdivide new faces only
settings = mm.SubdivideSettings()
settings.maxEdgeLen = 0.2
settings.maxEdgeSplits = 100000
settings.maxDeviationAfterFlip = 0.2
settings.region = newFacesBitSet
# accum new verts after subdivide for optional curvature positioning
newVertsBitSet = mm.VertBitSet()
settings.newVerts = newVertsBitSet

mm.subdivideMesh(mesh, settings)

# optional positioning
mm.positionVertsSmoothly(mesh, newVertsBitSet)

mm.saveMesh(mesh, mm.Path("res.ply"))

and got such result
image
res.zip

@Grantim
Copy link
Contributor

Grantim commented Jul 10, 2023

from meshlib import mrmeshpy as mm


def stitch(mesh, he1, he2):
	# Create parameters for filling hole
    params = mm.StitchHolesParams()
    newFacesBitSet = mm.FaceBitSet()
    params.outNewFaces = newFacesBitSet
    params.metric = mm.getEdgeLengthStitchMetric(mesh)

    mm.buildCylinderBetweenTwoHoles(mesh, he1, he2, params) 

    return mesh, newFacesBitSet


mesh1 = mm.loadMesh("test_input.ply")

holes = mesh1.topology.findHoleRepresentiveEdges()
mesh, newFacesBitSet = stitch(mesh1,holes[0],holes[1])

# subdivide new faces only
settings = mm.SubdivideSettings()
settings.maxEdgeLen = 0.2
settings.maxEdgeSplits = 100000
settings.maxDeviationAfterFlip = 0.2
settings.region = newFacesBitSet
# accum new verts after subdivide for optional curvature positioning
newVertsBitSet = mm.VertBitSet()
settings.newVerts = newVertsBitSet

mm.subdivideMesh(mesh, settings)

# optional positioning
mm.positionVertsSmoothly(mesh, newVertsBitSet)

mm.saveMesh(mesh, mm.Path("res2.ply"))

gave the same result as #1402 (comment)

@sysuyl
Copy link
Author

sysuyl commented Jul 10, 2023

Hello,

I found that the problem may be from the numpy_mrmesh_conversion code(in my test code below). The conversion is good for most of meshes I tested, but failed with the one here. I saved the testing mesh to npy format to show the problem, because the conversion will be correct after writing and reading back with ply format.

The test mesh data(npy format),
test_mesh_npy.zip

Below is my test code. It reads the npy mesh, and prints some mesh information to compare. The problem is, if directly converting the npy mesh using numpy_mrmesh_conversion code(mrmesh_from_numpy, mrmesh_to_numpy), the mesh will change a lot and look strange.

import numpy as np


def read_mesh_trimesh(filename):
    import trimesh

    mesh = trimesh.load(filename)
    v, f = mesh.vertices, mesh.faces
    v = np.array(v)[:, :3].astype(np.float32)
    f = np.array(f)[:, :3].astype(np.int32)
    return v, f


def write_mesh_trimesh(filename, v, f):
    import trimesh
    mesh = trimesh.Trimesh(v, f)
    mesh.export(filename)


def read_mesh_igl(filename):
    import igl
    v, f = igl.read_triangle_mesh(filename)
    return v, f


def write_mesh_igl(filename, v, f):
    import igl
    igl.write_triangle_mesh(filename, v, f)
    return v, f


def mrmesh_from_numpy(verts, faces):
    from meshlib import mrmeshpy as mm
    import meshlib.mrmeshnumpy as mrmeshnumpy
    verts, faces = verts.astype(np.float32), faces.astype(np.int32)
    mesh = mrmeshnumpy.meshFromFacesVerts(faces, verts)
    return mesh


def mrmesh_to_numpy(mesh):
    from meshlib import mrmeshpy as mm
    import meshlib.mrmeshnumpy as mrmeshnumpy
    verts = mrmeshnumpy.getNumpyVerts(mesh)
    faces = mrmeshnumpy.getNumpyFaces(mesh.topology)
    return verts, faces


def test():
    # python 3.8.15
    # meshlib version: 2.1.9
    # trimesh version: 3.17.1
    # igl version: 2.2.1

    verts = np.load("verts.npy")
    faces = np.load("faces.npy")

    print("\n## ----- input")
    print("#v, #f: ", verts.shape, faces.shape)
    print("sum: ", np.sum(verts))
    print("mean: ", np.mean(verts, axis=0))

    # write and read with trimesh
    print("\n## ----- trimesh")
    write_mesh_trimesh("trimesh.ply", verts, faces)
    verts_tri, faces_tri = read_mesh_trimesh("trimesh.ply")
    print("#v, #f: ", verts_tri.shape, faces_tri.shape)
    print("sum: ", np.sum(verts_tri))
    print("mean: ", np.mean(verts_tri, axis=0))

    # write and read with trimesh
    print("\n## ----- igl")
    write_mesh_igl("igl.ply", verts, faces)
    verts_igl, faces_igl = read_mesh_igl("igl.ply")
    print("#v, #f: ", verts_igl.shape, faces_igl.shape)
    print("sum: ", np.sum(verts_igl))
    print("mean: ", np.mean(verts_igl, axis=0))

    # to and from mrmesh
    print("\n## ----- mrmesh1, from numpy data")
    mesh = mrmesh_from_numpy(verts, faces)
    verts_mr, faces_mr = mrmesh_to_numpy(mesh)
    print("#v, #f: ", verts_mr.shape, faces_mr.shape)
    print("sum_mr: ", np.sum(verts_mr))
    print("mean: ", np.mean(verts_mr, axis=0))
    write_mesh_trimesh("mr_numpy.ply", verts_mr, faces_mr)

    # # to and from mrmesh using the trimesh one
    print("\n## ----- mrmesh2, from trimesh read")
    mesh = mrmesh_from_numpy(verts_tri, faces_tri)
    verts_mr, faces_mr = mrmesh_to_numpy(mesh)
    print("#v, #f: ", verts_mr.shape, faces_mr.shape)
    print("sum: ", np.sum(verts_mr))
    print("mean: ", np.mean(verts_mr, axis=0))
    write_mesh_trimesh("mr_trimesh.ply", verts_mr, faces_mr)

    # # to and from mrmesh using the igl one
    print("\n## ----- mrmesh2, from igl read")
    mesh = mrmesh_from_numpy(verts_igl, faces_igl)
    verts_mr, faces_mr = mrmesh_to_numpy(mesh)
    print("#v, #f: ", verts_mr.shape, faces_mr.shape)
    print("sum: ", np.sum(verts_mr))
    print("mean: ", np.mean(verts_mr, axis=0))
    write_mesh_trimesh("mr_igl.ply", verts_mr, faces_mr)


test()

And below is the console output,

## ----- input
#v, #f:  (8828, 3) (17090, 3)
sum:  906109.3522396502
mean:  [49.09825646 17.08983528 36.45229705]

## ----- trimesh
#v, #f:  (8828, 3) (17090, 3)
sum:  906109.4
mean:  [49.098206 17.089834 36.452335]

## ----- igl
#v, #f:  (8828, 3) (17090, 3)
sum:  906109.3522396502
mean:  [49.09825646 17.08983528 36.45229705]

## ----- mrmesh1, from numpy data
#v, #f:  (8828, 3) (17074, 3)
sum_mr:  906109.3521671295
mean:  [34.19764061 34.22750484 34.21524333]

## ----- mrmesh2, from trimesh read
#v, #f:  (8828, 3) (17090, 3)
sum:  906109.3521671295
mean:  [49.09825644 17.08983529 36.45229705]

## ----- mrmesh2, from igl read
#v, #f:  (8828, 3) (17090, 3)
sum:  906109.3521671295
mean:  [49.09825644 17.08983529 36.45229705]

And below is the output files,

res.zip

@Grantim
Copy link
Contributor

Grantim commented Jul 10, 2023

We are now relay on elements order in memory when reading numpy arrays, thats why it loads incorrectly

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


verts = np.load("verts.npy")
#make 'C' order for this array
vShape = verts.shape
verts = verts.flatten().reshape(vShape)
#change dtype to int32 and make 'C' order
faces = np.load("faces.npy").astype(np.int32,order='C');

mesh = mn.meshFromFacesVerts(faces, verts)

mm.saveMesh(mesh, "mesh.ply")

Looks like this should work, anyway we will try to improve this in future.
Thanks!

@sysuyl sysuyl closed this as completed Jul 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants