def test_complex_net(self): coords1 = [(0.0, 0.0, 0.0), (0.5, 0.0, 1.0), (1.0, -0.2, 0.5), (1.5, 0.0, 0.0), (2.0, -0.1, 0.5)] cpoints1 = [geom.Vertex(coord) for coord in coords1] curve1 = geom.generate_spline(cpoints1, mmath.interp_bezier_curve_2) coords2 = [(2.0, -0.1, 0.5), (2.0, 1.0, 1.0), (2.1, 2.0, 0.0)] cpoints2 = [geom.Vertex(coord) for coord in coords2] curve2 = geom.generate_spline(cpoints2, mmath.interp_bezier_curve_2) coords3 = [(2.1, 2.0, 0.0), (1.5, 2.0, 0.0), (1.0, 2.1, 0.5), (0.5, 2.0, 1.0), (0.0, 2.0, 0.0)] cpoints3 = [geom.Vertex(coord) for coord in coords3] curve3 = geom.generate_spline(cpoints3, mmath.interp_bezier_curve_2) coords4 = [(0.0, 2.0, 0.0), (-0.5, 1.0, 0.0), (0.0, 0.0, 0.0)] cpoints4 = [geom.Vertex(coord) for coord in coords4] curve4 = geom.generate_spline(cpoints4, mmath.interp_bezier_curve_2) polygon = geom.PolygonsNetQuad((curve1, curve2, curve3, curve4)) points = list(geom.sample_patch_samples(polygon, 50)) c1_points = list(geom.sample_curve_samples(curve1, 20)) c2_points = list(geom.sample_curve_samples(curve2, 20)) c3_points = list(geom.sample_curve_samples(curve3, 20)) c4_points = list(geom.sample_curve_samples(curve4, 20)) if DRAW_GRAPHS: fig = plt.figure() ax = fig.gca(projection="3d") ax.plot([p.x for p in c1_points], [p.y for p in c1_points], [p.z for p in c1_points]) ax.plot([p.x for p in c2_points], [p.y for p in c2_points], [p.z for p in c2_points]) ax.plot([p.x for p in c3_points], [p.y for p in c3_points], [p.z for p in c3_points]) ax.plot([p.x for p in c4_points], [p.y for p in c4_points], [p.z for p in c4_points]) ax.plot([p.x for p in points], [p.y for p in points], [p.z for p in points], "o", label="Geometry points") plt.show()
def create_function_from_patch(mesh, patch): #TODO This should be customized curves = [] for edge in patch: verts = (tuple((blender.convert_vert(mesh.verts[i]) for i in edge))) curves.append(geom.generate_spline(verts, mmath.interp_bezier_curve_2)) return geom.PolygonsNetQuad(curves) return curves
def test_complex_net(self): coords1 = [(0.0, 0.0, 0.0), (0.5, 0.0, 1.0), (1.0, -0.2, 0.5), (1.5, 0.0, 0.0), (2.0, -0.1, 0.5)] cpoints1 = [geom.Vertex(coord) for coord in coords1] curve1 = geom.generate_spline(cpoints1, mmath.interp_bezier_curve_2) coords2 = [(2.0, -0.1, 0.5), (2.0, 1.0, 1.0), (2.1, 2.0, 0.0)] cpoints2 = [geom.Vertex(coord) for coord in coords2] curve2 = geom.generate_spline(cpoints2, mmath.interp_bezier_curve_2) coords3 = [(2.1, 2.0, 0.0), (1.5, 2.0, 0.0), (1.0, 2.1, 0.5), (0.5, 2.0, 1.0), (0.0, 2.0, 0.0)] cpoints3 = [geom.Vertex(coord) for coord in coords3] curve3 = geom.generate_spline(cpoints3, mmath.interp_bezier_curve_2) coords4 = [(0.0, 2.0, 0.0), (-0.5, 1.0, 0.0), (0.0, 0.0, 0.0)] cpoints4 = [geom.Vertex(coord) for coord in coords4] curve4 = geom.generate_spline(cpoints4, mmath.interp_bezier_curve_2) polygon = geom.PolygonsNetQuad((curve1, curve2, curve3, curve4)) points = list(geom.sample_patch_samples(polygon, 50)) c1_points = list(geom.sample_curve_samples(curve1, 20)) c2_points = list(geom.sample_curve_samples(curve2, 20)) c3_points = list(geom.sample_curve_samples(curve3, 20)) c4_points = list(geom.sample_curve_samples(curve4, 20)) if DRAW_GRAPHS: fig = plt.figure() ax = fig.gca(projection='3d') ax.plot([p.x for p in c1_points], [p.y for p in c1_points], [p.z for p in c1_points]) ax.plot([p.x for p in c2_points], [p.y for p in c2_points], [p.z for p in c2_points]) ax.plot([p.x for p in c3_points], [p.y for p in c3_points], [p.z for p in c3_points]) ax.plot([p.x for p in c4_points], [p.y for p in c4_points], [p.z for p in c4_points]) ax.plot([p.x for p in points], [p.y for p in points], [p.z for p in points], "o", label="Geometry points") plt.show()
def run(bm): '''Run the algorithm on the given bmesh (blender mesh), returns the patches (TODO in a data structure we still need to decide)''' verts_list = list(bm.verts) faces_list = list(bm.faces) verts_indexes = [v.index for v in verts_list] # Extract the skeleton mesh. patches, macro_edges, singular_verts = extract_base_mesh(bm) def calculate_patches(macro_edges, bm): boundaries = set(sum(macro_edges, ())) # Now we need to understand which vertex belong to which face. We use the connected components approach. patch_verts_attribution = partition_mesh_vertices(verts_indexes, boundaries, bm) # Now we need to understand which superedges belong to which face. patches = compute_patch_edges(patch_verts_attribution, macro_edges) # We now need to reorder the vertices of each face so that we can build a spline on them. for i, part in enumerate(patches): try: patches[i] = reorder_patch_edges(part) except: patches[i] = None # Filter empty and quadrangualte. patches = [p for p in patches if p] patches = quadrangulate_patches_keep_separate_edges(patches, verts_list) return patch_verts_attribution, patches MIN_VERTS = 20 threshold = PATCH_THRESHOLD * size_estimate(bm) print("Using Threshold:", threshold) def is_patch_big_enough(patch): '''Returns if all the edges of the patch are big enough for it to be splitted.''' return all(len(edge) >= 5 for edge in flat_patch_edges(patch)) def can_simplify(patch_verts): '''Returns wheter the patch contains enough point to be simplified.''' return patch_verts and len(patch_verts) > MIN_VERTS patch_verts_attribution, patches = calculate_patches(macro_edges, bm) skip_patches = [] # Now that we defined the patches we should iterate to improve the error. result = [] patch_improved = True #patch_improved = False while patch_improved: patch_improved = False print("Number of patches", len(patches)) for i,current_patch in enumerate(patches): current_attr = patch_verts_attribution[i] if all([current_patch not in skip_patches, can_simplify(current_attr), is_patch_big_enough(current_patch), compute_patch_error(flat_patch_edges(current_patch), bm, current_attr) > threshold]): #pr("ERROR", compute_patch_error(current_patch, bm, current_attr)) try: old_edges = sum(current_patch,[]) new_edges = split_patch_4(current_patch, current_attr, bm) new_macro_edges = set(macro_edges) for edge in old_edges: if edge in new_macro_edges: new_macro_edges.remove(edge) if edge[::-1] in macro_edges: new_macro_edges.remove(edge[::-1]) for edge in new_edges: if edge[::-1] not in new_macro_edges: new_macro_edges.add(edge) patch_verts_attribution, patches = calculate_patches(new_macro_edges, bm) macro_edges = new_macro_edges patch_improved = True break except Exception as e: print("Patch discarded") skip_patches += [current_patch] result = patches #We need to fit the patch edges with lower degree curves #edges = set(sum(result, [])) edges = set(sum(sum(result, []), [])) edges = sorted(edges, key=len) edge_correspondence = {} old_verts = [geom.Vertex((v.co.x, v.co.y, v.co.z)) for v in verts_list] # TODO Disable this. Returns the patches without the edge fitting. #return old_verts, result, old_verts, result # Output values new_verts = [] new_edges = [] new_patches = [] SAMPLES = 20 spline_threshold = SPLINE_THRESHOLD * size_estimate(bm) THRESHOLD_DISTANCE = VERTEX_MERGE_THRESHOLD * size_estimate(bm) edge_conversion = {} def merge_all_edges(edges): if not edges: return [] result = edges[0] for e in edges[1:]: result += e[1:] return tuple(result) for i,edge in enumerate(edges): if edge_conversion.get(edge): pass else: cpoints = tuple([old_verts[i] for i in edge]) def squash_chunks(chunks): if len(chunks) == 1: return chunks[0] return chunks[0][:-1] + squash_chunks(chunks[1:]) new_cpoints = [] error = float("inf") n_splines = 1 try: while error > spline_threshold: cpoints_chunks = fit.fit_bezier_spline(cpoints, mmath.interp_bezier_curve_2, n_splines) verts_chunks = [[geom.Vertex(p) for p in chunk] for chunk in cpoints_chunks] verts_chunks[0][0] = cpoints[0] verts_chunks[-1][-1] = cpoints[-1] # Increase the precision at the next step n_splines += 1 # We need to use the original extremes control points to ensure continuity new_cpoints = squash_chunks(verts_chunks) # Check the error and fallback to default spline if issues new_curve = geom.generate_spline(new_cpoints, mmath.interp_bezier_curve_2) new_curve_points = list(geom.sample_curve_samples(new_curve, len(cpoints))) error = errors.simple_max_error(new_curve_points, cpoints) except: new_cpoints = cpoints print("Compressing edge: %s/%s. n.verts: %s -> %s" % (i+1, len(edges), len(cpoints), len(new_cpoints))) new_verts += new_cpoints new_verts_indexes = tuple([new_verts.index(v) for v in new_cpoints]) edge_conversion[edge] = new_verts_indexes edge_conversion[edge[::-1]] = new_verts_indexes[::-1] final_patches = [] # Convert the edges with the approximated ones. for patch in result: converted_edges = [[edge_conversion[edge] for edge in edges] for edges in patch] final_patches += [flat_patch_edges(converted_edges)] # TODO Remember to disable this. #return new_verts, final_patches, new_verts, final_patches def generate_convert(threshold_index, prev_index): def convert(index): if index < threshold_index: return index elif index == threshold_index: return prev_index else: return index - 1 return convert def update_edge(e, convert): return tuple([convert(i) for i in e]) def update_patch(p, convert): return [update_edge(e, convert) for e in p] def my_index(l, element): """Returns the element in the list, otherwise -1""" return l.index(element) if element in l else -1 def my_index_closest(l, element): temp = list(l) mod = lambda x: x[0] * x[0] + x[1] * x[1] + x[2] * x[2] closest = sorted(temp, key=lambda x: mod(x - element))[0] return l.index(closest) if mod(element - closest) < THRESHOLD_DISTANCE else -1 def recursive_max(l): if isinstance(l, (int, float)): return l return max((recursive_max(e) for e in l)) # Filter out vertices which are not unique. final_verts = [] for i, vert in enumerate(new_verts): index = my_index_closest(final_verts, vert) if final_verts else -1 if index == -1: final_verts += [vert] else: # The element is not in the list. This means that we need to decrement all the indexes and replace i with index. convert_func = generate_convert(len(final_verts), index) for i, p in enumerate(final_patches): final_patches[i] = update_patch(p, convert_func) input_size = compute_input_mesh_size(bm) output_size = compute_output_mesh_size(final_patches) print("Statistics ------------------------------------------") print("Input Vertices: ", len(verts_list)) print("Input Faces: ", len(faces_list)) print("Output Vertices: ", len(final_verts)) print("Output Faces: ", len(final_patches)) print("Input Size: ", input_size) print("Output Size: ", output_size) print("Compression Ratio:", (input_size / output_size)) print("-----------------------------------------------------") old_pathces = [flat_patch_edges(p) for p in result] return old_verts, old_pathces, final_verts, final_patches
def run(bm): '''Run the algorithm on the given bmesh (blender mesh), returns the patches (TODO in a data structure we still need to decide)''' verts_list = list(bm.verts) faces_list = list(bm.faces) verts_indexes = [v.index for v in verts_list] # Extract the skeleton mesh. patches, macro_edges, singular_verts = extract_base_mesh(bm) def calculate_patches(macro_edges, bm): boundaries = set(sum(macro_edges, ())) # Now we need to understand which vertex belong to which face. We use the connected components approach. patch_verts_attribution = partition_mesh_vertices( verts_indexes, boundaries, bm) # Now we need to understand which superedges belong to which face. patches = compute_patch_edges(patch_verts_attribution, macro_edges) # We now need to reorder the vertices of each face so that we can build a spline on them. for i, part in enumerate(patches): try: patches[i] = reorder_patch_edges(part) except: patches[i] = None # Filter empty and quadrangualte. patches = [p for p in patches if p] patches = quadrangulate_patches_keep_separate_edges( patches, verts_list) return patch_verts_attribution, patches MIN_VERTS = 20 threshold = PATCH_THRESHOLD * size_estimate(bm) print("Using Threshold:", threshold) def is_patch_big_enough(patch): '''Returns if all the edges of the patch are big enough for it to be splitted.''' return all(len(edge) >= 5 for edge in flat_patch_edges(patch)) def can_simplify(patch_verts): '''Returns wheter the patch contains enough point to be simplified.''' return patch_verts and len(patch_verts) > MIN_VERTS patch_verts_attribution, patches = calculate_patches(macro_edges, bm) skip_patches = [] # Now that we defined the patches we should iterate to improve the error. result = [] patch_improved = True #patch_improved = False while patch_improved: patch_improved = False print("Number of patches", len(patches)) for i, current_patch in enumerate(patches): current_attr = patch_verts_attribution[i] if all([ current_patch not in skip_patches, can_simplify(current_attr), is_patch_big_enough(current_patch), compute_patch_error(flat_patch_edges(current_patch), bm, current_attr) > threshold ]): #pr("ERROR", compute_patch_error(current_patch, bm, current_attr)) try: old_edges = sum(current_patch, []) new_edges = split_patch_4(current_patch, current_attr, bm) new_macro_edges = set(macro_edges) for edge in old_edges: if edge in new_macro_edges: new_macro_edges.remove(edge) if edge[::-1] in macro_edges: new_macro_edges.remove(edge[::-1]) for edge in new_edges: if edge[::-1] not in new_macro_edges: new_macro_edges.add(edge) patch_verts_attribution, patches = calculate_patches( new_macro_edges, bm) macro_edges = new_macro_edges patch_improved = True break except Exception as e: print("Patch discarded") skip_patches += [current_patch] result = patches #We need to fit the patch edges with lower degree curves #edges = set(sum(result, [])) edges = set(sum(sum(result, []), [])) edges = sorted(edges, key=len) edge_correspondence = {} old_verts = [geom.Vertex((v.co.x, v.co.y, v.co.z)) for v in verts_list] # TODO Disable this. Returns the patches without the edge fitting. #return old_verts, result, old_verts, result # Output values new_verts = [] new_edges = [] new_patches = [] SAMPLES = 20 spline_threshold = SPLINE_THRESHOLD * size_estimate(bm) THRESHOLD_DISTANCE = VERTEX_MERGE_THRESHOLD * size_estimate(bm) edge_conversion = {} def merge_all_edges(edges): if not edges: return [] result = edges[0] for e in edges[1:]: result += e[1:] return tuple(result) for i, edge in enumerate(edges): if edge_conversion.get(edge): pass else: cpoints = tuple([old_verts[i] for i in edge]) def squash_chunks(chunks): if len(chunks) == 1: return chunks[0] return chunks[0][:-1] + squash_chunks(chunks[1:]) new_cpoints = [] error = float("inf") n_splines = 1 try: while error > spline_threshold: cpoints_chunks = fit.fit_bezier_spline( cpoints, mmath.interp_bezier_curve_2, n_splines) verts_chunks = [[geom.Vertex(p) for p in chunk] for chunk in cpoints_chunks] verts_chunks[0][0] = cpoints[0] verts_chunks[-1][-1] = cpoints[-1] # Increase the precision at the next step n_splines += 1 # We need to use the original extremes control points to ensure continuity new_cpoints = squash_chunks(verts_chunks) # Check the error and fallback to default spline if issues new_curve = geom.generate_spline( new_cpoints, mmath.interp_bezier_curve_2) new_curve_points = list( geom.sample_curve_samples(new_curve, len(cpoints))) error = errors.simple_max_error(new_curve_points, cpoints) except: new_cpoints = cpoints print("Compressing edge: %s/%s. n.verts: %s -> %s" % (i + 1, len(edges), len(cpoints), len(new_cpoints))) new_verts += new_cpoints new_verts_indexes = tuple( [new_verts.index(v) for v in new_cpoints]) edge_conversion[edge] = new_verts_indexes edge_conversion[edge[::-1]] = new_verts_indexes[::-1] final_patches = [] # Convert the edges with the approximated ones. for patch in result: converted_edges = [[edge_conversion[edge] for edge in edges] for edges in patch] final_patches += [flat_patch_edges(converted_edges)] # TODO Remember to disable this. #return new_verts, final_patches, new_verts, final_patches def generate_convert(threshold_index, prev_index): def convert(index): if index < threshold_index: return index elif index == threshold_index: return prev_index else: return index - 1 return convert def update_edge(e, convert): return tuple([convert(i) for i in e]) def update_patch(p, convert): return [update_edge(e, convert) for e in p] def my_index(l, element): """Returns the element in the list, otherwise -1""" return l.index(element) if element in l else -1 def my_index_closest(l, element): temp = list(l) mod = lambda x: x[0] * x[0] + x[1] * x[1] + x[2] * x[2] closest = sorted(temp, key=lambda x: mod(x - element))[0] return l.index(closest) if mod(element - closest) < THRESHOLD_DISTANCE else -1 def recursive_max(l): if isinstance(l, (int, float)): return l return max((recursive_max(e) for e in l)) # Filter out vertices which are not unique. final_verts = [] for i, vert in enumerate(new_verts): index = my_index_closest(final_verts, vert) if final_verts else -1 if index == -1: final_verts += [vert] else: # The element is not in the list. This means that we need to decrement all the indexes and replace i with index. convert_func = generate_convert(len(final_verts), index) for i, p in enumerate(final_patches): final_patches[i] = update_patch(p, convert_func) input_size = compute_input_mesh_size(bm) output_size = compute_output_mesh_size(final_patches) print("Statistics ------------------------------------------") print("Input Vertices: ", len(verts_list)) print("Input Faces: ", len(faces_list)) print("Output Vertices: ", len(final_verts)) print("Output Faces: ", len(final_patches)) print("Input Size: ", input_size) print("Output Size: ", output_size) print("Compression Ratio:", (input_size / output_size)) print("-----------------------------------------------------") old_pathces = [flat_patch_edges(p) for p in result] return old_verts, old_pathces, final_verts, final_patches