Draft: move functions to draftgeoutils.faces
This commit is contained in:
@@ -34,6 +34,7 @@ SET (Draft_geoutils
|
||||
draftgeoutils/edges.py
|
||||
draftgeoutils/intersections.py
|
||||
draftgeoutils/sort_edges.py
|
||||
draftgeoutils/faces.py
|
||||
)
|
||||
|
||||
SET(Draft_tests
|
||||
|
||||
@@ -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):
|
||||
|
||||
231
src/Mod/Draft/draftgeoutils/faces.py
Normal file
231
src/Mod/Draft/draftgeoutils/faces.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# ***************************************************************************
|
||||
# * 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
|
||||
Reference in New Issue
Block a user