def read_mesh_from_filename(self, filename, meshcls): if not os.path.isfile(filename): raise FileNotFoundError("No such file: '%s'" % filename) extension = filename[(filename.rfind(".") + 1):] if extension == "dae": # no dae support yet #mesh = Mesh.from_dae(filename) obj_filename = filename.replace(".dae", ".obj") if os.path.isfile(obj_filename): mesh = Mesh.from_obj(obj_filename) # former DAE files have yaxis and zaxis swapped # TODO: already fix in conversion to obj frame = Frame([0,0,0], [1,0,0], [0,0,1]) T = Transformation.from_frame(frame) mesh_transform(mesh, T) else: raise FileNotFoundError("Please convert '%s' into an OBJ file, \ since DAE is currently not supported \ yet." % filename) elif extension == "obj": mesh = Mesh.from_obj(filename) elif extension == "stl": mesh = Mesh.from_stl(filename) else: raise ValueError("%s file types not yet supported" % extension.upper()) return meshcls(mesh)
def transform(self, transformation): """Transforms the element. Parameters ---------- transformation : :class:`Transformation` Returns ------- None Examples -------- >>> from compas.geometry import Box >>> from compas.geometry import Translation >>> element = Element.from_box(Box(Frame.worldXY(), 1, 1, 1)) >>> element.transform(Translation([1, 0, 0])) """ self.frame.transform(transformation) if self._gripping_frame: self.gripping_frame.transform(transformation) if self._source: if type(self._source) == Mesh: mesh_transform( self._source, transformation ) # it would be really good to have Mesh.transform() else: self._source.transform(transformation)
def mesh_transformed(mesh, transformation): """Transform a copy of ``mesh``. Parameters ---------- mesh : Mesh The mesh. transformation : Transformation The transformation. Returns ------- Mesh A transformed independent copy of ``mesh``. Notes ----- The original mesh is not modified. Instead a transformed independent copy is returned. Examples -------- >>> mesh = Mesh.from_obj(compas.get('cube.obj')) >>> T = matrix_from_axis_and_angle([0, 0, 1], pi / 4) >>> tmesh = mesh_transformed(mesh, T) >>> viewer.mesh = tmesh # this should be a list of meshes >>> viewer.show() """ mesh_copy = mesh.copy() mesh_transform(mesh_copy, transformation) return mesh_copy
def scale(self, transformation): """Scales the collision mesh. Parameters ---------- transformation : compas.geometry.Scale """ mesh_transform(self.mesh, transformation)
def _mesh_import(url, filename): """Internal function to load meshes using the correct loader. Name and file might be the same but not always, e.g. temp files.""" file_extension = _get_file_format(url) if file_extension not in SUPPORTED_FORMATS: raise NotImplementedError( 'Mesh type not supported: {}'.format(file_extension)) print(filename) if file_extension == "dae": # no dae support yet #mesh = Mesh.from_dae(filename) obj_filename = filename.replace(".dae", ".obj") if os.path.isfile(obj_filename): mesh = Mesh.from_obj(obj_filename) # former DAE files have yaxis and zaxis swapped # TODO: already fix in conversion to obj frame = Frame([0,0,0], [1,0,0], [0,0,1]) T = Transformation.from_frame(frame) mesh_transform(mesh, T) return mesh else: raise FileNotFoundError("Please convert '%s' into an OBJ file, \ since DAE is currently not supported \ yet." % filename) if file_extension == 'obj': return Mesh.from_obj(filename) elif file_extension == 'stl': return Mesh.from_stl(filename) elif file_extension == 'ply': return Mesh.from_ply(filename) raise Exception
def _dae_mesh_importer(filename, precision): """This is a very simple implementation of a DAE/Collada parser. Collada specification: https://www.khronos.org/files/collada_spec_1_5.pdf """ dae = XML.from_file(filename) meshes = [] visual_scenes = dae.root.find('library_visual_scenes') materials = dae.root.find('library_materials') effects = dae.root.find('library_effects') for geometry in dae.root.findall('library_geometries/geometry'): mesh_xml = geometry.find('mesh') mesh_id = geometry.attrib['id'] matrix_node = visual_scenes.find('visual_scene/node/instance_geometry[@url="#{}"]/../matrix'.format(mesh_id)) transform = None if matrix_node is not None: M = [float(i) for i in matrix_node.text.split()] # If it's the identity matrix, then ignore, we don't need to transform it if M != [1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.]: M = M[0:4], M[4:8], M[8:12], M[12:16] transform = Transformation.from_matrix(M) # primitive elements can be any combination of: # lines, linestrips, polygons, polylist, triangles, trifans, tristrips # The current implementation only supports triangles and polylist of triangular meshes primitive_element_sets = [] primitive_element_sets.extend(mesh_xml.findall('triangles')) primitive_element_sets.extend(mesh_xml.findall('polylist')) if len(primitive_element_sets) == 0: raise Exception('No primitive elements found (currently only triangles and polylist are supported)') for primitive_element_set in primitive_element_sets: primitive_tag = primitive_element_set.tag primitive_set_data = primitive_element_set.find('p').text.split() # Try to retrieve mesh colors mesh_colors = {} if materials is not None and effects is not None: try: instance_effect = None material_id = primitive_element_set.attrib.get('material') primitive_count = int(primitive_element_set.attrib['count']) if material_id is not None: instance_effect = materials.find('material[@id="{}"]/instance_effect'.format(material_id)) if instance_effect is not None: instance_effect_id = instance_effect.attrib['url'][1:] colors = effects.findall('effect[@id="{}"]/profile_COMMON/technique/phong/*/color'.format(instance_effect_id)) for color_node in colors: rgba = [float(i) for i in color_node.text.split()] mesh_colors['mesh_color.{}'.format(color_node.attrib['sid'])] = rgba except Exception: LOGGER.exception('Exception while loading materials, all materials of mesh file %s will be ignored ', filename) # Parse vertices all_offsets = sorted([int(i.attrib['offset']) for i in primitive_element_set.findall('input[@offset]')]) if not all_offsets: raise Exception('Primitive element node does not contain offset information! Primitive tag={}'.format(primitive_tag)) vertices_input = primitive_element_set.find('input[@semantic="VERTEX"]') vertices_id = vertices_input.attrib['source'][1:] vertices_link = mesh_xml.find('vertices[@id="{}"]/input'.format(vertices_id)) positions = mesh_xml.find('source[@id="{}"]/float_array'.format(vertices_link.attrib['source'][1:])) positions = positions.text.split() vertices = [[float(p) for p in positions[i:i + 3]] for i in range(0, len(positions), 3)] # Parse faces # Every nth element is a vertex key, we ignore the rest based on the offsets defined # Usually, every second item is the normal, but there can be other items offset in there (vertex tangents, etc) skip_step = 1 + all_offsets[-1] if primitive_tag == 'triangles': vcount = [3] * primitive_count elif primitive_tag == 'polylist': vcount = [int(v) for v in primitive_element_set.find('vcount').text.split()] if len(vcount) != primitive_count: raise Exception('Primitive count does not match vertex per face count, vertex input id={}'.format(vertices_id)) fkeys = [int(f) for f in primitive_set_data[::skip_step]] faces = [] for i in range(primitive_count): a = i * vcount[i] b = a + vcount[i] faces.append(fkeys[a:b]) # Rebuild vertices and faces using the same logic that other importers # use remapping everything based on a selected precision index_key = OrderedDict() vertex = OrderedDict() for i, xyz in enumerate(vertices): key = geometric_key(xyz, precision) index_key[i] = key vertex[key] = xyz key_index = {key: index for index, key in enumerate(vertex)} index_index = {index: key_index[key] for index, key in iter(index_key.items())} vertices = [xyz for xyz in iter(vertex.values())] faces = [[index_index[index] for index in face] for face in faces] mesh = Mesh.from_vertices_and_faces(vertices, faces) if mesh_colors: mesh.attributes.update(mesh_colors) if transform: mesh_transform(mesh, transform) meshes.append(mesh) return meshes
# empty assembly assembly = Assembly() # add bricks in a staggered pattern for i in range(number_of_courses): dy = i * height if i % 2 == 0: # in the even rows # add (number_of_even_bricks) full bricks for j in range(number_of_even_bricks): block = brick.copy() mesh_transform(block, Translation([j * (width + gap), 0, dy])) assembly.add_block(block) else: # in the uneven rows # add a half brick # add (number_of_even_bricks - 1) full bricks # add a half brick block = halfbrick.copy() mesh_transform(block, Translation([0, 0, dy])) assembly.add_block(block) for j in range(number_of_even_bricks - 1): block = brick.copy() mesh_transform(block, Translation([(0.5 + j) * (width + gap), 0, dy]))
assembly = Assembly() # default block box = Box.from_width_height_depth(W, H, D) block = Block.from_vertices_and_faces(box.vertices, box.faces) # make all blocks # place each block on top of previous # shift block randomly in XY plane for i in range(N): b = block.copy() factor = choice([0.01, -0.01, 0.05, -0.05, 0.1, -0.1]) axis = choice([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) vector = scale_vector(axis, factor) T = Translation([vector[0], vector[1], i * H]) mesh_transform(b, T) assembly.add_block(b, is_support=(i == 0)) # identify_interfaces assembly_interfaces_numpy(assembly) # compute interface forces compute_interface_forces_cvx(assembly, solver='CPLEX', verbose=True)
loop = trimesh_subdivide_loop frames = mesh_subdivide_frames box = Box.from_corner_corner_height((0.0, 0.0, 0.0), (1.0, 1.0, 0.0), 1.0) mesh = Mesh.from_shape(box) trimesh = mesh.copy() mesh_quads_to_triangles(trimesh) tri_mesh = tri(mesh, k=3) quad_mesh = quad(mesh, k=3) ck_mesh = ck(mesh, k=3) corner_mesh = corner(mesh, k=3) doosabin_mesh = doosabin(mesh, k=3) loop_mesh = loop(trimesh, k=3) frames_mesh = frames(mesh, 0.1) mesh_transform(quad_mesh, Translation([1.2, 0.0, 0.0])) mesh_transform(corner_mesh, Translation([1.2 * 2, 0.0, 0.0])) mesh_transform(frames_mesh, Translation([1.2 * 3, 0.0, 0.0])) mesh_transform(ck_mesh, Translation([1.2 * 4, 0.0, 0.0])) mesh_transform(doosabin_mesh, Translation([1.2 * 5, 0.0, 0.0])) mesh_transform(loop_mesh, Translation([1.2 * 6, 0.0, 0.0])) viewer = MultiMeshViewer() viewer.meshes = [ tri_mesh, quad_mesh, corner_mesh, frames_mesh, ck_mesh, doosabin_mesh, loop_mesh ] viewer.show()
key_color = vertex_coloring(dual.adjacency) c = len(set(key_color.values())) colors = Colormap(list(range(c)), 'rgb') facecolor = {key: colors(key_color[key]) for key in dual.vertices()} # the artist for drawing various versions of the mesh artist = MeshArtist(mesh) # mesh mesh.name = "Mesh" artist.clear() artist.draw_mesh() # edges mesh.name = "Edges" mesh_transform(mesh, X) artist.clear() artist.draw_edges() # vertices mesh.name = "Vertices" mesh_transform(mesh, X) artist.clear() artist.draw_vertices(color=vertexcolor) artist.draw_edges(color=(255, 255, 255)) # faces mesh.name = "Faces" mesh_transform(mesh, X) artist.clear() artist.draw_faces(color=facecolor)
ANGLES = [radians(1), radians(-1), radians(2), radians(-2)] # load an assembly assembly = Assembly.from_json(compas_assembly.get('stack.json')) # shift and rotate in random directions # use small increments ("imperfections") for key in assembly.vertices(): block = assembly.blocks[key] R = Rotation.from_axis_and_angle(choice(XYZ), choice(ANGLES), block.centroid()) T = Translation(scale_vector(choice(XYZ), choice([0.01, -0.01]))) mesh_transform(block, T.concatenate(R)) # serialise assembly.to_json(compas_assembly.get('assembly.json')) # visualise R = Rotation.from_axis_and_angle([1.0, 0, 0], -pi / 2) assembly_transform(assembly, R) plotter = AssemblyPlotter(assembly, tight=True) plotter.draw_vertices(text={key: str(key) for key, attr in assembly.vertices(True)}) plotter.draw_blocks( facecolor={key: '#ff0000' for key in assembly.vertices_where({'is_support': True})}
from compas.geometry import Translation from compas.geometry import Scale from compas.geometry import length_vector from compas_viewers.multimeshviewer import MultiMeshViewer tetra = Mesh.from_polyhedron(4) hexa = Mesh.from_polyhedron(6) octa = Mesh.from_polyhedron(8) dodeca = Mesh.from_polyhedron(12) icosa = Mesh.from_polyhedron(20) meshes = [tetra, hexa, octa, dodeca, icosa] for i, mesh in enumerate(meshes): radius = length_vector(mesh.vertex_attributes(mesh.get_any_vertex(), 'xyz')) scale = 1 / radius T = Translation([2.2 * i, 0.0, 0.0]) S = Scale([scale, scale, scale]) X = T * S mesh_transform(mesh, X) duals = [] for mesh in meshes: dual = mesh_dual(mesh) duals.append(dual) mesh_transform(dual, Translation([0.0, 2.2, 0.0])) viewer = MultiMeshViewer() viewer.meshes = meshes + duals viewer.show()
box = Box.from_width_height_depth(W, H, D) brick = Block.from_vertices_and_faces(box.vertices, box.faces) # make all blocks # place each block on top of previous # shift block randomly in XY plane for i in range(N): block = brick.copy() factor = choice([+0.01, -0.01, +0.05, -0.05, +0.1, -0.1]) axis = choice([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]) vector = add_vectors(scale_vector(axis, factor), [0.0, 0.0, i * H]) mesh_transform(block, Translation(vector)) if i == 0: assembly.add_block(block, is_support=True) else: assembly.add_block(block) # export to json assembly.to_json(compas_assembly.get('stack.json')) # visualise R = Rotation.from_axis_and_angle([1.0, 0, 0], -pi / 2) assembly_transform(assembly, R)
from compas_rbe.equilibrium import compute_interface_forces_cvx assembly = Assembly() b1 = Block.from_polyhedron(6) b2 = Block.from_polyhedron(6) u, v = list(b1.edges())[0] l = b1.edge_length(u, v) T1 = Translation([0, 0, -0.5 * l]) T2 = Translation([0, 0, +0.5 * l]) mesh_transform(b1, T1) mesh_transform(b2, T2) assembly.add_block(b1, is_support=True) assembly.add_block(b2) # identify block interfaces and update block_model assembly_interfaces( assembly, nmax=10, tmax=0.05, amin=0.01, lmin=0.01, )
from math import pi from compas.utilities import print_profile from compas.geometry import Box from compas.geometry import matrix_from_translation from compas.geometry import Translation from compas.geometry import Rotation from compas.datastructures import Mesh from compas.datastructures import mesh_transform mesh_transform = print_profile(mesh_transform) box = Box.from_corner_corner_height([0.0, 0.0, 0.0], [1.0, 1.0, 0.0], 1.0) mesh = Mesh.from_vertices_and_faces(box.vertices, box.faces) T = matrix_from_translation([-2.0, 0.0, 3.0]) T = Translation([-2.0, 0.0, 3.0]) R = Rotation.from_axis_and_angle([0.0, 0.0, 1.0], pi / 2) mesh_transform(mesh, R) print(mesh.get_vertices_attribute('x'))
bbox = bounding_box_xy(points) # make a support block of the same size as the bounding box support = Block.from_vertices_and_faces(bbox, [[0, 1, 2, 3]]) # scale the support lx = length_vector(subtract_vectors(bbox[1], bbox[0])) ly = length_vector(subtract_vectors(bbox[2], bbox[1])) sx = (0.0 + lx) / lx sy = (0.0 + ly) / ly S = Scale([sx, sy, 1.0]) mesh_transform(support, S) # align the centroid of the support with the centroid of the bounding box b = centroid_points(bbox) a = support.centroid() mesh_transform(support, Translation(subtract_vectors(b, a))) # add the support to the assembly assembly.add_block(support, is_support=True, is_placed=True) # serialise assembly.to_json(compas_assembly.get('wall_supported.json'))