Пример #1
0
def main():
    args = parse_args();

    input_meshes = [load_mesh(filename) for filename in args.input_meshes];
    output_mesh = merge_meshes(input_meshes);
    save_mesh(args.output, output_mesh,
            *output_mesh.get_attribute_names());
Пример #2
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    mesh.add_attribute("vertex_index");
    mesh.add_attribute("face_index");
    mesh.add_attribute("voxel_index");
    pymesh.save_mesh(args.output_mesh, mesh, *mesh.attribute_names);
Пример #3
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);

    f_indices = pymesh.get_degenerated_faces(mesh);
    if (len(f_indices) == 0):
        print("mesh does not have any degenerated faces");
        return;

    v_indices = mesh.faces[f_indices].ravel();
    degenerated_faces = np.zeros(mesh.num_faces);
    degenerated_faces[f_indices] = 1.0;
    degenerated = np.zeros(mesh.num_vertices);
    degenerated[v_indices] = 1.0;
    mesh.add_attribute("degenerated_faces");
    mesh.set_attribute("degenerated_faces", degenerated_faces);
    mesh.add_attribute("degenerated");
    mesh.set_attribute("degenerated", degenerated);

    print("{} degenerated faces, consisting of {} vertices.".format(
        len(f_indices), np.count_nonzero(degenerated)));
    if args.verbose:
        print("Degenerated faces indices: {}".format(f_indices));

    pymesh.save_mesh(args.output_mesh, mesh, "degenerated", "degenerated_faces");

    if args.extract_region is not None:
        region = pymesh.submesh(mesh, f_indices, 0);
        pymesh.save_mesh(args.extract_region, region,
                *region.get_attribute_names());
Пример #4
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.in_mesh);
    if (args.with_rounding):
        mesh = pymesh.form_mesh(
                np.round(mesh.vertices, args.precision),
                mesh.faces);
    intersecting_faces = pymesh.detect_self_intersection(mesh);

    counter = 0;
    while len(intersecting_faces) > 0 and counter < args.max_iterations:
        if (args.with_rounding):
            involved_vertices = np.unique(mesh.faces[intersecting_faces].ravel());
            mesh.vertices_ref[involved_vertices, :] =\
                    np.round(mesh.vertices[involved_vertices, :],
                            args.precision//2);
        mesh = pymesh.resolve_self_intersection(mesh, "igl");
        mesh, __ = pymesh.remove_duplicated_faces(mesh, fins_only=True);
        if (args.with_rounding):
            mesh = pymesh.form_mesh(
                    np.round(mesh.vertices, args.precision),
                    mesh.faces);
        intersecting_faces = pymesh.detect_self_intersection(mesh);
        counter += 1;

    if len(intersecting_faces) > 0:
        logging.warn("Resolving failed: max iteration reached!");

    pymesh.save_mesh(args.out_mesh, mesh);
Пример #5
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    if (mesh.vertex_per_face != 4):
        raise IOError("input mesh is not a quad mesh");
    mesh = pymesh.quad_to_tri(mesh, args.keep_symmetry);
    pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
Пример #6
0
def main():
    args = parse_args();

    mesh = load_mesh(args.mesh_in);
    comps = separate_mesh(mesh, args.connectivity_type);

    if args.highlight:
        if (args.connectivity_type == "face"):
            comp_indicator = np.zeros(mesh.num_faces);
        elif (args.connectivity_type == "voxel"):
            comp_indicator = np.zeros(mesh.num_voxels);
        elif (mesh.num_voxels > 0):
            comp_indicator = np.zeros(mesh.num_voxels);
        else:
            comp_indicator = np.zeros(mesh.num_faces);

        for i in range(len(comps)):
            elem_sources = comps[i].get_attribute("ori_elem_index")\
                    .ravel().astype(int);
            comp_indicator[elem_sources] = i;
        mesh.add_attribute("component_index");
        mesh.set_attribute("component_index", comp_indicator);
        save_mesh(args.mesh_out, mesh, *mesh.get_attribute_names());
    else:
        basename, ext = os.path.splitext(args.mesh_out);
        for i,comp in enumerate(comps):
            filename = "{}_cc{}{}".format(basename, i, ext);
            save_mesh(filename, comp, *comp.get_attribute_names());
Пример #7
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);

    vertices = mesh.vertices;
    voxels = mesh.voxels;
    num_voxels = mesh.num_voxels;
    orientation = np.zeros(num_voxels);

    for i in range(num_voxels):
        tet = voxels[i];
        orientation[i] = pymesh.orient_3D(
                vertices[tet[1]],
                vertices[tet[0]],
                vertices[tet[2]],
                vertices[tet[3]]);
        if orientation[i] < 0:
            orientation[i] = -1;
        elif orientation[i] > 0:
            orientation[i] = 1;

    mesh.add_attribute("orientation");
    mesh.set_attribute("orientation", orientation);

    pymesh.save_mesh(args.output_mesh, mesh, "orientation");
Пример #8
0
def main():
    args = parse_args();

    mesh = pymesh.load_mesh(args.input_mesh);
    mesh.add_attribute("vertex_gaussian_curvature");
    mesh.add_attribute("vertex_mean_curvature");
    pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
Пример #9
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    grid = pymesh.VoxelGrid(args.cell_size, mesh.dim);
    grid.insert_mesh(mesh);
    out_mesh = grid.mesh;
    pymesh.save_mesh(args.output_mesh, out_mesh);
