Files
create/src/Mod/Draft/draftgeoutils/faces.py
2020-05-25 13:35:34 +02:00

232 lines
7.7 KiB
Python

# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
# * *
# * 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