From 339be2f4bbb0ddf9861665e00ea051dc7a2199e9 Mon Sep 17 00:00:00 2001 From: vocx-fc Date: Fri, 22 May 2020 00:49:55 -0500 Subject: [PATCH] Draft: move functions to draftgeoutils.faces --- src/Mod/Draft/CMakeLists.txt | 1 + src/Mod/Draft/DraftGeomUtils.py | 167 +------------------ src/Mod/Draft/draftgeoutils/faces.py | 231 +++++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 162 deletions(-) create mode 100644 src/Mod/Draft/draftgeoutils/faces.py diff --git a/src/Mod/Draft/CMakeLists.txt b/src/Mod/Draft/CMakeLists.txt index 528120d6c1..751058c8f1 100644 --- a/src/Mod/Draft/CMakeLists.txt +++ b/src/Mod/Draft/CMakeLists.txt @@ -34,6 +34,7 @@ SET (Draft_geoutils draftgeoutils/edges.py draftgeoutils/intersections.py draftgeoutils/sort_edges.py + draftgeoutils/faces.py ) SET(Draft_tests diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index ea329bc21b..92711bef7b 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -244,38 +244,10 @@ def findClosest(basepoint, pointslist): return npoint -def concatenate(shape): - """concatenate(shape) -- turns several faces into one""" - edges = getBoundary(shape) - edges = Part.__sortEdges__(edges) - try: - wire=Part.Wire(edges) - face=Part.Face(wire) - except: - print("DraftGeomUtils: Couldn't join faces into one") - return(shape) - else: - if not wire.isClosed(): return(wire) - else: return(face) +from draftgeoutils.faces import concatenate -def getBoundary(shape): - """getBoundary(shape) -- this function returns the boundary edges of a group of faces""" - # make a lookup-table where we get the number of occurrences - # to each edge in the fused face - if isinstance(shape,list): - shape = Part.makeCompound(shape) - lut={} - for f in shape.Faces: - for e in f.Edges: - hc= e.hashCode() - if hc in lut: lut[hc]=lut[hc]+1 - else: lut[hc]=1 - # filter out the edges shared by more than one sub-face - bound=[] - for e in shape.Edges: - if lut[e.hashCode()] == 1: bound.append(e) - return bound +from draftgeoutils.faces import getBoundary from draftgeoutils.edges import isLine @@ -973,18 +945,7 @@ def findClosestCircle(point, circles): return closest -def isCoplanar(faces, tolerance=0): - """isCoplanar(faces,[tolerance]): checks if all faces in the given list are coplanar. Tolerance is the max deviation to be considered coplanar""" - if len(faces) < 2: - return True - base =faces[0].normalAt(0,0) - for i in range(1,len(faces)): - for v in faces[i].Vertexes: - chord = v.Point.sub(faces[0].Vertexes[0].Point) - dist = DraftVecUtils.project(chord,base) - if round(dist.Length,precision()) > tolerance: - return False - return True +from draftgeoutils.faces import isCoplanar def isPlanar(shape): @@ -1067,128 +1028,10 @@ def getTangent(edge, frompoint=None): return None -def bind(w1, w2): - """bind(wire1,wire2): binds 2 wires by their endpoints and - returns a face""" - if (not w1) or (not w2): - print("DraftGeomUtils: unable to bind wires") - return None - if w1.isClosed() and w2.isClosed(): - d1 = w1.BoundBox.DiagonalLength - d2 = w2.BoundBox.DiagonalLength - if d1 > d2: - #w2.reverse() - return Part.Face([w1,w2]) - else: - #w1.reverse() - return Part.Face([w2,w1]) - else: - try: - w3 = Part.LineSegment(w1.Vertexes[0].Point,w2.Vertexes[0].Point).toShape() - w4 = Part.LineSegment(w1.Vertexes[-1].Point,w2.Vertexes[-1].Point).toShape() - return Part.Face(Part.Wire(w1.Edges+[w3]+w2.Edges+[w4])) - except: - print("DraftGeomUtils: unable to bind wires") - return None +from draftgeoutils.faces import bind -def cleanFaces(shape): - """Remove inner edges from coplanar faces.""" - faceset = shape.Faces - def find(hc): - """finds a face with the given hashcode""" - for f in faceset: - if f.hashCode() == hc: - return f - def findNeighbour(hface,hfacelist): - """finds the first neighbour of a face in a list, and returns its index""" - eset = [] - for e in find(hface).Edges: - eset.append(e.hashCode()) - for i in range(len(hfacelist)): - for ee in find(hfacelist[i]).Edges: - if ee.hashCode() in eset: - return i - return None - - # build lookup table - lut = {} - for face in faceset: - for edge in face.Edges: - if edge.hashCode() in lut: - lut[edge.hashCode()].append(face.hashCode()) - else: - lut[edge.hashCode()] = [face.hashCode()] - # print("lut:",lut) - # take edges shared by 2 faces - sharedhedges = [] - for k,v in lut.items(): - if len(v) == 2: - sharedhedges.append(k) - # print(len(sharedhedges)," shared edges:",sharedhedges) - # find those with same normals - targethedges = [] - for hedge in sharedhedges: - faces = lut[hedge] - n1 = find(faces[0]).normalAt(0.5,0.5) - n2 = find(faces[1]).normalAt(0.5,0.5) - if n1 == n2: - targethedges.append(hedge) - # print(len(targethedges)," target edges:",targethedges) - # get target faces - hfaces = [] - for hedge in targethedges: - for f in lut[hedge]: - if not f in hfaces: - hfaces.append(f) - - # print(len(hfaces)," target faces:",hfaces) - # sort islands - islands = [[hfaces.pop(0)]] - currentisle = 0 - currentface = 0 - found = True - while hfaces: - if not found: - if len(islands[currentisle]) > (currentface + 1): - currentface += 1 - found = True - else: - islands.append([hfaces.pop(0)]) - currentisle += 1 - currentface = 0 - found = True - else: - f = findNeighbour(islands[currentisle][currentface],hfaces) - if f != None: - islands[currentisle].append(hfaces.pop(f)) - else: - found = False - # print(len(islands)," islands:",islands) - # make new faces from islands - newfaces = [] - treated = [] - for isle in islands: - treated.extend(isle) - fset = [] - for i in isle: fset.append(find(i)) - bounds = getBoundary(fset) - shp = Part.Wire(Part.__sortEdges__(bounds)) - shp = Part.Face(shp) - if shp.normalAt(0.5,0.5) != find(isle[0]).normalAt(0.5,0.5): - shp.reverse() - newfaces.append(shp) - # print("new faces:",newfaces) - # add remaining faces - for f in faceset: - if not f.hashCode() in treated: - newfaces.append(f) - # print("final faces") - # finishing - fshape = Part.makeShell(newfaces) - if shape.isClosed(): - fshape = Part.makeSolid(fshape) - return fshape +from draftgeoutils.faces import cleanFaces def isCubic(shape): diff --git a/src/Mod/Draft/draftgeoutils/faces.py b/src/Mod/Draft/draftgeoutils/faces.py new file mode 100644 index 0000000000..115ab68332 --- /dev/null +++ b/src/Mod/Draft/draftgeoutils/faces.py @@ -0,0 +1,231 @@ +# *************************************************************************** +# * Copyright (c) 2009, 2010 Yorik van Havre * +# * Copyright (c) 2009, 2010 Ken Cline * +# * * +# * This file is part of the FreeCAD CAx development system. * +# * * +# * This program is free software; you can redistribute it and/or modify * +# * it under the terms of the GNU Lesser General Public License (LGPL) * +# * as published by the Free Software Foundation; either version 2 of * +# * the License, or (at your option) any later version. * +# * for detail see the LICENCE text file. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, * +# * but WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +# * GNU Library General Public License for more details. * +# * * +# * You should have received a copy of the GNU Library General Public * +# * License along with FreeCAD; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** +"""Provides various functions for working with faces.""" +## @package faces +# \ingroup DRAFTGEOUTILS +# \brief Provides various functions for working with faces. + +import lazy_loader.lazy_loader as lz + +import DraftVecUtils + +from draftgeoutils.general import precision + +# Delay import of module until first use because it is heavy +Part = lz.LazyLoader("Part", globals(), "Part") + + +def concatenate(shape): + """Turn several faces into one.""" + edges = getBoundary(shape) + edges = Part.__sortEdges__(edges) + try: + wire = Part.Wire(edges) + face = Part.Face(wire) + except Part.OCCError: + print("DraftGeomUtils: Couldn't join faces into one") + return shape + else: + if not wire.isClosed(): + return wire + else: + return face + + +def getBoundary(shape): + """Return the boundary edges of a group of faces.""" + if isinstance(shape, list): + shape = Part.makeCompound(shape) + + # Make a lookup-table where we get the number of occurrences + # to each edge in the fused face + table = dict() + for f in shape.Faces: + for e in f.Edges: + hash_code = e.hashCode() + if hash_code in table: + table[hash_code] = table[hash_code] + 1 + else: + table[hash_code] = 1 + + # Filter out the edges shared by more than one sub-face + bound = list() + for e in shape.Edges: + if table[e.hashCode()] == 1: + bound.append(e) + return bound + + +def isCoplanar(faces, tolerance=0): + """Return True if all faces in the given list are coplanar. + + Tolerance is the maximum deviation to be considered coplanar. + """ + if len(faces) < 2: + return True + + base = faces[0].normalAt(0, 0) + + for i in range(1, len(faces)): + for v in faces[i].Vertexes: + chord = v.Point.sub(faces[0].Vertexes[0].Point) + dist = DraftVecUtils.project(chord, base) + if round(dist.Length, precision()) > tolerance: + return False + return True + + +def bind(w1, w2): + """Bind 2 wires by their endpoints and returns a face.""" + if not w1 or not w2: + print("DraftGeomUtils: unable to bind wires") + return None + + if w1.isClosed() and w2.isClosed(): + d1 = w1.BoundBox.DiagonalLength + d2 = w2.BoundBox.DiagonalLength + if d1 > d2: + # w2.reverse() + return Part.Face([w1, w2]) + else: + # w1.reverse() + return Part.Face([w2, w1]) + else: + try: + w3 = Part.LineSegment(w1.Vertexes[0].Point, + w2.Vertexes[0].Point).toShape() + w4 = Part.LineSegment(w1.Vertexes[-1].Point, + w2.Vertexes[-1].Point).toShape() + return Part.Face(Part.Wire(w1.Edges+[w3] + w2.Edges+[w4])) + except Part.OCCError: + print("DraftGeomUtils: unable to bind wires") + return None + + +def cleanFaces(shape): + """Remove inner edges from coplanar faces.""" + faceset = shape.Faces + + def find(hc): + """Find a face with the given hashcode.""" + for f in faceset: + if f.hashCode() == hc: + return f + + def findNeighbour(hface, hfacelist): + """Find the first neighbour of a face, and return its index.""" + eset = [] + for e in find(hface).Edges: + eset.append(e.hashCode()) + for i in range(len(hfacelist)): + for ee in find(hfacelist[i]).Edges: + if ee.hashCode() in eset: + return i + return None + + # build lookup table + lut = {} + for face in faceset: + for edge in face.Edges: + if edge.hashCode() in lut: + lut[edge.hashCode()].append(face.hashCode()) + else: + lut[edge.hashCode()] = [face.hashCode()] + + # print("lut:",lut) + # take edges shared by 2 faces + sharedhedges = [] + for k, v in lut.items(): + if len(v) == 2: + sharedhedges.append(k) + + # print(len(sharedhedges)," shared edges:",sharedhedges) + # find those with same normals + targethedges = [] + for hedge in sharedhedges: + faces = lut[hedge] + n1 = find(faces[0]).normalAt(0.5, 0.5) + n2 = find(faces[1]).normalAt(0.5, 0.5) + if n1 == n2: + targethedges.append(hedge) + + # print(len(targethedges)," target edges:",targethedges) + # get target faces + hfaces = [] + for hedge in targethedges: + for f in lut[hedge]: + if f not in hfaces: + hfaces.append(f) + + # print(len(hfaces)," target faces:",hfaces) + # sort islands + islands = [[hfaces.pop(0)]] + currentisle = 0 + currentface = 0 + found = True + while hfaces: + if not found: + if len(islands[currentisle]) > (currentface + 1): + currentface += 1 + found = True + else: + islands.append([hfaces.pop(0)]) + currentisle += 1 + currentface = 0 + found = True + else: + f = findNeighbour(islands[currentisle][currentface], hfaces) + if f is not None: + islands[currentisle].append(hfaces.pop(f)) + else: + found = False + + # print(len(islands)," islands:",islands) + # make new faces from islands + newfaces = [] + treated = [] + for isle in islands: + treated.extend(isle) + fset = [] + for i in isle: + fset.append(find(i)) + bounds = getBoundary(fset) + shp = Part.Wire(Part.__sortEdges__(bounds)) + shp = Part.Face(shp) + if shp.normalAt(0.5, 0.5) != find(isle[0]).normalAt(0.5, 0.5): + shp.reverse() + newfaces.append(shp) + + # print("new faces:",newfaces) + # add remaining faces + for f in faceset: + if not f.hashCode() in treated: + newfaces.append(f) + + # print("final faces") + # finishing + fshape = Part.makeShell(newfaces) + if shape.isClosed(): + fshape = Part.makeSolid(fshape) + return fshape