Пример #10
0
def map_boundary_to_sphere(mesh, basename):
    bd_mesh = pymesh.form_mesh(mesh.vertices, mesh.faces);
    bd_mesh, info = pymesh.remove_isolated_vertices(bd_mesh);
    bd_vertices = np.copy(bd_mesh.vertices);

    assembler = pymesh.Assembler(bd_mesh);
    L = assembler.assemble("graph_laplacian");

    # First, Laplacian smoothing to improve triangle quality.
    for i in range(100):
        c = np.mean(bd_vertices, axis=0);
        bd_vertices = bd_vertices - c;
        r = np.amax(norm(bd_vertices, axis=1));
        bd_vertices /= r;

        bd_vertices += L * bd_vertices;

        if i%10 == 0:
            sphere_mesh = pymesh.form_mesh(bd_vertices, bd_mesh.faces);
            pymesh.save_mesh("{}_flow_{:03}.msh".format(basename, i), sphere_mesh);

    # Then, run mean curvature flow.
    sphere_mesh = pymesh.form_mesh(bd_vertices, bd_mesh.faces);
    sphere_mesh = mean_curvature_flow(sphere_mesh, 100, use_graph_laplacian=True);
    pymesh.save_mesh("{}_flow_final.msh".format(basename), sphere_mesh);
    bd_vertices = sphere_mesh.vertices;

    # Lastly, project vertices onto unit sphere.
    bd_vertex_indices = info["ori_vertex_index"];
    bd_vertex_positions = np.divide(bd_vertices, norm(bd_vertices,
        axis=1).reshape((-1,1)));
    return bd_vertex_indices, bd_vertex_positions;
Пример #11
0
def main():
    args = parse_args();
    config = parse_config_file(args.config_file);
    if args.output is not None:
        config["output"] = args.output;
    mesh = tile(config);
    pymesh.save_mesh(config["output"], mesh, *mesh.get_attribute_names());
Пример #12
0
def repousse(mesh, logger):
    cell_ids = mesh.get_attribute("cell").ravel().astype(int);
    mesh.add_attribute("edge_length");
    tol = np.amax(mesh.get_attribute("edge_length")) * 0.1;

    bbox_min, bbox_max = mesh.bbox;
    scaling = 2.0 / norm(bbox_max - bbox_min);

    start_time = time();
    num_cells = np.amax(cell_ids)+1;
    results = [];
    for i in range(num_cells):
        to_keep = np.arange(mesh.num_faces, dtype=int)[cell_ids == i];
        if not np.any(to_keep):
            continue;

        cut_mesh = pymesh.submesh(mesh, to_keep, 0);
        pymesh.save_mesh("debug.msh", cut_mesh);
        cut_mesh, __ = pymesh.remove_degenerated_triangles(cut_mesh, 100);
        cut_mesh, __ = pymesh.split_long_edges(cut_mesh, tol);

        dof = cut_mesh.num_vertices;
        assembler = pymesh.Assembler(cut_mesh);
        L = assembler.assemble("laplacian");
        M = assembler.assemble("mass");

        L_rhs = M * np.ones(dof) * -0.5;
        bd_indices = cut_mesh.boundary_vertices;
        n = len(bd_indices);
        C = scipy.sparse.coo_matrix((np.ones(n),
            (np.arange(n, dtype=int), bd_indices)), shape=(n, dof));
        C_rhs = np.zeros(n);

        A = scipy.sparse.bmat([
            [-L, C.T],
            [C, None]
            ]);
        rhs = np.concatenate((L_rhs.ravel(), C_rhs));

        solver = pymesh.SparseSolver.create("SparseLU");
        solver.compute(A);
        x = solver.solve(rhs);

        z = x[:dof].reshape((-1, 1));

        vertices = np.hstack((cut_mesh.vertices, z));
        out_mesh = pymesh.form_mesh(vertices, cut_mesh.faces);
        results.append(out_mesh);

    finish_time = time();
    t = finish_time - start_time;
    logger.info("Repousse running time: {}".format(t));

    mesh = pymesh.merge_meshes(results);

    vertices = mesh.vertices[:,:2];
    mesh_2d = pymesh.form_mesh(vertices, mesh.faces);
    pymesh.save_mesh("out_2d.msh", mesh_2d)
    return mesh;
Пример #13
0
def main():
    args = parse_args()
    mesh = pymesh.load_mesh(args.input_mesh)
    wires = pymesh.wires.WireNetwork.create_from_file(args.path)
    path = chain_wires(wires)

    result = pymesh.minkowski_sum(mesh, path)
    pymesh.save_mesh(args.output_mesh, result)
Пример #14
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    mesh = pymesh.retriangulate(mesh,
            args.max_area,
            not args.no_split_boundary,
            not args.no_steiner_points);
    pymesh.save_mesh(args.output_mesh, mesh);
Пример #15
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh, drop_zero_dim=True);
    mesh,__ = pymesh.split_long_edges(mesh, 0.01);
    points = mesh.vertices[mesh.boundary_vertices,:];
    mesh = pymesh.triangulate_beta(points, args.engine);
    pymesh.save_mesh(args.output_mesh, mesh);
    pymesh.timethis.summarize();
Пример #16
0
def main():
    args = parse_args()
    mesh = pymesh.load_mesh(args.input_mesh)
    if mesh.num_voxels <= 0:
        raise RuntimeError("Input mesh contains 0 voxels.")
    if mesh.vertex_per_voxel != 4:
        raise RuntimeError("Input mesh is not a tet mesh.")
    mesh = tet_to_hex(mesh)
    pymesh.save_mesh(args.output_mesh, mesh)
Пример #17
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    out_mesh, info = pymesh.split_long_edges(mesh, args.max_edge_length);

    if mesh.has_attribute("corner_texture"):
        pymesh.map_corner_attribute(mesh, out_mesh, "corner_texture");

    pymesh.save_mesh(args.output_mesh, out_mesh, *out_mesh.attribute_names);
Пример #18
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    if (mesh.vertex_per_face != 4):
        logging.warning("Input mesh is not quad mesh.");
        pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
    else:
        mesh = pymesh.quad_to_tri(mesh, args.keep_symmetry);
        pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
Пример #19
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh, drop_zero_dim=True);
    r = pymesh.convex_hull(mesh, args.engine, args.with_timing);
    if args.with_timing:
        hull, running_time = r;
        print("Running time: {}s".format(running_time));
    else:
        hull = r;
    pymesh.save_mesh(args.output_mesh, hull, *hull.attribute_names);
Пример #20
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    bd_edges = mesh.boundary_edges;
    bd_vertices = np.unique(bd_edges.ravel());
    is_bd_vertices = np.zeros(mesh.num_vertices);
    is_bd_vertices[bd_vertices] = True;
    mesh.add_attribute("boundary_vertices");
    mesh.set_attribute("boundary_vertices", is_bd_vertices);

    pymesh.save_mesh(args.output_mesh, mesh, *mesh.attribute_names);
