├── README.md └── sheet_ufo.py /README.md: -------------------------------------------------------------------------------- 1 | # sheet_ufo 2 | -------------------------------------------------------------------------------- /sheet_ufo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # sheet_ufo.py 5 | # 6 | # Copyright 2014, 2018 Ulrich Brammer 7 | # 8 | # This program is free software; you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation; either version 2 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with this program; if not, write to the Free Software 20 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 21 | # MA 02110-1301, USA. 22 | # 23 | # 24 | # sheet_ufo.py git-version 25 | # July 2018 26 | # added sortEdgesTolerant: more robust generation of Wires for unbend Faces 27 | # generate fold lines, to be used in drawings of the unfolded part. 28 | # fixed calculation of Bend Angle, not working in some cases 29 | 30 | 31 | # sheet_ufo20.py 32 | # removal of dead code 33 | 34 | # sheet_ufo19.py 35 | # changes from June 2018 36 | # - found solution for the new unbendFace function. 37 | # - supports now non orthogonals cut in the bends 38 | # - seams do not get a face, just do not call makeSeamFace 39 | # this avoids internal faces in the unfold under certain cases. 40 | 41 | 42 | # sheet_ufo18.py 43 | # Changes done in 2016 and June 2018 44 | # allow more complex bends: not only straight cut side edges 45 | # tested some code, not published 46 | 47 | # sheet_ufo17.py 48 | # Refactored version December 2015 49 | # Clear division of tasks between analysis and folding 50 | 51 | # To do: 52 | # change code to handle face indexes in the node instead of faces 53 | 54 | 55 | # sheet_ufo16.py 56 | # Die Weiterreichung eines schon geschnittenen Seitenfaces macht Probleme. 57 | # Die Seitenfaces passen hinterher nicht mehr mit den Hauptflächen zusammen. 58 | 59 | # Geänderter Ansatz: lasse die Seitenflächen in der Suchliste und 60 | # schneide jeweils nur den benötigten Teil raus. 61 | # Ich brauche jetzt eine Suchliste und eine Unfoldliste für die 62 | # Face-Indices. 63 | 64 | # To do: 65 | # - handle a selected seam 66 | # - handle not-circle-curves in bends, done 67 | # - detect features like welded screws 68 | # - make a view-provider for bends 69 | # - make the k-factor selectable 70 | # - upfold or unfold single bends 71 | 72 | 73 | # ideas: 74 | # during analysis make a mesh-like structure for the bend-node 75 | # list of edges in the bend-node 76 | # for each face store a list with edge-indices. 77 | # the reason is, each edge has to be recalculated at unfolding 78 | # so the number of calculations could be half, as if for each 79 | # face all edges are calculated. 80 | # Edges perpendicular to the sheet may only be moved to the new location? 81 | # Need to think about it! No only at the end of the bend node. 82 | # Edges in the middle of the bend node will be sheared, because the 83 | # neutral line is not in the middle of the sheet-thickness. 84 | # OK this is more complex, than I thought at the beginning. 85 | 86 | # in a bend node all faces and edges are recreated 87 | # all vertices are translated except those from the parent node. 88 | # the code looked already at each of them. 89 | # a good storage structure is needed! 90 | 91 | ''' 92 | 93 | def main(): 94 | 95 | return 0 96 | 97 | if __name__ == '__main__': 98 | main() 99 | 100 | 101 | ''' 102 | 103 | 104 | import Part, FreeCADGui 105 | from PySide import QtGui 106 | from FreeCAD import Base 107 | import DraftVecUtils, DraftGeomUtils, math, time 108 | 109 | # to do: 110 | # - Put error numbers into the text 111 | # - Put user help into more texts 112 | unfold_error = { 113 | # error codes for the tree-object 114 | 1: ('starting: volume unusable, needs a real 3D-sheet-metal with thickness'), 115 | 2: ('Starting: invalid point for thickness measurement'), 116 | 3: ('Starting: invalid thickness'), 117 | 4: ('Starting: invalid shape'), 118 | 5: ('Starting: Shape has unneeded edges. Please use function refine shape from the Part Workbench before unfolding!'), 119 | # error codes for the bend-analysis 120 | 10: ('Analysis: zero wires in sheet edge analysis'), 121 | 11: ('Analysis: double bends not implemented'), 122 | 12: ('Analysis: more than one bend-child actually not supported'), 123 | 13: ('Analysis: Sheet thickness invalid for this face!'), 124 | 14: ('Analysis: the code can not handle edges without neighbor faces'), 125 | 15: ('Analysis: the code needs a face at all sheet edges'), 126 | 16: ('Analysis: did not find startangle of bend, please post failing sample for analysis'), 127 | 17: ('Analysis: Type of surface not supported for sheet metal parts'), # fix me? 128 | # error codes for the unfolding 129 | 20: ('Unfold: section wire with less than 4 edges'), 130 | 21: ('Unfold: Unfold: section wire not closed'), 131 | 22: ('Unfold: section failed'), 132 | 23: ('Unfold: CutToolWire not closed'), 133 | 24: ('Unfold: bend-face without child not implemented'), 134 | 25: ('Unfold: '), 135 | 26: ('Unfold: not handled curve type in unbendFace'), 136 | -1: ('unknown error')} 137 | 138 | 139 | 140 | 141 | 142 | def equal_vertex(vert1, vert2, p=5): 143 | # compares two vertices 144 | return (round(vert1.X - vert2.X,p)==0 and round(vert1.Y - vert2.Y,p)==0 and round(vert1.Z - vert2.Z,p)==0) 145 | 146 | def equal_vector(vec1, vec2, p=5): 147 | # compares two vectors 148 | return (round(vec1.x - vec2.x,p)==0 and round(vec1.y - vec2.y,p)==0 and round(vec1.z - vec2.z,p)==0) 149 | 150 | def radial_vector(point, axis_pnt, axis): 151 | chord = axis_pnt.sub(point) 152 | norm = axis.cross(chord) 153 | perp = axis.cross(norm) 154 | # FreeCAD.Console.PrintLog( str(chord) + ' ' + str(norm) + ' ' + str(perp)+'\n') 155 | dist_rv = DraftVecUtils.project(chord,perp) 156 | #test_line = Part.makeLine(axis_pnt.add(dist_rv),axis_pnt) 157 | # test_line = Part.makeLine(axis_pnt.add(perp),axis_pnt) 158 | # test_line = Part.makeLine(point, axis_pnt) 159 | # Part.show(test_line) 160 | return perp.normalize() 161 | 162 | def equal_angle(ang1, ang2, p=5): 163 | # compares two angles 164 | result = False 165 | if round(ang1 - ang2, p)==0: 166 | result = True 167 | if round((ang1-2.0*math.pi) - ang2, p)==0: 168 | result = True 169 | if round(ang1 - (ang2-2.0*math.pi), p)==0: 170 | result = True 171 | return result 172 | 173 | def equal_edge(edg1, edg2, p=5): 174 | result = True 175 | if len(edg1.Vertexes) > 1: 176 | if not (equal_vertex(edg1.Vertexes[0], edg2.Vertexes[0]) or equal_vertex(edg1.Vertexes[0], edg2.Vertexes[1])): 177 | result = False 178 | if not (equal_vertex(edg1.Vertexes[1], edg2.Vertexes[0]) or equal_vertex(edg1.Vertexes[1], edg2.Vertexes[1])): 179 | result = False 180 | else: 181 | if not (equal_vertex(edg1.Vertexes[0], edg2.Vertexes[0])): 182 | result = False 183 | if len(edg2.Vertexes) > 1: 184 | result = False 185 | return result 186 | 187 | 188 | class Simple_node(object): 189 | ''' This class defines the nodes of a tree, that is the result of 190 | the analysis of a sheet-metal-part. 191 | Each flat or bend part of the metal-sheet gets a node in the tree. 192 | The indexes are the number of the face in the original part. 193 | Faces of the edge of the metal-sheet need in cases to be split. 194 | These new faces are added to the index list. 195 | ''' 196 | def __init__(self, f_idx=None, Parent_node= None, Parent_edge = None): 197 | self.idx = f_idx # index of the "top-face" 198 | self.c_face_idx = None # face index to the opposite face of the sheet (counter-face) 199 | self.node_type = None # 'Flat' or 'Bend' 200 | self.p_node = Parent_node # Parent node 201 | self.p_edge = Parent_edge # the connecting edge to the parent node 202 | self.child_list = [] # List of child-nodes = link to tree structure 203 | self.child_idx_lists = [] # List of lists with child_idx and child_edge 204 | # need also a list of indices of child faces 205 | self.sheet_edges = [] # List of edges without child-face 206 | self.axis = None # direction of the axis of the detected cylindrical face 207 | self.facePosi = None 208 | self.bendCenter = None # Vector of the center of the detected cylindrical face 209 | self.distCenter = None # Value used to detect faces at opposite side of the bend 210 | self.innerRadius = None # nominal radius of the bend 211 | # self.axis for 'Flat'-face: vector pointing from the surface into the metal 212 | self.bend_dir = None # bend direction values: "up" or "down" 213 | self.bend_angle = None # angle in radians 214 | self.tan_vec = None # direction of translation for Bend nodes 215 | self.oppositePoint = None # Point of a vertex on the opposite site, used to align points to the sheet plane 216 | self.vertexDict = {} # Vertexes of a bend, original and unbend coordinates, flags p, c, t, o 217 | self.edgeDict = {} # unbend edges dictionary, key is a combination of indexes to vertexDict. 218 | self.k_Factor = None # k-factor according to DIN 6935 219 | self._trans_length = None # length of translation for Bend nodes, k-factor used according to DIN 6935 220 | self.analysis_ok = True # indicator if something went wrong with the analysis of the face 221 | self.error_code = None # index to unfold_error dictionary 222 | # here the new features of the nodes: 223 | self.nfIndexes = [] # list of all face-indexes of a node (flat and bend: folded state) 224 | self.seam_edges = [] # list with edges to seams 225 | # bend faces are needed for movement simulation at single other bends. 226 | # otherwise unfolded faces are recreated from self.b_edges 227 | self.node_flattened_faces = [] # faces of a flattened bend node. 228 | self.unfoldTopList = None # source of identical side edges 229 | self.unfoldCounterList = None # source of identical side edges 230 | self.actual_angle = None # state of angle in refolded sheet metal part 231 | self.p_wire = None # wire common with parent node, used for bend node 232 | self.c_wire = None # wire common with child node, used for bend node 233 | self.b_edges = [] # list of edges in a bend node, that needs to be recalculated, at unfolding 234 | 235 | def get_Face_idx(self): 236 | # get the face index from the tree-element 237 | return self.idx 238 | 239 | 240 | 241 | 242 | 243 | class SheetTree(object): 244 | def __init__(self, TheShape, f_idx): 245 | self.cFaceTol = 0.002 # tolerance to detect counter-face vertices 246 | # this high tolerance was needed for more real parts 247 | self.root = None # make_new_face_node adds the root node if parent_node == None 248 | self.__Shape = TheShape.copy() 249 | self.error_code = None 250 | self.failed_face_idx = None 251 | 252 | if not self.__Shape.isValid(): 253 | FreeCAD.Console.PrintLog("The shape is not valid!" + "\n") 254 | self.error_code = 4 # Starting: invalid shape 255 | self.failed_face_idx = f_idx 256 | 257 | #Part.show(self.__Shape) 258 | 259 | # List of indices to the shape.Faces. The list is used a lot for face searches. 260 | # Some faces will be cut and the new ones added to the list. 261 | # So a list of faces independent of the shape is needed. 262 | self.f_list = [] #self.__Shape.Faces.copy() does not work 263 | self.index_list =[] 264 | self.index_unfold_list = [] # indexes needed for unfolding 265 | for i in range(len (self.__Shape.Faces)): 266 | #for i in range(len (self.f_list)): 267 | # if i<>(f_idx): 268 | self.index_list.append(i) 269 | self.index_unfold_list.append(i) 270 | self.f_list.append(self.__Shape.Faces[i]) 271 | #print self.index_list 272 | self.max_f_idx = len(self.f_list) # need this value to make correct indices to new faces 273 | self.unfoldFaces = len(self.f_list) # need the original number of faces for error detection 274 | withoutSplitter = self.__Shape.removeSplitter() 275 | #if self.unfoldFaces > len(withoutSplitter.Faces): # This is not a good idea! Most sheet metal parts have unneeded edges. 276 | #print 'got case which needs a refine shape from the Part workbench!' 277 | #self.error_code = 5 278 | #self.failed_face_idx = f_idx 279 | 280 | 281 | theVol = self.__Shape.Volume 282 | if theVol < 0.0001: 283 | FreeCAD.Console.PrintLog("Shape is not a real 3D-object or to small for a metal-sheet!" + "\n") 284 | self.error_code = 1 285 | self.failed_face_idx = f_idx 286 | 287 | else: 288 | # Make a first estimate of the thickness 289 | estimated_thickness = theVol/(self.__Shape.Area / 2.0) 290 | FreeCAD.Console.PrintLog( "approximate Thickness: " + str(estimated_thickness) + "\n") 291 | # Measure the real thickness of the initial face: Use Orientation and 292 | # Axis to make an measurement vector 293 | 294 | 295 | if hasattr(self.__Shape.Faces[f_idx],'Surface'): 296 | # Part.show(self.__Shape.Faces[f_idx]) 297 | # print 'the object is a face! vertices: ', len(self.__Shape.Faces[f_idx].Vertexes) 298 | F_type = self.__Shape.Faces[f_idx].Surface 299 | # fixme: through an error, if not Plane Object 300 | FreeCAD.Console.PrintLog('It is a: ' + str(F_type) + '\n') 301 | FreeCAD.Console.PrintLog('Orientation: ' + str(self.__Shape.Faces[f_idx].Orientation) + '\n') 302 | 303 | # Need a point on the surface to measure the thickness. 304 | # Sheet edges could be sloping, so there is a danger to measure 305 | # right at the edge. 306 | # Try with Arithmetic mean of plane vertices 307 | m_vec = Base.Vector(0.0,0.0,0.0) # calculating a mean vector 308 | for Vvec in self.__Shape.Faces[f_idx].Vertexes: 309 | #m_vec = m_vec.add(Base.Vector(Vvec.X, Vvec.Y, Vvec.Z)) 310 | m_vec = m_vec.add(Vvec.Point) 311 | mvec = m_vec.multiply(1.0/len(self.__Shape.Faces[f_idx].Vertexes)) 312 | FreeCAD.Console.PrintLog("mvec: " + str(mvec) + "\n") 313 | 314 | #if hasattr(self.__Shape.Faces[f_idx].Surface,'Position'): 315 | #s_Posi = self.__Shape.Faces[f_idx].Surface.Position 316 | #k = 0 317 | # while k < len(self.__Shape.Faces[f_idx].Vertexes): 318 | # fixme: what if measurepoint is outside? 319 | 320 | if self.__Shape.isInside(mvec, 0.00001, True): 321 | measure_pos = mvec 322 | gotValidMeasurePosition = True 323 | else: 324 | gotValidMeasurePosition = False 325 | for pvert in self.__Shape.Faces[f_idx].OuterWire.Vertexes: 326 | #pvert = self.__Shape.Faces[f_idx].Vertexes[k] 327 | pvec = Base.Vector(pvert.X, pvert.Y, pvert.Z) 328 | shiftvec = mvec.sub(pvec) 329 | shiftvec = shiftvec.normalize()*2.0*estimated_thickness 330 | measure_pos = pvec.add(shiftvec) 331 | if self.__Shape.isInside(measure_pos, 0.00001, True): 332 | gotValidMeasurePosition = True 333 | break 334 | 335 | # Description: Checks if a point is inside a solid with a certain tolerance. 336 | # If the 3rd parameter is True a point on a face is considered as inside 337 | #if not self.__Shape.isInside(measure_pos, 0.00001, True): 338 | if not gotValidMeasurePosition: 339 | FreeCAD.Console.PrintLog("Starting measure_pos for thickness measurement is outside!\n") 340 | self.error_code = 2 341 | self.failed_face_idx = f_idx 342 | 343 | 344 | if hasattr(self.__Shape.Faces[f_idx].Surface,'Axis'): 345 | s_Axis = self.__Shape.Faces[f_idx].Surface.Axis 346 | # print 'We have an axis: ', s_Axis 347 | if hasattr(self.__Shape.Faces[f_idx].Surface,'Position'): 348 | s_Posi = self.__Shape.Faces[f_idx].Surface.Position 349 | # print 'We have a position: ', s_Posi 350 | s_Ori = self.__Shape.Faces[f_idx].Orientation 351 | s_Axismp = Base.Vector(s_Axis.x, s_Axis.y, s_Axis.z).multiply(2.0*estimated_thickness) 352 | if s_Ori == 'Forward': 353 | Meassure_axis = Part.makeLine(measure_pos,measure_pos.sub(s_Axismp)) 354 | ext_Vec = Base.Vector(-s_Axis.x, -s_Axis.y, -s_Axis.z) 355 | # Meassure_axis = Part.makeLine(measure_pos,measure_pos.sub(s_Axis.multiply(2.0*estimated_thickness))) 356 | else: 357 | # Meassure_axis = Part.makeLine(measure_pos,measure_pos.add(s_Axis.multiply(2.0*estimated_thickness))) 358 | Meassure_axis = Part.makeLine(measure_pos,measure_pos.add(s_Axismp)) 359 | ext_Vec = Base.Vector(s_Axis.x, s_Axis.y, s_Axis.z) 360 | # Part.show(Meassure_axis) 361 | 362 | lostShape = self.__Shape.copy() 363 | lLine = Meassure_axis.common(lostShape) 364 | lLine = Meassure_axis.common(self.__Shape) 365 | FreeCAD.Console.PrintLog("lLine number edges: " + str(len(lLine.Edges)) + "\n") 366 | measVert = Part.Vertex(measure_pos) 367 | for mEdge in lLine.Edges: 368 | if equal_vertex(mEdge.Vertexes[0], measVert) or equal_vertex(mEdge.Vertexes[1], measVert): 369 | self.__thickness = mEdge.Length 370 | 371 | # self.__thickness = lLine.Length 372 | if (self.__thickness < estimated_thickness) or (self.__thickness > 1.9 * estimated_thickness): 373 | self.error_code = 3 374 | self.failed_face_idx = f_idx 375 | FreeCAD.Console.PrintLog("estimated thickness: " + str(estimated_thickness) + " measured thickness: " + self.__thickness + "\n") 376 | Part.show(lLine, 'Measurement_Thickness_trial') 377 | 378 | 379 | def get_node_faces(self, theNode, wires_e_lists): 380 | ''' This function searches for all faces making up the node, except 381 | of the top and bottom face, which are already there. 382 | wires_e_list is the list of wires lists of the top face without the parent-edge 383 | theNode: the actual node to be filled with data. 384 | ''' 385 | 386 | # How to begin? 387 | # searching for all faces, that have two vertices in common with 388 | # an edge from the list should give the sheet edge. 389 | # But we also need to look at the sheet edge, in order to not claim 390 | # faces from the next node! 391 | # Then we have to treat thoses faces, that belongs to more than one 392 | # node. Those faces needs to be cut and the face list needs to be updated. 393 | # look also at the number of wires of the top face. More wires will 394 | # indicate a hole or a feature. 395 | #print " When will this be called" 396 | found_indices = [] 397 | # A search strategy for faces based on the wires_e_lists is needed. 398 | # 399 | 400 | for theWire in wires_e_lists: 401 | for theEdge in theWire: 402 | analyVert = theEdge.Vertexes[0] 403 | search_list = [] 404 | for x in self.index_list: 405 | search_list.append(x) 406 | for i in search_list: 407 | for lookVert in self.f_list[i].Vertexes: 408 | if equal_vertex(lookVert, analyVert): 409 | if len(theEdge.Vertexes) == 1: # Edge is a circle 410 | if not self.is_sheet_edge_face(theEdge, theNode): 411 | found_indices.append(i) # found a node face 412 | theNode.child_idx_lists.append([i,theEdge]) 413 | #self.index_list.remove(i) # remove this face from the index_list 414 | #Part.show(self.f_list[i]) 415 | else: 416 | nextVert = theEdge.Vertexes[1] 417 | for looknextVert in self.f_list[i].Vertexes: 418 | if equal_vertex(looknextVert, nextVert): 419 | if not self.is_sheet_edge_face(theEdge, theNode): 420 | found_indices.append(i) # found a node face 421 | theNode.child_idx_lists.append([i,theEdge]) 422 | #self.index_list.remove(i) # remove this face from the index_list 423 | #Part.show(self.f_list[i]) 424 | FreeCAD.Console.PrintLog("found_indices: " + str(found_indices) + "\n") 425 | 426 | 427 | 428 | def is_sheet_edge_face(self, ise_edge, tree_node): # ise_edge: IsSheetEdge_edge 429 | # idea: look at properties of neighbor face 430 | # look at edges with distance of sheet-thickness. 431 | # if found and surface == cylinder, check if it could be a bend-node. 432 | # look at number of edges: 433 | # A face with 3 edges is at the sheet edge Cylinder-face or triangle (oh no!) 434 | # need to look also at surface! 435 | # A sheet edge face with more as 4 edges, is common to more than 1 node. 436 | 437 | # get the face which has a common edge with ise_edge 438 | the_index = None 439 | has_sheet_distance_vertex = False 440 | for i in self.index_list: 441 | for sf_edge in self.f_list[i].Edges: 442 | if sf_edge.isSame(ise_edge): 443 | the_index = i 444 | #print 'got edge face: Face', str(i+1) 445 | break 446 | if the_index is not None: 447 | break 448 | 449 | # Simple strategy applied: look if the connecting face has vertexes 450 | # with sheet-thickness distance to the top face. 451 | # fix me: this will fail with sharpened sheet edges with two faces 452 | # between top and bottom. 453 | if the_index is not None: 454 | distVerts = 0 455 | vertList = [] 456 | F_type = str(self.f_list[tree_node.idx].Surface) 457 | # now we need to search for vertexes with sheet_thickness_distance 458 | for F_vert in self.f_list[i].Vertexes: 459 | #vDist = self.getDistanceToFace(F_vert, tree_node) 460 | #if vDist > maxDist: maxDist = vDist 461 | #if vDist < minDist: minDist = vDist 462 | #maxDist = maxDist- self.__thickness 463 | #if (minDist > -self.cFaceTol) and (maxDist < self.cFaceTol) and (maxDist > -self.cFaceTol): 464 | 465 | if self.isVertOpposite(F_vert, tree_node): 466 | has_sheet_distance_vertex = True 467 | if len(self.f_list[i].Edges)<5: 468 | tree_node.nfIndexes.append(i) 469 | self.index_list.remove(i) 470 | #Part.show(self.f_list[i]) 471 | else: 472 | # need to cut the face at the ends of ise_edge 473 | self.divideEdgeFace(i, ise_edge, F_vert, tree_node) 474 | break 475 | 476 | else: 477 | tree_node.analysis_ok = False 478 | tree_node.error_code = 15 # Analysis: the code needs a face at all sheet edges 479 | self.error_code = 15 480 | self.failed_face_idx = tree_node.idx 481 | Part.show(self.f_list[tree_node.idx]) 482 | 483 | return has_sheet_distance_vertex 484 | 485 | 486 | def isVertOpposite(self, theVert, theNode): 487 | F_type = str(self.f_list[theNode.idx].Surface) 488 | vF_vert = Base.Vector(theVert.X, theVert.Y, theVert.Z) 489 | if F_type == "": 490 | distFailure = vF_vert.distanceToPlane (theNode.facePosi, theNode.axis) - self.__thickness 491 | elif F_type == "": 492 | distFailure = vF_vert.distanceToLine (theNode.bendCenter, theNode.axis) - theNode.distCenter 493 | else: 494 | distFailure = 100.0 495 | theNode.error_code = 17 # Analysis: the code needs a face at all sheet edges 496 | self.error_code = 17 497 | self.failed_face_idx = theNode.idx 498 | #Part.show(self.f_list[theNode.idx], 'SurfaceType_not_supported') 499 | # print "counter face distance: ", dist_v + self.__thickness 500 | if (distFailure < self.cFaceTol) and (distFailure > -self.cFaceTol): 501 | return True 502 | else: 503 | return False 504 | 505 | def getDistanceToFace(self, theVert, theNode): 506 | F_type = str(self.f_list[theNode.idx].Surface) 507 | vF_vert = Base.Vector(theVert.X, theVert.Y, theVert.Z) 508 | # a positive distance should go through the sheet metal 509 | if F_type == "": 510 | dist = vF_vert.distanceToPlane (theNode.facePosi, theNode.axis) 511 | if F_type == "": 512 | dist = vF_vert.distanceToLine(theNode.bendCenter, theNode.axis) - self.f_list[theNode.idx].Surface.Radius 513 | if theNode.bend_dir == "down": 514 | dist = -dist 515 | return dist 516 | 517 | 518 | 519 | def divideEdgeFace(self, fIdx, ise_edge, F_vert, tree_node): 520 | FreeCAD.Console.PrintLog("Sheet edge face has more than 4 edges!\n") 521 | # first find out where the Sheet edge face has no edge to the opposite side of the sheet 522 | # There is a need to cut the face. 523 | # make a cut-tool perpendicular to the ise_edge 524 | # cut the face and select the good one to add to the node 525 | # make another cut, in order to add the residual face(s) to the face list. 526 | 527 | # Search edges in the face with a vertex common with ise_edge 528 | F_type = str(self.f_list[tree_node.idx].Surface) 529 | needCut0 = True 530 | firstCutFaceIdx = None 531 | for sEdge in self.f_list[fIdx].Edges: 532 | if equal_vertex(ise_edge.Vertexes[0], sEdge.Vertexes[0]) and \ 533 | self.isVertOpposite(sEdge.Vertexes[1], tree_node): 534 | needCut0 = False 535 | theEdge = sEdge 536 | if equal_vertex(ise_edge.Vertexes[0], sEdge.Vertexes[1]) and \ 537 | self.isVertOpposite(sEdge.Vertexes[0], tree_node): 538 | needCut0 = False 539 | theEdge = sEdge 540 | if needCut0: 541 | #print "need Cut at 0 with fIdx: ", fIdx 542 | nFace = self.cutEdgeFace(0, fIdx, ise_edge, tree_node) 543 | 544 | tree_node.nfIndexes.append(self.max_f_idx) 545 | self.f_list.append(nFace) 546 | firstCutFaceIdx = self.max_f_idx 547 | self.max_f_idx += 1 548 | #self.f_list.append(rFace) 549 | #self.index_list.append(self.max_f_idx) 550 | #self.max_f_idx += 1 551 | #self.index_list.remove(fIdx) 552 | #Part.show(nFace) 553 | #else: 554 | # Part.show(theEdge) 555 | 556 | needCut1 = True 557 | for sEdge in self.f_list[fIdx].Edges: 558 | if equal_vertex(ise_edge.Vertexes[1], sEdge.Vertexes[0]): 559 | if self.isVertOpposite(sEdge.Vertexes[1], tree_node): 560 | needCut1 = False 561 | theEdge = sEdge 562 | if equal_vertex(ise_edge.Vertexes[1], sEdge.Vertexes[1]): 563 | if self.isVertOpposite(sEdge.Vertexes[0], tree_node): 564 | needCut1 = False 565 | theEdge = sEdge 566 | if needCut1: 567 | if needCut0: 568 | fIdx = firstCutFaceIdx 569 | tree_node.nfIndexes.remove(fIdx) 570 | #print "need Cut at 1 with fIdx: ", fIdx 571 | nFace = self.cutEdgeFace(1, fIdx, ise_edge, tree_node) 572 | tree_node.nfIndexes.append(self.max_f_idx) 573 | self.f_list.append(nFace) 574 | firstCutFaceIdx = self.max_f_idx 575 | self.max_f_idx += 1 576 | #self.f_list.append(rFace) 577 | #self.index_list.append(self.max_f_idx) 578 | #self.max_f_idx += 1 579 | #if not needCut0: 580 | # self.index_list.remove(fIdx) 581 | #Part.show(nFace) 582 | #else: 583 | # Part.show(theEdge) 584 | 585 | 586 | 587 | def cutEdgeFace(self, eIdx, fIdx, theEdge, theNode): 588 | ''' This function cuts a face in two pieces. 589 | one piece is connected to the node. 590 | The residual piece is discarded. 591 | The function returns the piece that has a common edge with the top face of theNode. 592 | ''' 593 | #print "now the face cutter: ", fIdx, ' ', eIdx, ' ', theNode.idx 594 | #Part.show(theEdge, 'EdgeToCut'+ str(theNode.idx+1)+'_') 595 | #Part.show(self.f_list[fIdx], 'FaceToCut'+ str(theNode.idx+1)+'_') 596 | 597 | if eIdx == 0: 598 | otherIdx = 1 599 | else: 600 | otherIdx = 0 601 | 602 | origin = theEdge.Vertexes[eIdx].Point 603 | 604 | F_type = str(self.f_list[theNode.idx].Surface) 605 | if F_type == "": 606 | tan_vec = theEdge.Vertexes[eIdx].Point - theEdge.Vertexes[otherIdx].Point 607 | #o_thick = Base.Vector(o_vec.x, o_vec.y, o_vec.z) 608 | tan_vec.normalize() 609 | #New approach: search for the nearest vertex at the opposite site. 610 | # The cut is done between the Vertex indicated by eIdx and the nearest 611 | # opposite vertex. This approach should avoid the generation of 612 | # additional short edges in the side faces. 613 | searchAxis = theNode.axis 614 | #else: 615 | #searchAxis = radVector 616 | 617 | maxDistance = 1000 618 | oppoPoint = None 619 | print ('need to check Face', str(fIdx+1), ' with ', len(self.f_list[fIdx].Vertexes)) 620 | for theVert in self.f_list[fIdx].Vertexes: 621 | # need to check if theVert has 622 | if self.isVertOpposite(theVert, theNode): 623 | vertDist = theVert.Point.distanceToLine(origin, searchAxis) 624 | if vertDist < maxDistance: 625 | maxDistance = vertDist 626 | oppoPoint = theVert.Point 627 | 628 | if oppoPoint is None: 629 | print (' error need always an opposite point in a side face!') 630 | # fix me: need a proper error condition. 631 | 632 | #vec1 = Base.Vector(theNode.axis.x, theNode.axis.y, theNode.axis.z) # make a copy 633 | vec1 = (oppoPoint - origin).normalize() 634 | 635 | crossVec = tan_vec.cross(vec1) 636 | crossVec.multiply(3.0*self.__thickness) 637 | 638 | vec1.multiply(self.__thickness) 639 | # defining the points of the cutting plane: 640 | Spnt1 = origin - vec1 - crossVec 641 | Spnt2 = origin - vec1 + crossVec 642 | Spnt3 = origin + vec1 + vec1 + crossVec 643 | Spnt4 = origin + vec1 + vec1 - crossVec 644 | 645 | 646 | 647 | if F_type == "": 648 | ePar = theEdge.parameterAt(theEdge.Vertexes[eIdx]) 649 | FreeCAD.Console.PrintLog("Idx: " + str(eIdx) + " ePar: " + str(ePar) + "\n") 650 | otherPar = theEdge.parameterAt(theEdge.Vertexes[otherIdx]) 651 | tan_vec = theEdge.tangentAt(ePar) 652 | if ePar < otherPar: 653 | tan_vec.multiply(-1.0) 654 | 655 | #tan_line = Part.makeLine(theEdge.Vertexes[eIdx].Point.add(tan_vec), theEdge.Vertexes[eIdx].Point) 656 | #Part.show(tan_line, 'tan_line'+ str(theNode.idx+1)+'_') 657 | 658 | edge_vec = theEdge.Vertexes[eIdx].copy().Point 659 | radVector = radial_vector(edge_vec, theNode.bendCenter, theNode.axis) 660 | if theNode.bend_dir == "down": 661 | radVector.multiply(-1.0) 662 | 663 | #rad_line = Part.makeLine(theEdge.Vertexes[eIdx].Point.add(radVector), theEdge.Vertexes[eIdx].Point) 664 | #Part.show(rad_line, 'rad_line'+ str(theNode.idx+1)+'_') 665 | searchAxis = radVector 666 | 667 | maxDistance = 1000 668 | oppoPoint = None 669 | print ('need to check Face', str(fIdx+1), ' with ', len(self.f_list[fIdx].Vertexes)) 670 | for theVert in self.f_list[fIdx].Vertexes: 671 | # need to check if theVert has 672 | if self.isVertOpposite(theVert, theNode): 673 | vertDist = theVert.Point.distanceToLine(origin, searchAxis) 674 | if vertDist < maxDistance: 675 | maxDistance = vertDist 676 | oppoPoint = theVert.Point 677 | 678 | if oppoPoint is None: 679 | print (' error need always an opposite point in a side face!') 680 | # fix me: need a proper error condition. 681 | #vec1 = Base.Vector(radVector.x, radVector.y, radVector.z) # make a copy 682 | vec1 = (oppoPoint - origin).normalize() 683 | 684 | crossVec = tan_vec.cross(vec1) 685 | crossVec.multiply(3.0*self.__thickness) 686 | 687 | vec1.multiply(self.__thickness) 688 | # defining the points of the cutting plane: 689 | Spnt1 = origin - vec1 - crossVec 690 | Spnt2 = origin - vec1 + crossVec 691 | Spnt3 = origin + vec1 + vec1 + crossVec 692 | Spnt4 = origin + vec1 + vec1 - crossVec 693 | 694 | Sedge1 = Part.makeLine(Spnt1,Spnt2) 695 | Sedge2 = Part.makeLine(Spnt2,Spnt3) 696 | Sedge3 = Part.makeLine(Spnt3,Spnt4) 697 | Sedge4 = Part.makeLine(Spnt4,Spnt1) 698 | 699 | Sw1 = Part.Wire([Sedge1, Sedge2, Sedge3, Sedge4]) 700 | # Part.show(Sw1, 'cutWire'+ str(theNode.idx+1)+'_') 701 | Sf1=Part.Face(Sw1) # 702 | #Part.show(Sf1, 'cutFace'+ str(theNode.idx+1)+'_') 703 | #cut_solid = Sf1.extrude(tan_vec.multiply(5.0)) 704 | cut_solid = Sf1.extrude(tan_vec.multiply(self.__thickness)) 705 | #Part.show(cut_solid, 'cut_solid'+ str(theNode.idx+1)+'_') 706 | #cut_opposite = Sf1.extrude(tan_vec.multiply(-5.0)) 707 | 708 | cutFaces_node = self.f_list[fIdx].cut(cut_solid) 709 | for cFace in cutFaces_node.Faces: 710 | for myVert in cFace.Vertexes: 711 | if equal_vertex(theEdge.Vertexes[eIdx], myVert): 712 | nodeFace = cFace 713 | #print "The nodeFace Idx: ", fIdx, ' eIdx: ', eIdx 714 | #Part.show(nodeFace) 715 | break 716 | 717 | return nodeFace #, residueFace 718 | 719 | 720 | def getBendAngle(self, newNode, wires_e_lists): 721 | ''' Get the bend angle for a node connected to a bend face, 722 | Get the k-Factor 723 | Get the translation Length 724 | ''' 725 | # newNode = Simple_node(face_idx, P_node, P_edge) 726 | P_node = newNode.p_node 727 | P_edge = newNode.p_edge 728 | face_idx = newNode.idx 729 | theFace = self.__Shape.Faces[face_idx] 730 | 731 | s_Axis = newNode.axis 732 | s_Center = newNode.bendCenter 733 | 734 | #Start to investigate the angles at self.__Shape.Faces[face_idx].ParameterRange[0] 735 | angle_0 = theFace.ParameterRange[0] 736 | angle_1 = theFace.ParameterRange[1] 737 | 738 | # idea: identify the angle at edge_vec = P_edge.Vertexes[0].copy().Point 739 | # This will be = angle_start 740 | # calculate the tan_vec from valueAt 741 | 742 | edge_vec = P_edge.Vertexes[0].copy().Point 743 | edgeAngle, edgePar = theFace.Surface.parameter(edge_vec) 744 | 745 | #print 'the angles: ', angle_0, ' ', angle_1, ' ', edgeAngle, ' ', edgeAngle - 2*math.pi 746 | 747 | if equal_angle(angle_0, edgeAngle): 748 | angle_start = angle_0 749 | angle_end = angle_1 750 | else: 751 | angle_start = angle_1 752 | angle_end = angle_0 753 | len_start = edgePar 754 | 755 | newNode.bend_angle = angle_end - angle_start 756 | angle_tan = angle_start + newNode.bend_angle/6.0 # need to have the angle_tan before correcting the sign 757 | 758 | if newNode.bend_angle < 0.0: 759 | newNode.bend_angle = -newNode.bend_angle 760 | 761 | first_vec = radial_vector(edge_vec, s_Center, s_Axis) 762 | tanPos = self.__Shape.Faces[face_idx].valueAt(angle_tan,len_start) 763 | sec_vec = radial_vector(tanPos, s_Center, s_Axis) 764 | 765 | 766 | cross_vec = first_vec.cross(sec_vec) 767 | triple_prod = cross_vec.dot(s_Axis) 768 | if triple_prod < 0: 769 | newNode.axis = -newNode.axis 770 | s_Axis = -s_Axis 771 | 772 | #tan_vec = radial_vector(tanPos, s_Center, s_Axis) 773 | tan_vec = s_Axis.cross(first_vec) 774 | #Part.show(Part.makeLine(tanPos, tanPos + 10 * tan_vec), 'tan_Vec') 775 | newNode.tan_vec = tan_vec 776 | #make a better tan_vec based on the parent face normal and the parent edge: 777 | if P_node.node_type == 'Flat': 778 | pVec = P_edge.Vertexes[1].Point - P_edge.Vertexes[0].Point 779 | pVec = pVec.normalize() 780 | pTanVec = P_node.axis.cross(pVec) 781 | if (tan_vec - pTanVec).Length > 1.0: 782 | newNode.tan_vec = -pTanVec 783 | else: 784 | newNode.tan_vec = pTanVec 785 | 786 | 787 | if newNode.bend_dir == 'up': 788 | newNode.k_Factor = 0.65 + 0.5*math.log10(theFace.Surface.Radius/self.__thickness) 789 | if newNode.k_Factor < 0: 790 | newNode.k_Factor = 0 791 | 792 | FreeCAD.Console.PrintLog("up Face"+ str(newNode.idx+1)+ " k-factor: "+ str(newNode.k_Factor) + "\n") 793 | newNode._trans_length = (theFace.Surface.Radius + newNode.k_Factor * self.__thickness/2.0) * newNode.bend_angle 794 | else: 795 | newNode.k_Factor = 0.65 + 0.5*math.log10((theFace.Surface.Radius - self.__thickness)/self.__thickness) 796 | if newNode.k_Factor < 0: 797 | newNode.k_Factor = 0 798 | FreeCAD.Console.PrintLog("down Face"+ str(newNode.idx+1)+ " k-factor: "+ str(newNode.k_Factor) + "\n") 799 | newNode._trans_length = (theFace.Surface.Radius - self.__thickness \ 800 | + newNode.k_Factor * self.__thickness/2.0) * newNode.bend_angle 801 | 802 | #print 'newNode._trans_length: ', newNode._trans_length 803 | cAngle_0 = self.__Shape.Faces[newNode.c_face_idx].ParameterRange[0] 804 | cAngle_1 = self.__Shape.Faces[newNode.c_face_idx].ParameterRange[1] 805 | 806 | cFaceAngle = cAngle_1 - cAngle_0 807 | 808 | if newNode.bend_angle > 0: 809 | if cFaceAngle > 0: 810 | diffAngle = newNode.bend_angle - cFaceAngle 811 | else: 812 | diffAngle = newNode.bend_angle + cFaceAngle 813 | else: 814 | if cFaceAngle > 0: 815 | diffAngle = cFaceAngle + newNode.bend_angle 816 | else: 817 | diffAngle = newNode.bend_angle - cFaceAngle 818 | 819 | 820 | #print 'node angles: ', newNode.bend_angle, ' ', diffAngle 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | def make_new_face_node(self, face_idx, P_node, P_edge, wires_e_lists): 829 | # e_list: list of edges of the top face of a node without the parent-edge (P_edge) 830 | # analyze the face and get type of face ("Flat" or "Bend") 831 | # search the counter face, get axis of Face 832 | # In case of "Bend" get angle, k_factor and trans_length 833 | # put the node into the tree 834 | newNode = Simple_node(face_idx, P_node, P_edge) 835 | F_type = str(self.__Shape.Faces[face_idx].Surface) 836 | 837 | # This face should be a node in the tree, and is therefore known! 838 | # removed from the list of all unknown faces 839 | self.index_list.remove(face_idx) 840 | # This means, it could also not be found as neighbor face anymore. 841 | #newNode.node_faces.append(self.f_list[face_idx].copy()) 842 | newNode.nfIndexes.append(face_idx) 843 | 844 | such_list = [] 845 | for k in self.index_list: 846 | such_list.append(k) 847 | 848 | if F_type == "": 849 | newNode.node_type = 'Flat' # fixme 850 | FreeCAD.Console.PrintLog("Face"+ str(face_idx+1) + " Type: "+ str(newNode.node_type) + "\n") 851 | 852 | s_Posi = self.__Shape.Faces[face_idx].Surface.Position 853 | newNode.facePosi = s_Posi 854 | s_Ori = self.__Shape.Faces[face_idx].Orientation 855 | s_Axis = self.__Shape.Faces[face_idx].Surface.Axis 856 | if s_Ori == 'Forward': 857 | ext_Vec = Base.Vector(-s_Axis.x, -s_Axis.y, -s_Axis.z) 858 | else: 859 | ext_Vec = Base.Vector(s_Axis.x, s_Axis.y, s_Axis.z) 860 | 861 | newNode.axis = ext_Vec 862 | axis_line = Part.makeLine(s_Posi.add(ext_Vec), s_Posi) 863 | #Part.show(axis_line, 'axis_line'+str(face_idx+1)) 864 | 865 | # nead a mean point of the face to avoid false counter faces 866 | faceMiddle = Base.Vector(0.0,0.0,0.0) # calculating a mean vector 867 | for Vvec in self.__Shape.Faces[face_idx].OuterWire.Vertexes: 868 | faceMiddle = faceMiddle.add(Vvec.Point) 869 | faceMiddle = faceMiddle.multiply(1.0/len(self.__Shape.Faces[face_idx].OuterWire.Vertexes)) 870 | faceMiddle = faceMiddle.add(self.__thickness * ext_Vec) 871 | #Part.show(Part.makeLine(faceMiddle, faceMiddle + 2*ext_Vec), 'faceMiddle'+str(face_idx)) 872 | 873 | counterFaceList = [] 874 | gotCFace = False 875 | # search for the counter face 876 | for i in such_list: 877 | counter_found = True 878 | for F_vert in self.f_list[i].Vertexes: 879 | vF_vert = Base.Vector(F_vert.X, F_vert.Y, F_vert.Z) 880 | dist_v = vF_vert.distanceToPlane (s_Posi, ext_Vec) - self.__thickness 881 | # print "counter face distance: ", dist_v + self.__thickness 882 | #print 'checking Face', str(i+1), ' dist_v: ', dist_v 883 | if (dist_v > self.cFaceTol) or (dist_v < -self.cFaceTol): 884 | counter_found = False 885 | 886 | if counter_found: 887 | # nead a mean point of the face to avoid false counter faces 888 | counterMiddle = Base.Vector(0.0,0.0,0.0) # calculating a mean vector 889 | for Vvec in self.__Shape.Faces[i].OuterWire.Vertexes: 890 | counterMiddle = counterMiddle.add(Vvec.Point) 891 | counterMiddle = counterMiddle.multiply(1.0/len(self.__Shape.Faces[i].OuterWire.Vertexes)) 892 | 893 | distVector = counterMiddle.sub(faceMiddle) 894 | counterDistance = distVector.Length 895 | 896 | if counterDistance < 2*self.__thickness: # small stripes are a risk, fix me! 897 | FreeCAD.Console.PrintLog( "found counter-face"+ str(i + 1) + "\n") 898 | counterFaceList.append([i, counterDistance]) 899 | gotCFace = True 900 | else: 901 | counter_found = False 902 | FreeCAD.Console.PrintLog("faceMiddle: " + str(faceMiddle) + " counterMiddle: "+ str(counterMiddle) + "\n") 903 | 904 | if gotCFace: 905 | newNode.c_face_idx = counterFaceList[0][0] 906 | if len(counterFaceList) > 1: # check if more than one counterFace was detected! 907 | counterDistance = counterFaceList[0][1] 908 | for i in range(1,len(counterFaceList)): 909 | if counterDistance > counterFaceList[i][1]: 910 | counterDistance = counterFaceList[i][1] 911 | newNode.c_face_idx = counterFaceList[i][0] 912 | self.index_list.remove(newNode.c_face_idx) 913 | newNode.nfIndexes.append(newNode.c_face_idx) 914 | 915 | 916 | #if newNode.c_face_idx == None: 917 | # Part.show(axis_line) 918 | # if the parent is a bend: check the bend angle and correct it. 919 | if newNode.p_node: 920 | if newNode.p_node.node_type == 'Bend': 921 | if newNode.p_node.p_node.node_type == 'Flat': 922 | # calculate the angle on base of ext_Vec 923 | ppVec = newNode.p_node.p_node.axis # normal of the flat face 924 | myVec = newNode.axis # normal of the flat face 925 | theAxis = newNode.p_node.axis # Bend axis 926 | angle = math.atan2(ppVec.cross(myVec).dot(theAxis), ppVec.dot(myVec)) 927 | if angle < -math.pi/8: 928 | angle = angle + 2*math.pi 929 | #print 'compare angles, bend: ', newNode.p_node.bend_angle, ' ', angle 930 | newNode.p_node.bend_angle = angle # This seems to be an improvement! 931 | # newNode.p_node.bend_angle = (angle + newNode.p_node.bend_angle) / 2.0 # this is a bad approach 932 | 933 | # update the newNode.p_node.vertexDict with the Vertex data 934 | # from the own vertexes corresponding to the parent edge: P_edge 935 | topVertIndexes = range(len(self.__Shape.Faces[face_idx].Vertexes)) 936 | myFlatVertIndexes = [] 937 | # for theVert in self.__Shape.Faces[face_idx].Vertexes: 938 | for vertIdx in topVertIndexes: 939 | theVert = self.__Shape.Faces[face_idx].Vertexes[vertIdx] 940 | if equal_vertex(theVert, P_edge.Vertexes[0]): 941 | myFlatVertIndexes.append(vertIdx) 942 | if equal_vertex(theVert, P_edge.Vertexes[1]): 943 | myFlatVertIndexes.append(vertIdx) 944 | 945 | rotatedFace = self.f_list[face_idx].copy() 946 | trans_vec = newNode.p_node.tan_vec * newNode.p_node._trans_length 947 | rotatedFace.rotate(self.f_list[newNode.p_node.idx].Surface.Center,newNode.p_node.axis,math.degrees(-newNode.p_node.bend_angle)) 948 | rotatedFace.translate(trans_vec) 949 | 950 | for vKey in newNode.p_node.vertexDict: 951 | flagStr, origVec, unbendVec = newNode.p_node.vertexDict[vKey] 952 | #for theVert in myFlatVerts: 953 | for vertIdx in myFlatVertIndexes: 954 | theVert = self.__Shape.Faces[face_idx].Vertexes[vertIdx] 955 | if equal_vector(theVert.Point, origVec): 956 | flagStr = flagStr + 'c' 957 | newNode.p_node.vertexDict[vKey] = flagStr, origVec, rotatedFace.Vertexes[vertIdx].Point 958 | 959 | # update the newNode.p_node.vertexDict with the Vertex data 960 | # from the own vertexes corresponding to the opposite face 961 | oppVertIndexes = range(len(self.__Shape.Faces[newNode.c_face_idx].Vertexes)) 962 | myFlatVertIndexes = [] 963 | # for theVert in self.__Shape.Faces[face_idx].Vertexes: 964 | for vertIdx in oppVertIndexes: 965 | theVert = self.__Shape.Faces[newNode.c_face_idx].Vertexes[vertIdx] 966 | for cVert in self.__Shape.Faces[newNode.p_node.c_face_idx].Vertexes: 967 | if equal_vertex(theVert, cVert): 968 | myFlatVertIndexes.append(vertIdx) 969 | 970 | rotatedFace = self.f_list[newNode.c_face_idx].copy() 971 | trans_vec = newNode.p_node.tan_vec * newNode.p_node._trans_length 972 | rotatedFace.rotate(self.f_list[newNode.p_node.idx].Surface.Center,newNode.p_node.axis,math.degrees(-newNode.p_node.bend_angle)) 973 | rotatedFace.translate(trans_vec) 974 | 975 | for vKey in newNode.p_node.vertexDict: 976 | flagStr, origVec, unbendVec = newNode.p_node.vertexDict[vKey] 977 | #for theVert in myFlatVerts: 978 | for vertIdx in myFlatVertIndexes: 979 | theVert = self.__Shape.Faces[newNode.c_face_idx].Vertexes[vertIdx] 980 | if equal_vector(theVert.Point, origVec): 981 | flagStr = flagStr + 'c' 982 | newNode.p_node.vertexDict[vKey] = flagStr, origVec, rotatedFace.Vertexes[vertIdx].Point 983 | 984 | 985 | if F_type == "": 986 | newNode.node_type = 'Bend' # fixme 987 | s_Center = self.__Shape.Faces[face_idx].Surface.Center 988 | s_Axis = self.__Shape.Faces[face_idx].Surface.Axis 989 | newNode.axis = s_Axis 990 | newNode.bendCenter = s_Center 991 | edge_vec = P_edge.Vertexes[0].copy().Point 992 | FreeCAD.Console.PrintLog("edge_vec: "+ str(edge_vec) + "\n") 993 | 994 | if P_node.node_type == 'Flat': 995 | dist_c = edge_vec.distanceToPlane (s_Center, P_node.axis) # distance to center 996 | else: 997 | P_face = self.__Shape.Faces[P_node.idx] 998 | radVector = radial_vector(edge_vec, P_face.Surface.Center, P_face.Surface.Axis) 999 | if P_node.bend_dir == "down": 1000 | dist_c = edge_vec.distanceToPlane (s_Center, radVector.multiply(-1.0)) 1001 | else: 1002 | dist_c = edge_vec.distanceToPlane (s_Center, radVector) 1003 | 1004 | if dist_c < 0.0: 1005 | newNode.bend_dir = "down" 1006 | thick_test = self.__Shape.Faces[face_idx].Surface.Radius - self.__thickness 1007 | newNode.innerRadius = thick_test 1008 | else: 1009 | newNode.bend_dir = "up" 1010 | thick_test = self.__Shape.Faces[face_idx].Surface.Radius + self.__thickness 1011 | newNode.innerRadius = self.__Shape.Faces[face_idx].Surface.Radius 1012 | newNode.distCenter = thick_test 1013 | # print "Face idx: ", face_idx, " bend_dir: ", newNode.bend_dir 1014 | FreeCAD.Console.PrintLog("Face" + str(face_idx+1) + " Type: " + str(newNode.node_type)+ " bend_dir: "+ str(newNode.bend_dir) + "\n") 1015 | 1016 | 1017 | # calculate mean point of face: 1018 | # fixme implement also for cylindric faces 1019 | 1020 | # Search the face at the opposite site of the sheet: 1021 | #for i in range(len(such_list)): 1022 | for i in such_list: 1023 | counter_found = True 1024 | for F_vert in self.f_list[i].Vertexes: 1025 | vF_vert = Base.Vector(F_vert.X, F_vert.Y, F_vert.Z) 1026 | dist_c = vF_vert.distanceToLine (s_Center, s_Axis) - thick_test 1027 | if (dist_c > self.cFaceTol) or (dist_c < -self.cFaceTol): 1028 | counter_found = False 1029 | 1030 | if counter_found: 1031 | # to do calculate mean point of counter face 1032 | 1033 | #print "found counter Face", such_list[i]+1 1034 | newNode.c_face_idx = i 1035 | self.index_list.remove(i) 1036 | newNode.nfIndexes.append(i) 1037 | # Part.show(self.__Shape.Faces[newNode.c_face_idx]) 1038 | break 1039 | 1040 | if not counter_found: 1041 | newNode.analysis_ok = False 1042 | newNode.error_code = 13 # Analysis: counter face not found 1043 | self.error_code = 13 1044 | self.failed_face_idx = face_idx 1045 | FreeCAD.Console.PrintLog("No opposite face Debugging Thickness: "+ str(self.__thickness) + "\n") 1046 | Part.show(self.__Shape.Faces[face_idx], 'FailedFace'+ str(face_idx + 1) +'_') 1047 | return newNode 1048 | 1049 | 1050 | else: 1051 | # Need a Vertex from the parent node on the opposite side of the 1052 | # sheet metal part. This vertex is used to align other vertexes 1053 | # to the unbended sheet metal plane. 1054 | # The used vertex should be one of the opposite Face of the parent 1055 | # node with the closest distance to a line through edge_vec. 1056 | if P_node.node_type == 'Flat': 1057 | searchAxis = P_node.axis 1058 | else: 1059 | searchAxis = radVector 1060 | 1061 | maxDistance = 1000 1062 | bestPoint = None 1063 | for theVert in self.__Shape.Faces[P_node.c_face_idx].Vertexes: 1064 | vertDist = theVert.Point.distanceToLine(edge_vec, searchAxis) 1065 | if vertDist < maxDistance: 1066 | maxDistance = vertDist 1067 | bestPoint = theVert.Point 1068 | 1069 | newNode.oppositePoint = bestPoint 1070 | #Part.show(Part.makeLine(bestPoint, edge_vec), 'bestPoint'+str(face_idx+1)+'_') 1071 | 1072 | self.getBendAngle(newNode, wires_e_lists) 1073 | 1074 | # As I have learned, that it is necessary to apply corrections to Points / Vertexes, 1075 | # it will be difficult to have all vertexes of the faces of a bend to fit together. 1076 | # Therefore a dictionary is introduced, which holds the original coordinates and 1077 | # the unbend coordinates for the vertexes of the bend. It contains also flags, 1078 | # indicating if a point is part of the parent node (p) or child node (c), top face (t) or 1079 | # opposite face (o). All in newNode.vertexDict 1080 | # Structure: key: Flagstring, Base.Vector(original), Base.Vector(unbend) 1081 | # The unbend coordinates should be added before processing the top face and the 1082 | # opposite face in the generateBendShell2 procedure. 1083 | # Next is to identify for each vertex in the edges the corresponding vertex in 1084 | # newNode.vertexDict. 1085 | # Create a dictionary for the unbend edges. The key is a combination of the 1086 | # vertex indexes. The higher index is shifted 16 bits. simple_node.edgeDict 1087 | # Next is to unbend the edges, using the points in newNode.vertexDict as 1088 | # starting and ending vertex. 1089 | # Store the edge in self.edgeDict and process it to make a wire and a face. 1090 | # 1091 | # The side faces uses only the unbend vertexes from newNode.vertexDict, 1092 | # the edges from self.edgeDict are recycled. 1093 | # Only new to generate edges may need other vertexes too. 1094 | vertDictIdx = 0 # Index as key in newNode.vertexDict 1095 | for theVert in self.__Shape.Faces[face_idx].Vertexes: 1096 | flagStr = 't' 1097 | origVec = theVert.Point 1098 | unbendVec = None 1099 | if equal_vertex(theVert, P_edge.Vertexes[0]): 1100 | flagStr = flagStr + 'p0' 1101 | origVec = P_edge.Vertexes[0].Point 1102 | unbendVec = origVec 1103 | else: 1104 | if equal_vertex(theVert, P_edge.Vertexes[1]): 1105 | flagStr = flagStr + 'p1' 1106 | origVec = P_edge.Vertexes[1].Point 1107 | unbendVec = origVec 1108 | print ('make vertexDict: ', flagStr, ' ', str(face_idx+1)) 1109 | newNode.vertexDict[vertDictIdx] = flagStr, origVec, unbendVec 1110 | vertDictIdx += 1 1111 | 1112 | for theVert in self.__Shape.Faces[newNode.c_face_idx].Vertexes: 1113 | flagStr = 'o' 1114 | origVec = theVert.Point 1115 | unbendVec = None 1116 | for pVert in self.__Shape.Faces[P_node.c_face_idx].Vertexes: 1117 | if equal_vertex(theVert, pVert): 1118 | flagStr = flagStr + 'p' 1119 | origVec = pVert.Point 1120 | unbendVec = origVec 1121 | print ('make vertexDict: ', flagStr, ' ', str(face_idx+1)) 1122 | newNode.vertexDict[vertDictIdx] = flagStr, origVec, unbendVec 1123 | vertDictIdx += 1 1124 | 1125 | # Part.show(self.__Shape.Faces[newNode.c_face_idx]) 1126 | # Part.show(self.__Shape.Faces[newNode.idx]) 1127 | if newNode.c_face_idx is None: 1128 | newNode.analysis_ok = False 1129 | newNode.error_code = 13 # Analysis: counter face not found 1130 | self.error_code = 13 1131 | self.failed_face_idx = face_idx 1132 | FreeCAD.Console.PrintLog("No counter-face Debugging Thickness: "+ str(self.__thickness) + "\n") 1133 | Part.show(self.__Shape.Faces[face_idx], 'FailedFace'+ str(face_idx + 1) +'_') 1134 | 1135 | 1136 | 1137 | 1138 | # now we call the new code 1139 | self.get_node_faces(newNode, wires_e_lists) 1140 | #for nFace in newNode.nfIndexes: 1141 | # Part.show(nFace) 1142 | 1143 | 1144 | if P_node is None: 1145 | self.root = newNode 1146 | else: 1147 | P_node.child_list.append(newNode) 1148 | return newNode 1149 | 1150 | 1151 | 1152 | def Bend_analysis(self, face_idx, parent_node = None, parent_edge = None): 1153 | # This functions traverses the shape in order to build the bend-tree 1154 | # For each relevant face a t_node is created and linked into the tree 1155 | # the linking is done in the call of self.make_new_face_node 1156 | #print "Bend_analysis Face", face_idx +1 , 1157 | # analysis_ok = True # not used anymore? 1158 | # edge_list = [] 1159 | if self.error_code is None: 1160 | wires_edge_lists = [] 1161 | wire_idx = -1 1162 | for n_wire in self.f_list[face_idx].Wires: 1163 | wire_idx += 1 1164 | wires_edge_lists.append([]) 1165 | #for n_edge in self.__Shape.Faces[face_idx].Edges: 1166 | for n_edge in n_wire.Edges: 1167 | if parent_edge: 1168 | if not parent_edge.isSame(n_edge): 1169 | #edge_list.append(n_edge) 1170 | wires_edge_lists[wire_idx].append(n_edge) 1171 | # 1172 | else: 1173 | #edge_list.append(n_edge) 1174 | wires_edge_lists[wire_idx].append(n_edge) 1175 | if parent_node: 1176 | FreeCAD.Console.PrintLog(" Parent Face" + str(parent_node.idx + 1) + "\n") 1177 | FreeCAD.Console.PrintLog("The list: "+ str(self.index_list) + "\n") 1178 | t_node = self.make_new_face_node(face_idx, parent_node, parent_edge, wires_edge_lists) 1179 | # Need also the edge_list in the node! 1180 | FreeCAD.Console.PrintLog("The list after make_new_face_node: " + str(self.index_list) + "\n") 1181 | 1182 | # in the new code, only the list of child faces will be analyzed. 1183 | removalList = [] 1184 | for child_info in t_node.child_idx_lists: 1185 | if child_info[0] in self.index_list: 1186 | FreeCAD.Console.PrintLog("child in list: "+ str(child_info[0]) + "\n") 1187 | self.Bend_analysis(child_info[0], t_node, child_info[1]) 1188 | else: 1189 | FreeCAD.Console.PrintLog("remove child from List: " + str(child_info[0]) + "\n") 1190 | t_node.seam_edges.append(child_info[1]) # give Information to the node, that it has a seam. 1191 | FreeCAD.Console.PrintLog("node faces before: " + str(t_node.nfIndexes) + "\n") 1192 | # do not make Faces at a detected seam! 1193 | # self.makeSeamFace(child_info[1], t_node) 1194 | removalList.append(child_info) 1195 | FreeCAD.Console.PrintLog("node faces with seam: "+ str(t_node.nfIndexes) + "\n") 1196 | otherSeamNode = self.searchNode(child_info[0], self.root) 1197 | FreeCAD.Console.PrintLog("counterface on otherSeamNode: Face" + str(otherSeamNode.c_face_idx+1) + "\n") 1198 | # do not make Faces at a detected seam! 1199 | # self.makeSeamFace(child_info[1], otherSeamNode) 1200 | for seams in removalList: 1201 | t_node.child_idx_lists.remove(seams) 1202 | else: 1203 | FreeCAD.Console.PrintError('got error code: '+ str(self.error_code) + ' at Face'+ str(self.failed_face_idx+1) + "\n") 1204 | 1205 | 1206 | 1207 | 1208 | def searchNode(self, theIdx, sNode): 1209 | # search for a Node with theIdx in sNode.idx 1210 | FreeCAD.Console.PrintLog("my Idx: "+ str(sNode.idx) + "\n") 1211 | 1212 | if sNode.idx == theIdx: 1213 | return sNode 1214 | else: 1215 | result = None 1216 | childFaces = [] 1217 | for n_node in sNode.child_list: 1218 | childFaces.append(n_node.idx) 1219 | FreeCAD.Console.PrintLog("my children: "+ str(childFaces) + "\n") 1220 | 1221 | for n_node in sNode.child_list: 1222 | nextSearch = self.searchNode(theIdx, n_node) 1223 | if nextSearch is not None: 1224 | result = nextSearch 1225 | break 1226 | if result is not None: 1227 | FreeCAD.Console.PrintLog("This is the result: "+ str(result.idx) + "\n") 1228 | else: 1229 | FreeCAD.Console.PrintLog("This is the result: None\n") 1230 | 1231 | return result 1232 | 1233 | # suche bei mir. wenn ja liefere ab 1234 | # sonst sind Kinder da? 1235 | # Wenn Kinder vorhanden, frag solange Kinder bis gefunden 1236 | # oder kein Kind mehr da. 1237 | 1238 | 1239 | def rotateVec(self, vec, phi, rAxis): 1240 | ''' rotate a vector by the angle phi around the axis rAxis''' 1241 | #https://de.wikipedia.org/wiki/Drehmatrix 1242 | rVec = rAxis.cross(vec).cross(rAxis).multiply(math.cos(phi)) + rAxis.cross(vec)*math.sin(phi) + rAxis*rAxis.dot(vec) 1243 | return rVec 1244 | 1245 | 1246 | def unbendFace(self, fIdx, bend_node, nullVec, mode = 'side'): 1247 | ''' 1248 | The self.vertexDict requires a further data structure to hold for 1249 | each edge in a list the point indexes to the vertexes of the bend node. 1250 | key: Index to myEdgeList, content: List of indexes to the self.vertexDict. 1251 | ''' 1252 | 1253 | axis = bend_node.axis 1254 | cent = bend_node.bendCenter 1255 | bRad = bend_node.innerRadius 1256 | kFactor = bend_node.k_Factor 1257 | thick = self.__thickness 1258 | transRad = bRad + kFactor * thick/2.0 1259 | #print 'transRad Face', str(fIdx+1), ' ', bRad, ' ', kFactor, ' ', thick 1260 | tanVec = bend_node.tan_vec 1261 | aFace = self.f_list[fIdx] 1262 | 1263 | normVec = radial_vector(bend_node.p_edge.Vertexes[0].Point, cent, axis) 1264 | 1265 | if mode == 'top': 1266 | chord = cent.sub(bend_node.p_edge.Vertexes[0].Point) 1267 | norm = axis.cross(chord) 1268 | compRadialVec = axis.cross(norm) 1269 | 1270 | 1271 | if mode == 'counter': 1272 | chord = cent.sub(bend_node.oppositePoint) 1273 | norm = axis.cross(chord) 1274 | compRadialVec = axis.cross(norm) 1275 | 1276 | 1277 | def unbendPoint(poi): 1278 | radVec = radial_vector(poi, cent, axis) 1279 | angle = math.atan2(nullVec.cross(radVec).dot(axis), nullVec.dot(radVec)) 1280 | #print 'point Face', str(fIdx+1), ' ', angle 1281 | if angle < -math.pi/8: 1282 | angle = angle + 2*math.pi 1283 | rotVec = self.rotateVec(poi.sub(cent), -angle, axis) 1284 | #print 'point if Face', str(fIdx+1), ' ', angle, ' ', transRad*angle 1285 | if (mode == 'top') or (mode == 'counter'): 1286 | chord = cent.sub(cent + rotVec) 1287 | norm = axis.cross(chord) 1288 | correctionVec = compRadialVec.sub(axis.cross(norm)) 1289 | #correctionVec = axis.cross(norm).sub(compRadialVec) 1290 | #print 'origVec ', axis.cross(norm), ' compRadialVec ', compRadialVec 1291 | bPoint = cent + rotVec + correctionVec + tanVec*transRad*angle 1292 | else: 1293 | bPoint = cent + rotVec + tanVec*transRad*angle 1294 | 1295 | return bPoint 1296 | 1297 | divisions = 12 # fix me! need a dependence on something useful. 1298 | 1299 | 1300 | fWireList = aFace.Wires[:] 1301 | #newWires = [] 1302 | edgeLists = [] 1303 | 1304 | for aWire in fWireList: 1305 | uEdge = None 1306 | idxList, closedW = self.sortEdgesTolerant(aWire.Edges) 1307 | print ('Wire', str(fIdx+1), ' has ', len(idxList), ' edges, closed: ', closedW) 1308 | 1309 | eList = [] # is the list of unbend edges 1310 | j=0 1311 | for fEdgeIdx in idxList: 1312 | fEdge = aWire.Edges[fEdgeIdx] 1313 | eType = str(fEdge.Curve) 1314 | vertexCount = len(fEdge.Vertexes) 1315 | #print "the type of curve: ", eType 1316 | vert0 = None 1317 | vert1 = None 1318 | flags0 = None 1319 | flags1 = None 1320 | uVert0 = None 1321 | uVert1 = None 1322 | edgeKey = None 1323 | vert0Idx = None 1324 | vert1Idx = None 1325 | 1326 | #print 'edge vertexes: ', str(fIdx+1), ' ', mode, ' ', fEdge.Vertexes[0].Point, ' ', fEdge.Vertexes[1].Point 1327 | for oVertIdx in bend_node.vertexDict: 1328 | flagStr, origVec, unbendVec = bend_node.vertexDict[oVertIdx] 1329 | #print 'origVec: ', origVec 1330 | if equal_vector(fEdge.Vertexes[0].Point, origVec,5): 1331 | vert0Idx = oVertIdx 1332 | flags0 = flagStr 1333 | uVert0 = unbendVec 1334 | if vertexCount > 1: 1335 | if equal_vector(fEdge.Vertexes[1].Point, origVec,5): 1336 | vert1Idx = oVertIdx 1337 | flags1 = flagStr 1338 | uVert1 = unbendVec 1339 | # can we break the for loop at some condition? 1340 | # Handle cases, where a side face has additional vertexes 1341 | if mode == 'side': 1342 | if vert0Idx is None: 1343 | vert0Idx = len(bend_node.vertexDict) 1344 | print ('got additional side vertex0: ', vert0Idx, ' ', fEdge.Vertexes[0].Point) 1345 | flags0 = '' 1346 | origVec = fEdge.Vertexes[0].Point 1347 | uVert0 = unbendPoint(origVec) 1348 | bend_node.vertexDict[vert0Idx] = flags0, origVec, uVert0 1349 | if vertexCount >1: 1350 | if vert1Idx is None: 1351 | vert1Idx = len(bend_node.vertexDict) 1352 | print ('got additional side vertex1: ', vert1Idx, ' ', fEdge.Vertexes[1].Point) 1353 | flags1 = '' 1354 | origVec = fEdge.Vertexes[1].Point 1355 | uVert1 = unbendPoint(origVec) 1356 | bend_node.vertexDict[vert1Idx] = flags1, origVec, uVert1 1357 | 1358 | 1359 | # make the key for bend_node.edgeDict, shift vert1 and add both. 1360 | if vert0Idx is None: 1361 | #print 'catastrophy: ', fEdge.Vertexes[0].Point, ' ', fEdge.Vertexes[1].Point, ' ', eType 1362 | Part.show(fEdge, 'catastrophyEdge') 1363 | # fix me, need proper failure mode. 1364 | if vert1Idx: 1365 | if vert1Idx < vert0Idx: 1366 | edgeKey = vert0Idx + (vert1Idx << 8) 1367 | else: 1368 | edgeKey = vert1Idx + (vert0Idx << 8) 1369 | # x << n: x shifted left by n bits = Multiplication 1370 | else: 1371 | edgeKey = vert0Idx 1372 | 1373 | #print 'edgeKey: ', edgeKey, ' ', str(fIdx+1), ' ', mode, ' ', uVert0, ' ', uVert1 1374 | 1375 | urollPts = [] 1376 | 1377 | if ("" in eType): 1378 | minPar, maxPar = fEdge.ParameterRange 1379 | FreeCAD.Console.PrintLog("the Parameterrange: "+ str(minPar)+ " to " + str(maxPar)+ " Type: "+str(eType) + "\n") 1380 | 1381 | # compare minimal 1/curvature with curve-lenght to decide on division 1382 | iMulti = (maxPar-minPar)/24 1383 | maxCurva = 0.0 1384 | for i in range(24): 1385 | posi = fEdge.valueAt(minPar + i*iMulti) 1386 | # print 'testEdge ', i, ' curva: ' , testEdge.Curve.curvature(minPar + i*iMulti) 1387 | curva = fEdge.Curve.curvature(minPar + i*iMulti) 1388 | if curva > maxCurva: 1389 | maxCurva = curva 1390 | 1391 | decisionAngle = fEdge.Length * maxCurva 1392 | # print 'Face', str(fIdx+1), ' EllidecisionAngle: ', decisionAngle 1393 | # Part.show(fEdge, 'EllideciAng'+str(decisionAngle)+ '_') 1394 | 1395 | if decisionAngle < 0.1: 1396 | eDivisions = 4 1397 | elif decisionAngle < 0.5: 1398 | eDivisions = 6 1399 | else: 1400 | eDivisions = 12 1401 | 1402 | iMulti = (maxPar-minPar)/eDivisions 1403 | urollPts.append(uVert0) 1404 | for i in range(1,eDivisions): 1405 | posi = fEdge.valueAt(minPar + i*iMulti) 1406 | bPosi = unbendPoint(posi) 1407 | urollPts.append(bPosi) 1408 | urollPts.append(uVert1) 1409 | 1410 | uCurve = Part.BSplineCurve() 1411 | uCurve.interpolate(urollPts) 1412 | uEdge = uCurve.toShape() 1413 | #Part.show(uEdge, 'Elli'+str(j)+'_') 1414 | 1415 | elif "" in eType) or ("" in eType): 1433 | minPar, maxPar = fEdge.ParameterRange 1434 | #print "the Parameterrange: ", minPar, " - ", maxPar, " Type: ",eType 1435 | 1436 | # compare minimal 1/curvature with curve-lenght to decide on division 1437 | iMulti = (maxPar-minPar)/24 1438 | maxCurva = 0.0 1439 | testPts = [] 1440 | for i in range(24+1): 1441 | posi = fEdge.valueAt(minPar + i*iMulti) 1442 | bPosi = unbendPoint(posi) 1443 | testPts.append(bPosi) 1444 | testCurve = Part.BSplineCurve() 1445 | testCurve.interpolate(testPts) 1446 | testEdge = testCurve.toShape() 1447 | 1448 | for i in range(24+1): 1449 | posi = testEdge.valueAt(minPar + i*iMulti) 1450 | # print 'testEdge ', i, ' curva: ' , testEdge.Curve.curvature(minPar + i*iMulti) 1451 | curva = testEdge.Curve.curvature(minPar + i*iMulti) 1452 | if curva > maxCurva: 1453 | maxCurva = curva 1454 | 1455 | decisionAngle = testEdge.Length * maxCurva 1456 | #print 'Face', str(fIdx+1), ' decisionAngle: ', decisionAngle 1457 | #Part.show(testEdge, 'deciAng'+str(decisionAngle)+ '_') 1458 | 1459 | if decisionAngle > 1000.0: 1460 | bDivisions = 4 1461 | else: 1462 | bDivisions = 12 1463 | 1464 | iMulti = (maxPar-minPar)/bDivisions 1465 | if vertexCount > 1: 1466 | urollPts.append(uVert0) 1467 | for i in range(1,bDivisions): 1468 | posi = fEdge.valueAt(minPar + i*iMulti) 1469 | # curvature is 1/radius 1470 | #print 'Face', str(fIdx+1), ' 1/Curvature: ', 1/fEdge.Curve.curvature(minPar + i*iMulti), ' ', fEdge.Length 1471 | bPosi = unbendPoint(posi) 1472 | urollPts.append(bPosi) 1473 | urollPts.append(uVert1) 1474 | else: 1475 | urollPts.append(uVert0) 1476 | for i in range(1,bDivisions): 1477 | posi = fEdge.valueAt(minPar + i*iMulti) 1478 | bPosi = unbendPoint(posi) 1479 | urollPts.append(bPosi) 1480 | urollPts.append(uVert0) 1481 | #testPoly = Part.makePolygon(urollPts) 1482 | #Part.show(testPoly, 'testPoly'+ str(fIdx+1) + '_') 1483 | uCurve = Part.BSplineCurve() 1484 | try: 1485 | uCurve.interpolate(urollPts) 1486 | uEdge = uCurve.toShape() 1487 | #Part.show(theCurve, 'B_spline') 1488 | except: 1489 | #uEdge = Part.makeLine(urollPts[0], urollPts[-1]) 1490 | if bDivisions == 4: 1491 | uCurve.interpolate([urollPts[0], urollPts[2], urollPts[-1]]) 1492 | if bDivisions == 12: 1493 | uCurve.interpolate([urollPts[0],urollPts[3],urollPts[6],urollPts[9], urollPts[-1]]) 1494 | uEdge = uCurve.toShape() 1495 | else: 1496 | #print 'unbendFace, curve type not handled: ' + str(eType) + ' in Face' + str(fIdx+1) 1497 | FreeCAD.Console.PrintLog('unbendFace, curve type not handled: ' + str(eType) + ' in Face' + str(fIdx+1) + '\n') 1498 | self.error_code = 26 1499 | self.failed_face_idx = fIdx 1500 | 1501 | # in mode 'side' check, if not the top or counter edge can be used instead. 1502 | if mode == 'side': 1503 | if edgeKey in bend_node.edgeDict: 1504 | uEdge = bend_node.edgeDict[edgeKey] 1505 | #print 'found key in node.edgeDict: ', edgeKey, ' in mode: ', mode 1506 | #Part.show(uEdge, 'bendEdge'+str(fIdx+1)+'_') 1507 | eList.append(uEdge) 1508 | if not (edgeKey in bend_node.edgeDict): 1509 | bend_node.edgeDict[edgeKey] = uEdge 1510 | #print 'added key: ', edgeKey, ' to edgeDict in mode: ', mode 1511 | j += 1 1512 | edgeLists.append(eList) 1513 | # end of for what? 1514 | 1515 | # Here we store the unbend top and counter outer edge list in the node data. 1516 | # These are needed later as edges in the new side faces. 1517 | if mode == 'top': 1518 | bend_node.unfoldTopList = edgeLists[0] 1519 | if mode == 'counter': 1520 | bend_node.unfoldCounterList = edgeLists[0] 1521 | 1522 | 1523 | if len(edgeLists) == 1: 1524 | eList = Part.__sortEdges__(edgeLists[0]) 1525 | myWire = Part.Wire(eList) 1526 | FreeCAD.Console.PrintLog('len eList: '+ str(len(eList)) + '\n') 1527 | #Part.show(myWire, 'Wire_Face'+str(fIdx+1)+'_' ) 1528 | if (len(myWire.Vertexes) == 2) and (len(myWire.Edges) == 3): 1529 | #print 'got sweep condition!' 1530 | pWire = Part.Wire(myWire.Edges[1]) 1531 | fWire = Part.Wire(myWire.Edges[0]) # first sweep profile 1532 | lWire = Part.Wire(myWire.Edges[2]) # last sweep profile 1533 | theFace = pWire.makePipeShell([fWire, lWire], False, True) 1534 | theFace = theFace.Faces[0] 1535 | #Part.show(theFace, 'Loch') 1536 | else: 1537 | try: 1538 | #Part.show(myWire, 'myWire'+ str(bend_node.idx+1)+'_') 1539 | theFace = Part.Face(myWire) 1540 | #theFace = Part.makeFace(myWire, 'Part::FaceMakerSimple') 1541 | except: 1542 | FreeCAD.Console.PrintLog('got exception at Face: '+ str(fIdx+1) +' len eList: '+ str(len(eList)) + '\n') 1543 | #for w in eList: 1544 | #Part.show(w, 'exceptEdge') 1545 | #print 'exception type: ', str(w.Curve) 1546 | #Part.show(myWire, 'exceptionWire'+ str(fIdx+1)+'_') 1547 | secWireList = myWire.Edges[:] 1548 | thirdWireList = Part.__sortEdges__(secWireList) 1549 | theFace = Part.makeFilledFace(thirdWireList) 1550 | #Part.show(theFace, 'theFace'+ str(bend_node.idx+1)+'_') 1551 | else: 1552 | FreeCAD.Console.PrintLog('len edgeLists: '+ str(len(edgeLists))+'\n') 1553 | faces = [] 1554 | wires = [] 1555 | wireNumber = 0 1556 | for w in edgeLists: 1557 | eList = Part.__sortEdges__(w) 1558 | #print 'eList: ', eList 1559 | if wireNumber < 0: 1560 | #myWire = Part.Wire(eList.reverse()) 1561 | reversList = [] 1562 | for e in eList: 1563 | reversList.insert(0,e) 1564 | myWire = Part.Wire(reversList) 1565 | else: 1566 | myWire = Part.Wire(eList) 1567 | #Part.show(myWire, 'myWire'+ str(bend_node.idx+1)+'_') 1568 | nextFace = Part.Face(myWire) 1569 | faces.append(nextFace) 1570 | wires.append(myWire) 1571 | wireNumber += 1 1572 | #Part.show(Part.Face(myWire)) 1573 | try: 1574 | #theFace = Part.Face(wires) 1575 | #print 'make cutted face\n' 1576 | theFace = faces[0].copy() 1577 | for f in faces[1:]: 1578 | f.translate(-normVec) 1579 | cutter= f.extrude(2*normVec) 1580 | theFace = theFace.cut(cutter) 1581 | theFace = theFace.Faces[0] 1582 | #Part.show(theFace, 'theFace') 1583 | #theFace = Part.Face(wires[0], wires[1:]) 1584 | #theFace = Part.makeFace(myWire, 'Part::FaceMakerSimple') 1585 | except: 1586 | #theFace = Part.makeFilledFace(wires) 1587 | theFace = faces[0] 1588 | print ('got execption') 1589 | #Part.show(theFace, 'exception') 1590 | keyList = [] 1591 | for key in bend_node.edgeDict: 1592 | keyList.append(key) 1593 | #print 'edgeDict keys: ', keyList 1594 | #Part.show(theFace, 'unbendFace'+str(fIdx+1)) 1595 | return theFace 1596 | 1597 | def sortEdgesTolerant(self, myEdgeList): 1598 | ''' 1599 | sort edges from an existing wire. 1600 | returns: 1601 | a new sorted list of indexes to edges of the original wire 1602 | flag if wire is closed or not (a wire of a cylinder mantle is not closed!) 1603 | ''' 1604 | eIndex = 0 1605 | newEdgeList = [] 1606 | idxList = list(range(len(myEdgeList))) 1607 | newIdxList = [eIndex] 1608 | newEdgeList.append(myEdgeList[eIndex]) 1609 | idxList.remove(eIndex) 1610 | gotConnection = False 1611 | closedWire = False 1612 | 1613 | startVert = myEdgeList[eIndex].Vertexes[0] 1614 | if len(myEdgeList[eIndex].Vertexes) > 1: 1615 | vert = myEdgeList[eIndex].Vertexes[1] 1616 | else: 1617 | vert = myEdgeList[eIndex].Vertexes[0] 1618 | #Part.show(myEdgeList[0], 'tolEdge'+str(1)+'_') 1619 | while not gotConnection: 1620 | for eIdx in idxList: 1621 | edge = myEdgeList[eIdx] 1622 | if equal_vertex(vert, edge.Vertexes[0]): 1623 | idxList.remove(eIdx) 1624 | eIndex = eIdx 1625 | #print 'found eIdx: ', eIdx 1626 | newIdxList.append(eIdx) 1627 | if len(edge.Vertexes) > 1: 1628 | vert = edge.Vertexes[1] 1629 | break 1630 | if len(edge.Vertexes) > 1: 1631 | if equal_vertex(vert, edge.Vertexes[1]): 1632 | idxList.remove(eIdx) 1633 | eIndex = eIdx 1634 | #print 'found eIdx: ', eIdx 1635 | newIdxList.append(eIdx) 1636 | vert = edge.Vertexes[0] 1637 | break 1638 | if (len(idxList) == 0): 1639 | gotConnection = True 1640 | if equal_vertex(vert, startVert): 1641 | #print 'got last connection' 1642 | gotConnection = True 1643 | closedWire = True 1644 | #Part.show(myEdgeList[eIdx], 'tolEdge'+str(eIdx+1)+'_') 1645 | #print 'tolerant wire: ', len(myEdgeList) 1646 | return newIdxList, closedWire 1647 | 1648 | 1649 | def makeFoldLines(self, bend_node, nullVec): 1650 | axis = bend_node.axis 1651 | cent = bend_node.bendCenter 1652 | bRad = bend_node.innerRadius 1653 | kFactor = bend_node.k_Factor 1654 | thick = self.__thickness 1655 | transRad = bRad + kFactor * thick/2.0 1656 | tanVec = bend_node.tan_vec 1657 | theFace = self.f_list[bend_node.idx] 1658 | 1659 | angle_0 = theFace.ParameterRange[0] 1660 | angle_1 = theFace.ParameterRange[1] 1661 | length_0 = theFace.ParameterRange[2] 1662 | 1663 | halfAngle = (angle_0 + angle_1) / 2 1664 | bLinePoint0 = theFace.valueAt(halfAngle,length_0) 1665 | #bLinePoint1 = theFace.valueAt(halfAngle,length_1) 1666 | normVec = radial_vector(bLinePoint0, cent, axis) 1667 | sliceVec = normVec.cross(axis) 1668 | origin = Base.Vector(0.0,0.0,0.0) 1669 | distance = origin.distanceToPlane(bLinePoint0, sliceVec) 1670 | testDist = -bLinePoint0.distanceToPlane(sliceVec * distance, sliceVec) 1671 | if math.fabs(testDist) > math.fabs(distance): 1672 | sliceVec = -sliceVec 1673 | 1674 | 1675 | #Part.show(Part.makePolygon([origin,sliceVec * distance]), 'distance') 1676 | #print 'distance: ', distance, ' testDist: ', testDist 1677 | wires = [] 1678 | for i in theFace.slice(sliceVec, distance): 1679 | wires.append(i) 1680 | #print 'got ', len(wires), ' wires' 1681 | #Part.show(Part.Compound(wires), 'slice') 1682 | theComp = Part.Compound(wires) 1683 | # fix me, what if there are no wires? 1684 | wireList =[] 1685 | 1686 | for fEdge in theComp.Edges: 1687 | eType = str(fEdge.Curve) 1688 | #print "the type of curve: ", eType 1689 | urollPts = [] 1690 | 1691 | if "1: 1816 | if equal_vertex(theEdge.Vertexes[0], nextVert): 1817 | next_idx = 1 1818 | if equal_vertex(theEdge.Vertexes[1], nextVert): 1819 | next_idx = 0 1820 | if next_idx is not None: 1821 | if self.isVertOpposite(theEdge.Vertexes[next_idx], theNode): 1822 | nextEdge = theEdge.copy() 1823 | search_List.remove(i) 1824 | the_index = i 1825 | #Part.show(nextEdge) 1826 | break 1827 | else: 1828 | next_idx = None 1829 | if the_index is not None: 1830 | break 1831 | 1832 | #find the lastEdge 1833 | last_idx = None 1834 | FreeCAD.Console.PrintLog("This is the search_List: "+ str(search_List) + "\n") 1835 | for i in search_List: 1836 | #Part.show(self.f_list[i]) 1837 | for theEdge in self.f_list[i].Edges: 1838 | FreeCAD.Console.PrintLog("find last Edge in Face: "+ str(i)+ " at Edge: "+ str(theEdge) + "\n") 1839 | if len(theEdge.Vertexes)>1: 1840 | if equal_vertex(theEdge.Vertexes[0], startVert): 1841 | last_idx = 1 1842 | if equal_vertex(theEdge.Vertexes[1], startVert): 1843 | last_idx = 0 1844 | if last_idx is not None: 1845 | FreeCAD.Console.PrintLog("test for the last Edge\n") 1846 | if self.isVertOpposite(theEdge.Vertexes[last_idx], theNode): 1847 | lastEdge = theEdge.copy() 1848 | search_List.remove(i) 1849 | the_index = i 1850 | #Part.show(lastEdge) 1851 | break 1852 | else: 1853 | last_idx = None 1854 | if last_idx is not None: 1855 | break 1856 | 1857 | # find the middleEdge 1858 | mid_idx = None 1859 | midEdge = None 1860 | for theEdge in self.f_list[theNode.c_face_idx].Edges: 1861 | if len(theEdge.Vertexes)>1: 1862 | if equal_vertex(theEdge.Vertexes[0], nextEdge.Vertexes[next_idx]): 1863 | mid_idx = 1 1864 | if equal_vertex(theEdge.Vertexes[1], nextEdge.Vertexes[next_idx]): 1865 | mid_idx = 0 1866 | if mid_idx is not None: 1867 | if equal_vertex(theEdge.Vertexes[mid_idx], lastEdge.Vertexes[last_idx]): 1868 | midEdge = theEdge.copy() 1869 | #Part.show(midEdge) 1870 | break 1871 | else: 1872 | mid_idx = None 1873 | if midEdge: 1874 | break 1875 | 1876 | seam_wire = Part.Wire([sEdge, nextEdge, midEdge, lastEdge ]) 1877 | seamFace = Part.Face(seam_wire) 1878 | self.f_list.append(seamFace) 1879 | theNode.nfIndexes.append(self.max_f_idx) 1880 | self.max_f_idx += 1 1881 | 1882 | 1883 | def showFaces(self): 1884 | for i in self.index_list: 1885 | Part.show(self.f_list[i]) 1886 | 1887 | 1888 | def unfold_tree2(self, node): 1889 | # This function traverses the tree and unfolds the faces 1890 | # beginning at the outermost nodes. 1891 | #print "unfold_tree face", node.idx + 1 1892 | theShell = [] 1893 | nodeShell = [] 1894 | theFoldLines = [] 1895 | nodeFoldLines = [] 1896 | for n_node in node.child_list: 1897 | if self.error_code is None: 1898 | shell, foldLines = self.unfold_tree2(n_node) 1899 | theShell = theShell + shell 1900 | theFoldLines = theFoldLines + foldLines 1901 | if node.node_type == 'Bend': 1902 | trans_vec = node.tan_vec * node._trans_length 1903 | for bFaces in theShell: 1904 | bFaces.rotate(self.f_list[node.idx].Surface.Center,node.axis,math.degrees(-node.bend_angle)) 1905 | bFaces.translate(trans_vec) 1906 | for fold in theFoldLines: 1907 | fold.rotate(self.f_list[node.idx].Surface.Center,node.axis,math.degrees(-node.bend_angle)) 1908 | fold.translate(trans_vec) 1909 | if self.error_code is None: 1910 | #nodeShell = self.generateBendShell(node) 1911 | nodeShell, nodeFoldLines = self.generateBendShell2(node) 1912 | else: 1913 | if self.error_code is None: 1914 | # nodeShell = self.generateShell(node) 1915 | for idx in node.nfIndexes: 1916 | nodeShell.append(self.f_list[idx].copy()) 1917 | #if len(node.seam_edges)>0: 1918 | # for seamEdge in node.seam_edges: 1919 | # self.makeSeamFace(seamEdge, node) 1920 | FreeCAD.Console.PrintLog("ufo finish face" + str(node.idx +1) + "\n") 1921 | return (theShell + nodeShell, theFoldLines + nodeFoldLines) 1922 | 1923 | 1924 | 1925 | 1926 | def getUnfold(): 1927 | resPart = None 1928 | normalVect = None 1929 | folds = None 1930 | theName = None 1931 | mylist = Gui.Selection.getSelectionEx() 1932 | # print 'Die Selektion: ',mylist 1933 | # print 'Zahl der Selektionen: ', mylist.__len__() 1934 | 1935 | if mylist.__len__() == 0: 1936 | mw=FreeCADGui.getMainWindow() 1937 | QtGui.QMessageBox.information(mw,"Error","""One flat face needs to be selected!""") 1938 | else: 1939 | if mylist.__len__() > 1: 1940 | mw=FreeCADGui.getMainWindow() 1941 | QtGui.QMessageBox.information(mw,"Error","""Only one flat face has to be selected!""") 1942 | else: 1943 | o = Gui.Selection.getSelectionEx()[0] 1944 | theName = o.ObjectName 1945 | if len(o.SubObjects)>1: 1946 | mw=FreeCADGui.getMainWindow() 1947 | QtGui.QMessageBox.information(mw,"SubelementError","""Only one flat face has to be selected!""") 1948 | else: 1949 | subelement = o.SubObjects[0] 1950 | if hasattr(subelement,'Surface'): 1951 | s_type = str(subelement.Surface) 1952 | if s_type == "": 1953 | normalVect = subelement.normalAt(0,0) 1954 | mw=FreeCADGui.getMainWindow() 1955 | #QtGui.QMessageBox.information(mw,"Hurra","""Lets try unfolding!""") 1956 | FreeCAD.Console.PrintLog("name: "+ str(subelement) + "\n") 1957 | f_number = int(o.SubElementNames[0].lstrip('Face'))-1 1958 | #print f_number 1959 | startzeit = time.clock() 1960 | TheTree = SheetTree(o.Object.Shape, f_number) # initializes the tree-structure 1961 | if TheTree.error_code is None: 1962 | TheTree.Bend_analysis(f_number, None) # traverses the shape and builds the tree-structure 1963 | endzeit = time.clock() 1964 | FreeCAD.Console.PrintLog("Analytical time: "+ str(endzeit-startzeit) + "\n") 1965 | 1966 | if TheTree.error_code is None: 1967 | # TheTree.showFaces() 1968 | theFaceList, foldLines = TheTree.unfold_tree2(TheTree.root) # traverses the tree-structure 1969 | if TheTree.error_code is None: 1970 | unfoldTime = time.clock() 1971 | FreeCAD.Console.PrintLog("time to run the unfold: "+ str(unfoldTime - endzeit) + "\n") 1972 | folds = Part.Compound(foldLines) 1973 | #Part.show(folds, 'Fold_Lines') 1974 | 1975 | try: 1976 | newShell = Part.Shell(theFaceList) 1977 | except: 1978 | FreeCAD.Console.PrintLog("couldn't join some faces, show only single faces!\n") 1979 | resPart = Part.Compound(theFaceList) 1980 | #for newFace in theFaceList: 1981 | #Part.show(newFace) 1982 | else: 1983 | 1984 | try: 1985 | TheSolid = Part.Solid(newShell) 1986 | solidTime = time.clock() 1987 | FreeCAD.Console.PrintLog("time to make the solid: "+ str(solidTime - unfoldTime) + "\n") 1988 | except: 1989 | FreeCAD.Console.PrintLog("couldn't make a solid, show only a shell, Faces in List: "+ str(len(theFaceList)) +"\n") 1990 | resPart = newShell 1991 | #Part.show(newShell) 1992 | showTime = time.clock() 1993 | FreeCAD.Console.PrintLog("Show time: "+ str(showTime - unfoldTime) + "\n") 1994 | else: 1995 | try: 1996 | cleanSolid = TheSolid.removeSplitter() 1997 | #Part.show(cleanSolid) 1998 | resPart = cleanSolid 1999 | 2000 | except: 2001 | #Part.show(TheSolid) 2002 | resPart = TheSolid 2003 | showTime = time.clock() 2004 | FreeCAD.Console.PrintLog("Show time: "+ str(showTime - solidTime) + " total time: "+ str(showTime - startzeit) + "\n") 2005 | 2006 | if TheTree.error_code is not None: 2007 | FreeCAD.Console.PrintError("Error "+ unfold_error[TheTree.error_code] + 2008 | " at Face"+ str(TheTree.failed_face_idx+1) + "\n") 2009 | QtGui.QMessageBox.information(mw,"Error",unfold_error[TheTree.error_code]) 2010 | else: 2011 | FreeCAD.Console.PrintLog("unfold successful\n") 2012 | 2013 | 2014 | else: 2015 | mw=FreeCADGui.getMainWindow() 2016 | QtGui.QMessageBox.information(mw,"Selection Error","""Sheet UFO works only with a flat face as starter!\n Select a flat face.""") 2017 | else: 2018 | mw=FreeCADGui.getMainWindow() 2019 | QtGui.QMessageBox.information(mw,"Selection Error","""Sheet UFO works only with a flat face as starter!\n Select a flat face.""") 2020 | return resPart, folds, normalVect, theName 2021 | 2022 | if __name__ == '__main__': 2023 | theUnfold, foldLines, nVec, shapeName = getUnfold() 2024 | if theUnfold: 2025 | Part.show(theUnfold, shapeName+'_unfolded') 2026 | Part.show(foldLines, 'Foldlines') 2027 | 2028 | --------------------------------------------------------------------------------