Module Face
Expand source code
# Copyright (C) 2024
# Wassim Jabi <wassim.jabi@gmail.com>
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program 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 Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with
# this program. If not, see <https://www.gnu.org/licenses/>.
import topologic_core as topologic
import math
import os
import warnings
try:
import numpy as np
except:
print("Face - Installing required numpy library.")
try:
os.system("pip install numpy")
except:
os.system("pip install numpy --user")
try:
import numpy as np
print("Face - numpy library installed correctly.")
except:
warnings.warn("Face - Error: Could not import numpy.")
class Face():
@staticmethod
def AddInternalBoundaries(face, wires: list):
"""
Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
Parameters
----------
face : topologic_core.Face
The input face.
wires : list
The input list of internal boundaries (closed wires).
Returns
-------
topologic_core.Face
The created face with internal boundaries added to it.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.AddInternalBoundaries - Error: The input face parameter is not a valid topologic face. Returning None.")
return None
if not isinstance(wires, list):
print("Face.AddInternalBoundaries - Warning: The input wires parameter is not a valid list. Returning the input face.")
return face
wireList = [w for w in wires if Topology.IsInstance(w, "Wire")]
if len(wireList) < 1:
print("Face.AddInternalBoundaries - Warning: The input wires parameter does not contain any valid wires. Returning the input face.")
return face
faceeb = face.ExternalBoundary()
faceibList = []
_ = face.InternalBoundaries(faceibList)
for wire in wires:
faceibList.append(wire)
return Face.ByWires(faceeb, faceibList)
@staticmethod
def AddInternalBoundariesCluster(face, cluster):
"""
Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
Parameters
----------
face : topologic_core.Face
The input face.
cluster : topologic_core.Cluster
The input cluster of internal boundaries (topologic wires).
Returns
-------
topologic_core.Face
The created face with internal boundaries added to it.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.AddInternalBoundariesCluster - Warning: The input cluster parameter is not a valid cluster. Returning None.")
return None
if not cluster:
return face
if not Topology.IsInstance(cluster, "Cluster"):
return face
wires = []
_ = cluster.Wires(None, wires)
return Face.AddInternalBoundaries(face, wires)
@staticmethod
def Angle(faceA, faceB, mantissa: int = 6) -> float:
"""
Returns the angle in degrees between the two input faces.
Parameters
----------
faceA : topologic_core.Face
The first input face.
faceB : topologic_core.Face
The second input face.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
float
The angle in degrees between the two input faces.
"""
from topologicpy.Vector import Vector
from topologicpy.Topology import Topology
if not Topology.IsInstance(faceA, "Face"):
print("Face.Angle - Warning: The input faceA parameter is not a valid topologic face. Returning None.")
return None
if not Topology.IsInstance(faceB, "Face"):
print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.")
return None
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
return round((Vector.Angle(dirA, dirB)), mantissa)
@staticmethod
def Area(face, mantissa: int = 6) -> float:
"""
Returns the area of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
float
The area of the input face.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.Area - Warning: The input face parameter is not a valid topologic face. Returning None.")
return None
area = None
try:
area = round(topologic.FaceUtility.Area(face), mantissa) # Hook to Core
except:
area = None
return area
@staticmethod
def BoundingRectangle(topology, optimize: int = 0, tolerance: float = 0.0001):
"""
Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.
Parameters
----------
topology : topologic_core.Topology
The input topology.
optimize : int , optional
If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The bounding rectangle of the input topology.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
br_wire = Wire.BoundingRectangle(topology=topology, optimize=optimize, tolerance=tolerance)
br_face = Face.ByWire(br_wire)
br_face = Topology.SetDictionary(br_face, Topology.Dictionary(br_wire))
return br_face
@staticmethod
def ByEdges(edges: list, tolerance : float = 0.0001):
"""
Creates a face from the input list of edges.
Parameters
----------
edges : list
The input list of edges.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
face : topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not isinstance(edges, list):
print("Face.ByEdges - Error: The input edges parameter is not a valid list. Returning None.")
return None
edges = [e for e in edges if Topology.IsInstance(e, "Edge")]
if len(edges) < 1:
print("Face.ByEdges - Error: The input edges parameter does not contain any valid edges. Returning None.")
return None
wire = Wire.ByEdges(edges, tolerance=tolerance)
if not Topology.IsInstance(wire, "Wire"):
print("Face.ByEdges - Error: Could not create the required wire. Returning None.")
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def ByEdgesCluster(cluster, tolerance: float = 0.0001):
"""
Creates a face from the input cluster of edges.
Parameters
----------
cluster : topologic_core.Cluster
The input cluster of edges.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
face : topologic_core.Face
The created face.
"""
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
if not Topology.IsInstance(cluster, "Cluster"):
print("Face.ByEdgesCluster - Warning: The input cluster parameter is not a valid topologic cluster. Returning None.")
return None
edges = Cluster.Edges(cluster)
if len(edges) < 1:
print("Face.ByEdgesCluster - Warning: The input cluster parameter does not contain any valid edges. Returning None.")
return None
return Face.ByEdges(edges, tolerance=tolerance)
@staticmethod
def ByOffset(face, offset: float = 1.0, miter: bool = False,
miterThreshold: float = None, offsetKey: str = None,
miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001):
"""
Creates an offset face from the input face.
Parameters
----------
face : topologic_core.Face
The input face.
offset : float , optional
The desired offset distance. The default is 1.0.
miter : bool , optional
if set to True, the corners will be mitered. The default is False.
miterThreshold : float , optional
The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
offsetKey : str , optional
If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
miterThresholdKey : str , optional
If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
step : bool , optional
If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.")
return None
eb = Face.Wire(face)
internal_boundaries = Face.InternalBoundaries(face)
offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)
offset_internal_boundaries = []
for internal_boundary in internal_boundaries:
offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step))
return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance)
@staticmethod
def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001):
"""
Creates a face by merging the faces of the input shell.
Parameters
----------
shell : topologic_core.Shell
The input shell.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Wire import Wire
from topologicpy.Shell import Shell
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
from topologicpy.Dictionary import Dictionary
from topologicpy.Helper import Helper
def planarizeList(wireList):
returnList = []
for aWire in wireList:
returnList.append(Wire.Planarize(aWire))
return returnList
if not Topology.IsInstance(shell, "Shell"):
print("Face.ByShell - Error: The input shell parameter is not a valid toplogic shell. Returning None.")
return None
if origin == None:
origin = Topology.Centroid(shell)
if not Topology.IsInstance(origin, "Vertex"):
print("Face.ByShell - Error: The input origin parameter is not a valid topologic vertex. Returning None.")
return None
# Try the simple method first
face = None
ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell))
if Topology.IsInstance(ext_boundary, "Wire"):
face = Face.ByWire(ext_boundary)
elif Topology.IsInstance(ext_boundary, "Cluster"):
wires = Topology.Wires(ext_boundary)
faces = [Face.ByWire(w) for w in wires]
areas = [Face.Area(f) for f in faces]
wires = Helper.Sort(wires, areas, reverseFlags=[True])
face = Face.ByWires(wires[0], wires[1:])
if Topology.IsInstance(face, "Face"):
return face
world_origin = Vertex.Origin()
planar_shell = Shell.Planarize(shell)
normal = Face.Normal(Topology.Faces(planar_shell)[0])
planar_shell = Topology.Flatten(planar_shell, origin=origin, direction=normal)
vertices = Shell.Vertices(planar_shell)
new_vertices = []
for v in vertices:
x, y, z = Vertex.Coordinates(v)
new_v = Vertex.ByCoordinates(x,y,0)
new_vertices.append(new_v)
planar_shell = Topology.SelfMerge(Topology.ReplaceVertices(planar_shell, verticesA=vertices, verticesB=new_vertices), tolerance=tolerance)
ext_boundary = Shell.ExternalBoundary(planar_shell, tolerance=tolerance)
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
if not Topology.IsInstance(ext_boundary, "Topology"):
print("Face.ByShell - Error: Could not derive the external boundary of the input shell parameter. Returning None.")
return None
if Topology.IsInstance(ext_boundary, "Wire"):
if not Topology.IsPlanar(ext_boundary, tolerance=tolerance):
ext_boundary = Wire.Planarize(ext_boundary, origin=origin, tolerance=tolerance)
ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance)
try:
face = Face.ByWire(ext_boundary)
face = Topology.Unflatten(face, origin=origin, direction=normal)
return face
except:
print("Face.ByShell - Error: The operation failed. Returning None.")
return None
elif Topology.IsInstance(ext_boundary, "Cluster"): # The shell has holes.
wires = []
_ = ext_boundary.Wires(None, wires)
faces = []
areas = []
for wire in wires:
aFace = Face.ByWire(wire, tolerance=tolerance)
if not Topology.IsInstance(aFace, "Face"):
print("Face.ByShell - Error: The operation failed. Returning None.")
return None
anArea = abs(Face.Area(aFace))
faces.append(aFace)
areas.append(anArea)
max_index = areas.index(max(areas))
ext_boundary = faces[max_index]
int_boundaries = list(set(faces) - set([ext_boundary]))
int_wires = []
for int_boundary in int_boundaries:
temp_wires = []
_ = int_boundary.Wires(None, temp_wires)
int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance))
temp_wires = []
_ = ext_boundary.Wires(None, temp_wires)
ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)
face = Face.ByWires(ext_wire, int_wires)
face = Topology.Unflatten(face, origin=origin, direction=normal)
return face
else:
return None
@staticmethod
def ByVertices(vertices: list, tolerance: float = 0.0001):
"""
Creates a face from the input list of vertices.
Parameters
----------
vertices : list
The input list of vertices.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Topology import Topology
from topologicpy.Wire import Wire
if not isinstance(vertices, list):
return None
vertexList = [x for x in vertices if Topology.IsInstance(x, "Vertex")]
if len(vertexList) < 3:
return None
w = Wire.ByVertices(vertexList, tolerance=tolerance)
f = Face.ByWire(w, tolerance=tolerance)
return f
@staticmethod
def ByVerticesCluster(cluster, tolerance: float = 0.0001):
"""
Creates a face from the input cluster of vertices.
Parameters
----------
cluster : topologic_core.Cluster
The input cluster of vertices.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
if not Topology.IsInstance(cluster, "Cluster"):
return None
vertices = Cluster.Vertices(cluster)
return Face.ByVertices(vertices, tolerance=tolerance)
@staticmethod
def ByWire(wire, tolerance: float = 0.0001, silent=False):
"""
Creates a face from the input closed wire.
Parameters
----------
wire : topologic_core.Wire
The input wire.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
silent : bool , optional
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
Returns
-------
topologic_core.Face or list
The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Wire import Wire
from topologicpy.Shell import Shell
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
from topologicpy.Dictionary import Dictionary
import random
def triangulateWire(wire):
wire = Topology.RemoveCollinearEdges(wire)
vertices = Topology.Vertices(wire)
shell = Shell.Delaunay(vertices)
if Topology.IsInstance(shell, "Topology"):
return Topology.Faces(shell)
else:
return []
if not Topology.IsInstance(wire, "Wire"):
if not silent:
print("Face.ByWire - Error: The input wire parameter is not a valid topologic wire. Returning None.")
return None
if not Wire.IsClosed(wire):
if not silent:
print("Face.ByWire - Error: The input wire parameter is not a closed topologic wire. Returning None.")
return None
edges = Wire.Edges(wire)
wire = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance)
vertices = Topology.Vertices(wire)
fList = []
if Topology.IsInstance(wire, "Wire"):
try:
fList = topologic.Face.ByExternalBoundary(wire) # Hook to Core
except:
if not silent:
print("Face.ByWire - Warning: Could not create face by external boundary. Trying other methods.")
if len(vertices) > 3:
fList = triangulateWire(wire)
else:
fList = []
if not isinstance(fList, list):
fList = [fList]
returnList = []
for f in fList:
if Face.Area(f) < 0:
wire = Face.ExternalBoundary(f)
wire = Wire.Invert(wire)
try:
f = topologic.Face.ByExternalBoundary(wire) # Hook to Core
returnList.append(f)
except:
pass
else:
returnList.append(f)
if len(returnList) == 0:
if not silent:
print("Face.ByWire - Error: Could not build a face from the input wire parameter. Returning None.")
return None
elif len(returnList) == 1:
return returnList[0]
else:
if not silent:
print("Face.ByWire - Warning: Could not build a single face from the input wire parameter. Returning a list of faces.")
return returnList
@staticmethod
def ByWires(externalBoundary, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False):
"""
Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).
Parameters
----------
externalBoundary : topologic_core.Wire
The input external boundary.
internalBoundaries : list , optional
The input list of internal boundaries (closed wires). The default is an empty list.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
silent : bool , optional
If set to False, error messages are printed. Otherwise, they are not. The default is False.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(externalBoundary, "Wire"):
if not silent:
print("Face.ByWires - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
return None
if not Wire.IsClosed(externalBoundary):
if not silent:
print("Face.ByWires - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
return None
ibList = [x for x in internalBoundaries if Topology.IsInstance(x, "Wire") and Wire.IsClosed(x)]
face = None
try:
face = topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList, tolerance) # Hook to Core
except:
if not silent:
print("Face.ByWires - Error: The operation failed. Returning None.")
face = None
return face
@staticmethod
def ByWiresCluster(externalBoundary, internalBoundariesCluster = None, tolerance: float = 0.0001, silent: bool = False):
"""
Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).
Parameters
----------
externalBoundary topologic_core.Wire
The input external boundary (closed wire).
internalBoundariesCluster : topologic_core.Cluster
The input cluster of internal boundaries (closed wires). The default is None.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
silent : bool , optional
If set to False, error messages are printed. Otherwise, they are not. The default is False.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
if not Topology.IsInstance(externalBoundary, "Wire"):
if not silent:
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.")
return None
if not Wire.IsClosed(externalBoundary):
if not silent:
print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.")
return None
if not internalBoundariesCluster:
internalBoundaries = []
elif not Topology.IsInstance(internalBoundariesCluster, "Cluster"):
if not silent:
print("Face.ByWiresCluster - Error: The input internalBoundariesCluster parameter is not a valid topologic cluster. Returning None.")
return None
else:
internalBoundaries = Cluster.Wires(internalBoundariesCluster)
return Face.ByWires(externalBoundary, internalBoundaries, tolerance=tolerance, silent=silent)
@staticmethod
def NorthArrow(origin= None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0,
placement: str = "center", tolerance: float = 0.0001):
"""
Creates a north arrow.
Parameters
----------
origin : topologic_core.Vertex, optional
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
radius : float , optional
The radius of the circle. The default is 1.
sides : int , optional
The number of sides of the circle. The default is 16.
direction : list , optional
The vector representing the up direction of the circle. The default is [0, 0, 1].
northAngle : float , optional
The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
placement : str , optional
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created circle.
"""
from topologicpy.Topology import Topology
from topologicpy.Vertex import Vertex
if not origin:
origin = Vertex.Origin()
c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0, 0, 1], placement="center", tolerance=tolerance)
r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft")
r = Topology.Translate(r, -0.005*radius,0,0)
arrow = Topology.Difference(c, r, tolerance=tolerance)
arrow = Topology.Rotate(arrow, origin=Vertex.Origin(), axis=[0, 0, 1], angle=northAngle)
if placement.lower() == "lowerleft":
arrow = Topology.Translate(arrow, radius, radius, 0)
elif placement.lower() == "upperleft":
arrow = Topology.Translate(arrow, radius, -radius, 0)
elif placement.lower() == "lowerright":
arrow = Topology.Translate(arrow, -radius, radius, 0)
elif placement.lower() == "upperright":
arrow = Topology.Translate(arrow, -radius, -radius, 0)
arrow = Topology.Place(arrow, originA=Vertex.Origin(), originB=origin)
arrow = Topology.Orient(arrow, origin=origin, direction=direction)
return arrow
@staticmethod
def Circle(origin= None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1],
placement: str = "center", tolerance: float = 0.0001):
"""
Creates a circle.
Parameters
----------
origin : topologic_core.Vertex, optional
The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
radius : float , optional
The radius of the circle. The default is 1.
sides : int , optional
The number of sides of the circle. The default is 16.
fromAngle : float , optional
The angle in degrees from which to start creating the arc of the circle. The default is 0.
toAngle : float , optional
The angle in degrees at which to end creating the arc of the circle. The default is 360.
direction : list , optional
The vector representing the up direction of the circle. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created circle.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance)
if not Topology.IsInstance(wire, "Wire"):
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def Compactness(face, mantissa: int = 6) -> float:
"""
Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
Parameters
----------
face : topologic_core.Face
The input face.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
float
The compactness measure of the input face.
"""
from topologicpy.Edge import Edge
exb = face.ExternalBoundary()
edges = []
_ = exb.Edges(None, edges)
perimeter = 0.0
for anEdge in edges:
perimeter = perimeter + abs(Edge.Length(anEdge))
area = abs(Face.Area(face))
compactness = 0
#From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
if area <= 0:
return None
if perimeter <= 0:
return None
compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter
return round(compactness, mantissa)
@staticmethod
def CompassAngle(face, north: list = None, mantissa: int = 6) -> float:
"""
Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.
Parameters
----------
face : topologic_core.Face
The input face.
north : list , optional
The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
mantissa : int, optional
The length of the desired mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
float
The horizontal compass angle in degrees between the direction of the face and the second input vector.
"""
from topologicpy.Vector import Vector
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
if not north:
north = Vector.North()
dirA = Face.NormalAtParameters(face,mantissa=mantissa)
return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
@staticmethod
def Edges(face) -> list:
"""
Returns the edges of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
list
The list of edges.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
edges = []
_ = face.Edges(None, edges)
return edges
@staticmethod
def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1],
placement: str = "center", tolerance: float = 0.0001):
"""
Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798
Parameters
----------
origin : topologic_core.Vertex , optional
The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0, 0, 0).
radius : float , optional
The radius of the hexagon determining the size of the tile. The default is 0.5.
direction : list , optional
The vector representing the up direction of the ellipse. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
--------
topologic_core.Face
The created Einstein tile.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement)
if not Topology.IsInstance(wire, "Wire"):
print("Face.Einstein - Error: Could not create base wire for the Einstein tile. Returning None.")
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def ExteriorAngles(face, includeInternalBoundaries=False, mantissa: int = 6) -> list:
"""
Returns the exterior angles of the input face in degrees. The face must be planar.
Parameters
----------
face : topologic_core.Face
The input face.
includeInternalBoundaries : bool , optional
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the
internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be
a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The list of exterior angles.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.ExteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
return None
eb = Face.ExternalBoundary(face)
return_list = Wire.ExteriorAngles(eb, mantissa=mantissa)
if includeInternalBoundaries:
internal_boundaries = Face.InternalBoundaries(face)
ib_i_a_list = []
if len(internal_boundaries) > 0:
for ib in internal_boundaries:
ib_interior_angles = Wire.InteriorAngles(ib, mantissa=mantissa)
ib_i_a_list.append(ib_interior_angles)
if len(ib_i_a_list) > 0:
return_list = [return_list]+[ib_i_a_list]
return return_list
@staticmethod
def ExternalBoundary(face):
"""
Returns the external boundary (closed wire) of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
topologic_core.Wire
The external boundary of the input face.
"""
from topologicpy.Vector import Vector
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
eb = face.ExternalBoundary()
f_dir = Face.Normal(face)
faceVertices = Topology.Vertices(eb)
temp_face = Face.ByWire(eb)
temp_dir = Face.Normal(temp_face)
if Vector.IsAntiParallel(f_dir, temp_dir):
faceVertices.reverse()
eb = Wire.ByVertices(faceVertices)
return eb
@staticmethod
def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool:
"""
Returns True if the input face is facing toward the input direction.
Parameters
----------
face : topologic_core.Face
The input face.
direction : list , optional
The input direction. The default is [0,0,-1].
asVertex : bool , optional
If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
bool
True if the face is facing toward the direction. False otherwise.
"""
from topologicpy.Vector import Vector
faceNormal = Face.Normal(face)
faceCenter = Face.VertexByParameters(face,0.5,0.5)
cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()]
try:
vList = [direction.X(), direction.Y(), direction.Z()]
except:
try:
vList = [direction[0], direction[1], direction[2]]
except:
raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction")
if asVertex:
dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]]
else:
dV = vList
uV = Vector.Normalize(dV)
dot = sum([i*j for (i, j) in zip(uV, faceNormal)])
if dot < tolerance:
return False
return True
@staticmethod
def Fillet(face, radius: float = 0, radiusKey: str = None, tolerance: float = 0.0001, silent: bool = False):
"""
Fillets (rounds) the interior and exterior corners of the input face given the input radius. See https://en.wikipedia.org/wiki/Fillet_(mechanics)
Parameters
----------
face : topologic_core.Face
The input face.
radius : float
The desired radius of the fillet.
radiusKey : str , optional
If specified, the dictionary of the vertices will be queried for this key to specify the desired fillet radius. The default is None.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
silent : bool , optional
If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
Returns
-------
topologic_core.Face
The filleted face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
if not silent:
print("Face.Fillet - Error: The input face parameter is not a valid face. Returning None.")
return None
eb = Topology.Copy(Face.ExternalBoundary(face))
ib_list = Face.InternalBoundaries(face)
ib_list = [Topology.Copy(ib) for ib in ib_list]
f_vertices = Face.Vertices(face)
if isinstance(radiusKey, str):
eb = Topology.TransferDictionariesBySelectors(eb, selectors=f_vertices, tranVertices=True)
eb = Wire.Fillet(eb, radius=radius, radiusKey=radiusKey, tolerance=tolerance)
if not Topology.IsInstance(eb, "Wire"):
if not silent:
print("Face.Fillet - Error: The operation failed. Returning None.")
return None
ib_wires = []
for ib in ib_list:
ib = Wire.ByVertices(Topology.Vertices(ib))
ib = Wire.Reverse(ib)
if isinstance(radiusKey, str):
ib = Topology.TransferDictionariesBySelectors(ib, selectors=f_vertices, tranVertices=True)
ib_wire = Wire.Fillet(ib, radius=radius, radiusKey=radiusKey, tolerance=tolerance, silent=silent)
if Topology.IsInstance(ib, "Wire"):
ib_wires.append(ib_wire)
else:
if not silent:
print("Face.Fillet - Error: The operation for one of the interior boundaries failed. Skipping.")
return Face.ByWires(eb, ib_wires)
@staticmethod
def Harmonize(face, tolerance: float = 0.0001):
"""
Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner.
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The harmonized face.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
from topologicpy.Dictionary import Dictionary
if not Topology.IsInstance(face, "Face"):
print("Face.Harmonize - Error: The input face parameter is not a valid face. Returning None.")
return None
normal = Face.Normal(face)
origin = Topology.Centroid(face)
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
world_origin = Vertex.Origin()
vertices = Wire.Vertices(Face.ExternalBoundary(flatFace))
harmonizedEB = Wire.ByVertices(vertices)
internalBoundaries = Face.InternalBoundaries(flatFace)
harmonizedIB = []
for ib in internalBoundaries:
ibVertices = Wire.Vertices(ib)
harmonizedIB.append(Wire.ByVertices(ibVertices))
harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB, tolerance=tolerance)
harmonizedFace = Topology.Unflatten(harmonizedFace, origin=origin, direction=normal)
return harmonizedFace
@staticmethod
def InteriorAngles(face, includeInternalBoundaries: bool = False, mantissa: int = 6) -> list:
"""
Returns the interior angles of the input face in degrees. The face must be planar.
Parameters
----------
face : topologic_core.Face
The input face.
includeInternalBoundaries : bool , optional
If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list
of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the
internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be
a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the
internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The list of interior angles.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.InteriorAngles - Error: The input face parameter is not a valid face. Returning None.")
return None
eb = Face.ExternalBoundary(face)
return_list = Wire.InteriorAngles(eb, mantissa=mantissa)
if includeInternalBoundaries:
internal_boundaries = Face.InternalBoundaries(face)
ib_i_a_list = []
if len(internal_boundaries) > 0:
for ib in internal_boundaries:
ib_interior_angles = Wire.ExteriorAngles(ib, mantissa=mantissa)
ib_i_a_list.append(ib_interior_angles)
if len(ib_i_a_list) > 0:
return_list = [return_list]+[ib_i_a_list]
return return_list
@staticmethod
def InternalBoundaries(face) -> list:
"""
Returns the internal boundaries (closed wires) of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
list
The list of internal boundaries (closed wires).
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
wires = []
_ = face.InternalBoundaries(wires)
return list(wires)
@staticmethod
def InternalVertex(face, tolerance: float = 0.0001):
"""
Creates a vertex guaranteed to be inside the input face.
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Vertex
The created vertex.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
v = Topology.Centroid(face)
if Vertex.IsInternal(v, face, tolerance=tolerance):
return v
l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9]
for u in l:
for v in l:
v = Face.VertexByParameters(face, u, v)
if Vertex.IsInternal(v, face, tolerance=tolerance):
return v
v = topologic.FaceUtility.InternalVertex(face, tolerance) # Hook to Core
return v
@staticmethod
def Invert(face, tolerance: float = 0.0001):
"""
Creates a face that is an inverse (mirror) of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The inverted face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
eb = Face.ExternalBoundary(face)
vertices = Wire.Vertices(eb)
vertices.reverse()
inverted_wire = Wire.ByVertices(vertices)
internal_boundaries = Face.InternalBoundaries(face)
if not internal_boundaries:
inverted_face = Face.ByWire(inverted_wire, tolerance=tolerance)
else:
inverted_face = Face.ByWires(inverted_wire, internal_boundaries, tolerance=tolerance)
return inverted_face
@staticmethod
def IsCoplanar(faceA, faceB, tolerance: float = 0.0001) -> bool:
"""
Returns True if the two input faces are coplanar. Returns False otherwise.
Parameters
----------
faceA : topologic_core.Face
The first input face.
faceB : topologic_core.Face
The second input face
tolerance : float , optional
The desired tolerance. The deafault is 0.0001.
Raises
------
Exception
Raises an exception if the angle between the two input faces cannot be determined.
Returns
-------
bool
True if the two input faces are coplanar. False otherwise.
"""
from topologicpy.Vector import Vector
from topologicpy.Topology import Topology
if not Topology.IsInstance(faceA, "Face"):
print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.")
return None
if not Topology.IsInstance(faceB, "Face"):
print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.")
return None
dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3)
dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3)
return Vector.IsCollinear(dirA, dirB)
@staticmethod
def Isovist(face, vertex, obstacles = None, fromAngle=0, toAngle=360, tolerance: float = 0.0001):
"""
Returns the face representing the isovist projection from the input viewpoint.
This method assumes all input is in 2D. Z coordinates are ignored.
Parameters
----------
face : topologic_core.Face
The face representing the boundary of the isovist.
vertex : topologic_core.Vertex
The vertex representing the location of the viewpoint of the isovist.
obstacles : list , optional
A list of wires representing the obstacles within the face. All obstacles are assumed to be within the
boundary of the face.
fromAngle : float , optional
The angle in degrees from which to start creating the arc of the circle. The default is 0.
0 is considered to be in the positive X-axis direction. 90 is considered to be in the
positive Y-axis direction.
toAngle : float , optional
The angle in degrees at which to end creating the arc of the circle. The default is 360.
Angles are measured in an anti-clockwise fashion.
tolerance : float , optional:
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The face representing the isovist projection from the input viewpoint.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Edge import Edge
from topologicpy.Wire import Wire
from topologicpy.Face import Face
from topologicpy.Shell import Shell
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
def vertexPartofFace(vertex, face, tolerance):
vertices = []
_ = face.Vertices(None, vertices)
for v in vertices:
if Vertex.Distance(vertex, v) < tolerance:
return True
return False
if not Topology.IsInstance(face, "Face"):
print("Face.Isovist - Error: The input boundary parameter is not a valid Face. Returning None")
return None
if not Topology.IsInstance(vertex, "Vertex"):
print("Face.Isovist - Error: The input viewPoint parameter is not a valid Vertex. Returning None")
return None
if isinstance(obstacles, list):
obstacles = [obs for obs in obstacles if Topology.IsInstance(obs, "Wire")]
for obs in obstacles:
face = Topology.Difference(face, Face.ByWire(obs))
targets = Topology.Vertices(face)
distances = []
for target in targets:
distances.append(Vertex.Distance(vertex, target))
distances.sort()
max_d = distances[-1]*1.05
edges = []
for target in targets:
e = Edge.ByVertices(vertex, target)
e = Edge.SetLength(e, length=max_d, bothSides=False)
edges.append(e)
shell = Topology.Slice(face, Cluster.ByTopologies(edges))
faces = Topology.Faces(shell)
final_faces = []
for face in faces:
if vertexPartofFace(vertex, face, tolerance=0.001):
final_faces.append(face)
shell = Shell.ByFaces(final_faces)
return_face = Topology.RemoveCoplanarFaces(shell)
if abs(360 - toAngle - fromAngle) > tolerance:
c = Wire.Circle(origin= vertex, radius=max_d, sides=180, fromAngle=fromAngle, toAngle=toAngle, close = False)
e1 = Edge.ByVertices(Wire.StartVertex(c), vertex)
e2 = Edge.ByVertices(Wire.EndVertex(c), vertex)
edges = Topology.Edges(c) + [e1,e2]
pie = Face.ByWire(Topology.SelfMerge(Cluster.ByTopologies(edges)))
return_face = Topology.Intersect(pie, return_face)
return return_face
@staticmethod
def MedialAxis(face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001):
"""
Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.
Parameters
----------
face : topologic_core.Face
The input face.
resolution : int , optional
The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
externalVertices : bool , optional
If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
internalVertices : bool , optional
If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
toLeavesOnly : bool , optional
If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
angTolerance : float , optional
The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Wire
The medial axis of the input face.
"""
from topologicpy.Vertex import Vertex
from topologicpy.Edge import Edge
from topologicpy.Wire import Wire
from topologicpy.Shell import Shell
from topologicpy.Cluster import Cluster
from topologicpy.Topology import Topology
from topologicpy.Dictionary import Dictionary
def touchesEdge(vertex,edges, tolerance=0.0001):
if not Topology.IsInstance(vertex, "Vertex"):
return False
for edge in edges:
u = Edge.ParameterAtVertex(edge, vertex, mantissa=6)
if not u:
continue
if 0<u<1:
return True
return False
# Flatten the input face
origin = Topology.Centroid(face)
normal = Face.Normal(face)
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
# Create a Vertex at the world's origin (0, 0, 0)
world_origin = Vertex.Origin()
faceEdges = Face.Edges(flatFace)
vertices = []
resolution = 10 - resolution
resolution = min(max(resolution, 1), 10)
for e in faceEdges:
for n in range(resolution, 100, resolution):
vertices.append(Edge.VertexByParameter(e,n*0.01))
voronoi = Shell.Voronoi(vertices=vertices, face=flatFace)
voronoiEdges = Shell.Edges(voronoi)
medialAxisEdges = []
for e in voronoiEdges:
sv = Edge.StartVertex(e)
ev = Edge.EndVertex(e)
svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance)
evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance)
if not svTouchesEdge and not evTouchesEdge:
medialAxisEdges.append(e)
extBoundary = Face.ExternalBoundary(flatFace)
extVertices = Wire.Vertices(extBoundary)
intBoundaries = Face.InternalBoundaries(flatFace)
intVertices = []
for ib in intBoundaries:
intVertices = intVertices+Wire.Vertices(ib)
theVertices = []
if internalVertices:
theVertices = theVertices+intVertices
if externalVertices:
theVertices = theVertices+extVertices
tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
if Topology.IsInstance(tempWire, "Wire") and angTolerance > 0:
tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance)
medialAxisEdges = Wire.Edges(tempWire)
for v in theVertices:
nv = Vertex.NearestVertex(v, tempWire, useKDTree=False)
if Topology.IsInstance(nv, "Vertex"):
if toLeavesOnly:
adjVertices = Topology.AdjacentTopologies(nv, tempWire)
if len(adjVertices) < 2:
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
else:
medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance))
medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance)
if Topology.IsInstance(medialAxis, "Wire") and angTolerance > 0:
medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance)
medialAxis = Topology.Unflatten(medialAxis, origin=origin,direction=normal)
return medialAxis
@staticmethod
def Normal(face, outputType: str = "xyz", mantissa: int = 6) -> list:
"""
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
Parameters
----------
face : topologic_core.Face
The input face.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The normal vector to the input face. This is computed at the approximate center of the face.
"""
return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
@staticmethod
def NormalAtParameters(face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list:
"""
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
Parameters
----------
face : topologic_core.Face
The input face.
u : float , optional
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
v : float , optional
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The normal vector to the input face.
"""
returnResult = []
try:
coords = topologic.FaceUtility.NormalAtParameters(face, u, v) # Hook to Core
x = round(coords[0], mantissa)
y = round(coords[1], mantissa)
z = round(coords[2], mantissa)
outputType = list(outputType.lower())
for axis in outputType:
if axis == "x":
returnResult.append(x)
elif axis == "y":
returnResult.append(y)
elif axis == "z":
returnResult.append(z)
except:
returnResult = None
return returnResult
@staticmethod
def NormalEdge(face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False):
"""
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
Parameters
----------
face : topologic_core.Face
The input face.
length : float , optional
The desired length of the normal edge. The default is 1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Edge
The created normal edge to the input face. This is computed at the approximate center of the face.
"""
from topologicpy.Edge import Edge
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
if not silent:
print("Face.NormalEdge - Error: The input face parameter is not a valid face. Retuning None.")
return None
if length < tolerance:
if not silent:
print("Face.NormalEdge - Error: The input length parameter is less than the input tolerance. Retuning None.")
return None
iv = Face.InternalVertex(face)
u, v = Face.VertexParameters(face, iv)
vec = Face.NormalAtParameters(face, u=u, v=v)
ev = Topology.TranslateByDirectionDistance(iv, vec, length)
return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent)
@staticmethod
def NormalEdgeAtParameters(face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001):
"""
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
Parameters
----------
face : topologic_core.Face
The input face.
u : float , optional
The *u* parameter at which to compute the normal to the input face. The default is 0.5.
v : float , optional
The *v* parameter at which to compute the normal to the input face. The default is 0.5.
length : float , optional
The desired length of the normal edge. The default is 1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Edge
The created normal edge to the input face. This is computed at the approximate center of the face.
"""
from topologicpy.Edge import Edge
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
sv = Face.VertexByParameters(face=face, u=u, v=v)
vec = Face.NormalAtParameters(face, u=u, v=v)
ev = Topology.TranslateByDirectionDistance(sv, vec, length)
return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True)
@staticmethod
def PlaneEquation(face, mantissa: int = 6) -> dict:
"""
Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
dict
The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].
"""
from topologicpy.Topology import Topology
from topologicpy.Vertex import Vertex
import random
import time
if not Topology.IsInstance(face, "Face"):
print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.")
return None
vertices = Topology.Vertices(face)
if len(vertices) < 3:
print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.")
return None
return Vertex.PlaneEquation(vertices, mantissa=mantissa)
@staticmethod
def Planarize(face, origin= None,
tolerance: float = 0.0001):
"""
Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.
Parameters
----------
face : topologic_core.Face
The input face.
origin : topologic_core.Vertex , optional
The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The planarized face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
if not Topology.IsInstance(origin, "Vertex"):
origin = Topology.Centroid(face)
eb = Face.ExternalBoundary(face)
plan_eb = Wire.Planarize(eb, origin=origin)
ib_list = Face.InternalBoundaries(face)
plan_ib_list = []
for ib in ib_list:
plan_ib_list.append(Wire.Planarize(ib, origin=origin))
plan_face = Face.ByWires(plan_eb, plan_ib_list)
return plan_face
@staticmethod
def Project(faceA, faceB, direction : list = None,
mantissa: int = 6, tolerance: float = 0.0001):
"""
Creates a projection of the first input face unto the second input face.
Parameters
----------
faceA : topologic_core.Face
The face to be projected.
faceB : topologic_core.Face
The face unto which the first input face will be projected.
direction : list, optional
The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
mantissa : int , optional
The desired length of the mantissa. The default is 6.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The projected Face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not faceA:
return None
if not Topology.IsInstance(faceA, "Face"):
return None
if not faceB:
return None
if not Topology.IsInstance(faceB, "Face"):
return None
eb = faceA.ExternalBoundary()
ib_list = []
_ = faceA.InternalBoundaries(ib_list)
p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
p_ib_list = []
for ib in ib_list:
temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance)
if temp_ib:
p_ib_list.append(temp_ib)
return Face.ByWires(p_eb, p_ib_list, tolerance=tolerance)
@staticmethod
def RectangleByPlaneEquation(origin= None, width: float = 1.0, length: float = 1.0, placement: str = "center", equation: dict = None, tolerance: float = 0.0001):
from topologicpy.Vertex import Vertex
# Extract coefficients of the plane equation
a = equation['a']
b = equation['b']
c = equation['c']
d = equation['d']
# Calculate the normal vector of the plane
direction = np.array([a, b, c], dtype=float)
direction /= np.linalg.norm(direction)
direction = [x for x in direction]
return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance)
@staticmethod
def Rectangle(origin= None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
"""
Creates a rectangle.
Parameters
----------
origin : topologic_core.Vertex, optional
The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
width : float , optional
The width of the rectangle. The default is 1.0.
length : float , optional
The length of the rectangle. The default is 1.0.
direction : list , optional
The vector representing the up direction of the rectangle. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
if not Topology.IsInstance(wire, "Wire"):
print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.")
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def RemoveCollinearEdges(face, angTolerance: float = 0.1, tolerance: float = 0.0001):
"""
Removes any collinear edges in the input face.
Parameters
----------
face : topologic_core.Face
The input face.
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face without any collinear edges.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.RemoveCollinearEdges - Error: The input face parameter is not a valid face. Returning None.")
return None
eb = Wire.RemoveCollinearEdges(Face.Wire(face), angTolerance=angTolerance, tolerance=tolerance)
ib = [Wire.RemoveCollinearEdges(w, angTolerance=angTolerance, tolerance=tolerance) for w in Face.InternalBoundaries(face)]
return Face.ByWires(eb, ib)
@staticmethod
def Simplify(face, tolerance=0.0001):
"""
Simplifies the input face edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed.
Returns
-------
topologic_core.Face
The simplified face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.Simplify - Error: The input face parameter is not a valid face. Returning None.")
return None
eb = Face.ExternalBoundary(face)
eb = Wire.Simplify(eb, tolerance=tolerance)
ibList = Face.InternalBoundaries(face)
ibList = [Wire.Simplify(ib) for ib in ibList]
return Face.ByWires(eb, ibList)
@staticmethod
def Skeleton(face, tolerance=0.001):
"""
Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better)
Returns
-------
topologic_core.Wire
The created straight skeleton.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.")
return None
return Wire.Skeleton(face, tolerance=tolerance)
@staticmethod
def Square(origin= None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
"""
Creates a square.
Parameters
----------
origin : topologic_core.Vertex , optional
The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0).
size : float , optional
The size of the square. The default is 1.0.
direction : list , optional
The vector representing the up direction of the square. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created square.
"""
return Face.Rectangle(origin=origin, width=size, length=size, direction=direction, placement=placement, tolerance=tolerance)
@staticmethod
def Squircle(origin = None, radius: float = 0.5, sides: int = 121, a: float = 2.0, b: float = 2.0, direction: list = [0, 0, 1], placement: str = "center", angTolerance: float = 0.1, tolerance: float = 0.0001):
"""
Creates a Squircle which is a hybrid between a circle and a square. See https://en.wikipedia.org/wiki/Squircle
Parameters
----------
origin : topologic_core.Vertex , optional
The location of the origin of the squircle. The default is None which results in the squircle being placed at (0, 0, 0).
radius : float , optional
The radius of the squircle. The default is 0.5.
sides : int , optional
The number of sides of the squircle. The default is 121.
a : float , optional
The "a" factor affects the x position of the points to interpolate between a circle and a square.
A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0.
b : float , optional
The "b" factor affects the y position of the points to interpolate between a circle and a square.
A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0.
radius : float , optional
The desired radius of the squircle. The default is 0.5.
sides : int , optional
The desired number of sides for the squircle. The default is 100.
direction : list , optional
The vector representing the up direction of the circle. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
angTolerance : float , optional
The desired angular tolerance. The default is 0.1.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created squircle.
"""
from topologicpy.Wire import Wire
wire = Wire.Squircle(origin = origin, radius= radius, sides = sides, a = a, b = b, direction = direction, placement = placement, angTolerance = angTolerance, tolerance = tolerance)
return Face.ByWire(wire)
@staticmethod
def Star(origin= None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
"""
Creates a star.
Parameters
----------
origin : topologic_core.Vertex, optional
The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0).
radiusA : float , optional
The outer radius of the star. The default is 1.0.
radiusB : float , optional
The outer radius of the star. The default is 0.4.
rays : int , optional
The number of star rays. The default is 5.
direction : list , optional
The vector representing the up direction of the star. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance)
if not Topology.IsInstance(wire, "Wire"):
print("Face.Rectangle - Error: Could not create the base wire for the star. Returning None.")
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
"""
Creates a trapezoid.
Parameters
----------
origin : topologic_core.Vertex, optional
The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0).
widthA : float , optional
The width of the bottom edge of the trapezoid. The default is 1.0.
widthB : float , optional
The width of the top edge of the trapezoid. The default is 0.75.
offsetA : float , optional
The offset of the bottom edge of the trapezoid. The default is 0.0.
offsetB : float , optional
The offset of the top edge of the trapezoid. The default is 0.0.
length : float , optional
The length of the trapezoid. The default is 1.0.
direction : list , optional
The vector representing the up direction of the trapezoid. The default is [0, 0, 1].
placement : str , optional
The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
-------
topologic_core.Face
The created trapezoid.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance)
if not Topology.IsInstance(wire, "Wire"):
print("Face.Rectangle - Error: Could not create the base wire for the trapezoid. Returning None.")
return None
return Face.ByWire(wire, tolerance=tolerance)
@staticmethod
def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list:
"""
Triangulates the input face and returns a list of faces.
Parameters
----------
face : topologic_core.Face
The input face.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
mode : int , optional
The desired mode of meshing algorithm. Several options are available:
0: Classic
1: MeshAdapt
3: Initial Mesh Only
5: Delaunay
6: Frontal-Delaunay
7: BAMG
8: Fontal-Delaunay for Quads
9: Packing of Parallelograms
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
meshSize : float , optional
The desired size of the mesh when using the "mesh" option. If set to None, it will be
calculated automatically and set to 10% of the overall size of the face.
Returns
-------
list
The list of triangles of the input face.
"""
from topologicpy.Wire import Wire
from topologicpy.Topology import Topology
# This function was contributed by Yidan Xue.
def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001):
"""
Creates a gmsh of triangular meshes from the input face.
Parameters
----------
face : topologic_core.Face
The input face.
meshSize : float , optional
The desired mesh size.
tolerance : float , optional
The desired tolerance. The default is 0.0001.
Returns
----------
topologic_core.Shell
The shell of triangular meshes.
"""
import os
import warnings
try:
import numpy as np
except:
print("Face.Triangulate - Warning: Installing required numpy library.")
try:
os.system("pip install numpy")
except:
os.system("pip install numpy --user")
try:
import numpy as np
print("Face.Triangulate - Warning: numpy library installed correctly.")
except:
warnings.warn("Face.Triangulate - Error: Could not import numpy. Please try to install numpy manually. Returning None.")
return None
try:
import gmsh
except:
print("Face.Triangulate - Warning: Installing required gmsh library.")
try:
os.system("pip install gmsh")
except:
os.system("pip install gmsh --user")
try:
import gmsh
print("Face.Triangulate - Warning: gmsh library installed correctly.")
except:
warnings.warn("Face.Triangulate - Error: Could not import gmsh. Please try to install gmsh manually. Returning None.")
return None
import topologic_core as topologic
from topologicpy.Vertex import Vertex
from topologicpy.Wire import Wire
from topologicpy.Face import Face
if not Topology.IsInstance(face, "Face"):
print("Shell.ByMeshFace - Error: The input face parameter is not a valid face. Returning None.")
return None
if not meshSize:
bounding_face = Face.BoundingRectangle(face)
bounding_face_vertices = Face.Vertices(bounding_face)
bounding_face_vertices_x = [Vertex.X(i) for i in bounding_face_vertices]
bounding_face_vertices_y = [Vertex.Y(i) for i in bounding_face_vertices]
width = max(bounding_face_vertices_x)-min(bounding_face_vertices_x)
length = max(bounding_face_vertices_y)-min(bounding_face_vertices_y)
meshSize = max([width,length])//10
gmsh.initialize()
face_external_boundary = Face.ExternalBoundary(face)
external_vertices = Wire.Vertices(face_external_boundary)
external_vertex_number = len(external_vertices)
for i in range(external_vertex_number):
gmsh.model.geo.addPoint(Vertex.X(external_vertices[i]), Vertex.Y(external_vertices[i]), Vertex.Z(external_vertices[i]), meshSize, i+1)
for i in range(external_vertex_number):
if i < external_vertex_number-1:
gmsh.model.geo.addLine(i+1, i+2, i+1)
else:
gmsh.model.geo.addLine(i+1, 1, i+1)
gmsh.model.geo.addCurveLoop([i+1 for i in range(external_vertex_number)], 1)
current_vertex_number = external_vertex_number
current_edge_number = external_vertex_number
current_wire_number = 1
face_internal_boundaries = Face.InternalBoundaries(face)
if face_internal_boundaries:
internal_face_number = len(face_internal_boundaries)
for i in range(internal_face_number):
face_internal_boundary = face_internal_boundaries[i]
internal_vertices = Wire.Vertices(face_internal_boundary)
internal_vertex_number = len(internal_vertices)
for j in range(internal_vertex_number):
gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j]), Vertex.Z(internal_vertices[j]), meshSize, current_vertex_number+j+1)
for j in range(internal_vertex_number):
if j < internal_vertex_number-1:
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+j+2, current_edge_number+j+1)
else:
gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+1, current_edge_number+j+1)
gmsh.model.geo.addCurveLoop([current_edge_number+i+1 for i in range(internal_vertex_number)], current_wire_number+1)
current_vertex_number = current_vertex_number+internal_vertex_number
current_edge_number = current_edge_number+internal_vertex_number
current_wire_number = current_wire_number+1
gmsh.model.geo.addPlaneSurface([i+1 for i in range(current_wire_number)])
gmsh.model.geo.synchronize()
if mode not in [1,3,5,6,7,8,9]:
mode = 6
gmsh.option.setNumber("Mesh.Algorithm", mode)
gmsh.model.mesh.generate(2) # For a 2D mesh
nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(-1, -1)
elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(-1, -1)
gmsh.finalize()
vertex_number = len(nodeTags)
vertices = []
for i in range(vertex_number):
vertices.append(Vertex.ByCoordinates(nodeCoords[3*i],nodeCoords[3*i+1],nodeCoords[3*i+2]))
faces = []
for n in range(len(elemTypes)):
vn = elemTypes[n]+1
et = elemTags[n]
ent = elemNodeTags[n]
if vn==3:
for i in range(len(et)):
face_vertices = []
for j in range(vn):
face_vertices.append(vertices[np.where(nodeTags==ent[i*vn+j])[0][0]])
faces.append(Face.ByVertices(face_vertices))
return faces
if not Topology.IsInstance(face, "Face"):
print("Face.Triangulate - Error: The input face parameter is not a valid face. Returning None.")
return None
vertices = Topology.Vertices(face)
if len(vertices) == 3: # Already a triangle
return [face]
origin = Topology.Centroid(face)
normal = Face.Normal(face)
flatFace = Topology.Flatten(face, origin=origin, direction=normal)
if mode == 0:
shell_faces = []
for i in range(0,5,1):
try:
_ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, shell_faces) # Hook to Core
break
except:
continue
else:
shell_faces = generate_gmsh(flatFace, mode = mode, meshSize = meshSize, tolerance = tolerance)
if len(shell_faces) < 1:
return []
finalFaces = []
for f in shell_faces:
f = Topology.Unflatten(f, origin=origin, direction=normal)
if Face.Angle(face, f) > 90:
wire = Face.ExternalBoundary(f)
wire = Wire.Invert(wire)
f = Face.ByWire(wire)
finalFaces.append(f)
else:
finalFaces.append(f)
return finalFaces
@staticmethod
def TrimByWire(face, wire, reverse: bool = False):
"""
Trims the input face by the input wire.
Parameters
----------
face : topologic_core.Face
The input face.
wire : topologic_core.Wire
The input wire.
reverse : bool , optional
If set to True, the effect of the trim will be reversed. The default is False.
Returns
-------
topologic_core.Face
The resulting trimmed face.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
if not Topology.IsInstance(wire, "Wire"):
return face
trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False) # Hook to Core
if reverse:
trimmed_face = face.Difference(trimmed_face)
return trimmed_face
@staticmethod
def VertexByParameters(face, u: float = 0.5, v: float = 0.5):
"""
Creates a vertex at the *u* and *v* parameters of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
u : float , optional
The *u* parameter of the input face. The default is 0.5.
v : float , optional
The *v* parameter of the input face. The default is 0.5.
Returns
-------
vertex : topologic vertex
The created vertex.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
return topologic.FaceUtility.VertexAtParameters(face, u, v) # Hook to Core
@staticmethod
def VertexParameters(face, vertex, outputType: str = "uv", mantissa: int = 6) -> list:
"""
Returns the *u* and *v* parameters of the input face at the location of the input vertex.
Parameters
----------
face : topologic_core.Face
The input face.
vertex : topologic_core.Vertex
The input vertex.
outputType : string , optional
The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
mantissa : int , optional
The desired length of the mantissa. The default is 6.
Returns
-------
list
The list of *u* and/or *v* as specified by the outputType input.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
if not Topology.IsInstance(vertex, "Vertex"):
return None
params = topologic.FaceUtility.ParametersAtVertex(face, vertex) # Hook to Core
u = round(params[0], mantissa)
v = round(params[1], mantissa)
outputType = list(outputType.lower())
returnResult = []
for param in outputType:
if param == "u":
returnResult.append(u)
elif param == "v":
returnResult.append(v)
return returnResult
@staticmethod
def Vertices(face) -> list:
"""
Returns the vertices of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
list
The list of vertices.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
vertices = []
_ = face.Vertices(None, vertices)
return vertices
@staticmethod
def Wire(face):
"""
Returns the external boundary (closed wire) of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
topologic_core.Wire
The external boundary of the input face.
"""
return Face.ExternalBoundary(face)
@staticmethod
def Wires(face) -> list:
"""
Returns the wires of the input face.
Parameters
----------
face : topologic_core.Face
The input face.
Returns
-------
list
The list of wires.
"""
from topologicpy.Topology import Topology
if not Topology.IsInstance(face, "Face"):
return None
wires = []
_ = face.Wires(None, wires)
return wires
Classes
class Face
-
Expand source code
class Face(): @staticmethod def AddInternalBoundaries(face, wires: list): """ Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face. Parameters ---------- face : topologic_core.Face The input face. wires : list The input list of internal boundaries (closed wires). Returns ------- topologic_core.Face The created face with internal boundaries added to it. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.AddInternalBoundaries - Error: The input face parameter is not a valid topologic face. Returning None.") return None if not isinstance(wires, list): print("Face.AddInternalBoundaries - Warning: The input wires parameter is not a valid list. Returning the input face.") return face wireList = [w for w in wires if Topology.IsInstance(w, "Wire")] if len(wireList) < 1: print("Face.AddInternalBoundaries - Warning: The input wires parameter does not contain any valid wires. Returning the input face.") return face faceeb = face.ExternalBoundary() faceibList = [] _ = face.InternalBoundaries(faceibList) for wire in wires: faceibList.append(wire) return Face.ByWires(faceeb, faceibList) @staticmethod def AddInternalBoundariesCluster(face, cluster): """ Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face. Parameters ---------- face : topologic_core.Face The input face. cluster : topologic_core.Cluster The input cluster of internal boundaries (topologic wires). Returns ------- topologic_core.Face The created face with internal boundaries added to it. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.AddInternalBoundariesCluster - Warning: The input cluster parameter is not a valid cluster. Returning None.") return None if not cluster: return face if not Topology.IsInstance(cluster, "Cluster"): return face wires = [] _ = cluster.Wires(None, wires) return Face.AddInternalBoundaries(face, wires) @staticmethod def Angle(faceA, faceB, mantissa: int = 6) -> float: """ Returns the angle in degrees between the two input faces. Parameters ---------- faceA : topologic_core.Face The first input face. faceB : topologic_core.Face The second input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The angle in degrees between the two input faces. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(faceA, "Face"): print("Face.Angle - Warning: The input faceA parameter is not a valid topologic face. Returning None.") return None if not Topology.IsInstance(faceB, "Face"): print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.") return None dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3) dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3) return round((Vector.Angle(dirA, dirB)), mantissa) @staticmethod def Area(face, mantissa: int = 6) -> float: """ Returns the area of the input face. Parameters ---------- face : topologic_core.Face The input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The area of the input face. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Area - Warning: The input face parameter is not a valid topologic face. Returning None.") return None area = None try: area = round(topologic.FaceUtility.Area(face), mantissa) # Hook to Core except: area = None return area @staticmethod def BoundingRectangle(topology, optimize: int = 0, tolerance: float = 0.0001): """ Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned. Parameters ---------- topology : topologic_core.Topology The input topology. optimize : int , optional If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The bounding rectangle of the input topology. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology br_wire = Wire.BoundingRectangle(topology=topology, optimize=optimize, tolerance=tolerance) br_face = Face.ByWire(br_wire) br_face = Topology.SetDictionary(br_face, Topology.Dictionary(br_wire)) return br_face @staticmethod def ByEdges(edges: list, tolerance : float = 0.0001): """ Creates a face from the input list of edges. Parameters ---------- edges : list The input list of edges. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- face : topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not isinstance(edges, list): print("Face.ByEdges - Error: The input edges parameter is not a valid list. Returning None.") return None edges = [e for e in edges if Topology.IsInstance(e, "Edge")] if len(edges) < 1: print("Face.ByEdges - Error: The input edges parameter does not contain any valid edges. Returning None.") return None wire = Wire.ByEdges(edges, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.ByEdges - Error: Could not create the required wire. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def ByEdgesCluster(cluster, tolerance: float = 0.0001): """ Creates a face from the input cluster of edges. Parameters ---------- cluster : topologic_core.Cluster The input cluster of edges. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- face : topologic_core.Face The created face. """ from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(cluster, "Cluster"): print("Face.ByEdgesCluster - Warning: The input cluster parameter is not a valid topologic cluster. Returning None.") return None edges = Cluster.Edges(cluster) if len(edges) < 1: print("Face.ByEdgesCluster - Warning: The input cluster parameter does not contain any valid edges. Returning None.") return None return Face.ByEdges(edges, tolerance=tolerance) @staticmethod def ByOffset(face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001): """ Creates an offset face from the input face. Parameters ---------- face : topologic_core.Face The input face. offset : float , optional The desired offset distance. The default is 1.0. miter : bool , optional if set to True, the corners will be mitered. The default is False. miterThreshold : float , optional The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2. offsetKey : str , optional If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None. miterThresholdKey : str , optional If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None. step : bool , optional If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.") return None eb = Face.Wire(face) internal_boundaries = Face.InternalBoundaries(face) offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step) offset_internal_boundaries = [] for internal_boundary in internal_boundaries: offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)) return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance) @staticmethod def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Creates a face by merging the faces of the input shell. Parameters ---------- shell : topologic_core.Shell The input shell. angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary from topologicpy.Helper import Helper def planarizeList(wireList): returnList = [] for aWire in wireList: returnList.append(Wire.Planarize(aWire)) return returnList if not Topology.IsInstance(shell, "Shell"): print("Face.ByShell - Error: The input shell parameter is not a valid toplogic shell. Returning None.") return None if origin == None: origin = Topology.Centroid(shell) if not Topology.IsInstance(origin, "Vertex"): print("Face.ByShell - Error: The input origin parameter is not a valid topologic vertex. Returning None.") return None # Try the simple method first face = None ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell)) if Topology.IsInstance(ext_boundary, "Wire"): face = Face.ByWire(ext_boundary) elif Topology.IsInstance(ext_boundary, "Cluster"): wires = Topology.Wires(ext_boundary) faces = [Face.ByWire(w) for w in wires] areas = [Face.Area(f) for f in faces] wires = Helper.Sort(wires, areas, reverseFlags=[True]) face = Face.ByWires(wires[0], wires[1:]) if Topology.IsInstance(face, "Face"): return face world_origin = Vertex.Origin() planar_shell = Shell.Planarize(shell) normal = Face.Normal(Topology.Faces(planar_shell)[0]) planar_shell = Topology.Flatten(planar_shell, origin=origin, direction=normal) vertices = Shell.Vertices(planar_shell) new_vertices = [] for v in vertices: x, y, z = Vertex.Coordinates(v) new_v = Vertex.ByCoordinates(x,y,0) new_vertices.append(new_v) planar_shell = Topology.SelfMerge(Topology.ReplaceVertices(planar_shell, verticesA=vertices, verticesB=new_vertices), tolerance=tolerance) ext_boundary = Shell.ExternalBoundary(planar_shell, tolerance=tolerance) ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance) if not Topology.IsInstance(ext_boundary, "Topology"): print("Face.ByShell - Error: Could not derive the external boundary of the input shell parameter. Returning None.") return None if Topology.IsInstance(ext_boundary, "Wire"): if not Topology.IsPlanar(ext_boundary, tolerance=tolerance): ext_boundary = Wire.Planarize(ext_boundary, origin=origin, tolerance=tolerance) ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance) try: face = Face.ByWire(ext_boundary) face = Topology.Unflatten(face, origin=origin, direction=normal) return face except: print("Face.ByShell - Error: The operation failed. Returning None.") return None elif Topology.IsInstance(ext_boundary, "Cluster"): # The shell has holes. wires = [] _ = ext_boundary.Wires(None, wires) faces = [] areas = [] for wire in wires: aFace = Face.ByWire(wire, tolerance=tolerance) if not Topology.IsInstance(aFace, "Face"): print("Face.ByShell - Error: The operation failed. Returning None.") return None anArea = abs(Face.Area(aFace)) faces.append(aFace) areas.append(anArea) max_index = areas.index(max(areas)) ext_boundary = faces[max_index] int_boundaries = list(set(faces) - set([ext_boundary])) int_wires = [] for int_boundary in int_boundaries: temp_wires = [] _ = int_boundary.Wires(None, temp_wires) int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)) temp_wires = [] _ = ext_boundary.Wires(None, temp_wires) ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance) face = Face.ByWires(ext_wire, int_wires) face = Topology.Unflatten(face, origin=origin, direction=normal) return face else: return None @staticmethod def ByVertices(vertices: list, tolerance: float = 0.0001): """ Creates a face from the input list of vertices. Parameters ---------- vertices : list The input list of vertices. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Topology import Topology from topologicpy.Wire import Wire if not isinstance(vertices, list): return None vertexList = [x for x in vertices if Topology.IsInstance(x, "Vertex")] if len(vertexList) < 3: return None w = Wire.ByVertices(vertexList, tolerance=tolerance) f = Face.ByWire(w, tolerance=tolerance) return f @staticmethod def ByVerticesCluster(cluster, tolerance: float = 0.0001): """ Creates a face from the input cluster of vertices. Parameters ---------- cluster : topologic_core.Cluster The input cluster of vertices. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(cluster, "Cluster"): return None vertices = Cluster.Vertices(cluster) return Face.ByVertices(vertices, tolerance=tolerance) @staticmethod def ByWire(wire, tolerance: float = 0.0001, silent=False): """ Creates a face from the input closed wire. Parameters ---------- wire : topologic_core.Wire The input wire. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error and warning messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face or list The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary import random def triangulateWire(wire): wire = Topology.RemoveCollinearEdges(wire) vertices = Topology.Vertices(wire) shell = Shell.Delaunay(vertices) if Topology.IsInstance(shell, "Topology"): return Topology.Faces(shell) else: return [] if not Topology.IsInstance(wire, "Wire"): if not silent: print("Face.ByWire - Error: The input wire parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(wire): if not silent: print("Face.ByWire - Error: The input wire parameter is not a closed topologic wire. Returning None.") return None edges = Wire.Edges(wire) wire = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance) vertices = Topology.Vertices(wire) fList = [] if Topology.IsInstance(wire, "Wire"): try: fList = topologic.Face.ByExternalBoundary(wire) # Hook to Core except: if not silent: print("Face.ByWire - Warning: Could not create face by external boundary. Trying other methods.") if len(vertices) > 3: fList = triangulateWire(wire) else: fList = [] if not isinstance(fList, list): fList = [fList] returnList = [] for f in fList: if Face.Area(f) < 0: wire = Face.ExternalBoundary(f) wire = Wire.Invert(wire) try: f = topologic.Face.ByExternalBoundary(wire) # Hook to Core returnList.append(f) except: pass else: returnList.append(f) if len(returnList) == 0: if not silent: print("Face.ByWire - Error: Could not build a face from the input wire parameter. Returning None.") return None elif len(returnList) == 1: return returnList[0] else: if not silent: print("Face.ByWire - Warning: Could not build a single face from the input wire parameter. Returning a list of faces.") return returnList @staticmethod def ByWires(externalBoundary, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False): """ Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires). Parameters ---------- externalBoundary : topologic_core.Wire The input external boundary. internalBoundaries : list , optional The input list of internal boundaries (closed wires). The default is an empty list. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(externalBoundary, "Wire"): if not silent: print("Face.ByWires - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(externalBoundary): if not silent: print("Face.ByWires - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.") return None ibList = [x for x in internalBoundaries if Topology.IsInstance(x, "Wire") and Wire.IsClosed(x)] face = None try: face = topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList, tolerance) # Hook to Core except: if not silent: print("Face.ByWires - Error: The operation failed. Returning None.") face = None return face @staticmethod def ByWiresCluster(externalBoundary, internalBoundariesCluster = None, tolerance: float = 0.0001, silent: bool = False): """ Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires). Parameters ---------- externalBoundary topologic_core.Wire The input external boundary (closed wire). internalBoundariesCluster : topologic_core.Cluster The input cluster of internal boundaries (closed wires). The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(externalBoundary, "Wire"): if not silent: print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(externalBoundary): if not silent: print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.") return None if not internalBoundariesCluster: internalBoundaries = [] elif not Topology.IsInstance(internalBoundariesCluster, "Cluster"): if not silent: print("Face.ByWiresCluster - Error: The input internalBoundariesCluster parameter is not a valid topologic cluster. Returning None.") return None else: internalBoundaries = Cluster.Wires(internalBoundariesCluster) return Face.ByWires(externalBoundary, internalBoundaries, tolerance=tolerance, silent=silent) @staticmethod def NorthArrow(origin= None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0, placement: str = "center", tolerance: float = 0.0001): """ Creates a north arrow. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0). radius : float , optional The radius of the circle. The default is 1. sides : int , optional The number of sides of the circle. The default is 16. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. northAngle : float , optional The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created circle. """ from topologicpy.Topology import Topology from topologicpy.Vertex import Vertex if not origin: origin = Vertex.Origin() c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0, 0, 1], placement="center", tolerance=tolerance) r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft") r = Topology.Translate(r, -0.005*radius,0,0) arrow = Topology.Difference(c, r, tolerance=tolerance) arrow = Topology.Rotate(arrow, origin=Vertex.Origin(), axis=[0, 0, 1], angle=northAngle) if placement.lower() == "lowerleft": arrow = Topology.Translate(arrow, radius, radius, 0) elif placement.lower() == "upperleft": arrow = Topology.Translate(arrow, radius, -radius, 0) elif placement.lower() == "lowerright": arrow = Topology.Translate(arrow, -radius, radius, 0) elif placement.lower() == "upperright": arrow = Topology.Translate(arrow, -radius, -radius, 0) arrow = Topology.Place(arrow, originA=Vertex.Origin(), originB=origin) arrow = Topology.Orient(arrow, origin=origin, direction=direction) return arrow @staticmethod def Circle(origin= None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a circle. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0). radius : float , optional The radius of the circle. The default is 1. sides : int , optional The number of sides of the circle. The default is 16. fromAngle : float , optional The angle in degrees from which to start creating the arc of the circle. The default is 0. toAngle : float , optional The angle in degrees at which to end creating the arc of the circle. The default is 360. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created circle. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def Compactness(face, mantissa: int = 6) -> float: """ Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape Parameters ---------- face : topologic_core.Face The input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The compactness measure of the input face. """ from topologicpy.Edge import Edge exb = face.ExternalBoundary() edges = [] _ = exb.Edges(None, edges) perimeter = 0.0 for anEdge in edges: perimeter = perimeter + abs(Edge.Length(anEdge)) area = abs(Face.Area(face)) compactness = 0 #From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape if area <= 0: return None if perimeter <= 0: return None compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter return round(compactness, mantissa) @staticmethod def CompassAngle(face, north: list = None, mantissa: int = 6) -> float: """ Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered. Parameters ---------- face : topologic_core.Face The input face. north : list , optional The second vector representing the north direction. The default is the positive YAxis ([0,1,0]). mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- float The horizontal compass angle in degrees between the direction of the face and the second input vector. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not north: north = Vector.North() dirA = Face.NormalAtParameters(face,mantissa=mantissa) return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa) @staticmethod def Edges(face) -> list: """ Returns the edges of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of edges. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None edges = [] _ = face.Edges(None, edges) return edges @staticmethod def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798 Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0, 0, 0). radius : float , optional The radius of the hexagon determining the size of the tile. The default is 0.5. direction : list , optional The vector representing the up direction of the ellipse. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns -------- topologic_core.Face The created Einstein tile. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement) if not Topology.IsInstance(wire, "Wire"): print("Face.Einstein - Error: Could not create base wire for the Einstein tile. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def ExteriorAngles(face, includeInternalBoundaries=False, mantissa: int = 6) -> list: """ Returns the exterior angles of the input face in degrees. The face must be planar. Parameters ---------- face : topologic_core.Face The input face. includeInternalBoundaries : bool , optional If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of exterior angles. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.ExteriorAngles - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) return_list = Wire.ExteriorAngles(eb, mantissa=mantissa) if includeInternalBoundaries: internal_boundaries = Face.InternalBoundaries(face) ib_i_a_list = [] if len(internal_boundaries) > 0: for ib in internal_boundaries: ib_interior_angles = Wire.InteriorAngles(ib, mantissa=mantissa) ib_i_a_list.append(ib_interior_angles) if len(ib_i_a_list) > 0: return_list = [return_list]+[ib_i_a_list] return return_list @staticmethod def ExternalBoundary(face): """ Returns the external boundary (closed wire) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- topologic_core.Wire The external boundary of the input face. """ from topologicpy.Vector import Vector from topologicpy.Wire import Wire from topologicpy.Topology import Topology eb = face.ExternalBoundary() f_dir = Face.Normal(face) faceVertices = Topology.Vertices(eb) temp_face = Face.ByWire(eb) temp_dir = Face.Normal(temp_face) if Vector.IsAntiParallel(f_dir, temp_dir): faceVertices.reverse() eb = Wire.ByVertices(faceVertices) return eb @staticmethod def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool: """ Returns True if the input face is facing toward the input direction. Parameters ---------- face : topologic_core.Face The input face. direction : list , optional The input direction. The default is [0,0,-1]. asVertex : bool , optional If set to True, the direction is treated as an actual vertex in 3D space. The default is False. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- bool True if the face is facing toward the direction. False otherwise. """ from topologicpy.Vector import Vector faceNormal = Face.Normal(face) faceCenter = Face.VertexByParameters(face,0.5,0.5) cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()] try: vList = [direction.X(), direction.Y(), direction.Z()] except: try: vList = [direction[0], direction[1], direction[2]] except: raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction") if asVertex: dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]] else: dV = vList uV = Vector.Normalize(dV) dot = sum([i*j for (i, j) in zip(uV, faceNormal)]) if dot < tolerance: return False return True @staticmethod def Fillet(face, radius: float = 0, radiusKey: str = None, tolerance: float = 0.0001, silent: bool = False): """ Fillets (rounds) the interior and exterior corners of the input face given the input radius. See https://en.wikipedia.org/wiki/Fillet_(mechanics) Parameters ---------- face : topologic_core.Face The input face. radius : float The desired radius of the fillet. radiusKey : str , optional If specified, the dictionary of the vertices will be queried for this key to specify the desired fillet radius. The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error and warning messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The filleted face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): if not silent: print("Face.Fillet - Error: The input face parameter is not a valid face. Returning None.") return None eb = Topology.Copy(Face.ExternalBoundary(face)) ib_list = Face.InternalBoundaries(face) ib_list = [Topology.Copy(ib) for ib in ib_list] f_vertices = Face.Vertices(face) if isinstance(radiusKey, str): eb = Topology.TransferDictionariesBySelectors(eb, selectors=f_vertices, tranVertices=True) eb = Wire.Fillet(eb, radius=radius, radiusKey=radiusKey, tolerance=tolerance) if not Topology.IsInstance(eb, "Wire"): if not silent: print("Face.Fillet - Error: The operation failed. Returning None.") return None ib_wires = [] for ib in ib_list: ib = Wire.ByVertices(Topology.Vertices(ib)) ib = Wire.Reverse(ib) if isinstance(radiusKey, str): ib = Topology.TransferDictionariesBySelectors(ib, selectors=f_vertices, tranVertices=True) ib_wire = Wire.Fillet(ib, radius=radius, radiusKey=radiusKey, tolerance=tolerance, silent=silent) if Topology.IsInstance(ib, "Wire"): ib_wires.append(ib_wire) else: if not silent: print("Face.Fillet - Error: The operation for one of the interior boundaries failed. Skipping.") return Face.ByWires(eb, ib_wires) @staticmethod def Harmonize(face, tolerance: float = 0.0001): """ Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The harmonized face. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary if not Topology.IsInstance(face, "Face"): print("Face.Harmonize - Error: The input face parameter is not a valid face. Returning None.") return None normal = Face.Normal(face) origin = Topology.Centroid(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) world_origin = Vertex.Origin() vertices = Wire.Vertices(Face.ExternalBoundary(flatFace)) harmonizedEB = Wire.ByVertices(vertices) internalBoundaries = Face.InternalBoundaries(flatFace) harmonizedIB = [] for ib in internalBoundaries: ibVertices = Wire.Vertices(ib) harmonizedIB.append(Wire.ByVertices(ibVertices)) harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB, tolerance=tolerance) harmonizedFace = Topology.Unflatten(harmonizedFace, origin=origin, direction=normal) return harmonizedFace @staticmethod def InteriorAngles(face, includeInternalBoundaries: bool = False, mantissa: int = 6) -> list: """ Returns the interior angles of the input face in degrees. The face must be planar. Parameters ---------- face : topologic_core.Face The input face. includeInternalBoundaries : bool , optional If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of interior angles. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.InteriorAngles - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) return_list = Wire.InteriorAngles(eb, mantissa=mantissa) if includeInternalBoundaries: internal_boundaries = Face.InternalBoundaries(face) ib_i_a_list = [] if len(internal_boundaries) > 0: for ib in internal_boundaries: ib_interior_angles = Wire.ExteriorAngles(ib, mantissa=mantissa) ib_i_a_list.append(ib_interior_angles) if len(ib_i_a_list) > 0: return_list = [return_list]+[ib_i_a_list] return return_list @staticmethod def InternalBoundaries(face) -> list: """ Returns the internal boundaries (closed wires) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of internal boundaries (closed wires). """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None wires = [] _ = face.InternalBoundaries(wires) return list(wires) @staticmethod def InternalVertex(face, tolerance: float = 0.0001): """ Creates a vertex guaranteed to be inside the input face. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Vertex The created vertex. """ from topologicpy.Vertex import Vertex from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None v = Topology.Centroid(face) if Vertex.IsInternal(v, face, tolerance=tolerance): return v l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9] for u in l: for v in l: v = Face.VertexByParameters(face, u, v) if Vertex.IsInternal(v, face, tolerance=tolerance): return v v = topologic.FaceUtility.InternalVertex(face, tolerance) # Hook to Core return v @staticmethod def Invert(face, tolerance: float = 0.0001): """ Creates a face that is an inverse (mirror) of the input face. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The inverted face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None eb = Face.ExternalBoundary(face) vertices = Wire.Vertices(eb) vertices.reverse() inverted_wire = Wire.ByVertices(vertices) internal_boundaries = Face.InternalBoundaries(face) if not internal_boundaries: inverted_face = Face.ByWire(inverted_wire, tolerance=tolerance) else: inverted_face = Face.ByWires(inverted_wire, internal_boundaries, tolerance=tolerance) return inverted_face @staticmethod def IsCoplanar(faceA, faceB, tolerance: float = 0.0001) -> bool: """ Returns True if the two input faces are coplanar. Returns False otherwise. Parameters ---------- faceA : topologic_core.Face The first input face. faceB : topologic_core.Face The second input face tolerance : float , optional The desired tolerance. The deafault is 0.0001. Raises ------ Exception Raises an exception if the angle between the two input faces cannot be determined. Returns ------- bool True if the two input faces are coplanar. False otherwise. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(faceA, "Face"): print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.") return None if not Topology.IsInstance(faceB, "Face"): print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.") return None dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3) dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3) return Vector.IsCollinear(dirA, dirB) @staticmethod def Isovist(face, vertex, obstacles = None, fromAngle=0, toAngle=360, tolerance: float = 0.0001): """ Returns the face representing the isovist projection from the input viewpoint. This method assumes all input is in 2D. Z coordinates are ignored. Parameters ---------- face : topologic_core.Face The face representing the boundary of the isovist. vertex : topologic_core.Vertex The vertex representing the location of the viewpoint of the isovist. obstacles : list , optional A list of wires representing the obstacles within the face. All obstacles are assumed to be within the boundary of the face. fromAngle : float , optional The angle in degrees from which to start creating the arc of the circle. The default is 0. 0 is considered to be in the positive X-axis direction. 90 is considered to be in the positive Y-axis direction. toAngle : float , optional The angle in degrees at which to end creating the arc of the circle. The default is 360. Angles are measured in an anti-clockwise fashion. tolerance : float , optional: The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The face representing the isovist projection from the input viewpoint. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Wire import Wire from topologicpy.Face import Face from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology def vertexPartofFace(vertex, face, tolerance): vertices = [] _ = face.Vertices(None, vertices) for v in vertices: if Vertex.Distance(vertex, v) < tolerance: return True return False if not Topology.IsInstance(face, "Face"): print("Face.Isovist - Error: The input boundary parameter is not a valid Face. Returning None") return None if not Topology.IsInstance(vertex, "Vertex"): print("Face.Isovist - Error: The input viewPoint parameter is not a valid Vertex. Returning None") return None if isinstance(obstacles, list): obstacles = [obs for obs in obstacles if Topology.IsInstance(obs, "Wire")] for obs in obstacles: face = Topology.Difference(face, Face.ByWire(obs)) targets = Topology.Vertices(face) distances = [] for target in targets: distances.append(Vertex.Distance(vertex, target)) distances.sort() max_d = distances[-1]*1.05 edges = [] for target in targets: e = Edge.ByVertices(vertex, target) e = Edge.SetLength(e, length=max_d, bothSides=False) edges.append(e) shell = Topology.Slice(face, Cluster.ByTopologies(edges)) faces = Topology.Faces(shell) final_faces = [] for face in faces: if vertexPartofFace(vertex, face, tolerance=0.001): final_faces.append(face) shell = Shell.ByFaces(final_faces) return_face = Topology.RemoveCoplanarFaces(shell) if abs(360 - toAngle - fromAngle) > tolerance: c = Wire.Circle(origin= vertex, radius=max_d, sides=180, fromAngle=fromAngle, toAngle=toAngle, close = False) e1 = Edge.ByVertices(Wire.StartVertex(c), vertex) e2 = Edge.ByVertices(Wire.EndVertex(c), vertex) edges = Topology.Edges(c) + [e1,e2] pie = Face.ByWire(Topology.SelfMerge(Cluster.ByTopologies(edges))) return_face = Topology.Intersect(pie, return_face) return return_face @staticmethod def MedialAxis(face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis. Parameters ---------- face : topologic_core.Face The input face. resolution : int , optional The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0. externalVertices : bool , optional If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False. internalVertices : bool , optional If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False. toLeavesOnly : bool , optional If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False. angTolerance : float , optional The desired angular tolerance in degrees for removing collinear edges. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Wire The medial axis of the input face. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary def touchesEdge(vertex,edges, tolerance=0.0001): if not Topology.IsInstance(vertex, "Vertex"): return False for edge in edges: u = Edge.ParameterAtVertex(edge, vertex, mantissa=6) if not u: continue if 0<u<1: return True return False # Flatten the input face origin = Topology.Centroid(face) normal = Face.Normal(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) # Create a Vertex at the world's origin (0, 0, 0) world_origin = Vertex.Origin() faceEdges = Face.Edges(flatFace) vertices = [] resolution = 10 - resolution resolution = min(max(resolution, 1), 10) for e in faceEdges: for n in range(resolution, 100, resolution): vertices.append(Edge.VertexByParameter(e,n*0.01)) voronoi = Shell.Voronoi(vertices=vertices, face=flatFace) voronoiEdges = Shell.Edges(voronoi) medialAxisEdges = [] for e in voronoiEdges: sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance) evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance) if not svTouchesEdge and not evTouchesEdge: medialAxisEdges.append(e) extBoundary = Face.ExternalBoundary(flatFace) extVertices = Wire.Vertices(extBoundary) intBoundaries = Face.InternalBoundaries(flatFace) intVertices = [] for ib in intBoundaries: intVertices = intVertices+Wire.Vertices(ib) theVertices = [] if internalVertices: theVertices = theVertices+intVertices if externalVertices: theVertices = theVertices+extVertices tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance) if Topology.IsInstance(tempWire, "Wire") and angTolerance > 0: tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance) medialAxisEdges = Wire.Edges(tempWire) for v in theVertices: nv = Vertex.NearestVertex(v, tempWire, useKDTree=False) if Topology.IsInstance(nv, "Vertex"): if toLeavesOnly: adjVertices = Topology.AdjacentTopologies(nv, tempWire) if len(adjVertices) < 2: medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance)) else: medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance)) medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance) if Topology.IsInstance(medialAxis, "Wire") and angTolerance > 0: medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance) medialAxis = Topology.Unflatten(medialAxis, origin=origin,direction=normal) return medialAxis @staticmethod def Normal(face, outputType: str = "xyz", mantissa: int = 6) -> list: """ Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The normal vector to the input face. This is computed at the approximate center of the face. """ return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa) @staticmethod def NormalAtParameters(face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list: """ Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter at which to compute the normal to the input face. The default is 0.5. v : float , optional The *v* parameter at which to compute the normal to the input face. The default is 0.5. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The normal vector to the input face. """ returnResult = [] try: coords = topologic.FaceUtility.NormalAtParameters(face, u, v) # Hook to Core x = round(coords[0], mantissa) y = round(coords[1], mantissa) z = round(coords[2], mantissa) outputType = list(outputType.lower()) for axis in outputType: if axis == "x": returnResult.append(x) elif axis == "y": returnResult.append(y) elif axis == "z": returnResult.append(z) except: returnResult = None return returnResult @staticmethod def NormalEdge(face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False): """ Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. length : float , optional The desired length of the normal edge. The default is 1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Edge The created normal edge to the input face. This is computed at the approximate center of the face. """ from topologicpy.Edge import Edge from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): if not silent: print("Face.NormalEdge - Error: The input face parameter is not a valid face. Retuning None.") return None if length < tolerance: if not silent: print("Face.NormalEdge - Error: The input length parameter is less than the input tolerance. Retuning None.") return None iv = Face.InternalVertex(face) u, v = Face.VertexParameters(face, iv) vec = Face.NormalAtParameters(face, u=u, v=v) ev = Topology.TranslateByDirectionDistance(iv, vec, length) return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent) @staticmethod def NormalEdgeAtParameters(face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001): """ Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter at which to compute the normal to the input face. The default is 0.5. v : float , optional The *v* parameter at which to compute the normal to the input face. The default is 0.5. length : float , optional The desired length of the normal edge. The default is 1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Edge The created normal edge to the input face. This is computed at the approximate center of the face. """ from topologicpy.Edge import Edge from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None sv = Face.VertexByParameters(face=face, u=u, v=v) vec = Face.NormalAtParameters(face, u=u, v=v) ev = Topology.TranslateByDirectionDistance(sv, vec, length) return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True) @staticmethod def PlaneEquation(face, mantissa: int = 6) -> dict: """ Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar. Parameters ---------- face : topologic_core.Face The input face. Returns ------- dict The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"]. """ from topologicpy.Topology import Topology from topologicpy.Vertex import Vertex import random import time if not Topology.IsInstance(face, "Face"): print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.") return None vertices = Topology.Vertices(face) if len(vertices) < 3: print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.") return None return Vertex.PlaneEquation(vertices, mantissa=mantissa) @staticmethod def Planarize(face, origin= None, tolerance: float = 0.0001): """ Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction. Parameters ---------- face : topologic_core.Face The input face. origin : topologic_core.Vertex , optional The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The planarized face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(origin, "Vertex"): origin = Topology.Centroid(face) eb = Face.ExternalBoundary(face) plan_eb = Wire.Planarize(eb, origin=origin) ib_list = Face.InternalBoundaries(face) plan_ib_list = [] for ib in ib_list: plan_ib_list.append(Wire.Planarize(ib, origin=origin)) plan_face = Face.ByWires(plan_eb, plan_ib_list) return plan_face @staticmethod def Project(faceA, faceB, direction : list = None, mantissa: int = 6, tolerance: float = 0.0001): """ Creates a projection of the first input face unto the second input face. Parameters ---------- faceA : topologic_core.Face The face to be projected. faceB : topologic_core.Face The face unto which the first input face will be projected. direction : list, optional The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None. mantissa : int , optional The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The projected Face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not faceA: return None if not Topology.IsInstance(faceA, "Face"): return None if not faceB: return None if not Topology.IsInstance(faceB, "Face"): return None eb = faceA.ExternalBoundary() ib_list = [] _ = faceA.InternalBoundaries(ib_list) p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance) p_ib_list = [] for ib in ib_list: temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance) if temp_ib: p_ib_list.append(temp_ib) return Face.ByWires(p_eb, p_ib_list, tolerance=tolerance) @staticmethod def RectangleByPlaneEquation(origin= None, width: float = 1.0, length: float = 1.0, placement: str = "center", equation: dict = None, tolerance: float = 0.0001): from topologicpy.Vertex import Vertex # Extract coefficients of the plane equation a = equation['a'] b = equation['b'] c = equation['c'] d = equation['d'] # Calculate the normal vector of the plane direction = np.array([a, b, c], dtype=float) direction /= np.linalg.norm(direction) direction = [x for x in direction] return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance) @staticmethod def Rectangle(origin= None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a rectangle. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0). width : float , optional The width of the rectangle. The default is 1.0. length : float , optional The length of the rectangle. The default is 1.0. direction : list , optional The vector representing the up direction of the rectangle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def RemoveCollinearEdges(face, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Removes any collinear edges in the input face. Parameters ---------- face : topologic_core.Face The input face. angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face without any collinear edges. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.RemoveCollinearEdges - Error: The input face parameter is not a valid face. Returning None.") return None eb = Wire.RemoveCollinearEdges(Face.Wire(face), angTolerance=angTolerance, tolerance=tolerance) ib = [Wire.RemoveCollinearEdges(w, angTolerance=angTolerance, tolerance=tolerance) for w in Face.InternalBoundaries(face)] return Face.ByWires(eb, ib) @staticmethod def Simplify(face, tolerance=0.0001): """ Simplifies the input face edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35 Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed. Returns ------- topologic_core.Face The simplified face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Simplify - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) eb = Wire.Simplify(eb, tolerance=tolerance) ibList = Face.InternalBoundaries(face) ibList = [Wire.Simplify(ib) for ib in ibList] return Face.ByWires(eb, ibList) @staticmethod def Skeleton(face, tolerance=0.001): """ Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com> This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better) Returns ------- topologic_core.Wire The created straight skeleton. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.") return None return Wire.Skeleton(face, tolerance=tolerance) @staticmethod def Square(origin= None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a square. Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0). size : float , optional The size of the square. The default is 1.0. direction : list , optional The vector representing the up direction of the square. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created square. """ return Face.Rectangle(origin=origin, width=size, length=size, direction=direction, placement=placement, tolerance=tolerance) @staticmethod def Squircle(origin = None, radius: float = 0.5, sides: int = 121, a: float = 2.0, b: float = 2.0, direction: list = [0, 0, 1], placement: str = "center", angTolerance: float = 0.1, tolerance: float = 0.0001): """ Creates a Squircle which is a hybrid between a circle and a square. See https://en.wikipedia.org/wiki/Squircle Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the squircle. The default is None which results in the squircle being placed at (0, 0, 0). radius : float , optional The radius of the squircle. The default is 0.5. sides : int , optional The number of sides of the squircle. The default is 121. a : float , optional The "a" factor affects the x position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0. b : float , optional The "b" factor affects the y position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0. radius : float , optional The desired radius of the squircle. The default is 0.5. sides : int , optional The desired number of sides for the squircle. The default is 100. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created squircle. """ from topologicpy.Wire import Wire wire = Wire.Squircle(origin = origin, radius= radius, sides = sides, a = a, b = b, direction = direction, placement = placement, angTolerance = angTolerance, tolerance = tolerance) return Face.ByWire(wire) @staticmethod def Star(origin= None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a star. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0). radiusA : float , optional The outer radius of the star. The default is 1.0. radiusB : float , optional The outer radius of the star. The default is 0.4. rays : int , optional The number of star rays. The default is 5. direction : list , optional The vector representing the up direction of the star. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the star. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a trapezoid. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0). widthA : float , optional The width of the bottom edge of the trapezoid. The default is 1.0. widthB : float , optional The width of the top edge of the trapezoid. The default is 0.75. offsetA : float , optional The offset of the bottom edge of the trapezoid. The default is 0.0. offsetB : float , optional The offset of the top edge of the trapezoid. The default is 0.0. length : float , optional The length of the trapezoid. The default is 1.0. direction : list , optional The vector representing the up direction of the trapezoid. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created trapezoid. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the trapezoid. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance) @staticmethod def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list: """ Triangulates the input face and returns a list of faces. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. mode : int , optional The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry. meshSize : float , optional The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face. Returns ------- list The list of triangles of the input face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology # This function was contributed by Yidan Xue. def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): """ Creates a gmsh of triangular meshes from the input face. Parameters ---------- face : topologic_core.Face The input face. meshSize : float , optional The desired mesh size. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ---------- topologic_core.Shell The shell of triangular meshes. """ import os import warnings try: import numpy as np except: print("Face.Triangulate - Warning: Installing required numpy library.") try: os.system("pip install numpy") except: os.system("pip install numpy --user") try: import numpy as np print("Face.Triangulate - Warning: numpy library installed correctly.") except: warnings.warn("Face.Triangulate - Error: Could not import numpy. Please try to install numpy manually. Returning None.") return None try: import gmsh except: print("Face.Triangulate - Warning: Installing required gmsh library.") try: os.system("pip install gmsh") except: os.system("pip install gmsh --user") try: import gmsh print("Face.Triangulate - Warning: gmsh library installed correctly.") except: warnings.warn("Face.Triangulate - Error: Could not import gmsh. Please try to install gmsh manually. Returning None.") return None import topologic_core as topologic from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Face import Face if not Topology.IsInstance(face, "Face"): print("Shell.ByMeshFace - Error: The input face parameter is not a valid face. Returning None.") return None if not meshSize: bounding_face = Face.BoundingRectangle(face) bounding_face_vertices = Face.Vertices(bounding_face) bounding_face_vertices_x = [Vertex.X(i) for i in bounding_face_vertices] bounding_face_vertices_y = [Vertex.Y(i) for i in bounding_face_vertices] width = max(bounding_face_vertices_x)-min(bounding_face_vertices_x) length = max(bounding_face_vertices_y)-min(bounding_face_vertices_y) meshSize = max([width,length])//10 gmsh.initialize() face_external_boundary = Face.ExternalBoundary(face) external_vertices = Wire.Vertices(face_external_boundary) external_vertex_number = len(external_vertices) for i in range(external_vertex_number): gmsh.model.geo.addPoint(Vertex.X(external_vertices[i]), Vertex.Y(external_vertices[i]), Vertex.Z(external_vertices[i]), meshSize, i+1) for i in range(external_vertex_number): if i < external_vertex_number-1: gmsh.model.geo.addLine(i+1, i+2, i+1) else: gmsh.model.geo.addLine(i+1, 1, i+1) gmsh.model.geo.addCurveLoop([i+1 for i in range(external_vertex_number)], 1) current_vertex_number = external_vertex_number current_edge_number = external_vertex_number current_wire_number = 1 face_internal_boundaries = Face.InternalBoundaries(face) if face_internal_boundaries: internal_face_number = len(face_internal_boundaries) for i in range(internal_face_number): face_internal_boundary = face_internal_boundaries[i] internal_vertices = Wire.Vertices(face_internal_boundary) internal_vertex_number = len(internal_vertices) for j in range(internal_vertex_number): gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j]), Vertex.Z(internal_vertices[j]), meshSize, current_vertex_number+j+1) for j in range(internal_vertex_number): if j < internal_vertex_number-1: gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+j+2, current_edge_number+j+1) else: gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+1, current_edge_number+j+1) gmsh.model.geo.addCurveLoop([current_edge_number+i+1 for i in range(internal_vertex_number)], current_wire_number+1) current_vertex_number = current_vertex_number+internal_vertex_number current_edge_number = current_edge_number+internal_vertex_number current_wire_number = current_wire_number+1 gmsh.model.geo.addPlaneSurface([i+1 for i in range(current_wire_number)]) gmsh.model.geo.synchronize() if mode not in [1,3,5,6,7,8,9]: mode = 6 gmsh.option.setNumber("Mesh.Algorithm", mode) gmsh.model.mesh.generate(2) # For a 2D mesh nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(-1, -1) elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(-1, -1) gmsh.finalize() vertex_number = len(nodeTags) vertices = [] for i in range(vertex_number): vertices.append(Vertex.ByCoordinates(nodeCoords[3*i],nodeCoords[3*i+1],nodeCoords[3*i+2])) faces = [] for n in range(len(elemTypes)): vn = elemTypes[n]+1 et = elemTags[n] ent = elemNodeTags[n] if vn==3: for i in range(len(et)): face_vertices = [] for j in range(vn): face_vertices.append(vertices[np.where(nodeTags==ent[i*vn+j])[0][0]]) faces.append(Face.ByVertices(face_vertices)) return faces if not Topology.IsInstance(face, "Face"): print("Face.Triangulate - Error: The input face parameter is not a valid face. Returning None.") return None vertices = Topology.Vertices(face) if len(vertices) == 3: # Already a triangle return [face] origin = Topology.Centroid(face) normal = Face.Normal(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) if mode == 0: shell_faces = [] for i in range(0,5,1): try: _ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, shell_faces) # Hook to Core break except: continue else: shell_faces = generate_gmsh(flatFace, mode = mode, meshSize = meshSize, tolerance = tolerance) if len(shell_faces) < 1: return [] finalFaces = [] for f in shell_faces: f = Topology.Unflatten(f, origin=origin, direction=normal) if Face.Angle(face, f) > 90: wire = Face.ExternalBoundary(f) wire = Wire.Invert(wire) f = Face.ByWire(wire) finalFaces.append(f) else: finalFaces.append(f) return finalFaces @staticmethod def TrimByWire(face, wire, reverse: bool = False): """ Trims the input face by the input wire. Parameters ---------- face : topologic_core.Face The input face. wire : topologic_core.Wire The input wire. reverse : bool , optional If set to True, the effect of the trim will be reversed. The default is False. Returns ------- topologic_core.Face The resulting trimmed face. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(wire, "Wire"): return face trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False) # Hook to Core if reverse: trimmed_face = face.Difference(trimmed_face) return trimmed_face @staticmethod def VertexByParameters(face, u: float = 0.5, v: float = 0.5): """ Creates a vertex at the *u* and *v* parameters of the input face. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter of the input face. The default is 0.5. v : float , optional The *v* parameter of the input face. The default is 0.5. Returns ------- vertex : topologic vertex The created vertex. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None return topologic.FaceUtility.VertexAtParameters(face, u, v) # Hook to Core @staticmethod def VertexParameters(face, vertex, outputType: str = "uv", mantissa: int = 6) -> list: """ Returns the *u* and *v* parameters of the input face at the location of the input vertex. Parameters ---------- face : topologic_core.Face The input face. vertex : topologic_core.Vertex The input vertex. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of *u* and/or *v* as specified by the outputType input. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(vertex, "Vertex"): return None params = topologic.FaceUtility.ParametersAtVertex(face, vertex) # Hook to Core u = round(params[0], mantissa) v = round(params[1], mantissa) outputType = list(outputType.lower()) returnResult = [] for param in outputType: if param == "u": returnResult.append(u) elif param == "v": returnResult.append(v) return returnResult @staticmethod def Vertices(face) -> list: """ Returns the vertices of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of vertices. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None vertices = [] _ = face.Vertices(None, vertices) return vertices @staticmethod def Wire(face): """ Returns the external boundary (closed wire) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- topologic_core.Wire The external boundary of the input face. """ return Face.ExternalBoundary(face) @staticmethod def Wires(face) -> list: """ Returns the wires of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of wires. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None wires = [] _ = face.Wires(None, wires) return wires
Static methods
def AddInternalBoundaries(face, wires: list)
-
Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
Parameters
face
:topologic_core.Face
- The input face.
wires
:list
- The input list of internal boundaries (closed wires).
Returns
topologic_core.Face
- The created face with internal boundaries added to it.
Expand source code
@staticmethod def AddInternalBoundaries(face, wires: list): """ Adds internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face. Parameters ---------- face : topologic_core.Face The input face. wires : list The input list of internal boundaries (closed wires). Returns ------- topologic_core.Face The created face with internal boundaries added to it. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.AddInternalBoundaries - Error: The input face parameter is not a valid topologic face. Returning None.") return None if not isinstance(wires, list): print("Face.AddInternalBoundaries - Warning: The input wires parameter is not a valid list. Returning the input face.") return face wireList = [w for w in wires if Topology.IsInstance(w, "Wire")] if len(wireList) < 1: print("Face.AddInternalBoundaries - Warning: The input wires parameter does not contain any valid wires. Returning the input face.") return face faceeb = face.ExternalBoundary() faceibList = [] _ = face.InternalBoundaries(faceibList) for wire in wires: faceibList.append(wire) return Face.ByWires(faceeb, faceibList)
def AddInternalBoundariesCluster(face, cluster)
-
Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face.
Parameters
face
:topologic_core.Face
- The input face.
cluster
:topologic_core.Cluster
- The input cluster of internal boundaries (topologic wires).
Returns
topologic_core.Face
- The created face with internal boundaries added to it.
Expand source code
@staticmethod def AddInternalBoundariesCluster(face, cluster): """ Adds the input cluster of internal boundaries (closed wires) to the input face. Internal boundaries are considered holes in the input face. Parameters ---------- face : topologic_core.Face The input face. cluster : topologic_core.Cluster The input cluster of internal boundaries (topologic wires). Returns ------- topologic_core.Face The created face with internal boundaries added to it. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.AddInternalBoundariesCluster - Warning: The input cluster parameter is not a valid cluster. Returning None.") return None if not cluster: return face if not Topology.IsInstance(cluster, "Cluster"): return face wires = [] _ = cluster.Wires(None, wires) return Face.AddInternalBoundaries(face, wires)
def Angle(faceA, faceB, mantissa: int = 6) ‑> float
-
Returns the angle in degrees between the two input faces.
Parameters
faceA
:topologic_core.Face
- The first input face.
faceB
:topologic_core.Face
- The second input face.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
float
- The angle in degrees between the two input faces.
Expand source code
@staticmethod def Angle(faceA, faceB, mantissa: int = 6) -> float: """ Returns the angle in degrees between the two input faces. Parameters ---------- faceA : topologic_core.Face The first input face. faceB : topologic_core.Face The second input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The angle in degrees between the two input faces. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(faceA, "Face"): print("Face.Angle - Warning: The input faceA parameter is not a valid topologic face. Returning None.") return None if not Topology.IsInstance(faceB, "Face"): print("Face.Angle - Warning: The input faceB parameter is not a valid topologic face. Returning None.") return None dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3) dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3) return round((Vector.Angle(dirA, dirB)), mantissa)
def Area(face, mantissa: int = 6) ‑> float
-
Returns the area of the input face.
Parameters
face
:topologic_core.Face
- The input face.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
float
- The area of the input face.
Expand source code
@staticmethod def Area(face, mantissa: int = 6) -> float: """ Returns the area of the input face. Parameters ---------- face : topologic_core.Face The input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The area of the input face. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Area - Warning: The input face parameter is not a valid topologic face. Returning None.") return None area = None try: area = round(topologic.FaceUtility.Area(face), mantissa) # Hook to Core except: area = None return area
def BoundingRectangle(topology, optimize: int = 0, tolerance: float = 0.0001)
-
Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned.
Parameters
topology
:topologic_core.Topology
- The input topology.
optimize
:int
, optional- If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The bounding rectangle of the input topology.
Expand source code
@staticmethod def BoundingRectangle(topology, optimize: int = 0, tolerance: float = 0.0001): """ Returns a face representing a bounding rectangle of the input topology. The returned face contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting face will become axis-aligned. Parameters ---------- topology : topologic_core.Topology The input topology. optimize : int , optional If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding rectangle so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding rectangle. The default is 0. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The bounding rectangle of the input topology. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology br_wire = Wire.BoundingRectangle(topology=topology, optimize=optimize, tolerance=tolerance) br_face = Face.ByWire(br_wire) br_face = Topology.SetDictionary(br_face, Topology.Dictionary(br_wire)) return br_face
def ByEdges(edges: list, tolerance: float = 0.0001)
-
Creates a face from the input list of edges.
Parameters
edges
:list
- The input list of edges.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
face
:topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByEdges(edges: list, tolerance : float = 0.0001): """ Creates a face from the input list of edges. Parameters ---------- edges : list The input list of edges. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- face : topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not isinstance(edges, list): print("Face.ByEdges - Error: The input edges parameter is not a valid list. Returning None.") return None edges = [e for e in edges if Topology.IsInstance(e, "Edge")] if len(edges) < 1: print("Face.ByEdges - Error: The input edges parameter does not contain any valid edges. Returning None.") return None wire = Wire.ByEdges(edges, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.ByEdges - Error: Could not create the required wire. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance)
def ByEdgesCluster(cluster, tolerance: float = 0.0001)
-
Creates a face from the input cluster of edges.
Parameters
cluster
:topologic_core.Cluster
- The input cluster of edges.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
face
:topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByEdgesCluster(cluster, tolerance: float = 0.0001): """ Creates a face from the input cluster of edges. Parameters ---------- cluster : topologic_core.Cluster The input cluster of edges. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- face : topologic_core.Face The created face. """ from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(cluster, "Cluster"): print("Face.ByEdgesCluster - Warning: The input cluster parameter is not a valid topologic cluster. Returning None.") return None edges = Cluster.Edges(cluster) if len(edges) < 1: print("Face.ByEdgesCluster - Warning: The input cluster parameter does not contain any valid edges. Returning None.") return None return Face.ByEdges(edges, tolerance=tolerance)
def ByOffset(face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001)
-
Creates an offset face from the input face.
Parameters
face
:topologic_core.Face
- The input face.
offset
:float
, optional- The desired offset distance. The default is 1.0.
miter
:bool
, optional- if set to True, the corners will be mitered. The default is False.
miterThreshold
:float
, optional- The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2.
offsetKey
:str
, optional- If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None.
miterThresholdKey
:str
, optional- If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None.
step
:bool
, optional- If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByOffset(face, offset: float = 1.0, miter: bool = False, miterThreshold: float = None, offsetKey: str = None, miterThresholdKey: str = None, step: bool = True, tolerance: float = 0.0001): """ Creates an offset face from the input face. Parameters ---------- face : topologic_core.Face The input face. offset : float , optional The desired offset distance. The default is 1.0. miter : bool , optional if set to True, the corners will be mitered. The default is False. miterThreshold : float , optional The distance beyond which a miter should be added. The default is None which means the miter threshold is set to the offset distance multiplied by the square root of 2. offsetKey : str , optional If specified, the dictionary of the edges will be queried for this key to sepcify the desired offset. The default is None. miterThresholdKey : str , optional If specified, the dictionary of the vertices will be queried for this key to sepcify the desired miter threshold distance. The default is None. step : bool , optional If set to True, The transition between collinear edges with different offsets will be a step. Otherwise, it will be a continous edge. The default is True. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.ByOffset - Warning: The input face parameter is not a valid toplogic face. Returning None.") return None eb = Face.Wire(face) internal_boundaries = Face.InternalBoundaries(face) offset_external_boundary = Wire.ByOffset(wire=eb, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step) offset_internal_boundaries = [] for internal_boundary in internal_boundaries: offset_internal_boundaries.append(Wire.ByOffset(wire=internal_boundary, offset=offset, miter=miter, miterThreshold=miterThreshold, offsetKey=offsetKey, miterThresholdKey=miterThresholdKey, step=step)) return Face.ByWires(offset_external_boundary, offset_internal_boundaries, tolerance=tolerance)
def ByShell(shell, origin=None, angTolerance: float = 0.1, tolerance: float = 0.0001)
-
Creates a face by merging the faces of the input shell.
Parameters
shell
:topologic_core.Shell
- The input shell.
angTolerance
:float
, optional- The desired angular tolerance. The default is 0.1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Creates a face by merging the faces of the input shell. Parameters ---------- shell : topologic_core.Shell The input shell. angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary from topologicpy.Helper import Helper def planarizeList(wireList): returnList = [] for aWire in wireList: returnList.append(Wire.Planarize(aWire)) return returnList if not Topology.IsInstance(shell, "Shell"): print("Face.ByShell - Error: The input shell parameter is not a valid toplogic shell. Returning None.") return None if origin == None: origin = Topology.Centroid(shell) if not Topology.IsInstance(origin, "Vertex"): print("Face.ByShell - Error: The input origin parameter is not a valid topologic vertex. Returning None.") return None # Try the simple method first face = None ext_boundary = Wire.RemoveCollinearEdges(Shell.ExternalBoundary(shell)) if Topology.IsInstance(ext_boundary, "Wire"): face = Face.ByWire(ext_boundary) elif Topology.IsInstance(ext_boundary, "Cluster"): wires = Topology.Wires(ext_boundary) faces = [Face.ByWire(w) for w in wires] areas = [Face.Area(f) for f in faces] wires = Helper.Sort(wires, areas, reverseFlags=[True]) face = Face.ByWires(wires[0], wires[1:]) if Topology.IsInstance(face, "Face"): return face world_origin = Vertex.Origin() planar_shell = Shell.Planarize(shell) normal = Face.Normal(Topology.Faces(planar_shell)[0]) planar_shell = Topology.Flatten(planar_shell, origin=origin, direction=normal) vertices = Shell.Vertices(planar_shell) new_vertices = [] for v in vertices: x, y, z = Vertex.Coordinates(v) new_v = Vertex.ByCoordinates(x,y,0) new_vertices.append(new_v) planar_shell = Topology.SelfMerge(Topology.ReplaceVertices(planar_shell, verticesA=vertices, verticesB=new_vertices), tolerance=tolerance) ext_boundary = Shell.ExternalBoundary(planar_shell, tolerance=tolerance) ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance) if not Topology.IsInstance(ext_boundary, "Topology"): print("Face.ByShell - Error: Could not derive the external boundary of the input shell parameter. Returning None.") return None if Topology.IsInstance(ext_boundary, "Wire"): if not Topology.IsPlanar(ext_boundary, tolerance=tolerance): ext_boundary = Wire.Planarize(ext_boundary, origin=origin, tolerance=tolerance) ext_boundary = Topology.RemoveCollinearEdges(ext_boundary, angTolerance) try: face = Face.ByWire(ext_boundary) face = Topology.Unflatten(face, origin=origin, direction=normal) return face except: print("Face.ByShell - Error: The operation failed. Returning None.") return None elif Topology.IsInstance(ext_boundary, "Cluster"): # The shell has holes. wires = [] _ = ext_boundary.Wires(None, wires) faces = [] areas = [] for wire in wires: aFace = Face.ByWire(wire, tolerance=tolerance) if not Topology.IsInstance(aFace, "Face"): print("Face.ByShell - Error: The operation failed. Returning None.") return None anArea = abs(Face.Area(aFace)) faces.append(aFace) areas.append(anArea) max_index = areas.index(max(areas)) ext_boundary = faces[max_index] int_boundaries = list(set(faces) - set([ext_boundary])) int_wires = [] for int_boundary in int_boundaries: temp_wires = [] _ = int_boundary.Wires(None, temp_wires) int_wires.append(Topology.RemoveCollinearEdges(temp_wires[0], angTolerance)) temp_wires = [] _ = ext_boundary.Wires(None, temp_wires) ext_wire = Topology.RemoveCollinearEdges(temp_wires[0], angTolerance) face = Face.ByWires(ext_wire, int_wires) face = Topology.Unflatten(face, origin=origin, direction=normal) return face else: return None
def ByVertices(vertices: list, tolerance: float = 0.0001)
-
Creates a face from the input list of vertices.
Parameters
vertices
:list
- The input list of vertices.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByVertices(vertices: list, tolerance: float = 0.0001): """ Creates a face from the input list of vertices. Parameters ---------- vertices : list The input list of vertices. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Topology import Topology from topologicpy.Wire import Wire if not isinstance(vertices, list): return None vertexList = [x for x in vertices if Topology.IsInstance(x, "Vertex")] if len(vertexList) < 3: return None w = Wire.ByVertices(vertexList, tolerance=tolerance) f = Face.ByWire(w, tolerance=tolerance) return f
def ByVerticesCluster(cluster, tolerance: float = 0.0001)
-
Creates a face from the input cluster of vertices.
Parameters
cluster
:topologic_core.Cluster
- The input cluster of vertices.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByVerticesCluster(cluster, tolerance: float = 0.0001): """ Creates a face from the input cluster of vertices. Parameters ---------- cluster : topologic_core.Cluster The input cluster of vertices. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(cluster, "Cluster"): return None vertices = Cluster.Vertices(cluster) return Face.ByVertices(vertices, tolerance=tolerance)
def ByWire(wire, tolerance: float = 0.0001, silent=False)
-
Creates a face from the input closed wire.
Parameters
wire
:topologic_core.Wire
- The input wire.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
silent
:bool
, optional- If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
Returns
topologic_core.Face
orlist
- The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces.
Expand source code
@staticmethod def ByWire(wire, tolerance: float = 0.0001, silent=False): """ Creates a face from the input closed wire. Parameters ---------- wire : topologic_core.Wire The input wire. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error and warning messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face or list The created face. If the wire is non-planar, the method will attempt to triangulate the wire and return a list of faces. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary import random def triangulateWire(wire): wire = Topology.RemoveCollinearEdges(wire) vertices = Topology.Vertices(wire) shell = Shell.Delaunay(vertices) if Topology.IsInstance(shell, "Topology"): return Topology.Faces(shell) else: return [] if not Topology.IsInstance(wire, "Wire"): if not silent: print("Face.ByWire - Error: The input wire parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(wire): if not silent: print("Face.ByWire - Error: The input wire parameter is not a closed topologic wire. Returning None.") return None edges = Wire.Edges(wire) wire = Topology.SelfMerge(Cluster.ByTopologies(edges), tolerance=tolerance) vertices = Topology.Vertices(wire) fList = [] if Topology.IsInstance(wire, "Wire"): try: fList = topologic.Face.ByExternalBoundary(wire) # Hook to Core except: if not silent: print("Face.ByWire - Warning: Could not create face by external boundary. Trying other methods.") if len(vertices) > 3: fList = triangulateWire(wire) else: fList = [] if not isinstance(fList, list): fList = [fList] returnList = [] for f in fList: if Face.Area(f) < 0: wire = Face.ExternalBoundary(f) wire = Wire.Invert(wire) try: f = topologic.Face.ByExternalBoundary(wire) # Hook to Core returnList.append(f) except: pass else: returnList.append(f) if len(returnList) == 0: if not silent: print("Face.ByWire - Error: Could not build a face from the input wire parameter. Returning None.") return None elif len(returnList) == 1: return returnList[0] else: if not silent: print("Face.ByWire - Warning: Could not build a single face from the input wire parameter. Returning a list of faces.") return returnList
def ByWires(externalBoundary, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False)
-
Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires).
Parameters
externalBoundary
:topologic_core.Wire
- The input external boundary.
internalBoundaries
:list
, optional- The input list of internal boundaries (closed wires). The default is an empty list.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
silent
:bool
, optional- If set to False, error messages are printed. Otherwise, they are not. The default is False.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByWires(externalBoundary, internalBoundaries: list = [], tolerance: float = 0.0001, silent: bool = False): """ Creates a face from the input external boundary (closed wire) and the input list of internal boundaries (closed wires). Parameters ---------- externalBoundary : topologic_core.Wire The input external boundary. internalBoundaries : list , optional The input list of internal boundaries (closed wires). The default is an empty list. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(externalBoundary, "Wire"): if not silent: print("Face.ByWires - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(externalBoundary): if not silent: print("Face.ByWires - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.") return None ibList = [x for x in internalBoundaries if Topology.IsInstance(x, "Wire") and Wire.IsClosed(x)] face = None try: face = topologic.Face.ByExternalInternalBoundaries(externalBoundary, ibList, tolerance) # Hook to Core except: if not silent: print("Face.ByWires - Error: The operation failed. Returning None.") face = None return face
def ByWiresCluster(externalBoundary, internalBoundariesCluster=None, tolerance: float = 0.0001, silent: bool = False)
-
Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires).
Parameters
- externalBoundary topologic_core.Wire
- The input external boundary (closed wire).
internalBoundariesCluster
:topologic_core.Cluster
- The input cluster of internal boundaries (closed wires). The default is None.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
silent
:bool
, optional- If set to False, error messages are printed. Otherwise, they are not. The default is False.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def ByWiresCluster(externalBoundary, internalBoundariesCluster = None, tolerance: float = 0.0001, silent: bool = False): """ Creates a face from the input external boundary (closed wire) and the input cluster of internal boundaries (closed wires). Parameters ---------- externalBoundary topologic_core.Wire The input external boundary (closed wire). internalBoundariesCluster : topologic_core.Cluster The input cluster of internal boundaries (closed wires). The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology if not Topology.IsInstance(externalBoundary, "Wire"): if not silent: print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a valid topologic wire. Returning None.") return None if not Wire.IsClosed(externalBoundary): if not silent: print("Face.ByWiresCluster - Error: The input externalBoundary parameter is not a closed topologic wire. Returning None.") return None if not internalBoundariesCluster: internalBoundaries = [] elif not Topology.IsInstance(internalBoundariesCluster, "Cluster"): if not silent: print("Face.ByWiresCluster - Error: The input internalBoundariesCluster parameter is not a valid topologic cluster. Returning None.") return None else: internalBoundaries = Cluster.Wires(internalBoundariesCluster) return Face.ByWires(externalBoundary, internalBoundaries, tolerance=tolerance, silent=silent)
def Circle(origin=None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates a circle.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
radius
:float
, optional- The radius of the circle. The default is 1.
sides
:int
, optional- The number of sides of the circle. The default is 16.
fromAngle
:float
, optional- The angle in degrees from which to start creating the arc of the circle. The default is 0.
toAngle
:float
, optional- The angle in degrees at which to end creating the arc of the circle. The default is 360.
direction
:list
, optional- The vector representing the up direction of the circle. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created circle.
Expand source code
@staticmethod def Circle(origin= None, radius: float = 0.5, sides: int = 16, fromAngle: float = 0.0, toAngle: float = 360.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a circle. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0). radius : float , optional The radius of the circle. The default is 1. sides : int , optional The number of sides of the circle. The default is 16. fromAngle : float , optional The angle in degrees from which to start creating the arc of the circle. The default is 0. toAngle : float , optional The angle in degrees at which to end creating the arc of the circle. The default is 360. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created circle. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Circle(origin=origin, radius=radius, sides=sides, fromAngle=fromAngle, toAngle=toAngle, close=True, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): return None return Face.ByWire(wire, tolerance=tolerance)
def Compactness(face, mantissa: int = 6) ‑> float
-
Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape
Parameters
face
:topologic_core.Face
- The input face.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
float
- The compactness measure of the input face.
Expand source code
@staticmethod def Compactness(face, mantissa: int = 6) -> float: """ Returns the compactness measure of the input face. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape Parameters ---------- face : topologic_core.Face The input face. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- float The compactness measure of the input face. """ from topologicpy.Edge import Edge exb = face.ExternalBoundary() edges = [] _ = exb.Edges(None, edges) perimeter = 0.0 for anEdge in edges: perimeter = perimeter + abs(Edge.Length(anEdge)) area = abs(Face.Area(face)) compactness = 0 #From https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape if area <= 0: return None if perimeter <= 0: return None compactness = (math.pi*(2*math.sqrt(area/math.pi)))/perimeter return round(compactness, mantissa)
def CompassAngle(face, north: list = None, mantissa: int = 6) ‑> float
-
Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered.
Parameters
face
:topologic_core.Face
- The input face.
north
:list
, optional- The second vector representing the north direction. The default is the positive YAxis ([0,1,0]).
mantissa
:int
, optional- The length of the desired mantissa. The default is 6.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
float
- The horizontal compass angle in degrees between the direction of the face and the second input vector.
Expand source code
@staticmethod def CompassAngle(face, north: list = None, mantissa: int = 6) -> float: """ Returns the horizontal compass angle in degrees between the normal vector of the input face and the input vector. The angle is measured in counter-clockwise fashion. Only the first two elements of the vectors are considered. Parameters ---------- face : topologic_core.Face The input face. north : list , optional The second vector representing the north direction. The default is the positive YAxis ([0,1,0]). mantissa : int, optional The length of the desired mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- float The horizontal compass angle in degrees between the direction of the face and the second input vector. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not north: north = Vector.North() dirA = Face.NormalAtParameters(face,mantissa=mantissa) return Vector.CompassAngle(vectorA=dirA, vectorB=north, mantissa=mantissa)
def Edges(face) ‑> list
-
Returns the edges of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
list
- The list of edges.
Expand source code
@staticmethod def Edges(face) -> list: """ Returns the edges of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of edges. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None edges = [] _ = face.Edges(None, edges) return edges
def Einstein(origin=None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0, 0, 0).
radius
:float
, optional- The radius of the hexagon determining the size of the tile. The default is 0.5.
direction
:list
, optional- The vector representing the up direction of the ellipse. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face The created Einstein tile.
Expand source code
@staticmethod def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates an aperiodic monotile, also called an 'einstein' tile (meaning one tile in German, not the name of the famous physist). See https://arxiv.org/abs/2303.10798 Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the tile. The default is None which results in the tiles first vertex being placed at (0, 0, 0). radius : float , optional The radius of the hexagon determining the size of the tile. The default is 0.5. direction : list , optional The vector representing the up direction of the ellipse. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the hexagon determining the location of the tile. This can be "center", or "lowerleft". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns -------- topologic_core.Face The created Einstein tile. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Einstein(origin=origin, radius=radius, direction=direction, placement=placement) if not Topology.IsInstance(wire, "Wire"): print("Face.Einstein - Error: Could not create base wire for the Einstein tile. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance)
def ExteriorAngles(face, includeInternalBoundaries=False, mantissa: int = 6) ‑> list
-
Returns the exterior angles of the input face in degrees. The face must be planar.
Parameters
face
:topologic_core.Face
- The input face.
includeInternalBoundaries
:bool
, optional- If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The list of exterior angles.
Expand source code
@staticmethod def ExteriorAngles(face, includeInternalBoundaries=False, mantissa: int = 6) -> list: """ Returns the exterior angles of the input face in degrees. The face must be planar. Parameters ---------- face : topologic_core.Face The input face. includeInternalBoundaries : bool , optional If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of exterior angles of the external boundary and the second list will contain lists of the exterior angles of each of the internal boundaries (holes). For example: [[270,270,270,270], [[270,270,270,270],[300,300,300]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [270,270,270,270]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of exterior angles. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.ExteriorAngles - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) return_list = Wire.ExteriorAngles(eb, mantissa=mantissa) if includeInternalBoundaries: internal_boundaries = Face.InternalBoundaries(face) ib_i_a_list = [] if len(internal_boundaries) > 0: for ib in internal_boundaries: ib_interior_angles = Wire.InteriorAngles(ib, mantissa=mantissa) ib_i_a_list.append(ib_interior_angles) if len(ib_i_a_list) > 0: return_list = [return_list]+[ib_i_a_list] return return_list
def ExternalBoundary(face)
-
Returns the external boundary (closed wire) of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
topologic_core.Wire
- The external boundary of the input face.
Expand source code
@staticmethod def ExternalBoundary(face): """ Returns the external boundary (closed wire) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- topologic_core.Wire The external boundary of the input face. """ from topologicpy.Vector import Vector from topologicpy.Wire import Wire from topologicpy.Topology import Topology eb = face.ExternalBoundary() f_dir = Face.Normal(face) faceVertices = Topology.Vertices(eb) temp_face = Face.ByWire(eb) temp_dir = Face.Normal(temp_face) if Vector.IsAntiParallel(f_dir, temp_dir): faceVertices.reverse() eb = Wire.ByVertices(faceVertices) return eb
def FacingToward(face, direction: list = [0, 0, -1], asVertex: bool = False, tolerance: float = 0.0001) ‑> bool
-
Returns True if the input face is facing toward the input direction.
Parameters
face
:topologic_core.Face
- The input face.
direction
:list
, optional- The input direction. The default is [0,0,-1].
asVertex
:bool
, optional- If set to True, the direction is treated as an actual vertex in 3D space. The default is False.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
bool
- True if the face is facing toward the direction. False otherwise.
Expand source code
@staticmethod def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool: """ Returns True if the input face is facing toward the input direction. Parameters ---------- face : topologic_core.Face The input face. direction : list , optional The input direction. The default is [0,0,-1]. asVertex : bool , optional If set to True, the direction is treated as an actual vertex in 3D space. The default is False. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- bool True if the face is facing toward the direction. False otherwise. """ from topologicpy.Vector import Vector faceNormal = Face.Normal(face) faceCenter = Face.VertexByParameters(face,0.5,0.5) cList = [faceCenter.X(), faceCenter.Y(), faceCenter.Z()] try: vList = [direction.X(), direction.Y(), direction.Z()] except: try: vList = [direction[0], direction[1], direction[2]] except: raise Exception("Face.FacingToward - Error: Could not get the vector from the input direction") if asVertex: dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]] else: dV = vList uV = Vector.Normalize(dV) dot = sum([i*j for (i, j) in zip(uV, faceNormal)]) if dot < tolerance: return False return True
def Fillet(face, radius: float = 0, radiusKey: str = None, tolerance: float = 0.0001, silent: bool = False)
-
Fillets (rounds) the interior and exterior corners of the input face given the input radius. See https://en.wikipedia.org/wiki/Fillet_(mechanics)
Parameters
face
:topologic_core.Face
- The input face.
radius
:float
- The desired radius of the fillet.
radiusKey
:str
, optional- If specified, the dictionary of the vertices will be queried for this key to specify the desired fillet radius. The default is None.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
silent
:bool
, optional- If set to False, error and warning messages are printed. Otherwise, they are not. The default is False.
Returns
topologic_core.Face
- The filleted face.
Expand source code
@staticmethod def Fillet(face, radius: float = 0, radiusKey: str = None, tolerance: float = 0.0001, silent: bool = False): """ Fillets (rounds) the interior and exterior corners of the input face given the input radius. See https://en.wikipedia.org/wiki/Fillet_(mechanics) Parameters ---------- face : topologic_core.Face The input face. radius : float The desired radius of the fillet. radiusKey : str , optional If specified, the dictionary of the vertices will be queried for this key to specify the desired fillet radius. The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. silent : bool , optional If set to False, error and warning messages are printed. Otherwise, they are not. The default is False. Returns ------- topologic_core.Face The filleted face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): if not silent: print("Face.Fillet - Error: The input face parameter is not a valid face. Returning None.") return None eb = Topology.Copy(Face.ExternalBoundary(face)) ib_list = Face.InternalBoundaries(face) ib_list = [Topology.Copy(ib) for ib in ib_list] f_vertices = Face.Vertices(face) if isinstance(radiusKey, str): eb = Topology.TransferDictionariesBySelectors(eb, selectors=f_vertices, tranVertices=True) eb = Wire.Fillet(eb, radius=radius, radiusKey=radiusKey, tolerance=tolerance) if not Topology.IsInstance(eb, "Wire"): if not silent: print("Face.Fillet - Error: The operation failed. Returning None.") return None ib_wires = [] for ib in ib_list: ib = Wire.ByVertices(Topology.Vertices(ib)) ib = Wire.Reverse(ib) if isinstance(radiusKey, str): ib = Topology.TransferDictionariesBySelectors(ib, selectors=f_vertices, tranVertices=True) ib_wire = Wire.Fillet(ib, radius=radius, radiusKey=radiusKey, tolerance=tolerance, silent=silent) if Topology.IsInstance(ib, "Wire"): ib_wires.append(ib_wire) else: if not silent: print("Face.Fillet - Error: The operation for one of the interior boundaries failed. Skipping.") return Face.ByWires(eb, ib_wires)
def Harmonize(face, tolerance: float = 0.0001)
-
Returns a harmonized version of the input face such that the u and v origins are always in the upperleft corner.
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The harmonized face.
Expand source code
@staticmethod def Harmonize(face, tolerance: float = 0.0001): """ Returns a harmonized version of the input face such that the *u* and *v* origins are always in the upperleft corner. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The harmonized face. """ from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary if not Topology.IsInstance(face, "Face"): print("Face.Harmonize - Error: The input face parameter is not a valid face. Returning None.") return None normal = Face.Normal(face) origin = Topology.Centroid(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) world_origin = Vertex.Origin() vertices = Wire.Vertices(Face.ExternalBoundary(flatFace)) harmonizedEB = Wire.ByVertices(vertices) internalBoundaries = Face.InternalBoundaries(flatFace) harmonizedIB = [] for ib in internalBoundaries: ibVertices = Wire.Vertices(ib) harmonizedIB.append(Wire.ByVertices(ibVertices)) harmonizedFace = Face.ByWires(harmonizedEB, harmonizedIB, tolerance=tolerance) harmonizedFace = Topology.Unflatten(harmonizedFace, origin=origin, direction=normal) return harmonizedFace
def InteriorAngles(face, includeInternalBoundaries: bool = False, mantissa: int = 6) ‑> list
-
Returns the interior angles of the input face in degrees. The face must be planar.
Parameters
face
:topologic_core.Face
- The input face.
includeInternalBoundaries
:bool
, optional- If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The list of interior angles.
Expand source code
@staticmethod def InteriorAngles(face, includeInternalBoundaries: bool = False, mantissa: int = 6) -> list: """ Returns the interior angles of the input face in degrees. The face must be planar. Parameters ---------- face : topologic_core.Face The input face. includeInternalBoundaries : bool , optional If set to True and if the face has internal boundaries (holes), the returned list will be a nested list where the first list is the list of interior angles of the external boundary and the second list will contain lists of the interior angles of each of the internal boundaries (holes). For example: [[90,90,90,90], [[90,90,90,90],[60,60,60]]]. If not, the returned list will be a simple list of interior angles of the external boundary. For example: [90,90,90,90]. Please note that that the interior angles of the internal boundaries are considered to be those interior to the original face. Thus, they are exterior to the internal boundary. mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of interior angles. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.InteriorAngles - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) return_list = Wire.InteriorAngles(eb, mantissa=mantissa) if includeInternalBoundaries: internal_boundaries = Face.InternalBoundaries(face) ib_i_a_list = [] if len(internal_boundaries) > 0: for ib in internal_boundaries: ib_interior_angles = Wire.ExteriorAngles(ib, mantissa=mantissa) ib_i_a_list.append(ib_interior_angles) if len(ib_i_a_list) > 0: return_list = [return_list]+[ib_i_a_list] return return_list
def InternalBoundaries(face) ‑> list
-
Returns the internal boundaries (closed wires) of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
list
- The list of internal boundaries (closed wires).
Expand source code
@staticmethod def InternalBoundaries(face) -> list: """ Returns the internal boundaries (closed wires) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of internal boundaries (closed wires). """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None wires = [] _ = face.InternalBoundaries(wires) return list(wires)
def InternalVertex(face, tolerance: float = 0.0001)
-
Creates a vertex guaranteed to be inside the input face.
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Vertex
- The created vertex.
Expand source code
@staticmethod def InternalVertex(face, tolerance: float = 0.0001): """ Creates a vertex guaranteed to be inside the input face. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Vertex The created vertex. """ from topologicpy.Vertex import Vertex from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None v = Topology.Centroid(face) if Vertex.IsInternal(v, face, tolerance=tolerance): return v l = [0.4,0.6,0.3,0.7,0.2,0.8,0.1,0.9] for u in l: for v in l: v = Face.VertexByParameters(face, u, v) if Vertex.IsInternal(v, face, tolerance=tolerance): return v v = topologic.FaceUtility.InternalVertex(face, tolerance) # Hook to Core return v
def Invert(face, tolerance: float = 0.0001)
-
Creates a face that is an inverse (mirror) of the input face.
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The inverted face.
Expand source code
@staticmethod def Invert(face, tolerance: float = 0.0001): """ Creates a face that is an inverse (mirror) of the input face. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The inverted face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None eb = Face.ExternalBoundary(face) vertices = Wire.Vertices(eb) vertices.reverse() inverted_wire = Wire.ByVertices(vertices) internal_boundaries = Face.InternalBoundaries(face) if not internal_boundaries: inverted_face = Face.ByWire(inverted_wire, tolerance=tolerance) else: inverted_face = Face.ByWires(inverted_wire, internal_boundaries, tolerance=tolerance) return inverted_face
def IsCoplanar(faceA, faceB, tolerance: float = 0.0001) ‑> bool
-
Returns True if the two input faces are coplanar. Returns False otherwise.
Parameters
faceA
:topologic_core.Face
- The first input face.
faceB
:topologic_core.Face
- The second input face
tolerance
:float
, optional- The desired tolerance. The deafault is 0.0001.
Raises
Exception
- Raises an exception if the angle between the two input faces cannot be determined.
Returns
bool
- True if the two input faces are coplanar. False otherwise.
Expand source code
@staticmethod def IsCoplanar(faceA, faceB, tolerance: float = 0.0001) -> bool: """ Returns True if the two input faces are coplanar. Returns False otherwise. Parameters ---------- faceA : topologic_core.Face The first input face. faceB : topologic_core.Face The second input face tolerance : float , optional The desired tolerance. The deafault is 0.0001. Raises ------ Exception Raises an exception if the angle between the two input faces cannot be determined. Returns ------- bool True if the two input faces are coplanar. False otherwise. """ from topologicpy.Vector import Vector from topologicpy.Topology import Topology if not Topology.IsInstance(faceA, "Face"): print("Face.IsInide - Error: The input faceA parameter is not a valid topologic face. Returning None.") return None if not Topology.IsInstance(faceB, "Face"): print("Face.IsInide - Error: The input faceB parameter is not a valid topologic face. Returning None.") return None dirA = Face.NormalAtParameters(faceA, 0.5, 0.5, "xyz", 3) dirB = Face.NormalAtParameters(faceB, 0.5, 0.5, "xyz", 3) return Vector.IsCollinear(dirA, dirB)
def Isovist(face, vertex, obstacles=None, fromAngle=0, toAngle=360, tolerance: float = 0.0001)
-
Returns the face representing the isovist projection from the input viewpoint. This method assumes all input is in 2D. Z coordinates are ignored.
Parameters
face
:topologic_core.Face
- The face representing the boundary of the isovist.
vertex
:topologic_core.Vertex
- The vertex representing the location of the viewpoint of the isovist.
obstacles
:list
, optional- A list of wires representing the obstacles within the face. All obstacles are assumed to be within the boundary of the face.
fromAngle
:float
, optional- The angle in degrees from which to start creating the arc of the circle. The default is 0. 0 is considered to be in the positive X-axis direction. 90 is considered to be in the positive Y-axis direction.
toAngle
:float
, optional- The angle in degrees at which to end creating the arc of the circle. The default is 360. Angles are measured in an anti-clockwise fashion.
tolerance
:float
, optional:
- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The face representing the isovist projection from the input viewpoint.
Expand source code
@staticmethod def Isovist(face, vertex, obstacles = None, fromAngle=0, toAngle=360, tolerance: float = 0.0001): """ Returns the face representing the isovist projection from the input viewpoint. This method assumes all input is in 2D. Z coordinates are ignored. Parameters ---------- face : topologic_core.Face The face representing the boundary of the isovist. vertex : topologic_core.Vertex The vertex representing the location of the viewpoint of the isovist. obstacles : list , optional A list of wires representing the obstacles within the face. All obstacles are assumed to be within the boundary of the face. fromAngle : float , optional The angle in degrees from which to start creating the arc of the circle. The default is 0. 0 is considered to be in the positive X-axis direction. 90 is considered to be in the positive Y-axis direction. toAngle : float , optional The angle in degrees at which to end creating the arc of the circle. The default is 360. Angles are measured in an anti-clockwise fashion. tolerance : float , optional: The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The face representing the isovist projection from the input viewpoint. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Wire import Wire from topologicpy.Face import Face from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology def vertexPartofFace(vertex, face, tolerance): vertices = [] _ = face.Vertices(None, vertices) for v in vertices: if Vertex.Distance(vertex, v) < tolerance: return True return False if not Topology.IsInstance(face, "Face"): print("Face.Isovist - Error: The input boundary parameter is not a valid Face. Returning None") return None if not Topology.IsInstance(vertex, "Vertex"): print("Face.Isovist - Error: The input viewPoint parameter is not a valid Vertex. Returning None") return None if isinstance(obstacles, list): obstacles = [obs for obs in obstacles if Topology.IsInstance(obs, "Wire")] for obs in obstacles: face = Topology.Difference(face, Face.ByWire(obs)) targets = Topology.Vertices(face) distances = [] for target in targets: distances.append(Vertex.Distance(vertex, target)) distances.sort() max_d = distances[-1]*1.05 edges = [] for target in targets: e = Edge.ByVertices(vertex, target) e = Edge.SetLength(e, length=max_d, bothSides=False) edges.append(e) shell = Topology.Slice(face, Cluster.ByTopologies(edges)) faces = Topology.Faces(shell) final_faces = [] for face in faces: if vertexPartofFace(vertex, face, tolerance=0.001): final_faces.append(face) shell = Shell.ByFaces(final_faces) return_face = Topology.RemoveCoplanarFaces(shell) if abs(360 - toAngle - fromAngle) > tolerance: c = Wire.Circle(origin= vertex, radius=max_d, sides=180, fromAngle=fromAngle, toAngle=toAngle, close = False) e1 = Edge.ByVertices(Wire.StartVertex(c), vertex) e2 = Edge.ByVertices(Wire.EndVertex(c), vertex) edges = Topology.Edges(c) + [e1,e2] pie = Face.ByWire(Topology.SelfMerge(Cluster.ByTopologies(edges))) return_face = Topology.Intersect(pie, return_face) return return_face
def MedialAxis(face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001)
-
Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis.
Parameters
face
:topologic_core.Face
- The input face.
resolution
:int
, optional- The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0.
externalVertices
:bool
, optional- If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
internalVertices
:bool
, optional- If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False.
toLeavesOnly
:bool
, optional- If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False.
angTolerance
:float
, optional- The desired angular tolerance in degrees for removing collinear edges. The default is 0.1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Wire
- The medial axis of the input face.
Expand source code
@staticmethod def MedialAxis(face, resolution: int = 0, externalVertices: bool = False, internalVertices: bool = False, toLeavesOnly: bool = False, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Returns a wire representing an approximation of the medial axis of the input topology. See https://en.wikipedia.org/wiki/Medial_axis. Parameters ---------- face : topologic_core.Face The input face. resolution : int , optional The desired resolution of the solution (range is 0: standard resolution to 10: high resolution). This determines the density of the sampling along each edge. The default is 0. externalVertices : bool , optional If set to True, the external vertices of the face will be connected to the nearest vertex on the medial axis. The default is False. internalVertices : bool , optional If set to True, the internal vertices of the face will be connected to the nearest vertex on the medial axis. The default is False. toLeavesOnly : bool , optional If set to True, the vertices of the face will be connected to the nearest vertex on the medial axis only if this vertex is a leaf (end point). Otherwise, it will connect to any nearest vertex. The default is False. angTolerance : float , optional The desired angular tolerance in degrees for removing collinear edges. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Wire The medial axis of the input face. """ from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Wire import Wire from topologicpy.Shell import Shell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary def touchesEdge(vertex,edges, tolerance=0.0001): if not Topology.IsInstance(vertex, "Vertex"): return False for edge in edges: u = Edge.ParameterAtVertex(edge, vertex, mantissa=6) if not u: continue if 0<u<1: return True return False # Flatten the input face origin = Topology.Centroid(face) normal = Face.Normal(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) # Create a Vertex at the world's origin (0, 0, 0) world_origin = Vertex.Origin() faceEdges = Face.Edges(flatFace) vertices = [] resolution = 10 - resolution resolution = min(max(resolution, 1), 10) for e in faceEdges: for n in range(resolution, 100, resolution): vertices.append(Edge.VertexByParameter(e,n*0.01)) voronoi = Shell.Voronoi(vertices=vertices, face=flatFace) voronoiEdges = Shell.Edges(voronoi) medialAxisEdges = [] for e in voronoiEdges: sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) svTouchesEdge = touchesEdge(sv, faceEdges, tolerance=tolerance) evTouchesEdge = touchesEdge(ev, faceEdges, tolerance=tolerance) if not svTouchesEdge and not evTouchesEdge: medialAxisEdges.append(e) extBoundary = Face.ExternalBoundary(flatFace) extVertices = Wire.Vertices(extBoundary) intBoundaries = Face.InternalBoundaries(flatFace) intVertices = [] for ib in intBoundaries: intVertices = intVertices+Wire.Vertices(ib) theVertices = [] if internalVertices: theVertices = theVertices+intVertices if externalVertices: theVertices = theVertices+extVertices tempWire = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance) if Topology.IsInstance(tempWire, "Wire") and angTolerance > 0: tempWire = Wire.RemoveCollinearEdges(tempWire, angTolerance=angTolerance) medialAxisEdges = Wire.Edges(tempWire) for v in theVertices: nv = Vertex.NearestVertex(v, tempWire, useKDTree=False) if Topology.IsInstance(nv, "Vertex"): if toLeavesOnly: adjVertices = Topology.AdjacentTopologies(nv, tempWire) if len(adjVertices) < 2: medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance)) else: medialAxisEdges.append(Edge.ByVertices([nv, v], tolerance=tolerance)) medialAxis = Topology.SelfMerge(Cluster.ByTopologies(medialAxisEdges), tolerance=tolerance) if Topology.IsInstance(medialAxis, "Wire") and angTolerance > 0: medialAxis = Topology.RemoveCollinearEdges(medialAxis, angTolerance=angTolerance) medialAxis = Topology.Unflatten(medialAxis, origin=origin,direction=normal) return medialAxis
def Normal(face, outputType: str = 'xyz', mantissa: int = 6) ‑> list
-
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
Parameters
face
:topologic_core.Face
- The input face.
outputType
:string
, optional- The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The normal vector to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod def Normal(face, outputType: str = "xyz", mantissa: int = 6) -> list: """ Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The normal vector to the input face. This is computed at the approximate center of the face. """ return Face.NormalAtParameters(face, u=0.5, v=0.5, outputType=outputType, mantissa=mantissa)
def NormalAtParameters(face, u: float = 0.5, v: float = 0.5, outputType: str = 'xyz', mantissa: int = 6) ‑> list
-
Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it.
Parameters
face
:topologic_core.Face
- The input face.
u
:float
, optional- The u parameter at which to compute the normal to the input face. The default is 0.5.
v
:float
, optional- The v parameter at which to compute the normal to the input face. The default is 0.5.
outputType
:string
, optional- The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz".
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The normal vector to the input face.
Expand source code
@staticmethod def NormalAtParameters(face, u: float = 0.5, v: float = 0.5, outputType: str = "xyz", mantissa: int = 6) -> list: """ Returns the normal vector to the input face. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter at which to compute the normal to the input face. The default is 0.5. v : float , optional The *v* parameter at which to compute the normal to the input face. The default is 0.5. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "xyz". It is case insensitive. The default is "xyz". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The normal vector to the input face. """ returnResult = [] try: coords = topologic.FaceUtility.NormalAtParameters(face, u, v) # Hook to Core x = round(coords[0], mantissa) y = round(coords[1], mantissa) z = round(coords[2], mantissa) outputType = list(outputType.lower()) for axis in outputType: if axis == "x": returnResult.append(x) elif axis == "y": returnResult.append(y) elif axis == "z": returnResult.append(z) except: returnResult = None return returnResult
def NormalEdge(face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False)
-
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
Parameters
face
:topologic_core.Face
- The input face.
length
:float
, optional- The desired length of the normal edge. The default is 1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Edge
- The created normal edge to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod def NormalEdge(face, length: float = 1.0, tolerance: float = 0.0001, silent: bool = False): """ Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. length : float , optional The desired length of the normal edge. The default is 1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Edge The created normal edge to the input face. This is computed at the approximate center of the face. """ from topologicpy.Edge import Edge from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): if not silent: print("Face.NormalEdge - Error: The input face parameter is not a valid face. Retuning None.") return None if length < tolerance: if not silent: print("Face.NormalEdge - Error: The input length parameter is less than the input tolerance. Retuning None.") return None iv = Face.InternalVertex(face) u, v = Face.VertexParameters(face, iv) vec = Face.NormalAtParameters(face, u=u, v=v) ev = Topology.TranslateByDirectionDistance(iv, vec, length) return Edge.ByVertices([iv, ev], tolerance=tolerance, silent=silent)
def NormalEdgeAtParameters(face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001)
-
Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it.
Parameters
face
:topologic_core.Face
- The input face.
u
:float
, optional- The u parameter at which to compute the normal to the input face. The default is 0.5.
v
:float
, optional- The v parameter at which to compute the normal to the input face. The default is 0.5.
length
:float
, optional- The desired length of the normal edge. The default is 1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Edge
- The created normal edge to the input face. This is computed at the approximate center of the face.
Expand source code
@staticmethod def NormalEdgeAtParameters(face, u: float = 0.5, v: float = 0.5, length: float = 1.0, tolerance: float = 0.0001): """ Returns the normal vector to the input face as an edge with the desired input length. A normal vector of a face is a vector perpendicular to it. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter at which to compute the normal to the input face. The default is 0.5. v : float , optional The *v* parameter at which to compute the normal to the input face. The default is 0.5. length : float , optional The desired length of the normal edge. The default is 1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Edge The created normal edge to the input face. This is computed at the approximate center of the face. """ from topologicpy.Edge import Edge from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None sv = Face.VertexByParameters(face=face, u=u, v=v) vec = Face.NormalAtParameters(face, u=u, v=v) ev = Topology.TranslateByDirectionDistance(sv, vec, length) return Edge.ByVertices([sv, ev], tolerance=tolerance, silent=True)
def NorthArrow(origin=None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0, placement: str = 'center', tolerance: float = 0.0001)
-
Creates a north arrow.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0).
radius
:float
, optional- The radius of the circle. The default is 1.
sides
:int
, optional- The number of sides of the circle. The default is 16.
direction
:list
, optional- The vector representing the up direction of the circle. The default is [0, 0, 1].
northAngle
:float
, optional- The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X.
placement
:str
, optional- The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created circle.
Expand source code
@staticmethod def NorthArrow(origin= None, radius: float = 0.5, sides: int = 16, direction: list = [0, 0, 1], northAngle: float = 0.0, placement: str = "center", tolerance: float = 0.0001): """ Creates a north arrow. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the circle. The default is None which results in the circle being placed at (0, 0, 0). radius : float , optional The radius of the circle. The default is 1. sides : int , optional The number of sides of the circle. The default is 16. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. northAngle : float , optional The angular offset in degrees from the positive Y axis direction. The angle is measured in a counter-clockwise fashion where 0 is positive Y, 90 is negative X, 180 is negative Y, and 270 is positive X. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created circle. """ from topologicpy.Topology import Topology from topologicpy.Vertex import Vertex if not origin: origin = Vertex.Origin() c = Face.Circle(origin=origin, radius=radius, sides=sides, direction=[0, 0, 1], placement="center", tolerance=tolerance) r = Face.Rectangle(origin=origin, width=radius*0.01,length=radius*1.2, placement="lowerleft") r = Topology.Translate(r, -0.005*radius,0,0) arrow = Topology.Difference(c, r, tolerance=tolerance) arrow = Topology.Rotate(arrow, origin=Vertex.Origin(), axis=[0, 0, 1], angle=northAngle) if placement.lower() == "lowerleft": arrow = Topology.Translate(arrow, radius, radius, 0) elif placement.lower() == "upperleft": arrow = Topology.Translate(arrow, radius, -radius, 0) elif placement.lower() == "lowerright": arrow = Topology.Translate(arrow, -radius, radius, 0) elif placement.lower() == "upperright": arrow = Topology.Translate(arrow, -radius, -radius, 0) arrow = Topology.Place(arrow, originA=Vertex.Origin(), originB=origin) arrow = Topology.Orient(arrow, origin=origin, direction=direction) return arrow
def Planarize(face, origin=None, tolerance: float = 0.0001)
-
Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction.
Parameters
face
:topologic_core.Face
- The input face.
origin
:topologic_core.Vertex
, optional- The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The planarized face.
Expand source code
@staticmethod def Planarize(face, origin= None, tolerance: float = 0.0001): """ Planarizes the input face such that its center of mass is located at the input origin and its normal is pointed in the input direction. Parameters ---------- face : topologic_core.Face The input face. origin : topologic_core.Vertex , optional The desired vertex to use as the origin of the plane to project the face unto. If set to None, the centroidof the input face is used. The default is None. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The planarized face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(origin, "Vertex"): origin = Topology.Centroid(face) eb = Face.ExternalBoundary(face) plan_eb = Wire.Planarize(eb, origin=origin) ib_list = Face.InternalBoundaries(face) plan_ib_list = [] for ib in ib_list: plan_ib_list.append(Wire.Planarize(ib, origin=origin)) plan_face = Face.ByWires(plan_eb, plan_ib_list) return plan_face
def PlaneEquation(face, mantissa: int = 6) ‑> dict
-
Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar.
Parameters
face
:topologic_core.Face
- The input face.
Returns
dict
- The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"].
Expand source code
@staticmethod def PlaneEquation(face, mantissa: int = 6) -> dict: """ Returns the a, b, c, d coefficients of the plane equation of the input face. The input face is assumed to be planar. Parameters ---------- face : topologic_core.Face The input face. Returns ------- dict The dictionary containing the coefficients of the plane equation. The keys of the dictionary are: ["a", "b", "c", "d"]. """ from topologicpy.Topology import Topology from topologicpy.Vertex import Vertex import random import time if not Topology.IsInstance(face, "Face"): print("Face.PlaneEquation - Error: The input face is not a valid topologic face. Returning None.") return None vertices = Topology.Vertices(face) if len(vertices) < 3: print("Face.PlaneEquation - Error: The input face has less than 3 vertices. Returning None.") return None return Vertex.PlaneEquation(vertices, mantissa=mantissa)
def Project(faceA, faceB, direction: list = None, mantissa: int = 6, tolerance: float = 0.0001)
-
Creates a projection of the first input face unto the second input face.
Parameters
faceA
:topologic_core.Face
- The face to be projected.
faceB
:topologic_core.Face
- The face unto which the first input face will be projected.
direction
:list
, optional- The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None.
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The projected Face.
Expand source code
@staticmethod def Project(faceA, faceB, direction : list = None, mantissa: int = 6, tolerance: float = 0.0001): """ Creates a projection of the first input face unto the second input face. Parameters ---------- faceA : topologic_core.Face The face to be projected. faceB : topologic_core.Face The face unto which the first input face will be projected. direction : list, optional The vector direction of the projection. If None, the reverse vector of the receiving face normal will be used. The default is None. mantissa : int , optional The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The projected Face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not faceA: return None if not Topology.IsInstance(faceA, "Face"): return None if not faceB: return None if not Topology.IsInstance(faceB, "Face"): return None eb = faceA.ExternalBoundary() ib_list = [] _ = faceA.InternalBoundaries(ib_list) p_eb = Wire.Project(wire=eb, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance) p_ib_list = [] for ib in ib_list: temp_ib = Wire.Project(wire=ib, face = faceB, direction=direction, mantissa=mantissa, tolerance=tolerance) if temp_ib: p_ib_list.append(temp_ib) return Face.ByWires(p_eb, p_ib_list, tolerance=tolerance)
def Rectangle(origin=None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates a rectangle.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0).
width
:float
, optional- The width of the rectangle. The default is 1.0.
length
:float
, optional- The length of the rectangle. The default is 1.0.
direction
:list
, optional- The vector representing the up direction of the rectangle. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def Rectangle(origin= None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a rectangle. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the rectangle. The default is None which results in the rectangle being placed at (0, 0, 0). width : float , optional The width of the rectangle. The default is 1.0. length : float , optional The length of the rectangle. The default is 1.0. direction : list , optional The vector representing the up direction of the rectangle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance)
def RectangleByPlaneEquation(origin=None, width: float = 1.0, length: float = 1.0, placement: str = 'center', equation: dict = None, tolerance: float = 0.0001)
-
Expand source code
@staticmethod def RectangleByPlaneEquation(origin= None, width: float = 1.0, length: float = 1.0, placement: str = "center", equation: dict = None, tolerance: float = 0.0001): from topologicpy.Vertex import Vertex # Extract coefficients of the plane equation a = equation['a'] b = equation['b'] c = equation['c'] d = equation['d'] # Calculate the normal vector of the plane direction = np.array([a, b, c], dtype=float) direction /= np.linalg.norm(direction) direction = [x for x in direction] return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance)
def RemoveCollinearEdges(face, angTolerance: float = 0.1, tolerance: float = 0.0001)
-
Removes any collinear edges in the input face.
Parameters
face
:topologic_core.Face
- The input face.
angTolerance
:float
, optional- The desired angular tolerance. The default is 0.1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face without any collinear edges.
Expand source code
@staticmethod def RemoveCollinearEdges(face, angTolerance: float = 0.1, tolerance: float = 0.0001): """ Removes any collinear edges in the input face. Parameters ---------- face : topologic_core.Face The input face. angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face without any collinear edges. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.RemoveCollinearEdges - Error: The input face parameter is not a valid face. Returning None.") return None eb = Wire.RemoveCollinearEdges(Face.Wire(face), angTolerance=angTolerance, tolerance=tolerance) ib = [Wire.RemoveCollinearEdges(w, angTolerance=angTolerance, tolerance=tolerance) for w in Face.InternalBoundaries(face)] return Face.ByWires(eb, ib)
def Simplify(face, tolerance=0.0001)
-
Simplifies the input face edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed.
Returns
topologic_core.Face
- The simplified face.
Expand source code
@staticmethod def Simplify(face, tolerance=0.0001): """ Simplifies the input face edges based on the Douglas Peucker algorthim. See https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm Part of this code was contributed by gaoxipeng. See https://github.com/wassimj/topologicpy/issues/35 Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed. Returns ------- topologic_core.Face The simplified face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Simplify - Error: The input face parameter is not a valid face. Returning None.") return None eb = Face.ExternalBoundary(face) eb = Wire.Simplify(eb, tolerance=tolerance) ibList = Face.InternalBoundaries(face) ibList = [Wire.Simplify(ib) for ib in ibList] return Face.ByWires(eb, ibList)
def Skeleton(face, tolerance=0.001)
-
Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao gaoxipeng1998@gmail.com This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better)
Returns
topologic_core.Wire
- The created straight skeleton.
Expand source code
@staticmethod def Skeleton(face, tolerance=0.001): """ Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com> This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better) Returns ------- topologic_core.Wire The created straight skeleton. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): print("Face.Skeleton - Error: The input face is not a valid topologic face. Retruning None.") return None return Wire.Skeleton(face, tolerance=tolerance)
def Square(origin=None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates a square.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0).
size
:float
, optional- The size of the square. The default is 1.0.
direction
:list
, optional- The vector representing the up direction of the square. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created square.
Expand source code
@staticmethod def Square(origin= None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a square. Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the square. The default is None which results in the square being placed at (0, 0, 0). size : float , optional The size of the square. The default is 1.0. direction : list , optional The vector representing the up direction of the square. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the square. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created square. """ return Face.Rectangle(origin=origin, width=size, length=size, direction=direction, placement=placement, tolerance=tolerance)
def Squircle(origin=None, radius: float = 0.5, sides: int = 121, a: float = 2.0, b: float = 2.0, direction: list = [0, 0, 1], placement: str = 'center', angTolerance: float = 0.1, tolerance: float = 0.0001)
-
Creates a Squircle which is a hybrid between a circle and a square. See https://en.wikipedia.org/wiki/Squircle
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the squircle. The default is None which results in the squircle being placed at (0, 0, 0).
radius
:float
, optional- The radius of the squircle. The default is 0.5.
sides
:int
, optional- The number of sides of the squircle. The default is 121.
a
:float
, optional- The "a" factor affects the x position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0.
b
:float
, optional- The "b" factor affects the y position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0.
radius
:float
, optional- The desired radius of the squircle. The default is 0.5.
sides
:int
, optional- The desired number of sides for the squircle. The default is 100.
direction
:list
, optional- The vector representing the up direction of the circle. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
angTolerance
:float
, optional- The desired angular tolerance. The default is 0.1.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created squircle.
Expand source code
@staticmethod def Squircle(origin = None, radius: float = 0.5, sides: int = 121, a: float = 2.0, b: float = 2.0, direction: list = [0, 0, 1], placement: str = "center", angTolerance: float = 0.1, tolerance: float = 0.0001): """ Creates a Squircle which is a hybrid between a circle and a square. See https://en.wikipedia.org/wiki/Squircle Parameters ---------- origin : topologic_core.Vertex , optional The location of the origin of the squircle. The default is None which results in the squircle being placed at (0, 0, 0). radius : float , optional The radius of the squircle. The default is 0.5. sides : int , optional The number of sides of the squircle. The default is 121. a : float , optional The "a" factor affects the x position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0. b : float , optional The "b" factor affects the y position of the points to interpolate between a circle and a square. A value of 1 will create a circle. Higher values will create a more square-like shape. The default is 2.0. radius : float , optional The desired radius of the squircle. The default is 0.5. sides : int , optional The desired number of sides for the squircle. The default is 100. direction : list , optional The vector representing the up direction of the circle. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the circle. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". angTolerance : float , optional The desired angular tolerance. The default is 0.1. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created squircle. """ from topologicpy.Wire import Wire wire = Wire.Squircle(origin = origin, radius= radius, sides = sides, a = a, b = b, direction = direction, placement = placement, angTolerance = angTolerance, tolerance = tolerance) return Face.ByWire(wire)
def Star(origin=None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates a star.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0).
radiusA
:float
, optional- The outer radius of the star. The default is 1.0.
radiusB
:float
, optional- The outer radius of the star. The default is 0.4.
rays
:int
, optional- The number of star rays. The default is 5.
direction
:list
, optional- The vector representing the up direction of the star. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created face.
Expand source code
@staticmethod def Star(origin= None, radiusA: float = 1.0, radiusB: float = 0.4, rays: int = 5, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a star. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the star. The default is None which results in the star being placed at (0, 0, 0). radiusA : float , optional The outer radius of the star. The default is 1.0. radiusB : float , optional The outer radius of the star. The default is 0.4. rays : int , optional The number of star rays. The default is 5. direction : list , optional The vector representing the up direction of the star. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the star. This can be "center", "lowerleft", "upperleft", "lowerright", or "upperright". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Star(origin=origin, radiusA=radiusA, radiusB=radiusB, rays=rays, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the star. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance)
def Trapezoid(origin=None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = 'center', tolerance: float = 0.0001)
-
Creates a trapezoid.
Parameters
origin
:topologic_core.Vertex
, optional- The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0).
widthA
:float
, optional- The width of the bottom edge of the trapezoid. The default is 1.0.
widthB
:float
, optional- The width of the top edge of the trapezoid. The default is 0.75.
offsetA
:float
, optional- The offset of the bottom edge of the trapezoid. The default is 0.0.
offsetB
:float
, optional- The offset of the top edge of the trapezoid. The default is 0.0.
length
:float
, optional- The length of the trapezoid. The default is 1.0.
direction
:list
, optional- The vector representing the up direction of the trapezoid. The default is [0, 0, 1].
placement
:str
, optional- The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center".
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
Returns
topologic_core.Face
- The created trapezoid.
Expand source code
@staticmethod def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): """ Creates a trapezoid. Parameters ---------- origin : topologic_core.Vertex, optional The location of the origin of the trapezoid. The default is None which results in the trapezoid being placed at (0, 0, 0). widthA : float , optional The width of the bottom edge of the trapezoid. The default is 1.0. widthB : float , optional The width of the top edge of the trapezoid. The default is 0.75. offsetA : float , optional The offset of the bottom edge of the trapezoid. The default is 0.0. offsetB : float , optional The offset of the top edge of the trapezoid. The default is 0.0. length : float , optional The length of the trapezoid. The default is 1.0. direction : list , optional The vector representing the up direction of the trapezoid. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the trapezoid. This can be "center", or "lowerleft". It is case insensitive. The default is "center". tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ------- topologic_core.Face The created trapezoid. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology wire = Wire.Trapezoid(origin=origin, widthA=widthA, widthB=widthB, offsetA=offsetA, offsetB=offsetB, length=length, direction=direction, placement=placement, tolerance=tolerance) if not Topology.IsInstance(wire, "Wire"): print("Face.Rectangle - Error: Could not create the base wire for the trapezoid. Returning None.") return None return Face.ByWire(wire, tolerance=tolerance)
def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) ‑> list
-
Triangulates the input face and returns a list of faces.
Parameters
face
:topologic_core.Face
- The input face.
tolerance
:float
, optional- The desired tolerance. The default is 0.0001.
mode
:int
, optional- The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
meshSize
:float
, optional- The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face.
Returns
list
- The list of triangles of the input face.
Expand source code
@staticmethod def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list: """ Triangulates the input face and returns a list of faces. Parameters ---------- face : topologic_core.Face The input face. tolerance : float , optional The desired tolerance. The default is 0.0001. mode : int , optional The desired mode of meshing algorithm. Several options are available: 0: Classic 1: MeshAdapt 3: Initial Mesh Only 5: Delaunay 6: Frontal-Delaunay 7: BAMG 8: Fontal-Delaunay for Quads 9: Packing of Parallelograms All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry. meshSize : float , optional The desired size of the mesh when using the "mesh" option. If set to None, it will be calculated automatically and set to 10% of the overall size of the face. Returns ------- list The list of triangles of the input face. """ from topologicpy.Wire import Wire from topologicpy.Topology import Topology # This function was contributed by Yidan Xue. def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): """ Creates a gmsh of triangular meshes from the input face. Parameters ---------- face : topologic_core.Face The input face. meshSize : float , optional The desired mesh size. tolerance : float , optional The desired tolerance. The default is 0.0001. Returns ---------- topologic_core.Shell The shell of triangular meshes. """ import os import warnings try: import numpy as np except: print("Face.Triangulate - Warning: Installing required numpy library.") try: os.system("pip install numpy") except: os.system("pip install numpy --user") try: import numpy as np print("Face.Triangulate - Warning: numpy library installed correctly.") except: warnings.warn("Face.Triangulate - Error: Could not import numpy. Please try to install numpy manually. Returning None.") return None try: import gmsh except: print("Face.Triangulate - Warning: Installing required gmsh library.") try: os.system("pip install gmsh") except: os.system("pip install gmsh --user") try: import gmsh print("Face.Triangulate - Warning: gmsh library installed correctly.") except: warnings.warn("Face.Triangulate - Error: Could not import gmsh. Please try to install gmsh manually. Returning None.") return None import topologic_core as topologic from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Face import Face if not Topology.IsInstance(face, "Face"): print("Shell.ByMeshFace - Error: The input face parameter is not a valid face. Returning None.") return None if not meshSize: bounding_face = Face.BoundingRectangle(face) bounding_face_vertices = Face.Vertices(bounding_face) bounding_face_vertices_x = [Vertex.X(i) for i in bounding_face_vertices] bounding_face_vertices_y = [Vertex.Y(i) for i in bounding_face_vertices] width = max(bounding_face_vertices_x)-min(bounding_face_vertices_x) length = max(bounding_face_vertices_y)-min(bounding_face_vertices_y) meshSize = max([width,length])//10 gmsh.initialize() face_external_boundary = Face.ExternalBoundary(face) external_vertices = Wire.Vertices(face_external_boundary) external_vertex_number = len(external_vertices) for i in range(external_vertex_number): gmsh.model.geo.addPoint(Vertex.X(external_vertices[i]), Vertex.Y(external_vertices[i]), Vertex.Z(external_vertices[i]), meshSize, i+1) for i in range(external_vertex_number): if i < external_vertex_number-1: gmsh.model.geo.addLine(i+1, i+2, i+1) else: gmsh.model.geo.addLine(i+1, 1, i+1) gmsh.model.geo.addCurveLoop([i+1 for i in range(external_vertex_number)], 1) current_vertex_number = external_vertex_number current_edge_number = external_vertex_number current_wire_number = 1 face_internal_boundaries = Face.InternalBoundaries(face) if face_internal_boundaries: internal_face_number = len(face_internal_boundaries) for i in range(internal_face_number): face_internal_boundary = face_internal_boundaries[i] internal_vertices = Wire.Vertices(face_internal_boundary) internal_vertex_number = len(internal_vertices) for j in range(internal_vertex_number): gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j]), Vertex.Z(internal_vertices[j]), meshSize, current_vertex_number+j+1) for j in range(internal_vertex_number): if j < internal_vertex_number-1: gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+j+2, current_edge_number+j+1) else: gmsh.model.geo.addLine(current_vertex_number+j+1, current_vertex_number+1, current_edge_number+j+1) gmsh.model.geo.addCurveLoop([current_edge_number+i+1 for i in range(internal_vertex_number)], current_wire_number+1) current_vertex_number = current_vertex_number+internal_vertex_number current_edge_number = current_edge_number+internal_vertex_number current_wire_number = current_wire_number+1 gmsh.model.geo.addPlaneSurface([i+1 for i in range(current_wire_number)]) gmsh.model.geo.synchronize() if mode not in [1,3,5,6,7,8,9]: mode = 6 gmsh.option.setNumber("Mesh.Algorithm", mode) gmsh.model.mesh.generate(2) # For a 2D mesh nodeTags, nodeCoords, nodeParams = gmsh.model.mesh.getNodes(-1, -1) elemTypes, elemTags, elemNodeTags = gmsh.model.mesh.getElements(-1, -1) gmsh.finalize() vertex_number = len(nodeTags) vertices = [] for i in range(vertex_number): vertices.append(Vertex.ByCoordinates(nodeCoords[3*i],nodeCoords[3*i+1],nodeCoords[3*i+2])) faces = [] for n in range(len(elemTypes)): vn = elemTypes[n]+1 et = elemTags[n] ent = elemNodeTags[n] if vn==3: for i in range(len(et)): face_vertices = [] for j in range(vn): face_vertices.append(vertices[np.where(nodeTags==ent[i*vn+j])[0][0]]) faces.append(Face.ByVertices(face_vertices)) return faces if not Topology.IsInstance(face, "Face"): print("Face.Triangulate - Error: The input face parameter is not a valid face. Returning None.") return None vertices = Topology.Vertices(face) if len(vertices) == 3: # Already a triangle return [face] origin = Topology.Centroid(face) normal = Face.Normal(face) flatFace = Topology.Flatten(face, origin=origin, direction=normal) if mode == 0: shell_faces = [] for i in range(0,5,1): try: _ = topologic.FaceUtility.Triangulate(flatFace, float(i)*0.1, shell_faces) # Hook to Core break except: continue else: shell_faces = generate_gmsh(flatFace, mode = mode, meshSize = meshSize, tolerance = tolerance) if len(shell_faces) < 1: return [] finalFaces = [] for f in shell_faces: f = Topology.Unflatten(f, origin=origin, direction=normal) if Face.Angle(face, f) > 90: wire = Face.ExternalBoundary(f) wire = Wire.Invert(wire) f = Face.ByWire(wire) finalFaces.append(f) else: finalFaces.append(f) return finalFaces
def TrimByWire(face, wire, reverse: bool = False)
-
Trims the input face by the input wire.
Parameters
face
:topologic_core.Face
- The input face.
wire
:topologic_core.Wire
- The input wire.
reverse
:bool
, optional- If set to True, the effect of the trim will be reversed. The default is False.
Returns
topologic_core.Face
- The resulting trimmed face.
Expand source code
@staticmethod def TrimByWire(face, wire, reverse: bool = False): """ Trims the input face by the input wire. Parameters ---------- face : topologic_core.Face The input face. wire : topologic_core.Wire The input wire. reverse : bool , optional If set to True, the effect of the trim will be reversed. The default is False. Returns ------- topologic_core.Face The resulting trimmed face. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(wire, "Wire"): return face trimmed_face = topologic.FaceUtility.TrimByWire(face, wire, False) # Hook to Core if reverse: trimmed_face = face.Difference(trimmed_face) return trimmed_face
def VertexByParameters(face, u: float = 0.5, v: float = 0.5)
-
Creates a vertex at the u and v parameters of the input face.
Parameters
face
:topologic_core.Face
- The input face.
u
:float
, optional- The u parameter of the input face. The default is 0.5.
v
:float
, optional- The v parameter of the input face. The default is 0.5.
Returns
vertex
:topologic vertex
- The created vertex.
Expand source code
@staticmethod def VertexByParameters(face, u: float = 0.5, v: float = 0.5): """ Creates a vertex at the *u* and *v* parameters of the input face. Parameters ---------- face : topologic_core.Face The input face. u : float , optional The *u* parameter of the input face. The default is 0.5. v : float , optional The *v* parameter of the input face. The default is 0.5. Returns ------- vertex : topologic vertex The created vertex. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None return topologic.FaceUtility.VertexAtParameters(face, u, v) # Hook to Core
def VertexParameters(face, vertex, outputType: str = 'uv', mantissa: int = 6) ‑> list
-
Returns the u and v parameters of the input face at the location of the input vertex.
Parameters
face
:topologic_core.Face
- The input face.
vertex
:topologic_core.Vertex
- The input vertex.
outputType
:string
, optional- The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv".
mantissa
:int
, optional- The desired length of the mantissa. The default is 6.
Returns
list
- The list of u and/or v as specified by the outputType input.
Expand source code
@staticmethod def VertexParameters(face, vertex, outputType: str = "uv", mantissa: int = 6) -> list: """ Returns the *u* and *v* parameters of the input face at the location of the input vertex. Parameters ---------- face : topologic_core.Face The input face. vertex : topologic_core.Vertex The input vertex. outputType : string , optional The string defining the desired output. This can be any subset or permutation of "uv". It is case insensitive. The default is "uv". mantissa : int , optional The desired length of the mantissa. The default is 6. Returns ------- list The list of *u* and/or *v* as specified by the outputType input. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None if not Topology.IsInstance(vertex, "Vertex"): return None params = topologic.FaceUtility.ParametersAtVertex(face, vertex) # Hook to Core u = round(params[0], mantissa) v = round(params[1], mantissa) outputType = list(outputType.lower()) returnResult = [] for param in outputType: if param == "u": returnResult.append(u) elif param == "v": returnResult.append(v) return returnResult
def Vertices(face) ‑> list
-
Returns the vertices of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
list
- The list of vertices.
Expand source code
@staticmethod def Vertices(face) -> list: """ Returns the vertices of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of vertices. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None vertices = [] _ = face.Vertices(None, vertices) return vertices
def Wire(face)
-
Returns the external boundary (closed wire) of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
topologic_core.Wire
- The external boundary of the input face.
Expand source code
@staticmethod def Wire(face): """ Returns the external boundary (closed wire) of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- topologic_core.Wire The external boundary of the input face. """ return Face.ExternalBoundary(face)
def Wires(face) ‑> list
-
Returns the wires of the input face.
Parameters
face
:topologic_core.Face
- The input face.
Returns
list
- The list of wires.
Expand source code
@staticmethod def Wires(face) -> list: """ Returns the wires of the input face. Parameters ---------- face : topologic_core.Face The input face. Returns ------- list The list of wires. """ from topologicpy.Topology import Topology if not Topology.IsInstance(face, "Face"): return None wires = [] _ = face.Wires(None, wires) return wires