Пример #21
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);

    intersecting_faces = pymesh.detect_self_intersection(mesh);
    intersection_marker = np.zeros(mesh.num_faces);
    for i,face_pair in enumerate(intersecting_faces):
        intersection_marker[face_pair] = i+1;

    mesh.add_attribute("intersecting_faces");
    mesh.set_attribute("intersecting_faces", intersection_marker);
    pymesh.save_mesh(args.output_mesh, mesh, "intersecting_faces");
Пример #22
0
def main():
    args = parse_args();
    scale = np.ones(3) * args.scale;
    if args.scale_x is not None:
        scale[0] = args.scale_x;
    if args.scale_y is not None:
        scale[1] = args.scale_y;
    if args.scale_z is not None:
        scale[2] = args.scale_z;

    mesh = pymesh.load_mesh(args.input_mesh);
    mesh = pymesh.form_mesh(mesh.vertices * scale, mesh.faces, mesh.voxels);
    pymesh.save_mesh(args.output_mesh, mesh);
Пример #23
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    mesh.add_attribute("face_area");
    areas = mesh.get_attribute("face_area");
    zero_faces = areas == 0.0;

    roi = np.zeros(mesh.num_vertices);
    roi[mesh.faces[zero_faces]] = 1.0;
    mesh.add_attribute("zero_faces");
    mesh.set_attribute("zero_faces", roi);

    pymesh.save_mesh(args.output_mesh, mesh, "zero_faces");
Пример #24
0
def main():
    args = parse_args();
    mesh1 = pymesh.load_mesh(args.input_mesh_1);
    mesh2 = pymesh.load_mesh(args.input_mesh_2);
    if args.timing:
        mesh, t = pymesh.boolean(mesh1, mesh2, args.operation, args.engine,
                with_timing = args.timing,
                exact_mesh_file = args.exact);
        print("Timing: {}".format(t));
    else:
        mesh = pymesh.boolean(mesh1, mesh2, args.operation, args.engine,
                exact_mesh_file = args.exact);
    pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
Пример #25
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    assert(mesh.has_attribute("corner_texture"));

    mesh.enable_connectivity();
    vertices = np.copy(mesh.vertices);

    bad_vertex = np.logical_not(np.all(np.isfinite(mesh.vertices), axis=1));
    bad_vertex_indices = np.arange(mesh.num_vertices, dtype=int)[bad_vertex];
    for i in bad_vertex_indices:
        adj_v = mesh.get_vertex_adjacent_vertices(i);
        adj_v = adj_v[np.logical_not(bad_vertex[adj_v])];
        vertices[i] = np.mean(vertices[adj_v,:], axis=0);

    out_mesh = pymesh.form_mesh(vertices, mesh.faces);

    if mesh.has_attribute("corner_texture"):
        faces = mesh.faces;
        uv = np.copy(mesh.get_attribute("corner_texture").reshape((-1, 2)));
        bad_uv = np.logical_not(np.all(np.isfinite(uv), axis=1));
        bad_uv_indices = np.arange(len(uv), dtype=int)[bad_uv];
        for i in bad_uv_indices:
            fi = i // mesh.vertex_per_face;
            ci = i % mesh.vertex_per_face;
            vi = faces[fi, ci];
            adj_f = mesh.get_vertex_adjacent_faces(vi);
            adj_c = [adj_f[j]*mesh.vertex_per_face +
                    np.argwhere(f==vi).ravel()[0]
                    for j,f in enumerate(faces[adj_f, :])];
            adj_c = np.array(adj_c, dtype=int);
            adj_c = adj_c[np.logical_not(bad_uv[adj_c])];
            if len(adj_c) > 0:
                uv[i] = np.mean(uv[adj_c,:], axis=0);
            else:
                # All corner uv at this vertex is NaN. Average the one-ring instead.
                adj_c = [adj_f[j]*mesh.vertex_per_face +
                        (np.argwhere(f==vi).ravel()[0]+1)%mesh.vertex_per_face
                        for j,f in enumerate(faces[adj_f, :])];
                adj_c += [adj_f[j]*mesh.vertex_per_face +
                        (np.argwhere(f==vi).ravel()[0]+2)%mesh.vertex_per_face
                        for j,f in enumerate(faces[adj_f, :])];
                adj_c = np.array(adj_c, dtype=int);
                adj_c = adj_c[np.logical_not(bad_uv[adj_c])];
                uv[i] = np.mean(uv[adj_c,:], axis=0);

        out_mesh.add_attribute("corner_texture");
        out_mesh.set_attribute("corner_texture", uv);

    pymesh.save_mesh(args.output_mesh, out_mesh);
Пример #26
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);

    formula = Formula(mesh);
    values = formula.eval_formula(args.formula);
    if isinstance(values, list):
        values = np.array(values, dtype=float);
    elif isinstance(values, (int, float)):
        values = np.ones(mesh.num_elements, dtype=float) * values;
    mesh.add_attribute(args.name);
    mesh.set_attribute(args.name, values);

    pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names());
