def triangulate_face(face, vnp): if ( (len(face) == 1) and (len(face[0]) == 3) ): # print ("Already a triangle") return face sf = np.array([], dtype=np.int32) for ring in face: sf = np.hstack( (sf, np.array(ring)) ) sfv = vnp[sf] rings = np.zeros(len(face), dtype=np.int32) total = 0 for i in range(len(face)): total += len(face[i]) rings[i] = total # 1. normal with Newell's method n = get_normal_newell(sfv) sfv2d = np.zeros( (sfv.shape[0], 2)) for i,p in enumerate(sfv): xy = to_2d(p, n) sfv2d[i][0] = xy[0] sfv2d[i][1] = xy[1] result = mapbox_earcut.triangulate_float32(sfv2d, rings) for i,each in enumerate(result): result[i] = int(sf[each]) # print (type(result.reshape(-1, 3).tolist()[0][0])) return result.reshape(-1, 3).tolist()
def lineToSTL(pts, name, z = 1): # 3D vertices of the base and top faces. pts_base = [ [ax,ay,0] for (ax,ay) in pts] pts_top = [ [ax,ay,z] for (ax,ay) in pts] # Triangulate the 2D curve triangles_indices = earcut.triangulate_float32(np.array(pts).reshape(-1,2), np.array([len(pts)])).reshape(-1,3) # 2d triangles to 3d triangles base = [ [pts_base[a], pts_base[b], pts_base[c]] for (a,b,c) in triangles_indices] top = [ [pts_top[a], pts_top[b], pts_top[c]] for (a,b,c) in triangles_indices] # Need to get the number of triangles: 2 per segment plus twice the number given by our triangulization n_facets = 2 * len(pts) + 2 * triangles_indices.shape[0] data = np.zeros(n_facets, dtype=mesh.Mesh.dtype) #Add base and top triangles for i in range(len(base)): data['vectors'][2*i] = np.array([base[i][0], base[i][1], base[i][2]]) data['vectors'][2*i + 1] = np.array([top[i][0], top[i][1], top[i][2]]) #Add side triangles. for i in range(len(pts)): data['vectors'][2*len(base) + 2*i] = np.array([pts_base[i], pts_top[i-1], pts_base[i-1]]) data['vectors'][2*len(base) + 2*i + 1] = np.array([pts_base[i], pts_top[i], pts_top[i-1]]) new_mesh = mesh.Mesh(data) new_mesh.save(name)
def test_empty_data(): verts = np.array([]).reshape(-1, 2) rings = np.array([]) result = earcut.triangulate_float32(verts, rings) assert result.shape == (0, )
def lineToSTL(pts, name, z=19): pts_base = [[ax, ay, 0] for (ax, ay) in pts] pts_top = [[ax, ay, z] for (ax, ay) in pts] #points to triangles triangles_indices = earcut.triangulate_float32( np.array(pts).reshape(-1, 2), np.array([len(pts)])).reshape(-1, 3) #2d triangles to 3d triangles base = [[pts_base[a], pts_base[b], pts_base[c]] for (a, b, c) in triangles_indices] top = [[pts_top[a], pts_top[b], pts_top[c]] for (a, b, c) in triangles_indices] n_facets = 2 * len(pts) + 2 * triangles_indices.shape[0] data = np.zeros(n_facets, dtype=mesh.Mesh.dtype) for i in range(len(base)): data['vectors'][2 * i] = np.array([base[i][0], base[i][1], base[i][2]]) data['vectors'][2 * i + 1] = np.array( [top[i][0], top[i][1], top[i][2]]) for i in range(len(pts)): data['vectors'][2 * len(base) + 2 * i] = np.array( [pts_base[i], pts_top[i - 1], pts_base[i - 1]]) data['vectors'][2 * len(base) + 2 * i + 1] = np.array( [pts_base[i], pts_top[i], pts_top[i - 1]]) new_mesh = mesh.Mesh(data) new_mesh.save(name)
def test_no_triangles(): verts = np.array([[0, 0], [1, 0], [1, 1]], dtype=np.int32).reshape(-1, 2) rings = np.array([2, 3]) result = earcut.triangulate_float32(verts, rings) assert result.dtype == np.uint32 assert result.shape == (0, )
def test_valid_triangulation_int64(): verts = np.array([[0, 0], [1, 0], [1, 1]], dtype=np.int64).reshape(-1, 2) rings = np.array([3]) result = earcut.triangulate_float32(verts, rings) assert result.dtype == np.uint32 assert result.shape == (3, ) assert np.all(result == np.array([1, 2, 0]))
def test_inverted_vertex_order(): verts = np.array(list(reversed([[0, 0], [1, 0], [1, 1]])), dtype=np.int32).reshape(-1, 2) rings = np.array([3]) result = earcut.triangulate_float32(verts, rings) assert result.dtype == np.uint32 assert result.shape == (3, ) assert np.all(result == np.array([1, 0, 2]))
def _fill_indices_mapbox_earcut(self): """Indexes for drawing the fill as TRIANGLES. This version uses the mapbox_earcut library, which is C++, but currently doesn't have a Python 3.8 release. See https://github.com/skogler/mapbox_earcut_python/issues/2 """ verts = self.orig_verts[:, :2] rings = np.array([len(verts)], dtype=np.uint32) idxs = mapbox_earcut.triangulate_float32(verts, rings) return idxs.reshape(-1)
def triangulate_face(self, face, vnp): #-- if already a triangle then return it if ((len(face) == 1) and (len(face[0]) == 3)): return face sf = np.array([], dtype=np.int32) for ring in face: sf = np.hstack((sf, np.array(ring))) sfv = vnp[sf] # print(sf) # print(sfv) rings = np.zeros(len(face), dtype=np.int32) total = 0 for i in range(len(face)): total += len(face[i]) rings[i] = total # print(rings) # 1. normal with Newell's method n = geom_help.get_normal_newell(sfv) # print ("Newell:", n) # 2. project to the plane to get xy sfv2d = np.zeros((sfv.shape[0], 2)) # print (sfv2d) for i, p in enumerate(sfv): xy = geom_help.to_2d(p, n) # print("xy", xy) sfv2d[i][0] = xy[0] sfv2d[i][1] = xy[1] result = mapbox_earcut.triangulate_float32(sfv2d, rings) # print (result.reshape(-1, 3)) for i, each in enumerate(result): # print (sf[i]) result[i] = sf[each] # print (result.reshape(-1, 3)) return result.reshape(-1, 3)
def interpolate_stl(target, target_var, c1_1, c2_1, c1_2, c2_2, twist, z, steps, name): # linear interpolation of targets, c1s, c2s and twist angles which are the parameters of the curve at each slice targets = np.linspace(target - target_var/2, target + target_var/2, steps) c1s = np.linspace(c1_1, c1_2, steps) c2s = np.linspace(c2_1, c2_2, steps) twists = np.linspace(0, T, steps) pts_3d = [[]]*steps height_step = z / (steps-1) tuples = [()]*steps # For each 'slice' for i in range(steps): # Partialize the function with the right values for c1 and c2 func_to_opt = partial(optimizable_function, targets[i], c1s[i], c2s[i]) # Inital value for the radius, works for c1 = c2 = 0 x0=[38] # minimize the length difference res = minimize(func_to_opt, np.array(x0), method='nelder-mead', options={'xatol': 1e-8, 'disp': False}) # We get out (R, c1, c2) tuple, with optimized R tuples[i] = (res.x[0], c1s[i], c2s[i]) # compute radii for current slice's parameters temp_polar = utils.radius_SC(theta, *tuples[i]) # Convert polar coordinates to cartesian, adding the twist of the slice temp_cart = utils.polarToCart(theta + twists[i], temp_polar) # Convert to 3d and store in our array pts_3d[i] = [ [ax,ay,i*height_step] for (ax,ay) in temp_cart] # We need to triangulize the base and top, so first we recompute the cartesian coordinates of those pts_base = utils.polarToCart(theta, utils.radius_SC(theta, *tuples[0])) pts_top = utils.polarToCart(theta + twist, utils.radius_SC(theta, *tuples[steps-1])) #triangulate those faces triangles_indices_base = earcut.triangulate_float32(np.array(pts_base).reshape(-1,2), np.array([len(pts_base)])).reshape(-1,3) triangles_indices_top = earcut.triangulate_float32(np.array(pts_top).reshape(-1,2), np.array([len(pts_top)])).reshape(-1,3) #sort the results to fit our data formatting base = [ [pts_3d[0][a], pts_3d[0][b], pts_3d[0][c]] for (a,b,c) in triangles_indices_base] top = [ [pts_3d[steps-1][a], pts_3d[steps-1][b], pts_3d[steps-1][c]] for (a,b,c) in triangles_indices_top] # number of facets, necessary to create our mesh object. # between 2 slices, we have 2 faces per segment, ie 2 faces per point because our curve is closed. # Plus we have the number of triangles of the top and base faces. n_facets = 2 * len(theta) * (steps-1) + len(base) + len(top) data = np.zeros(n_facets, dtype=mesh.Mesh.dtype) # Add faces of the base for i in range(len(base)): data['vectors'][i] = np.array([base[i][0], base[i][1], base[i][2]]) offset = len(base) # Add faces of the top, minding the offset for i in range(len(top)): data['vectors'][offset + i] = np.array([top[i][0], top[i][1], top[i][2]]) offset += len(top) # Add the faces of the sides. This should work out so that their normals are pointing outward. for i in range(steps-1): for j in range(len(theta)): data['vectors'][offset + 2*j] = np.array([pts_3d[i][j], pts_3d[i+1][j-1], pts_3d[i][j-1]]) data['vectors'][offset + 2*j + 1] = np.array([pts_3d[i][j], pts_3d[i+1][j], pts_3d[i+1][j-1]]) offset += 2 * len(theta) new_mesh = mesh.Mesh(data) new_mesh.save(name)
def get_single_image_mesh_plane( plane_params, segmentations, img_file, height=480, width=640, focal_length=517.97, webvis=False, tolerance=0, ): plane_params = np.array(plane_params) offsets = np.linalg.norm(plane_params, ord=2, axis=1) norms = plane_params / offsets.reshape(-1, 1) if type(segmentations[0]) == dict: poly_segmentations = rle2polygon(segmentations, tolerance) else: poly_segmentations = segmentations verts_list = [] faces_list = [] verts_uvs = [] uv_maps = [] imgs = [] for segm, normal, offset in zip(poly_segmentations, norms, offsets): if len(segm) == 0: continue I = np.array(imageio.imread(img_file)) HUse = None # save uv_map tmp_verts = [] for s in segm: tmp_verts.extend(s) tmp_verts = np.array(tmp_verts).reshape(-1, 2) # pick an arbitrary point # get 3d pointcloud tmp_pcd = get_pcd(tmp_verts, normal, offset, focal_length) point0 = tmp_pcd[0, :] # pick the furthest point from here dPoint0 = np.sum((tmp_pcd - point0[np.newaxis, :]) ** 2, axis=1) point1 = tmp_pcd[np.argmax(dPoint0), :] # dir1 and dir2 are orthogonal to the normal dir1 = point1 - point0 dir1 = dir1 / np.linalg.norm(dir1) dir2 = np.cross(dir1, normal) # control points in 3D control3D = [point0, point0 + dir1, point0 + dir2, point0 + dir1 + dir2] control3D = np.vstack([p[None, :] for p in control3D]) control3DProject = project2D(control3D, focal_length) # pick an arbitrary square targetSize = 300 fakePoints = np.array( [[0, 0], [0, targetSize], [targetSize, 0], [targetSize, targetSize]] ).astype(np.float32) # fit, then adjust H = cv2.getPerspectiveTransform(control3DProject.astype(np.float32), fakePoints) # this maps the control points to the square; now make sure the full mask warps in P = cv2.perspectiveTransform(tmp_verts.reshape(1, -1, 2), H)[0, :, :] xTrans, yTrans = P[:, 0].min(), P[:, 1].min() maxScale = max(P[:, 0].max() - P[:, 0].min(), P[:, 1].max() - P[:, 1].min()) HShuffle = np.array( [ [targetSize / maxScale, 0, -xTrans * targetSize / maxScale], [0, targetSize / maxScale, -yTrans * targetSize / maxScale], [0, 0, 1], ] ) HUse = HShuffle @ H # warped_image is now the rectified image; warped_image2 has it with a 100px fudge factor warped_image = cv2.warpPerspective(I, HUse, (targetSize, targetSize)) uv_maps.append(warped_image) verts_3d = [] faces = [] uvs = [] for ring in segm: verts = np.array(ring).reshape(-1, 2) # get 3d pointcloud pcd = get_pcd(verts, normal, offset, focal_length) if webvis: # Rotate by 11 degree around x axis to push things on the ground. pcd = ( np.array([[-1, 0, 0], [0, 1, 0], [0, 0, -1]]) @ np.array( [ [1, 0, 0], [0, 0.9816272, -0.1908090], [0, 0.1908090, 0.9816272], ] ) @ np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]]) @ pcd.T ).T uvsRectified = cv2.perspectiveTransform( verts.astype(np.float32).reshape(1, -1, 2), HUse )[0, :, :] uvsRectified = np.array([0, 1]) + np.array( [1, -1] ) * uvsRectified / np.array([targetSize, targetSize]) uvs.extend(uvsRectified) # triangulate polygon using earcut algorithm triangles = earcut.triangulate_float32(verts, [len(verts)]) # add base index of vertice triangles += len(verts_3d) triangles = triangles.reshape(-1, 3) # convert to counter-clockwise triangles[:, [0, 2]] = triangles[:, [2, 0]] if triangles.shape[0] == 0: continue verts_3d.extend(pcd) faces.extend(triangles) verts_list.append(torch.tensor(verts_3d, dtype=torch.float32)) faces_list.append(torch.tensor(faces, dtype=torch.int32)) verts_uvs.append(torch.tensor(uvs, dtype=torch.float32)) imgs.append(torch.FloatTensor(imageio.imread(img_file))) # pytorch3d mesh verts_uvs = pad_sequence(verts_uvs, batch_first=True) faces_uvs = pad_sequence(faces_list, batch_first=True, padding_value=-1) tex = Textures(verts_uvs=verts_uvs, faces_uvs=faces_uvs, maps=imgs) meshes = Meshes(verts=verts_list, faces=faces_list, textures=tex) return meshes, uv_maps
def test_end_index_too_small(): verts = np.array([[0, 0], [1, 0], [1, 1]]).reshape(-1, 2) rings = np.array([2]) with pytest.raises(ValueError, message="Expecting ValueError"): result = earcut.triangulate_float32(verts, rings)
def test_rings_not_increasing(): verts = np.array([[0, 0], [1, 0], [1, 1]]).reshape(-1, 2) rings = np.array([3, 0, 3]) with pytest.raises(ValueError): result = earcut.triangulate_float32(verts, rings)
def test_end_index_neg(): verts = np.array([[0, 0], [1, 0], [1, 1]]).reshape(-1, 2) rings = np.array([-1]) with pytest.raises(ValueError): result = earcut.triangulate_float32(verts, rings)
def _fill_indices(self): """Indexes for drawing the fill as TRIANGLES.""" verts = self.orig_verts[:, :2] rings = np.array([len(verts)], dtype=np.uint32) idxs = earcut.triangulate_float32(verts, rings) return idxs.reshape(-1)
def get_single_image_mesh( plane_params, segmentations, img_file, height=480, width=640, focal_length=517.97, webvis=False, reduce_size=True, ): plane_params = np.array(plane_params) offsets = np.linalg.norm(plane_params, ord=2, axis=1) norms = plane_params / offsets.reshape(-1, 1) if type(segmentations[0]) == dict: poly_segmentations = rle2polygon(segmentations) else: poly_segmentations = segmentations verts_list = [] faces_list = [] verts_uvs = [] img_files = [] imgs = [] for segm, normal, offset in zip(poly_segmentations, norms, offsets): if len(segm) == 0: continue verts_3d = [] faces = [] uvs = [] if reduce_size: for ring in segm: verts = np.array(ring).reshape(-1, 2) # get 3d pointcloud pcd = get_pcd(verts, normal, offset, focal_length) if webvis: # Rotate by 11 degree around x axis to push things on the ground. pcd = ( np.array([[-1, 0, 0], [0, 1, 0], [0, 0, -1]]) @ np.array( [ [1, 0, 0], [0, 0.9816272, -0.1908090], [0, 0.1908090, 0.9816272], ] ) @ np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]]) @ pcd.T ).T # triangulate polygon using earcut algorithm triangles = earcut.triangulate_float32(verts, [len(verts)]) # add base index of vertice triangles += len(verts_3d) triangles = triangles.reshape(-1, 3) # convert to counter-clockwise triangles[:, [0, 2]] = triangles[:, [2, 0]] verts_3d.extend(pcd) faces.extend(triangles) uvs.extend( np.array([0, 1]) + np.array([1, -1]) * verts / np.array([width, height]) ) else: bitmask = polygons_to_bitmask(segm, height=height, width=width) verts = np.transpose(bitmask.nonzero()) vert_id_map = defaultdict(dict) for idx, vert in enumerate(verts): vert_id_map[vert[0]][vert[1]] = idx + len(verts_3d) verts_3d = get_pcd(verts[:, ::-1], normal, offset) if webvis: # Rotate by 11 degree around x axis to push things on the ground. verts_3d = ( np.array([[-1, 0, 0], [0, 1, 0], [0, 0, -1]]) @ np.array( [ [1, 0, 0], [0, 0.9816272, -0.1908090], [0, 0.1908090, 0.9816272], ] ) @ np.array([[-1, 0, 0], [0, -1, 0], [0, 0, 1]]) @ pcd.T ).T triangles = [] for vert in verts: # upper right triangle if ( vert[0] < height - 1 and vert[1] < width - 1 and bitmask[vert[0]][vert[1] + 1] and bitmask[vert[0] + 1][vert[1] + 1] ): triangles.append( [ vert_id_map[vert[0]][vert[1]], vert_id_map[vert[0] + 1][vert[1] + 1], vert_id_map[vert[0]][vert[1] + 1], ] ) # bottom left triangle if ( vert[0] < height - 1 and vert[1] < width - 1 and bitmask[vert[0] + 1][vert[1]] and bitmask[vert[0] + 1][vert[1] + 1] ): triangles.append( [ vert_id_map[vert[0]][vert[1]], vert_id_map[vert[0] + 1][vert[1]], vert_id_map[vert[0] + 1][vert[1] + 1], ] ) triangles = np.array(triangles) faces.extend(triangles) uvs.extend( np.array([0, 1]) + np.array([1, -1]) * verts[:, ::-1] / np.array([width, height]) ) verts_list.append(torch.tensor(verts_3d, dtype=torch.float32)) faces_list.append(torch.tensor(faces, dtype=torch.int32)) verts_uvs.append(torch.tensor(uvs, dtype=torch.float32)) img_files.append(img_file) imgs.append(torch.FloatTensor(imageio.imread(img_file))) verts_uvs = pad_sequence(verts_uvs, batch_first=True) faces_uvs = pad_sequence(faces_list, batch_first=True, padding_value=-1) tex = Textures(verts_uvs=verts_uvs, faces_uvs=faces_uvs, maps=imgs) # Initialise the mesh with textures meshes = Meshes(verts=verts_list, faces=faces_list, textures=tex) return meshes, img_files
def test_rings_same(): verts = np.array([[0, 0], [1, 0], [1, 1]]).reshape(-1, 2) rings = np.array([3, 3]) with pytest.raises(ValueError, message="Expecting ValueError"): result = earcut.triangulate_float32(verts, rings)