def mesh_unweld_edges(mesh, edges): """Unwelds a mesh along edges. Parameters ---------- mesh : Mesh A mesh. edges: list List of edges as tuples of vertex keys. """ # set of vertices in edges to unweld vertices = set([i for edge in edges for i in edge]) # to store changes to do all at once vertex_changes = {} for vkey in vertices: # maps between old mesh face index and new network vertex index old_to_new = {nbr: i for i, nbr in enumerate(mesh.vertex_faces(vkey))} new_to_old = {i: nbr for i, nbr in enumerate(mesh.vertex_faces(vkey))} # get adjacency network of faces around the vertex excluding adjacency # through the edges to unweld network_vertices = [ mesh.face_centroid(fkey) for fkey in mesh.vertex_faces(vkey) ] network_edges = [] for nbr in mesh.vertex_neighbors(vkey): if not mesh.is_edge_on_boundary(vkey, nbr) and ( vkey, nbr) not in edges and (nbr, vkey) not in edges: network_edges.append((old_to_new[mesh.halfedge[vkey][nbr]], old_to_new[mesh.halfedge[nbr][vkey]])) adjacency = adjacency_from_edges(network_edges) for key, values in adjacency.items(): adjacency[key] = {value: None for value in values} # include non connected vertices edge_vertices = list(set([i for edge in network_edges for i in edge])) for i in range(len(mesh.vertex_faces(vkey))): if i not in edge_vertices: adjacency[i] = {} # collect the disconnected parts around the vertex due to unwelding vertex_changes[vkey] = [[new_to_old[key] for key in part] for part in connected_components(adjacency)] for vkey, changes in vertex_changes.items(): # for each disconnected part replace the vertex by a new vertex in the # faces of the part for change in changes: mesh_substitute_vertex_in_faces( mesh, vkey, mesh.add_vertex(attr_dict=mesh.vertex[vkey]), change) # delete old vertices mesh.delete_vertex(vkey)
def quad_mesh_strip_n_coloring(quad_mesh): """Color the strips of a quad mesh with a minimum number of colors without overlapping strips with the same color. Parameters ---------- quad_mesh : QuadMesh A quad mesh. Returns ------- dict A dictionary with strip keys pointing to colors. """ vertices, edges = quad_mesh.strip_graph() return vertex_coloring(adjacency_from_edges(edges))
def quad_mesh_polyedge_n_coloring(quad_mesh): """Color the polyedges of a quad mesh with a minimum number of colors without overlapping polyedges with the same color. Polyedges connected by their extremities, which are singularities, do not count as overlapping. Parameters ---------- quad_mesh : QuadMesh A quad mesh. Returns ------- dict A dictionary with polyedge keys pointing to colors. """ vertices, edges = quad_mesh.polyedge_graph() return vertex_coloring(adjacency_from_edges(edges))
def quad_mesh_strip_2_coloring(quad_mesh): """Try to color the strips of a quad mesh with two colors only without overlapping strips with the same color. Parameters ---------- quad_mesh : QuadMesh A quad mesh. Returns ------- dict, None A dictionary with strip keys pointing to colors, if two-colorable. None if not two-colorable. """ vertices, edges = quad_mesh.strip_graph() return is_adjacency_two_colorable(adjacency_from_edges(edges))
def quad_mesh_polyedge_2_coloring(quad_mesh): """Try to color the polyedges of a quad mesh with two colors only without overlapping polyedges with the same color. Polyedges connected by their extremities, which are singularities, do not count as overlapping. Parameters ---------- quad_mesh : QuadMesh A quad mesh. Returns ------- dict, None A dictionary with polyedge keys pointing to colors, if two-colorable. None if not two-colorable. """ vertices, edges = quad_mesh.polyedge_graph() return is_adjacency_two_colorable(adjacency_from_edges(edges))
def mesh_face_n_coloring(mesh): """Color the faces of a mesh with a minimum number of colors without adjacent faces with the same color. Parameters ---------- mesh : Mesh A mesh. Returns ------- dict A dictionary with face keys pointing to colors. """ edges = [(mesh.halfedge[u][v], mesh.halfedge[v][u]) for u, v in mesh.edges() if not mesh.is_edge_on_boundary(u, v)] return vertex_coloring(adjacency_from_edges(edges))
def mesh_face_2_coloring(mesh): """Try to color the faces of a mesh with two colors only without adjacent faces with the same color. Parameters ---------- mesh : Mesh A mesh. Returns ------- dict, None A dictionary with face keys pointing to colors, if two-colorable. None if not two-colorable. """ edges = [(mesh.halfedge[u][v], mesh.halfedge[v][u]) for u, v in mesh.edges() if not mesh.is_edge_on_boundary(u, v)] return is_adjacency_two_colorable(adjacency_from_edges(edges))
def dr_python(vertices, edges, fixed, loads, qpre, fpre, lpre, linit, E, radius, kmax=100, dt=1.0, tol1=1e-3, tol2=1e-6, c=0.1, callback=None, callback_args=None): """Implementation of dynamic relaxation with RK integration scheme in pure Python. Parameters ---------- vertices : list XYZ coordinates of the vertices. edges : list Connectivity of the vertices. fixed : list Indices of the fixed vertices. loads : list XYZ components of the loads on the vertices. qpre : list Prescribed force densities in the edges. fpre : list Prescribed forces in the edges. lpre : list Prescribed lengths of the edges. linit : list Initial length of the edges. E : list Stiffness of the edges. radius : list Radius of the edges. kmax : int, optional Maximum number of iterations. dt : float, optional The time step. tol1 : float, optional Convergence criterion for the residual forces. tol2 : float, optional Convergence criterion for the displacements in between interations. c : float, optional Damping factor for viscous damping. callback : callable, optional A user-defined callback that is called after every iteration. callback_args : tuple, optional Additional arguments to be passed to the callback. Examples -------- .. plot:: :include-source: import random import compas from compas.datastructures import Network from compas.plotters import NetworkPlotter from compas.utilities import i_to_rgb from compas.numerical import dr # make a network # and set the default vertex and edge attributes network = Network.from_obj(compas.get('lines.obj')) dva = { 'is_fixed': False, 'px': 0.0, 'py': 0.0, 'pz': 0.0, 'rx': 0.0, 'ry': 0.0, 'rz': 0.0, } dea = { 'qpre': 1.0, 'fpre': 0.0, 'lpre': 0.0, 'linit': 0.0, 'E': 0.0, 'radius': 0.0, } network.update_default_vertex_attributes(dva) network.update_default_edge_attributes(dea) # identify the fixed vertices # and assign random prescribed force densities to the edges for key, attr in network.vertices(True): attr['is_fixed'] = network.vertex_degree(key) == 1 for u, v, attr in network.edges(True): attr['qpre'] = 1.0 * random.randint(1, 7) # extract numerical data from the datastructure vertices = network.get_vertices_attributes(('x', 'y', 'z')) edges = list(network.edges()) fixed = network.vertices_where({'is_fixed': True}) loads = network.get_vertices_attributes(('px', 'py', 'pz')) qpre = network.get_edges_attribute('qpre') fpre = network.get_edges_attribute('fpre') lpre = network.get_edges_attribute('lpre') linit = network.get_edges_attribute('linit') E = network.get_edges_attribute('E') radius = network.get_edges_attribute('radius') # make a plotter for (dynamic) visualization # plot the lines of the original configuration of the network as reference plotter = NetworkPlotter(network) lines = [] for u, v in network.edges(): lines.append({ 'start': network.vertex_coordinates(u, 'xy'), 'end' : network.vertex_coordinates(v, 'xy'), 'color': '#cccccc', 'width': 0.5 }) plotter.draw_lines(lines) # run the dynamic relaxation # update vertices and edges # visualize the final geometry # color the edges according to the size of the forces # set the width of the edges proportional to the internal forces xyz, q, f, l, r = dr(vertices, edges, fixed, loads, qpre, fpre, lpre, linit, E, radius, kmax=100) for key, attr in network.vertices(True): attr['x'] = xyz[key][0] attr['y'] = xyz[key][1] attr['z'] = xyz[key][2] for index, (u, v, attr) in enumerate(network.edges(True)): attr['f'] = f[index] attr['l'] = l[index] fmax = max(network.get_edges_attribute('f')) plotter.clear_vertices() plotter.clear_edges() plotter.draw_vertices( facecolor={key: '#000000' for key in network.vertices_where({'is_fixed': True})} ) plotter.draw_edges( text={(u, v): '{:.0f}'.format(attr['f']) for u, v, attr in network.edges(True)}, color={(u, v): i_to_rgb(attr['f'] / fmax) for u, v, attr in network.edges(True)}, width={(u, v): 10 * attr['f'] / fmax for u, v, attr in network.edges(True)} ) plotter.show() See Also -------- * :func:`compas.numerical.dr_numpy` * :func:`compas.numerical.drx_numpy` """ if callback: if not callable(callback): raise Exception('The callback is not callable.') # -------------------------------------------------------------------------- # preprocess # -------------------------------------------------------------------------- n = len(vertices) # i_nbrs = {i: [ij[1] if ij[0] == i else ij[0] for ij in edges if i in ij] for i in range(n)} i_nbrs = adjacency_from_edges(edges) ij_e = {(i, j): index for index, (i, j) in enumerate(edges)} ij_e.update({(j, i): index for (i, j), index in ij_e.items()}) coeff = Coeff(c) ca = coeff.a cb = coeff.b free = list(set(range(n)) - set(fixed)) # -------------------------------------------------------------------------- # attribute arrays # -------------------------------------------------------------------------- X = vertices P = loads Q = qpre # -------------------------------------------------------------------------- # initial values # -------------------------------------------------------------------------- M = [ sum(0.5 * dt**2 * Q[ij_e[(i, j)]] for j in i_nbrs[i]) for i in range(n) ] V = [[0.0, 0.0, 0.0] for _ in range(n)] R = [[0.0, 0.0, 0.0] for _ in range(n)] dX = [[0.0, 0.0, 0.0] for _ in range(n)] # -------------------------------------------------------------------------- # helpers # -------------------------------------------------------------------------- def update_R(): for i in range(n): x = X[i][0] y = X[i][1] z = X[i][2] f = [0.0, 0.0, 0.0] for j in i_nbrs[i]: q = Q[ij_e[(i, j)]] f[0] += q * (X[j][0] - x) f[1] += q * (X[j][1] - y) f[2] += q * (X[j][2] - z) R[i] = [P[i][axis] + f[axis] for axis in (0, 1, 2)] def rk(X0, V0, steps=2): def a(t, V): dX = [[V[i][axis] * t for axis in (0, 1, 2)] for i in range(n)] for i in free: X[i] = [X0[i][axis] + dX[i][axis] for axis in (0, 1, 2)] update_R() return [[cb * R[i][axis] / M[i] for axis in (0, 1, 2)] for i in range(n)] if steps == 2: B = [0.0, 1.0] a0 = a(K[0][0] * dt, V0) k0 = [[dt * a0[i][axis] for axis in (0, 1, 2)] for i in range(n)] a1 = a( K[1][0] * dt, [[V0[i][axis] + K[1][1] * k0[i][axis] for axis in (0, 1, 2)] for i in range(n)]) k1 = [[dt * a1[i][axis] for axis in (0, 1, 2)] for i in range(n)] return [[ B[0] * k0[i][axis] + B[1] * k1[i][axis] for axis in (0, 1, 2) ] for i in range(n)] if steps == 4: B = [1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0] a0 = a(K[0][0] * dt, V0) k0 = [[dt * a0[i][axis] for axis in (0, 1, 2)] for i in range(n)] a1 = a( K[1][0] * dt, [[V0[i][axis] + K[1][1] * k0[i][axis] for axis in (0, 1, 2)] for i in range(n)]) k1 = [[dt * a1[i][axis] for axis in (0, 1, 2)] for i in range(n)] a2 = a(K[2][0] * dt, [[ V0[i][axis] + K[2][1] * k0[i][axis] + K[2][2] * k1[i][axis] for axis in (0, 1, 2) ] for i in range(n)]) k2 = [[dt * a2[i][axis] for axis in (0, 1, 2)] for i in range(n)] a3 = a(K[3][0] * dt, [[ V0[i][axis] + K[3][1] * k0[i][axis] + K[3][2] * k1[i][axis] + K[3][3] * k2[i][axis] for axis in (0, 1, 2) ] for i in range(n)]) k3 = [[dt * a3[i][axis] for axis in (0, 1, 2)] for i in range(n)] return [[ B[0] * k0[i][axis] + B[1] * k1[i][axis] + B[2] * k2[i][axis] + B[3] * k3[i][axis] for axis in (0, 1, 2) ] for i in range(n)] raise NotImplementedError # -------------------------------------------------------------------------- # start iterating # -------------------------------------------------------------------------- for k in range(kmax): X0 = deepcopy(X) V0 = [[ca * V[i][axis] for axis in (0, 1, 2)] for i in range(n)] # RK dV = rk(X0, V0, steps=4) # update for i in free: V[i] = [V0[i][axis] + dV[i][axis] for axis in (0, 1, 2)] dX[i] = [V[i][axis] * dt for axis in (0, 1, 2)] X[i] = [X0[i][axis] + dX[i][axis] for axis in (0, 1, 2)] L = [ sum((X[i][axis] - X[j][axis])**2 for axis in (0, 1, 2))**0.5 for i, j in iter(edges) ] F = [q * l for q, l in zip(Q, L)] update_R() # crits crit1 = max(norm_vectors([R[i] for i in free])) crit2 = max(norm_vectors([dX[i] for i in free])) # callback if callback: callback(k, X, (crit1, crit2), callback_args) # convergence if crit1 < tol1: break if crit2 < tol2: break # -------------------------------------------------------------------------- # update # -------------------------------------------------------------------------- update_R() return X, Q, F, L, R
def projection_0(self, kmax=1): """Projection of a coarse quad mesh to the closest two-colourable sub-spaces. Parameters ---------- mesh : CoarseQuadMesh A coarse quad mesh. Returns ------- results : dict The combination pointing to the its result. If the combination is valid, the result is a tuple of the the two-colourable mesh, the two-colourable network, and the network vertex colors. References ---------- .. [1] Oval et al., *Topology Finding of Two-Colourable Quad-Mesh Patterns in Structural Design*. Submitted. """ mesh = self.quad_mesh # result for input mesh vertices, edges = mesh.strip_graph() if is_adjacency_two_colorable(adjacency_from_edges(edges)) is not None: self.results = True return True results = {} # guarantee valid kmax n = mesh.number_of_strips() if kmax < 1 or kmax > n: kmax = n t0 = time.time() t1 = -float('inf') t2 = -float('inf') total_valid = 0 # start iteration k = 0 discarding_combination = [] discarding_combination_type = {} while k < kmax: k += 1 to_continue = False at_least_one_valid_k = False # test all combinations of (n k) strips for combination in itertools.combinations(mesh.strips(), k): set_combi = set(combination) # check results from potential previous sub-combinations for disc_comb in discarding_combination: if disc_comb.issubset(set_combi): #if are_items_in_list(previous_combination, combination): # if a sub-combination yielded an invalid topology do not pursue if discarding_combination_type[tuple( disc_comb)] == 'invalid shape topology': results[ combination] = 'already invalid shape topology' break elif discarding_combination_type[tuple( disc_comb)] == 'two-colourable': results[combination] = 'already two-colourable' break if len(collateral_strip_deletions(mesh, combination)) > 0: results[combination] = 'collateral deletions' if len(total_boundary_deletions(mesh, combination)) > 0: results[combination] = 'invalid shape topology' discarding_combination.append(set(combination)) discarding_combination_type[tuple( combination)] = 'invalid shape topology' if combination in results: continue # delete strips in mesh and check validity copy_mesh = mesh.copy() #copy_mesh.collect_strips() delete_strips(copy_mesh, combination, preserve_boundaries=True) topological_validity = copy_mesh.is_manifold( ) and copy_mesh.euler() == mesh.euler() if not topological_validity: results[combination] = 'invalid shape topology' discarding_combination.append(set(combination)) discarding_combination_type[tuple( combination)] = 'invalid shape topology' # delete strip vertices in network and check colourability else: #vertices, edges = copy_mesh.strip_graph() new_vertices = { vkey: xyz for vkey, xyz in vertices.items() if vkey not in combination } new_edges = [ (u, v) for u, v in edges if u not in combination and v not in combination ] two_colourability = is_adjacency_two_colorable( adjacency_from_edges(new_edges)) if not two_colourability: results[combination] = 'not two-colourable' to_continue = True else: results[combination] = (copy_mesh, (new_vertices, new_edges), two_colourability) discarding_combination.append(set(combination)) discarding_combination_type[tuple( combination)] = 'two-colourable' at_least_one_valid_k = True total_valid += 1 if t1 < 0: t1 = time.time() if t2 < 0 and total_valid > 0 and not at_least_one_valid_k: t2 = time.time() if not to_continue: break t3 = time.time() print(len(discarding_combination)) print(t3 - t0) self.results = results self.times = (t1 - t0, t2 - t0, t3 - t0)
def projection(self, kmax=1): """Projection of a coarse quad mesh to the closest two-colourable sub-spaces. Parameters ---------- mesh : CoarseQuadMesh A coarse quad mesh. Returns ------- results : dict The combination pointing to the its result. If the combination is valid, the result is a tuple of the the two-colourable mesh, the two-colourable network, and the network vertex colors. References ---------- .. [1] Oval et al., *Topology Finding of Two-Colourable Quad-Mesh Patterns in Structural Design*. Submitted. """ mesh = self.quad_mesh # result for input mesh vertices, edges = mesh.strip_graph() if is_adjacency_two_colorable(adjacency_from_edges(edges)) is not None: self.results = True return True results = {} # guarantee valid kmax n = mesh.number_of_strips() if kmax < 1 or kmax > n: kmax = n t0 = time.time() # start iteration k = 0 while k < kmax: k += 1 to_continue = False # test all combinations of (n k) strips for combination in itertools.combinations(mesh.strips(), k): # check results from potential previous sub-combinations for previous_combination in results: if are_items_in_list(previous_combination, combination): # if a sub-combination yielded an invalid topology do not pursue if results[ previous_combination] == 'invalid shape topology': results[ combination] = 'already invalid shape topology' break elif type(results[previous_combination]) == tuple: results[combination] = 'already two-colourable' break if combination in results: continue if len(collateral_strip_deletions(mesh, combination)) > 0: to_continue = True continue if len(total_boundary_deletions(mesh, combination)) > 0: results[combination] = 'invalid shape topology' continue # delete strips in mesh and check validity copy_mesh = mesh.copy() copy_mesh.collect_strips() delete_strips(copy_mesh, combination, preserve_boundaries=True) topological_validity = copy_mesh.is_manifold( ) and copy_mesh.euler() == mesh.euler() if not topological_validity: results[combination] = 'invalid shape topology' # delete strip vertices in network and check colourability else: vertices, edges = copy_mesh.strip_graph() two_colourability = is_adjacency_two_colorable( adjacency_from_edges(edges)) if not two_colourability: results[combination] = 'not two-colourable' to_continue = True else: results[combination] = (copy_mesh, (vertices, edges), two_colourability) if not to_continue: break t1 = time.time() self.results = results self.time = t1 - t0
def projection_4(self, kmax=1): """Projection of a coarse quad mesh to the closest two-colourable sub-spaces. Parameters ---------- mesh : CoarseQuadMesh A coarse quad mesh. Returns ------- results : dict The combination pointing to the its result. If the combination is valid, the result is a tuple of the the two-colourable mesh, the two-colourable network, and the network vertex colors. References ---------- .. [1] Oval et al., *Topology Finding of Two-Colourable Quad-Mesh Patterns in Structural Design*. Submitted. """ mesh = self.quad_mesh vertices, edges = mesh.strip_graph() if is_adjacency_two_colorable(adjacency_from_edges(edges)) is not None: self.results = True return True results = {} t0 = time.time() k = 0 current_pool = [[skey] for skey in mesh.strips()] while k < kmax: k += 1 next_pool = [] #print(current_pool) for combination in current_pool: if len(combination) > 1: combination = list( set([i for item in combination for i in item])) else: combination = list(set(combination)) if len(collateral_strip_deletions(mesh, combination)) > 0: continue if len(total_boundary_deletions(mesh, combination)) > 0: continue # delete strips in mesh and check validity copy_mesh = mesh.copy() delete_strips(copy_mesh, combination, preserve_boundaries=True) topological_validity = copy_mesh.is_manifold( ) and copy_mesh.euler() == mesh.euler() if not topological_validity: pass # delete strip vertices in network and check colourability else: new_vertices = { vkey: xyz for vkey, xyz in vertices.items() if vkey not in combination } new_edges = [ (u, v) for u, v in edges if u not in combination and v not in combination ] two_colourability = is_adjacency_two_colorable( adjacency_from_edges(new_edges)) if not two_colourability: next_pool.append(combination) else: results[tuple(combination)] = (copy_mesh, (new_vertices, new_edges), two_colourability) current_pool = itertools.combinations(next_pool, 2) t1 = time.time() print(t1 - t0) self.results = results
def projection_2(self, kmax=1): """Projection of a coarse quad mesh to the closest two-colourable sub-spaces. Parameters ---------- mesh : CoarseQuadMesh A coarse quad mesh. Returns ------- results : dict The combination pointing to the its result. If the combination is valid, the result is a tuple of the the two-colourable mesh, the two-colourable network, and the network vertex colors. References ---------- .. [1] Oval et al., *Topology Finding of Two-Colourable Quad-Mesh Patterns in Structural Design*. Submitted. """ mesh = self.quad_mesh # result for input mesh vertices, edges = mesh.strip_graph() if is_adjacency_two_colorable(adjacency_from_edges(edges)) is not None: self.results = True return True results = {} # guarantee valid kmax n = mesh.number_of_strips() if kmax < 1 or kmax > n: kmax = n t0 = time.time() # start iteration k = 0 discarding_combination = [] while k < kmax: k += 1 to_continue = False at_least_one_valid_k = False # test all combinations of (n k) strips for combination in itertools.combinations(mesh.strips(), k): set_combi = set(combination) # check results from potential previous sub-combinations for disc_comb in discarding_combination: if disc_comb.issubset(set_combi): break if len(collateral_strip_deletions(mesh, combination)) > 0: to_continue = True continue if len(total_boundary_deletions(mesh, combination)) > 0: discarding_combination.append(set(combination)) continue # delete strips in mesh and check validity copy_mesh = mesh.copy() delete_strips(copy_mesh, combination, preserve_boundaries=True) topological_validity = copy_mesh.is_manifold( ) and copy_mesh.euler() == mesh.euler() if not topological_validity: discarding_combination.append(set(combination)) # delete strip vertices in network and check colourability else: new_vertices = { vkey: xyz for vkey, xyz in vertices.items() if vkey not in combination } new_edges = [ (u, v) for u, v in edges if u not in combination and v not in combination ] two_colourability = is_adjacency_two_colorable( adjacency_from_edges(new_edges)) if not two_colourability: to_continue = True else: results[combination] = (copy_mesh, (new_vertices, new_edges), two_colourability) discarding_combination.append(set(combination)) if not to_continue: break t1 = time.time() print(t1 - t0) #print(results) self.results = results
def from_quad_mesh(cls, quad_mesh, collect_strips=True, collect_polyedges=True, attribute_density=True): """Build coarse quad mesh from quad mesh with density and child-parent element data. Parameters ---------- quad_mesh : QuadMesh A quad mesh. attribute_density : bool, optional Keep density data of dense quad mesh and inherit it as aatribute. Returns ---------- coarse_quad_mesh : CoarseQuadMesh A coarse quad mesh with density data. """ polyedges = quad_mesh.singularity_polyedge_decomposition() # vertex data vertices = { vkey: quad_mesh.vertex_coordinates(vkey) for vkey in quad_mesh.vertices() } coarse_vertices_children = { vkey: vkey for polyedge in polyedges for vkey in [polyedge[0], polyedge[-1]] } coarse_vertices = { vkey: quad_mesh.vertex_coordinates(vkey) for vkey in coarse_vertices_children } # edge data coarse_edges_children = {(polyedge[0], polyedge[-1]): polyedge for polyedge in polyedges} singularity_edges = [(x, y) for polyedge in polyedges for u, v in pairwise(polyedge) for x, y in [(u, v), (v, u)]] # face data faces = { fkey: quad_mesh.face_vertices(fkey) for fkey in quad_mesh.faces() } adj_edges = {(f1, f2) for f1 in quad_mesh.faces() for f2 in quad_mesh.face_neighbors(f1) if f1 < f2 and quad_mesh.face_adjacency_halfedge(f1, f2) not in singularity_edges} coarse_faces_children = {} for i, connected_faces in enumerate( connected_components(adjacency_from_edges(adj_edges))): mesh = Mesh.from_vertices_and_faces( vertices, [faces[face] for face in connected_faces]) coarse_faces_children[i] = [ vkey for vkey in reversed(mesh.boundaries()[0]) if mesh.vertex_degree(vkey) == 2 ] coarse_quad_mesh = cls.from_vertices_and_faces(coarse_vertices, coarse_faces_children) # attribute relation child-parent element between coarse and dense quad meshes coarse_quad_mesh.data['attributes'][ 'vertex_coarse_to_dense'] = coarse_vertices_children coarse_quad_mesh.data['attributes']['edge_coarse_to_dense'] = { u: {} for u in coarse_quad_mesh.vertices() } for (u, v), polyedge in coarse_edges_children.items(): coarse_quad_mesh.data['attributes']['edge_coarse_to_dense'][u][ v] = polyedge coarse_quad_mesh.data['attributes']['edge_coarse_to_dense'][v][ u] = list(reversed(polyedge)) # collect strip and polyedge attributes if collect_strips: coarse_quad_mesh.collect_strips() if collect_polyedges: coarse_quad_mesh.collect_polyedges() # store density attribute from input dense quad mesh if attribute_density: coarse_quad_mesh.set_strips_density(1) for skey in coarse_quad_mesh.strips(): u, v = coarse_quad_mesh.strip_edges(skey)[0] d = len( coarse_edges_children.get((u, v), coarse_edges_children.get((v, u), []))) coarse_quad_mesh.set_strip_density(skey, d) # store quad mesh and use as polygonal mesh coarse_quad_mesh.set_quad_mesh(quad_mesh) coarse_quad_mesh.set_polygonal_mesh(quad_mesh.copy()) return coarse_quad_mesh