Пример #27
0
def main():
    args = parse_args();
    mesh_1 = pymesh.load_mesh(args.input_mesh_1);
    mesh_2 = pymesh.load_mesh(args.input_mesh_2);
    assert(mesh_1.dim == 3);
    assert(mesh_2.dim == 3);

    bbox_min_1, bbox_max_1 = mesh_1.bbox;
    bbox_min_2, bbox_max_2 = mesh_2.bbox;

    bbox_min = np.minimum(bbox_min_1, bbox_min_2);
    bbox_max = np.maximum(bbox_max_1, bbox_max_2);

    #queries = grid_sample(bbox_min, bbox_max, args.num_samples);
    queries = random_sample(bbox_min, bbox_max, args.num_samples);

    winding_number_1 = pymesh.compute_winding_number(mesh_1, queries,
            engine=args.winding_number_engine) > 0.5;
    winding_number_2 = pymesh.compute_winding_number(mesh_2, queries,
            engine=args.winding_number_engine) > 0.5;

    diff = np.logical_xor(winding_number_1, winding_number_2);
    num_diff = np.count_nonzero(diff);
    print("Winding numbers of {} out of {} samples differ".format(
        num_diff, len(queries)));

    if args.output is not None:
        r = np.amax(bbox_max - bbox_min) * 0.01;
        box = pymesh.generate_box_mesh(np.ones(3) * -r, np.ones(3) * r);

        vertices = [];
        faces = [];
        for i in range(len(queries)):
            vertices.append(box.vertices + queries[i]);
            faces.append(box.faces + box.num_vertices * i);
        vertices = np.vstack(vertices);
        faces = np.vstack(faces);
        mesh = pymesh.form_mesh(vertices, faces);
        mesh.add_attribute("diff");
        mesh.set_attribute("diff", np.repeat(diff, box.num_faces));
        pymesh.save_mesh(args.output, mesh, "diff");

    if args.export:
        info = load_info(args.input_mesh_2);
        info["diff"] = num_diff
        dump_info(args.input_mesh_2, info);

    if args.timing:
        pymesh.timethis.summarize();
Пример #28
0
def main():
    args = parse_args();
    in_mesh = load_mesh(args.input_mesh);
    in_mesh.add_attribute("vertex_normal");
    v_normals = in_mesh.get_vertex_attribute("vertex_normal");

    out_mesh = form_mesh(in_mesh.vertices, np.zeros((0, 3), dtype=int));
    out_mesh.add_attribute("nx");
    out_mesh.add_attribute("ny");
    out_mesh.add_attribute("nz");
    out_mesh.set_attribute("nx", v_normals[:,0].ravel());
    out_mesh.set_attribute("ny", v_normals[:,1].ravel());
    out_mesh.set_attribute("nz", v_normals[:,2].ravel());

    save_mesh(args.output_mesh, out_mesh, "nx", "ny", "nz");
Пример #29
0
def main():
    args = parse_args();
    #  Config 0     Config 1     Config 2     Config 3
    # 1 +----+ 2   3 +----+ 2   1 +----+ 0   3 +    + 0
    #   |    |            |       |            |    |
    # 0 +    + 3   0 +----+ 1   2 +----+ 3   2 +----+ 1
    configs = np.array([
            [[0, 3],[1, 2]],
            [[0, 1],[3, 2]],
            [[2, 3],[1, 0]],
            [[2, 1],[3, 0]] ], dtype=int);
    config_transform = np.array([
            [1, 0, 0, 2],
            [0, 1, 1, 3],
            [3, 2, 2, 0],
            [2, 3, 3, 1] ], dtype=int);

    pts = np.zeros((4**args.resolution, 2));

    for x in range(2**args.resolution):
        for y in range(2**args.resolution):
            X = x;
            Y = y;
            p = np.copy([x,y]);
            quadrant = 0;
            index = 0;
            for order in range(args.resolution):
                exp = args.resolution-order-1;
                mask = 1 << exp;
                step = 2**(args.resolution-order-1);
                i = (X & mask) >> exp;
                j = (Y & mask) >> exp;
                local_index = configs[quadrant][i][j];
                quadrant = config_transform[quadrant][local_index];
                index += local_index * step*step;
                X >> 1;
                Y >> 1;
            pts[index,:] = p;

    vertices = np.array(pts);
    edges = np.array([[i, i+1] for i in range(4**args.resolution - 1)]);
    wires = pymesh.wires.WireNetwork.create_from_data(vertices, edges);
    wires.write_to_file("tmp.obj");
    inflator = pymesh.wires.Inflator(wires);
    inflator.inflate(0.2);

    mesh = inflator.mesh;
    pymesh.save_mesh(args.output_mesh, mesh);
Пример #30
0
def main():
    args = parse_args();
    basename, ext = os.path.splitext(args.output_mesh);

    mesh = pymesh.load_mesh(args.input_mesh);
    mesh = generate_tet_mesh(mesh);
    pymesh.save_mesh("{}_source.msh".format(basename), mesh);

    bd_vertex_indices, bd_vertex_positions = map_boundary_to_sphere(
            mesh, basename);
    out_mesh = tutte_3D(mesh, bd_vertex_indices, bd_vertex_positions);

    tet_orientations = pymesh.get_tet_orientations(out_mesh);
    out_mesh.add_attribute("orientation");
    out_mesh.set_attribute("orientation", tet_orientations);
    pymesh.save_mesh(args.output_mesh, out_mesh, "orientation");
Пример #31
0
elif back != None:
    c = back

a, info = pymesh.remove_isolated_vertices(a)
b, info = pymesh.remove_isolated_vertices(b)
c, info = pymesh.remove_isolated_vertices(c)

a, info = pymesh.remove_duplicated_faces(a)
b, info = pymesh.remove_duplicated_faces(b)
c, info = pymesh.remove_duplicated_faces(c)

if a == None and b == None and c == None:
    print('No mesh to convert')
    exit()

elif a != None and b != None and c != None:
    a = pymesh.boolean(a, c, operation="intersection")
# a = pymesh.boolean(b, a, operation="intersection")

elif a != None and b != None and c == None:
    a = pymesh.boolean(a, b, operation="intersection")

elif a != None and b == None and c != None:
    a = pymesh.boolean(a, c, operation="intersection")

if a == None and b != None and c != None:
    a = pymesh.boolean(b, c, operation="intersection")

pymesh.save_mesh(save_path + "/mesh.obj", a, ascii=True)

