def reflect_across_2D_pts(edgepts): x1 = edgepts[0][0] y1 = edgepts[0][1] x2 = edgepts[1][0] y2 = edgepts[1][1] dx = (x1 - x2) if dx == 0: #Edge is a vertical line shift = np.array([[1, 0, 0, -x1], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) shift2 = np.array([[1, 0, 0, x1], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) reflect = np.array([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) r = np.dot(shift2, np.dot(reflect, shift)) return r m = (y1 - y2) / dx n = 1 + m * m b = y1 - (m * x1) a = m * -1 d = 1 + (m * m) #Product of two opposite shifts of b in the y direction and a reflection across y = mx shift = np.array([[1, 0, 0, 0], [0, 1, 0, -b], [0, 0, 1, 0], [0, 0, 0, 1]]) shift2 = np.array([[1, 0, 0, 0], [0, 1, 0, b], [0, 0, 1, 0], [0, 0, 0, 1]]) reflec = np.array([[1 - 2 * (a * a) / n, -2 * a / n, 0, 0], [-2 * a / n, 1 - 2 / n, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) return np.dot(shift2, np.dot(reflec, shift))
def transform(self, scale=1, angle=0, origin=(0, 0)): r = np.array([[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]) * scale o = np.array([origin] * len(self.pts_2D)) pts = np.transpose(np.dot(r, np.transpose(np.array(self.pts_2D)))) + o self.pts_2D = [tuple(x) for x in np.rows(pts)] for (i, d) in enumerate(self.decorations): o = np.array([origin] * len(d[0])) pts = np.transpose(np.dot(r, np.transpose(np.array(d[0])))) + o self.decorations[i] = ([tuple(x) for x in np.rows(pts)], d[1])
def get_2D_decorations(self): if self.transform_2D is not None: edges = [] for i, e in enumerate(self.decorations): if e[1] == "hole": for j in range(len(e[0])): name = self.name + ".d%d.e%d" % (i, j) pt1 = np.dot(self.transform_2D, np.array(list(e[0][j - 1]) + [0, 1]))[0:2] pt2 = np.dot(self.transform_2D, np.array(list(e[0][j]) + [0, 1]))[0:2] # XXX use EdgeType appropriately edges.append([name, pt1, pt2, 1]) else: name = self.name + ".d%d" % i pt1 = np.dot(self.transform_2D, np.array(list(e[0][0]) + [0, 1]))[0:2] pt2 = np.dot(self.transform_2D, np.array(list(e[0][1]) + [0, 1]))[0:2] edges.append([name, pt1, pt2, e[1]]) return edges return []
def place_faces(self, face, edge_from, transform_2D, placed=None, allow_overlap=False): """Recursively adds faces to the 2D drawing. Args: face: the current face being placed edge_from: the edge by which to attach the current face transform_2D: A tranformation matrix to move the face into its position in 2D space placed: dictionary containing metadata about previously placed entities allow_overlap: Whether or not to allow two face to be placed on top of each other """ if placed is not None and face in placed['faces']: #This face has already been placed, do not place again return if placed is None: #No faces have been placed yet, initialize data structures placed = {'faces': [], 'edges': {}, 'overlapping': []} check_for_overlap = not allow_overlap else: #Overlap checking is handled only in top level call check_for_overlap = False """ Placing faces involves the notion of "pretransformed" and "transformed" values. Pretransformation moves the edge a face is being connected by to the x axis, and can be thought of as a face's relative position to its neighbor. Transformation moves a face to its absolute position in 2D space. """ if edge_from is not None: #Align connected edges pretransform_matrix = face.pre_transform(edge_from) else: #Place edge as is pretransform_matrix = np.eye(4) transform_matrix = np.dot(transform_2D, pretransform_matrix) #4D pts are the homogenous coordinates of the face i.e. [x,y,z,1] pretransformed_pts_4D = np.dot(pretransform_matrix, face.pts_4D) transfromed_pts_4D = np.dot(transform_matrix, face.pts_4D) pretransformed_pts_2D = pretransformed_pts_4D[0:2,:] #Numerical values for the coordinates are required for placement transfromed_pts_2D = eval_equation(transfromed_pts_4D[0:2, :]) if not self.add_face(transfromed_pts_2D): #If face cannot be placed without collisions, attempt to reflect it reflection_matrix = np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) #Recompute pretransform and transform with rotation pretransform_matrix = np.dot(reflection_matrix, pretransform_matrix) transform_matrix = np.dot(transform_2D, pretransform_matrix) pretransformed_pts_4D = np.dot(pretransform_matrix, face.pts_4D) transfromed_pts_4D = np.dot(transform_matrix, face.pts_4D) pretransformed_pts_2D = pretransformed_pts_4D[0:2,:] transfromed_pts_2D = eval_equation(transfromed_pts_4D[0:2, :]) if not self.add_face(transfromed_pts_2D) and not allow_overlap: #Face was not able to be placed connected to this edge #Keep track of face and hope it gets placed elsewhere #TODO: Try connecting along other edges or undoing previous placements? placed['overlapping'].append(face) return #Face is being placed placed['faces'].append(face) if face in placed['overlapping']: placed['overlapping'].remove(face) face.transform_2D = transform_matrix #Will this break if a face has to be flipped? ## find out how to transform the decoration. ## for e in face.get_2D_decorations(): self.edges[e[0]] = DrawingEdge(e[0], [eval_equation(x) for x in e[1]], [eval_equation(x) for x in e[2]], EdgeType(e[3])) ## edges here is a dictionary, the key is decoration edge name. e is nested list and its first element is the edge name. ## #Place each edge for (i, edge) in enumerate(face.edges): #HACK: Do not place temporary edges if edge is None or edge.name[:4] == "temp": continue #Get the endpoints of the edge edge_pts_2D = (transfromed_pts_2D[:,i - 1],transfromed_pts_2D[:,i]) if edge.name in placed['edges'].keys(): edge_alias = placed['edges'][edge.name] if diff_edge(edge_alias, edge_pts_2D,2): #If the edge has already been placed in a different place, a cut must be made #Create a new edge self.edges['temp' + edge.name] = DrawingEdge('temp' + edge.name, edge_pts_2D[0], edge_pts_2D[1], Cut()) # Make old edge into a cut self.edges[edge.name] = DrawingEdge(edge.name, edge_alias[0], edge_alias[1], Cut()) else: #Add edge normally if len(edge.faces) == 1: edge_type = Cut() else: edge_type = Fold() self.edges[edge.name] = DrawingEdge(edge.name, edge_pts_2D[0], edge_pts_2D[1], edge_type) placed['edges'][edge.name] = edge_pts_2D if len(edge.faces) <= 1: # No other faces to be found, move on to next edge. continue if edge.is_tab(): # Don't follow faces off of a Tab continue #Compute new transform matrix for next face rotation_matrix = rotate_x_to(pretransformed_pts_2D[:,i],pretransformed_pts_2D[:,i-1]) origin_matrix = move_origin_to(pretransformed_pts_2D[:,i-1]) next_transfrom_2D = np.dot(transform_2D,np.dot(origin_matrix,np.dot(rotation_matrix,np.eye(4)))) #Place faces connected to edge for(f,a) in edge.faces.iteritems(): self.place_faces(f, edge, next_transfrom_2D, placed) if check_for_overlap and len(placed['overlapping']): #Placement has finished, but some edges are still unplaced raise Exception('One or more faces could not be placed without overlap!')
def stl_write(faces, filename, thickness=0): """ Writes a graph object represented by faces to an STL file. Args: faces (list): list of faces that compose the object. filename (str): filename to write STL to. thickness (int or float): thickness of each face to draw. """ import triangle shape = None shells = [] triangles = [] for f in faces: r = f[0] # print ("the tramsform function is ",r) # if r is None: # continue # if r is None: #No transform # r = np.eye(4) A = f[1] facets = [] B = triangle.triangulate(A, opts='p') if not 'triangles' in B: print "No triangles in " + f[2] continue if thickness: for t in [ np.transpose( np.array([ list(B['vertices'][x]) + [0, 1] for x in (face[0], face[1], face[2]) ])) for face in B['triangles'] ]: facets.extend( [np.dot(r, x) for x in inflate(t, thickness=thickness)]) for t in [ np.transpose( np.array([ list(A['vertices'][x]) + [0, 1] for x in (edge[0], edge[1]) ])) for edge in A['segments'] ]: facets.extend([ np.dot(r, x) for x in inflate(t, thickness=thickness, edges=True) ]) else: for t in [ np.transpose( np.array([ list(B['vertices'][x]) + [0, 1] for x in (face[0], face[1], face[2]) ])) for face in B['triangles'] ]: #print "Here",r,t facets.append(np.dot(r, t)) triangles.extend(facets) if thickness: FREECADPATH = '/usr/lib64/freecad/lib' import sys sys.path.append(FREECADPATH) import Part meshes = [] for f in (np.transpose(t[0:3, :]) for t in facets): try: meshes.append( Part.Face( Part.Wire([ Part.makeLine(tuple(f[x]), tuple(f[x - 1])) for x in range(3) ]))) except RuntimeError: print "Skipping face: " + repr(f) shell = Part.makeShell(meshes) shells.append(shell) if shape is None: shape = shell else: shape = shape.fuse(shell) if shape: with open("freecad" + filename, 'wb') as fp: shape.exportStl("freecad" + filename) from stlwriter import Binary_STL_Writer faces = triangles with open(filename, 'wb') as fp: writer = Binary_STL_Writer(fp) writer.add_faces(faces) writer.close()
def get_3D_normal(self): if self.transform_3D is not None: o = np.dot(self.transform_3D, np.array([0, 0, 0, 1])) z = np.dot(self.transform_3D, np.array([0, 0, 1, 1])) return (z - o)[0:3, :]
def get_3D_com(self): if self.transform_3D is not None: return np.dot(self.transform_3D, self.com_4D)[0:3, :]
def get_3D_coords(self): if self.transform_3D is not None: return np.dot(self.transform_3D, self.pts_4D)[0:3, :]
def get_2D_com(self): if self.transform_2D is not None: return np.dot(self.transform_2D, self.com_4D)[0:2, :]
def get_2D_coords(self): if self.transform_2D is not None: return np.dot(self.transform_2D, self.pts_4D)[0:2, :]
def place(self, edge_from, transform_2D, transform_3D, placed=None): if self.transform_2D is not None and self.transform_3D is not None and placed is not None and self in \ placed['faces']: # TODO : verify that it connects appropriately along alternate path # print "Repeated face : " + self.name return if placed is None: # Replacing the entire component placed = {'faces': []} ## create a dictionary: placed, the key is 'faces'. ## Face is being placed into the list of key:'faces'. placed={'faces':[face1,face2,...,facen]} ## placed['faces'].append(self) if edge_from is not None: r = self.pre_transform(edge_from) else: r = np.eye(4) ## create a identity unit matrix. ## self.transform_2D = np.dot(transform_2D, r) self.transform_3D = np.dot(transform_3D, r) pts_2D = np.dot(r, self.pts_4D)[0:2, :] ##using numpy, 0-2 rows and all columns. ## coords_2D = self.get_2D_coords() coords_3D = self.get_3D_coords() for (i, e) in enumerate(self.edges): # XXX hack: don't follow small edges if e is None or e.is_tab(): ## do nothing to edges which is tabbed or without connection? ## continue el = self.edge_length(i) ##return the length of edge by edge index i. ## try: if el <= 0.01: continue except TypeError: # print 'sympyicized variable detected - ignoring edge length check' pass da = e.faces[self] ## e is an edge? ## if da[1]: e.place((coords_2D[:, i - 1], coords_2D[:, i]), (coords_3D[:, i - 1], coords_3D[:, i])) else: e.place((coords_2D[:, i], coords_2D[:, i - 1]), (coords_3D[:, i], coords_3D[:, i - 1])) if len(e.faces) <= 1: ## how !!!!! ## # No other faces to be found, move on to next edge. continue pt1 = pts_2D[:, i - 1] pt2 = pts_2D[:, i] # TODO : Only skip self and the face that you came from to verify multi-connected edges # XXX : Assumes both faces have opposite edge orientation # Only works for non-hyper edges -- need to store edge orientation info for a +/- da for (f, a) in e.faces.iteritems(): if a[1] ^ da[1]: # opposite orientation pta, ptb = pt1, pt2 else: # same orientation pta, ptb = pt2, pt1 x = rotate_x_to(ptb, pta) r2d = np.eye(4) r2d = np.dot(x, r2d) r2d = np.dot(move_origin_to(pta), r2d) r3d = rotate_x(np.deg2rad(a[0] + da[0])) r3d = np.dot(x, r3d) r3d = np.dot(move_origin_to(pta), r3d) f.place(e, np.dot(transform_2D, r2d), np.dot(transform_3D, r3d), placed=placed)
def pre_transform(self, edge): index = self.edges.index(edge) # print edge # print self.edgeCoords(index) return np.dot(rotate_onto_x(*self.edge_coords(index)), move_to_origin(self.pts_2D[index]))