print('Koniec')
Пример #32
0
    def __init__(self, nested_view, plane, interior_color, exterior_color,
            cut_ratio):
        super(ClippedView, self).__init__(nested_view);
        self.plane = plane;
        bbox_min, bbox_max = self.mesh.bbox;
        center = bbox_min * cut_ratio + bbox_max * (1.0 - cut_ratio);
        if plane == "+X":
            should_keep = lambda v: v[0] > center[0];
        elif plane == "+Y":
            should_keep = lambda v: v[1] > center[1];
        elif plane == "+Z":
            should_keep = lambda v: v[2] > center[2];
        elif plane == "-X":
            should_keep = lambda v: v[0] < center[0];
        elif plane == "-Y":
            should_keep = lambda v: v[1] < center[1];
        elif plane == "-Z":
            should_keep = lambda v: v[2] < center[2];
        elif isinstance(plane, list) and len(plane) == 4:
            n = np.array(plane[:3]);
            n = n / norm(n);
            should_keep = lambda v: np.dot(v-center, n) < plane[3];
        else:
            raise NotImplementedError("Unknown plane type: {}".format(plane));

        if len(self.view.voxels) > 0:
            mesh = pymesh.form_mesh(self.view.vertices, np.array([]),
                    self.view.voxels);
            mesh.add_attribute("voxel_centroid");
            mesh.add_attribute("voxel_face_index");
            voxel_face_id = mesh.get_voxel_attribute("voxel_face_index").astype(int);
            centroids = mesh.get_voxel_attribute("voxel_centroid");
            self.V_to_keep = [should_keep(v) for v in centroids];
            voxels_to_keep = self.view.voxels[self.V_to_keep];
            voxel_face_id = voxel_face_id[self.V_to_keep];
            self.mesh = pymesh.form_mesh(self.view.vertices, np.zeros((0,3)), voxels_to_keep);
            self.mesh.add_attribute("voxel_face_index");
            new_voxel_face_id = self.mesh.get_voxel_attribute("voxel_face_index").astype(int);

            old_vertex_colors = self.vertex_colors;
            new_vertex_colors = np.zeros((self.mesh.num_faces, 3, 4));
            cut_color = color_table[interior_color];
            cut_color = np.array([cut_color.red, cut_color.green,
                cut_color.blue, cut_color.alpha]);
            is_interface = np.zeros(self.mesh.num_faces, dtype=bool);
            for old_i,new_i in zip(voxel_face_id.ravel(), new_voxel_face_id.ravel()):
                if new_i >= 0 and old_i >= 0:
                    new_vertex_colors[new_i] = old_vertex_colors[old_i];
                elif new_i >= 0:
                    is_interface[new_i] = True;
                    new_vertex_colors[new_i,:] = cut_color;

            self.vertex_colors = new_vertex_colors;
            self.interface = pymesh.form_mesh(self.mesh.vertices,
                    self.mesh.faces[is_interface]);
            self.boundary = pymesh.form_mesh(self.mesh.vertices,
                    self.mesh.faces[np.logical_not(is_interface)]);
            #self.vertex_colors = new_vertex_colors[is_interface];
            tmp_dir = tempfile.gettempdir();
            m = hashlib.md5();
            m.update(self.mesh.vertices);
            name = m.hexdigest();
            bd_mesh_file = os.path.join(tmp_dir, "{}_bd.msh".format(name));
            interface_mesh_file = os.path.join(tmp_dir, "{}_interface.msh".format(name));
            pymesh.save_mesh(bd_mesh_file, self.boundary);
            pymesh.save_mesh(interface_mesh_file, self.interface);
            self.subviews = [
                    MeshView.create_from_setting({
                        "type": "mesh_only",
                        "mesh": bd_mesh_file,
                        "color": exterior_color,
                        "wire_frame": True,
                        "bbox": [self.bmin, self.bmax]
                        }),
                    MeshView.create_from_setting({
                        "type": "mesh_only",
                        "mesh": interface_mesh_file,
                        "color": interior_color,
                        "wire_frame": True,
                        "bbox": [self.bmin, self.bmax]
                        }),
                    ];
        else:
            centroids = np.mean(self.view.vertices[self.view.faces], axis=1);
            self.V_to_keep = [should_keep(v) for v in centroids];
            faces = self.view.faces[self.V_to_keep];
            self.mesh = pymesh.form_mesh(self.view.vertices, faces);

        self.mesh.add_attribute("face_normal");
Пример #33
0
def symmetrize(verts, faces, eps=1e-3):
    """
    verts: N,3 (x,y,z) tensor
    faces: F,3 (0,1,2) tensor

    Modifies mesh to make it symmetric about y-z plane
    - Cut mesh into half
    - Copy left half into right half
    - merge, remove duplicate vertices
    """
    # Snap vertices close to centre to centre
    verts_centre_mask = (verts[:, 0].abs() < eps)
    verts[verts_centre_mask, 0] = 0

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/1.obj', pmesh)

    # Categorize vertices into left (-1), centre(0), right(1)
    verts_side = torch.sign(verts[:, 0])

    # Categorize faces into left (-1), centre(0), right(1)
    face_verts_side = torch.index_select(verts_side, 0, faces.view(-1))
    face_verts_side = face_verts_side.contiguous().view(faces.shape[0], 3)
    face_left_mask = (face_verts_side[:, 0] == -1) & (
        face_verts_side[:, 1] == -1) & (face_verts_side[:, 2] == -1)
    face_right_mask = (face_verts_side[:, 0] == 1) & (
        face_verts_side[:, 1] == 1) & (face_verts_side[:, 2] == 1)
    face_intesects_yz = (~face_left_mask) & (~face_right_mask)

    # Split intersecting faces
    new_verts = []
    new_faces = []
    for f in face_intesects_yz.nonzero().squeeze(1):
        i0, i1, i2 = faces[f]
        if verts_side[i0] == verts_side[i1]:
            i0, i1, i2 = i2, i0, i1
        elif verts_side[i2] == verts_side[i1]:
            i0, i1, i2 = i0, i1, i2
        elif verts_side[i0] == verts_side[i2]:
            i0, i1, i2 = i1, i0, i2
        elif verts_side[i0] == -1:
            i0, i1, i2 = i0, i1, i2
        elif verts_side[i1] == -1:
            i0, i1, i2 = i1, i0, i2
        elif verts_side[i2] == -1:
            i0, i1, i2 = i2, i0, i1
        else:
            import ipdb
            ipdb.set_trace()

        # yz axis intersects i0->i1 & i0->i2
        assert (verts_side[i0] != verts_side[i1])
        assert (verts_side[i0] != verts_side[i2])

        v0 = verts[i0]
        v1 = verts[i1]
        v2 = verts[i2]

        v_n1 = (v0 * v1[0] - v1 * v0[0]) / (v1[0] - v0[0])
        v_n2 = (v0 * v2[0] - v2 * v0[0]) / (v2[0] - v0[0])

        i_n1 = verts.shape[0] + len(new_verts)
        i_n2 = verts.shape[0] + len(new_verts) + 1
        new_verts.append(v_n1)
        new_verts.append(v_n2)

        new_faces.append((i0, i_n1, i_n2))
        new_faces.append((i1, i_n1, i_n2))
        new_faces.append((i1, i2, i_n2))

    new_verts = torch.stack(new_verts, dim=0)
    new_faces = torch.tensor(new_faces, dtype=faces.dtype, device=faces.device)

    verts = torch.cat([verts, new_verts], dim=0)
    faces = torch.index_select(faces, 0,
                               (~face_intesects_yz).nonzero().squeeze(1))
    faces = torch.cat([faces, new_faces], dim=0)

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/2.obj', pmesh)

    # Merge vertices that are very close together
    vertex_mapping = []
    verts_new = []
    for v_id in range(verts.shape[0]):
        if v_id == 0:
            vertex_mapping.append(0)
            verts_new.append(verts[0])
            continue
        min_d, min_idx = (verts[0:v_id] - verts[v_id]).norm(dim=-1).min(dim=0)
        if min_d < eps:
            vertex_mapping.append(vertex_mapping[min_idx])
        else:
            vertex_mapping.append(len(verts_new))
            verts_new.append(verts[v_id])
    assert (len(vertex_mapping) == verts.shape[0])
    vertex_mapping = torch.tensor(vertex_mapping,
                                  dtype=faces.dtype,
                                  device=faces.device)
    verts = torch.stack(verts_new, dim=0)
    faces = vertex_mapping[faces]

    # Remove degenerate faces
    faces_degenerate = (faces[:, 0] == faces[:, 1]) | (
        faces[:, 0] == faces[:, 2]) | (faces[:, 2] == faces[:, 1])
    faces = torch.index_select(faces, 0,
                               (~faces_degenerate).nonzero().squeeze(1))

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/3.obj', pmesh)

    # Delete faces that lie on right side (verts_side==1)
    verts_centre_mask = (verts[:, 0].abs() < eps)
    verts[verts_centre_mask, 0] = 0
    verts_side = torch.sign(verts[:, 0])
    face_verts_side = torch.index_select(verts_side, 0, faces.view(-1))
    face_verts_side = face_verts_side.contiguous().view(faces.shape[0], 3)
    face_right_mask = (face_verts_side[:, 0] == 1) | (
        face_verts_side[:, 1] == 1) | (face_verts_side[:, 2] == 1)
    faces = torch.index_select(faces, 0,
                               (~face_right_mask).nonzero().squeeze(1))

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/4.obj', pmesh)

    # Flip mesh, merge
    faces_flip = faces + verts.shape[0]
    faces = torch.cat([faces, faces_flip], dim=0)
    verts_flip = verts * torch.tensor(
        [-1, 1, 1], dtype=verts.dtype, device=verts.device)
    vertex_mapping_flip = torch.arange(verts.shape[0],
                                       dtype=faces.dtype,
                                       device=faces.device)
    vertex_mapping_flip[~verts_centre_mask] += verts.shape[0]
    vertex_mapping = torch.arange(verts.shape[0],
                                  dtype=faces.dtype,
                                  device=faces.device)
    vertex_mapping = torch.cat([vertex_mapping, vertex_mapping_flip], dim=0)
    verts = torch.cat([verts, verts_flip], dim=0)
    faces = vertex_mapping[faces]

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/5.obj', pmesh)

    pymesh.save_mesh(f'csm_mesh/debug/5.8.obj', pmesh)
    numv = pmesh.num_vertices
    while True:
        pmesh, __ = pymesh.collapse_short_edges(pmesh, rel_threshold=0.4)

        verts = torch.as_tensor(pmesh.vertices,
                                dtype=verts.dtype,
                                device=verts.device)
        faces = torch.as_tensor(pmesh.faces,
                                dtype=faces.dtype,
                                device=faces.device)
        verts_centre_mask = (verts[:, 0].abs() < 1e-3)
        verts[verts_centre_mask, 0] = 0

        pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
        pmesh, __ = pymesh.remove_isolated_vertices(pmesh)
        pmesh, __ = pymesh.remove_duplicated_vertices(pmesh, tol=eps)
        pmesh, __ = pymesh.remove_duplicated_faces(pmesh)
        pmesh, __ = pymesh.remove_degenerated_triangles(pmesh)
        pmesh, __ = pymesh.remove_isolated_vertices(pmesh)
        # pmesh, __ = pymesh.remove_obtuse_triangles(pmesh, 120.0, 100)
        pmesh, __ = pymesh.collapse_short_edges(pmesh, 1e-2)
        pmesh, __ = pymesh.remove_duplicated_vertices(pmesh, tol=eps)

        if pmesh.num_vertices == numv:
            break
        numv = pmesh.num_vertices
    pymesh.save_mesh(f'csm_mesh/debug/5.9.obj', pmesh)

    verts = torch.as_tensor(pmesh.vertices,
                            dtype=verts.dtype,
                            device=verts.device)
    faces = torch.as_tensor(pmesh.faces,
                            dtype=faces.dtype,
                            device=faces.device)

    # Remove unused vertices
    vertices_used = torch.unique(faces.view(-1))
    vertex_mapping = torch.zeros((verts.shape[0], ),
                                 dtype=faces.dtype,
                                 device=faces.device)
    vertex_mapping[vertices_used] = torch.arange(vertices_used.shape[0],
                                                 dtype=faces.dtype,
                                                 device=faces.device)
    faces = vertex_mapping[faces]
    verts = verts[vertices_used]

    import pymesh
    pmesh = pymesh.form_mesh(verts.numpy(), faces.numpy())
    pymesh.save_mesh(f'csm_mesh/debug/6.obj', pmesh)

    return verts, faces
def insert(filename_in,
           filename_out='out.obj',
           data=32 * [0, 1],
           secret=123456,
           strength=100,
           partitions=-1):

    print
    print '########## Embedding started ##########'

    # Scramble the data
    scrambled_data = scramble(data, secret)

    mesh = pymesh.load_mesh(filename_in)

    if partitions == -1:
        partitions = mesh.num_vertices / 500

    # Partition the mesh into patches
    patches, mapping = partitioning.mesh_partitioning(filename_in, mesh,
                                                      partitions)

    print 'Step 1: Mesh patched'

    processed = 0
    bits_inserted = 0
    updated_vertices = []

    # Set a progress bar
    utils.progress(processed, len(patches), 'Inserting data in patches...')

    # Insert the data in all the patches
    for i, patch in enumerate(patches):

        # Get the eigenVectors, either by computing them or by reading the from a file if they already have been computed
        npy_file = 'saved_eig/' + str(partitions) + '/embedding_' + str(
            i) + '.npy'

        if os.path.exists(npy_file):
            B = np.load(npy_file)
        else:
            B = compute_eigenvectors(patch.num_vertices, patch.faces)
            np.save(npy_file, B)

        # Get the spectral coefficients
        P = np.matmul(B, patch.vertices[:, 0])
        Q = np.matmul(B, patch.vertices[:, 1])
        R = np.matmul(B, patch.vertices[:, 2])

        # Insert the data
        P, Q, R, count = embedding.write(P, Q, R, scrambled_data, strength)

        BM1 = np.linalg.pinv(B)

        new_vertices = np.zeros((patch.num_vertices, 3))

        # Retrieve the new coordinates of the vertices
        new_vertices[:, 0] = np.matmul(BM1, P)
        new_vertices[:, 1] = np.matmul(BM1, Q)
        new_vertices[:, 2] = np.matmul(BM1, R)

        updated_vertices.append(pymesh.form_mesh(new_vertices, patch.faces))

        bits_inserted += count

        # Update the progress bar
        processed += 1
        utils.progress(processed, len(patches), 'Inserting data in patches...')

    print
    print 'Step 2: Data inserted'

    if bits_inserted < len(data):
        print 'Only %s bits inserted, the bit error rate when retriving data might be significant' % (
            bits_inserted)

    new_vertices = np.zeros((mesh.num_vertices, 3))

    # Merge the different patches into a single mesh
    for i in range(mesh.num_vertices):
        for j in range(partitions):
            val = mapping[j][i]
            if val != -1:
                new_vertices[i] = updated_vertices[j].vertices[int(val)]
                continue

    pymesh.save_mesh(filename_out, pymesh.form_mesh(new_vertices, mesh.faces))

    print '########## Embedding finished ##########'
    print

    return bits_inserted
Пример #35
0
def automatic_marching_cube_reconstruction(dirpath, filename):
    """
        An automatic routine to convert image stacks to 3D triangular meshes.
        :param dirpath: The full directory path where the file(s) are stored,
        ex : /home/user/Documents/spineReconstruction/Images/ or C:/Users/Documents/spineReconstruction/Images
        :param filename: The name of the image stack file in tiff, ex : stackimage.tiff or stackimage.tif
        :return:
    """
    print(f'Computing : {filename.strip(".tif").split("/")[-1]}_mesh')
    imageStack = io.imread(f'{dirpath}/{filename}')
    zSpacing = 0.35 / 1.518

    levelThreshold = 0
    stability = True

    mesh = construct_mesh_from_lewiner(imageStack, (zSpacing, 0.05, 0.05),
                                       levelThreshold)

    # Todo : refactoring and cut into more functions
    while stability:
        if levelThreshold > 200:
            print(
                'Image seems to be mostly noise, or resolution is super good. Stopping at levelTreshold 200'
            )
            stability = False
            levelThreshold = 200

        mesh2 = construct_mesh_from_lewiner(imageStack, (zSpacing, 0.05, 0.05),
                                            levelThreshold)
        meshList = optimise.get_size_of_meshes(optimise.create_graph(mesh2))
        meshList.sort(reverse=True)
        meshList = [x if x > 0.01 * np.sum(meshList) else 0 for x in meshList]
        if len(meshList) > 1:
            if (meshList[0] + meshList[1]) / np.sum(
                    meshList) > 0.9 and meshList[0] / np.sum(meshList) < 0.8:
                stability = False
                stability2 = False
                # neck and head are dissociated
                while not stability2:
                    levelThreshold -= 1
                    mesh2 = construct_mesh_from_lewiner(
                        imageStack, (zSpacing, 0.05, 0.05), levelThreshold)
                    meshL = optimise.remove_noise(mesh2)
                    if len(meshL) > 1:
                        if ((len(meshL[0].vertices) + len(meshL[1].vertices)) /
                                len(mesh2.vertices) <= 0.9
                                or len(meshL[0].vertices) / len(mesh2.vertices)
                                >= 0.8):
                            levelThreshold -= levelThreshold / 10
                            break
                    else:
                        levelThreshold -= levelThreshold / 10
                        break
            else:
                levelThreshold += 5
        else:
            levelThreshold += 5
            # Look for narrow reconstruction
    mesh3 = construct_and_optimise_from_lewiner(imageStack,
                                                (zSpacing, 0.05, 0.05),
                                                levelThreshold)
    print(
        f'Saving mesh with level threshold : {levelThreshold} in optimisedMeshes'
    )
    save_mesh(
        f'optimisedMeshes/{filename.split(".")[0]}_{levelThreshold}_optimised.stl',
        mesh3)
def sample_modelnet_40(input_dir, output_dir, point_cloud_size):
    """
    Sample ModelNet40 meshes into point clouds of certain sizes (random sampling).

    Args:
        input_dir (str): Input dir of ModelNet40.
        output_dir (str): Output dir with sampled point clouds.
        point_cloud_size (int): Output size of sampling algorithm.
    """

    # Is data present?
    if not os.path.exists(input_dir):
        raise AssertionError(
            'No ModelNet40 dataset found in the following directory: ' +
            input_dir)

    #######################################################################
    # load data
    #######################################################################

    train_clouds = []
    train_labels = []
    test_clouds = []
    test_labels = []
    clsnms = []

    # Classes dir
    class_names = [
        el for el in os.listdir(input_dir)
        if os.path.isdir(os.path.join(input_dir, el))
    ]
    class_dirs = [os.path.join(input_dir, el) for el in class_names]

    for idx, class_dir in enumerate(class_dirs):

        # Class name
        class_name = os.path.split(class_dir)[1]
        clsnms.append(class_name)

        # For train/test split
        for part_name in ['train', 'test']:

            # For each file in part
            print('Converting', part_name, 'part of', class_name, 'class')
            part_dir = os.path.join(class_dir, part_name)
            for filename in tqdm(
                [f for f in os.listdir(part_dir) if '.off' in f]):

                # Load object
                f = filename.split('.')
                f_ply = str(f[0]) + '.ply'
                f_pcd = str(f[0]) + '_' + str(point_cloud_size) + '_' + '.pcd'
                object_path = os.path.join(os.path.join(part_dir, filename))
                pymesh_path = os.path.join(os.path.join(part_dir, f_ply))
                pclpcd_path = os.path.join(os.path.join(part_dir, f_pcd))

                # No off
                if not os.path.exists(object_path):
                    print('No OFF path at: ', object_path)
                    continue

                try:

                    # OFF file fix
                    need_fix = False
                    with open(object_path) as f:
                        first_line = f.readline()
                        if 'OFF' in first_line and len(first_line) != 4:
                            need_fix = True
                            content = f.readlines()
                    if need_fix:
                        with open(object_path, 'w') as f:
                            f.write(first_line[:3] + '\n')
                            f.write(first_line[3:])
                            for c in content:
                                f.write(c)

                    # Pymesh
                    mesh = pymesh.load_mesh(object_path)
                    pymesh.save_mesh(pymesh_path, mesh)

                    # pyntcloud
                    pynt = pyntcloud.PyntCloud.from_file(pymesh_path)
                    cloud = pynt.get_sample('mesh_random', n=point_cloud_size)
                    cloud = cloud.values

                    # Zero mean
                    for dim in [0, 1, 2]:
                        dim_mean = np.mean(cloud[:, dim])
                        cloud[:, dim] -= dim_mean

                    # Scale to unit-ball
                    distances = [np.linalg.norm(point) for point in cloud]
                    scale = 1. / np.max(distances)
                    cloud *= scale

                    # PCD
                    pcd = pcl.PointCloud(cloud)
                    pcl.save(pcd, pclpcd_path)

                    # Append
                    if part_name == 'train':
                        train_clouds.append(cloud)
                        train_labels.append(idx)
                    else:
                        test_clouds.append(cloud)
                        test_labels.append(idx)

                except ValueError:
                    print("An exception occurred: " + object_path)
                    return

    # Numpy arr
    train_clouds = np.array(train_clouds)
    train_labels = np.array(train_labels)
    test_clouds = np.array(test_clouds)
    test_labels = np.array(test_labels)
    clsnms = np.array(clsnms)

    #######################################################################
    # Flat pointclouds and shuffle
    #######################################################################

    train_shuffle_idx = np.arange(len(train_clouds))
    np.random.shuffle(train_shuffle_idx)
    train_clouds = train_clouds[train_shuffle_idx]
    train_labels = train_labels[train_shuffle_idx]

    test_shuffle_idx = np.arange(len(test_clouds))
    np.random.shuffle(test_shuffle_idx)
    test_clouds = test_clouds[test_shuffle_idx]
    test_labels = test_labels[test_shuffle_idx]

    #######################################################################
    # Flat pointclouds and shuffle
    #######################################################################

    file_max_length = 2048

    for file_idx in range(math.ceil(len(train_clouds) / file_max_length)):
        filename = 'data_train_' + str(file_idx) + '.h5'
        filepath = os.path.join(output_dir, filename)
        file = h5py.File(filepath)
        start_idx = file_max_length * file_idx
        end_idx = min(len(train_clouds), file_max_length * (file_idx + 1))
        file['data'] = train_clouds[start_idx:end_idx]
        file['label'] = train_labels[start_idx:end_idx]
        file.close()

    for file_idx in range(math.ceil(len(test_clouds) / file_max_length)):
        filename = 'data_test_' + str(file_idx) + '.h5'
        filepath = os.path.join(output_dir, filename)
        file = h5py.File(filepath)
        start_idx = file_max_length * file_idx
        end_idx = min(len(test_clouds), file_max_length * (file_idx + 1))
        file['data'] = test_clouds[start_idx:end_idx]
        file['label'] = test_labels[start_idx:end_idx]
        file.close()

    with open(os.path.join(output_dir, 'shape_names.txt'), 'w') as f:
        for class_name in clsnms:
            f.write(class_name + '\n')
Пример #37
0
def main():
    args = parse_args();
    mesh = pymesh.load_mesh(args.input_mesh);
    add_attribute(mesh, args.attribute_name);
    pymesh.save_mesh(args.output_mesh, mesh, args.attribute_name);
    pymesh.timethis.summarize();
box_min = np.array ( [ -0.11, +0.10, -0.01 ] )
box_max = np.array ( [ -0.04, +0.17, +0.06 ] )
box = pymesh.generate_box_mesh ( box_min, box_max )


# In[36]:


bunny = pymesh.load_mesh ( "bunny.obj" )


# In[37]:


intersection = pymesh.boolean ( box, bunny, "intersection", "carve")


# In[38]:


pymesh.save_mesh("output.obj", intersection, ascii=True)


# In[39]:


mesh = gel.obj_load("output.obj")
js.display(mesh, smooth=False)

Пример #39
0
def main():
    args = parse_args()
    mesh = pymesh.load_mesh(args.input_mesh)
    mesh = pymesh.submesh(mesh, args.face_indices, args.n_ring)
    pymesh.save_mesh(args.output_mesh, mesh, *mesh.get_attribute_names())