From ba63b1466d7ee82d8a7d198801fe9284c47151ce Mon Sep 17 00:00:00 2001 From: Wassim Jabi Date: Sun, 26 May 2024 17:39:18 +0100 Subject: [PATCH] Mantissa fixes and new OWL ontology The work on adding mantissa input parameters continues This release has a new OWL ontology --- resources/topologicpy.ttl | 218 +++++++++++++++++++++++++++++++++ src/topologicpy/Cell.py | 115 +++++++++-------- src/topologicpy/CellComplex.py | 18 +-- src/topologicpy/Edge.py | 37 +++--- src/topologicpy/EnergyModel.py | 33 +++-- src/topologicpy/Face.py | 48 ++++---- src/topologicpy/Graph.py | 97 +++++++++------ src/topologicpy/Grid.py | 17 +-- src/topologicpy/Honeybee.py | 31 ++--- src/topologicpy/Neo4j.py | 32 +++-- src/topologicpy/Plotly.py | 111 ++++++++--------- src/topologicpy/Shell.py | 133 ++++++++++++-------- src/topologicpy/Topology.py | 145 ++++++++++++++-------- src/topologicpy/Vector.py | 6 +- src/topologicpy/Vertex.py | 106 +++++++++------- src/topologicpy/Wire.py | 35 ++++-- src/topologicpy/version.py | 2 +- tests/test_04Face.py | 2 +- tests/test_09Topology.py | 2 +- 19 files changed, 779 insertions(+), 409 deletions(-) create mode 100644 resources/topologicpy.ttl diff --git a/resources/topologicpy.ttl b/resources/topologicpy.ttl new file mode 100644 index 0000000..6e585cd --- /dev/null +++ b/resources/topologicpy.ttl @@ -0,0 +1,218 @@ +@prefix top: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +top:3DSpatialModelingOntology a owl:Ontology . + +# Classes +top:Topology a owl:Class ; + rdfs:comment "A superclass of Vertex, Edge, Wire, Face, Shell, Cell, CellComplex, and Cluster" . + +top:Vertex a top:Topology ; + rdfs:comment "A point in 3D space defined by coordinates." . + +top:Edge a top:Topology ; + rdfs:comment "A line segment connecting two vertices." . + +top:Wire a top:Topology ; + rdfs:comment "A sequence of connected edges." . + +top:Face a top:Topology ; + rdfs:comment "A flat surface bounded by a wire(loop) of edges and optional holes." . + +top:Shell a top:Topology ; + rdfs:comment "A collection of faces that share edges forming a segmented surface." . + +top:Cell a top:Topology ; + rdfs:comment "A volumetric element bounded by faces." . + +top:CellComplex a top:Topology ; + rdfs:comment "A collection of cells that share faces forming a segmented volumetric element." . + +top:Cluster a top:Topology ; + rdfs:comment "A group of related objects (e.g., cells, faces)." . + +top:Grid a top:Topology ; + rdfs:comment "A spatial structure dividing space into regular intervals." . + +top:Dictionary a owl:Class ; + rdfs:comment "A key-value store for metadata." . + +top:Aperture a top:Topology ; + rdfs:comment "An opening or hole in a face or shell." . + +top:Context a owl:Class ; + rdfs:comment "The environment or settings in which objects exist." . + +top:Vector a owl:Class ; + rdfs:comment "A mathematical entity with magnitude and direction." . + +top:Matrix a owl:Class ; + rdfs:comment "A rectangular 4x4 array of numbers used for transformations." . + +top:Graph a top:Topology ; + rdfs:comment "A collection of nodes (vertices) and edges representing relationships." . + +# Object Properties +top:hasDictionary a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Dictionary ; + owl:maxCardinality 1 ; + rdfs:comment "The dictionary of a topology." . + +top:hasStartVertex a owl:ObjectProperty ; + rdfs:domain top:Edge ; + rdfs:range top:Vertex ; + owl:minCardinality 1 ; + owl:maxCardinality 1 ; + rdfs:comment "The starting vertex of an edge." . + +top:hasEndVertex a owl:ObjectProperty ; + rdfs:domain top:Edge ; + rdfs:range top:Vertex ; + owl:minCardinality 1 ; + owl:maxCardinality 1 ; + rdfs:comment "The ending vertex of an edge." . + +top:hasEdges a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Edge ; + owl:maxCardinality owl:many ; + rdfs:comment "The edges that belong to a topology." . + +top:hasVertices a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Vertex ; + owl:minCardinality 1 ; + owl:maxCardinality owl:many ; + rdfs:comment "The vertices that belong to an object." . + +top:hasExternalBoundary a owl:ObjectProperty ; + rdfs:domain top:Face, top:Shell ; + rdfs:range top:Wire, top:Face ; + owl:minCardinality 1 ; + owl:maxCardinality 1 ; + rdfs:comment "The external boundary wire of a face or boundary face of a shell." . + +top:hasInternalBoundaries a owl:ObjectProperty ; + rdfs:domain top:Face, top:Cell ; + rdfs:range top:Wire, top:Shell ; + owl:maxCardinality owl:many ; + rdfs:comment "The internal boundary wires of a face or shells of a Cell." . + +top:hasFaces a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Face ; + owl:maxCardinality owl:many ; + rdfs:comment "The faces that belong to an object." . + +top:hasWires a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Wire ; + owl:maxCardinality owl:many ; + rdfs:comment "The wires that belong to an object." . + +top:hasShells a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Shell ; + owl:maxCardinality owl:many ; + rdfs:comment "The shells that belong to an object." . + +top:hasCells a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Cell ; + owl:maxCardinality owl:many ; + rdfs:comment "The cells that belong to an object." . + +top:hasAdjacentTopologies a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Topology ; # Can link to any object type + owl:maxCardinality owl:many ; + rdfs:comment "An adjacent topology of a topology" . + +top:hasSuperTopologies a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Topology ; # Can link to any object type + owl:maxCardinality owl:many ; + rdfs:comment "A parent/super topology of a topology" . + +top:hasSubTopologies a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Topology ; # Can link to any object type + owl:maxCardinality owl:many ; + rdfs:comment "A parent/super topology of a topology" . + +top:hasContents a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Topology ; # Can link to any object type + owl:maxCardinality owl:many ; + rdfs:comment "A content topology of a topology" . + +top:hasApertures a owl:ObjectProperty ; + rdfs:domain top:Topology ; + rdfs:range top:Topology ; # Can link to any object type + owl:maxCardinality owl:many ; + rdfs:comment "A content topology of a topology" . + +top:hasKeys a owl:ObjectProperty ; + rdfs:domain top:Dictionary ; + rdfs:range rdf:Property ; + rdfs:comment "The keys in a dictionary." . + +top:hasValues a owl:ObjectProperty ; + rdfs:domain top:Dictionary ; + rdfs:range rdfs:Literal ; + rdfs:comment "The values in a dictionary." . + +# Data Properties +top:hasX a owl:DatatypeProperty ; + rdfs:domain top:Vertex ; + rdfs:range xsd:float ; + rdfs:comment "The X coordinate of a vertex." . + +top:hasY a owl:DatatypeProperty ; + rdfs:domain top:Vertex ; + rdfs:range xsd:float ; + rdfs:comment "The Y coordinate of a vertex." . + +top:hasZ a owl:DatatypeProperty ; + rdfs:domain top:Vertex ; + rdfs:range xsd:float ; + rdfs:comment "The Z coordinate of a vertex." . + +top:hasLength a owl:DatatypeProperty ; + rdfs:domain top:Edge, top:Wire ; + rdfs:range xsd:float ; + rdfs:comment "The length of an edge or wire." . + +top:hasArea a owl:DatatypeProperty ; + rdfs:domain top:Face, top:Shell, top:Cell, top:CellComplex ; + rdfs:range xsd:float ; + rdfs:comment "The area of a face, shell, cell, or cell complex." . + +top:hasVolume a owl:DatatypeProperty ; + rdfs:domain top:Cell, top:CellComplex ; + rdfs:range xsd:float ; + rdfs:comment "The volume of a cell or cell complex." . + +top:hasMantissa a owl:DatatypeProperty ; + rdfs:domain [ rdfs:subClassOf [ a owl:Class ; owl:unionOf ( top:Vertex top:Edge top:Wire top:Face top:Shell top:Cell top:CellComplex ) ] ] ; + rdfs:range xsd:int ; + rdfs:comment "The number of digits after the decimal point to use to report the value." . + +top:hasUnit a owl:DatatypeProperty ; + rdfs:domain [ rdfs:subClassOf [ a owl:Class ; owl:unionOf ( top:Vertex top:Edge top:Wire top:Face top:Shell top:Cell top:CellComplex ) ] ] ; + rdfs:range xsd:string ; + rdfs:comment "The unit of measurement for numeric values." . + +top:createdAt a owl:DatatypeProperty ; + rdfs:domain owl:Thing ; + rdfs:range xsd:dateTime ; + rdfs:comment "The creation date and time of an object." . + +top:updatedAt a owl:DatatypeProperty ; + rdfs:domain owl:Thing ; + rdfs:range xsd:dateTime ; + rdfs:comment "The last update date and time of an object." . diff --git a/src/topologicpy/Cell.py b/src/topologicpy/Cell.py index 08b77ef..576f857 100644 --- a/src/topologicpy/Cell.py +++ b/src/topologicpy/Cell.py @@ -638,7 +638,7 @@ def Compactness(cell, reference = "sphere", mantissa: int = 6) -> float: @staticmethod def Cone(origin = None, baseRadius: float = 0.5, topRadius: float = 0, height: float = 1, uSides: int = 16, vSides: int = 1, direction: list = [0, 0, 1], - dirZ: float = 1, placement: str = "center", tolerance: float = 0.0001): + dirZ: float = 1, placement: str = "center", mantissa: int = 6, tolerance: float = 0.0001): """ Creates a cone. @@ -658,6 +658,8 @@ def Cone(origin = None, baseRadius: float = 0.5, topRadius: float = 0, height: f The vector representing the up direction of the cone. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the cone. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center". + mantissa : int , optional + The desired length of the mantissa. The default is 6 tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -719,13 +721,13 @@ def createCone(baseWire, topWire, baseVertex, topVertex, tolerance): for i in range(uSides): angle = math.radians(360/uSides)*i if baseRadius > 0: - baseX = math.cos(angle)*baseRadius + origin.X() + xOffset - baseY = math.sin(angle)*baseRadius + origin.Y() + yOffset - baseZ = origin.Z() + zOffset + baseX = math.cos(angle)*baseRadius + Vertex.X(origin, mantissa=mantissa) + xOffset + baseY = math.sin(angle)*baseRadius + Vertex.Y(origin, mantissa=mantissa) + yOffset + baseZ = Vertex.Z(origin, mantissa=mantissa) + zOffset baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ)) if topRadius > 0: - topX = math.cos(angle)*topRadius + origin.X() + xOffset - topY = math.sin(angle)*topRadius + origin.Y() + yOffset + topX = math.cos(angle)*topRadius + Vertex.X(origin, mantissa=mantissa) + xOffset + topY = math.sin(angle)*topRadius + Vertex.Y(origin, mantissa=mantissa) + yOffset topV.append(Vertex.ByCoordinates(topX,topY,topZ)) if baseRadius > 0: baseWire = Wire.ByVertices(baseV) @@ -735,8 +737,8 @@ def createCone(baseWire, topWire, baseVertex, topVertex, tolerance): topWire = Wire.ByVertices(topV) else: topWire = None - baseVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset) - topVertex = Vertex.ByCoordinates(origin.X()+xOffset, origin.Y()+yOffset, origin.Z()+zOffset+height) + baseVertex = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+xOffset, Vertex.Y(origin, mantissa=mantissa)+yOffset, Vertex.Z(origin, mantissa=mantissa)+zOffset) + topVertex = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+xOffset, Vertex.Y(origin, mantissa=mantissa)+yOffset, Vertex.Z(origin, mantissa=mantissa)+zOffset+height) cone = createCone(baseWire, topWire, baseVertex, topVertex, tolerance) if cone == None: print("Cell.Cone - Error: Could not create a cone. Returning None.") @@ -744,11 +746,11 @@ def createCone(baseWire, topWire, baseVertex, topVertex, tolerance): if vSides > 1: cutting_planes = [] - baseX = origin.X() + xOffset - baseY = origin.Y() + yOffset + baseX = Vertex.X(origin, mantissa=mantissa) + xOffset + baseY = Vertex.Y(origin, mantissa=mantissa) + yOffset size = max(baseRadius, topRadius)*3 for i in range(1, vSides): - baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i + baseZ = Vertex.Z(origin, mantissa=mantissa) + zOffset + float(height)/float(vSides)*i tool_origin = Vertex.ByCoordinates(baseX, baseY, baseZ) cutting_planes.append(Face.ByWire(Wire.Rectangle(origin=tool_origin, width=size, length=size), tolerance=tolerance)) cutting_planes_cluster = Cluster.ByTopologies(cutting_planes) @@ -800,7 +802,7 @@ def ContainmentStatus(cell, vertex, tolerance: float = 0.0001) -> int: @staticmethod def Cylinder(origin = None, radius: float = 0.5, height: float = 1, uSides: int = 16, vSides: int = 1, direction: list = [0, 0, 1], - placement: str = "center", tolerance: float = 0.0001): + placement: str = "center", mantissa: int = 6, tolerance: float = 0.0001): """ Creates a cylinder. @@ -820,6 +822,8 @@ def Cylinder(origin = None, radius: float = 0.5, height: float = 1, uSides: int The vector representing the up direction of the cylinder. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the cylinder. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "bottom". + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -848,7 +852,7 @@ def Cylinder(origin = None, radius: float = 0.5, height: float = 1, uSides: int elif placement.lower() == "lowerleft": xOffset = radius yOffset = radius - circle_origin = Vertex.ByCoordinates(origin.X() + xOffset, origin.Y() + yOffset, origin.Z() + zOffset) + circle_origin = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa) + xOffset, Vertex.Y(origin, mantissa=mantissa) + yOffset, Vertex.Z(origin, mantissa=mantissa) + zOffset) baseWire = Wire.Circle(origin=circle_origin, radius=radius, sides=uSides, fromAngle=0, toAngle=360, close=True, direction=[0, 0, 1], placement="center", tolerance=tolerance) baseFace = Face.ByWire(baseWire, tolerance=tolerance) @@ -856,8 +860,8 @@ def Cylinder(origin = None, radius: float = 0.5, height: float = 1, uSides: int tolerance=tolerance) if vSides > 1: cutting_planes = [] - baseX = origin.X() + xOffset - baseY = origin.Y() + yOffset + baseX = Vertex.X(origin, mantissa=mantissa) + xOffset + baseY = Vertex.Y(origin, mantissa=mantissa) + yOffset size = radius*3 for i in range(1, vSides): baseZ = origin.Z() + zOffset + float(height)/float(vSides)*i @@ -1212,7 +1216,7 @@ def Faces(cell) -> list: @staticmethod def Hyperboloid(origin = None, baseRadius: float = 0.5, topRadius: float = 0.5, height: float = 1, sides: int = 24, direction: list = [0, 0, 1], - twist: float = 60, placement: str = "center", tolerance: float = 0.0001): + twist: float = 60, placement: str = "center", mantissa: int = 6, tolerance: float = 0.0001): """ Creates a hyperboloid. @@ -1234,6 +1238,8 @@ def Hyperboloid(origin = None, baseRadius: float = 0.5, topRadius: float = 0.5, The angle to twist the base cylinder. The default is 60. placement : str , optional The description of the placement of the origin of the hyperboloid. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center". + mantissa : int , optional + The desired length of the mantissa. The default is 6 tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -1243,10 +1249,10 @@ def Hyperboloid(origin = None, baseRadius: float = 0.5, topRadius: float = 0.5, The created hyperboloid. """ - from topologicpy.Cluster import Cluster from topologicpy.Vertex import Vertex from topologicpy.Wire import Wire from topologicpy.Face import Face + from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology import math @@ -1295,13 +1301,13 @@ def createHyperboloid(baseVertices, topVertices, tolerance): for i in range(sides): angle = math.radians(360/sides)*i if baseRadius > 0: - baseX = math.sin(angle+math.radians(twist))*baseRadius + w_origin.X() + xOffset - baseY = math.cos(angle+math.radians(twist))*baseRadius + w_origin.Y() + yOffset - baseZ = w_origin.Z() + zOffset + baseX = math.sin(angle+math.radians(twist))*baseRadius + Vertex.X(w_origin, mantissa=mantissa) + xOffset + baseY = math.cos(angle+math.radians(twist))*baseRadius + Vertex.Y(w_origin, mantissa=mantissa) + yOffset + baseZ = Vertex.Z(w_origin, mantissa=mantissa) + zOffset baseV.append(Vertex.ByCoordinates(baseX,baseY,baseZ)) if topRadius > 0: - topX = math.sin(angle-math.radians(twist))*topRadius + w_origin.X() + xOffset - topY = math.cos(angle-math.radians(twist))*topRadius + w_origin.Y() + yOffset + topX = math.sin(angle-math.radians(twist))*topRadius + Vertex.X(w_origin, mantissa=mantissa) + xOffset + topY = math.cos(angle-math.radians(twist))*topRadius + Vertex.Y(w_origin, mantissa=mantissa) + yOffset topV.append(Vertex.ByCoordinates(topX,topY,topZ)) hyperboloid = createHyperboloid(baseV, topV, tolerance) @@ -1546,7 +1552,7 @@ def Octahedron(origin= None, radius: float = 0.5, return octahedron @staticmethod - def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset: float = 0, endOffset: float = 0, endcapA = None, endcapB = None) -> dict: + def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset: float = 0, endOffset: float = 0, endcapA = None, endcapB = None, mantissa: int = 6) -> dict: """ Description ---------- @@ -1570,7 +1576,9 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset The topology to place at the start vertex of the centerline edge. The positive Z direction of the end cap will be oriented in the direction of the centerline edge. endcapB, optional The topology to place at the end vertex of the centerline edge. The positive Z direction of the end cap will be oriented in the inverse direction of the centerline edge. - + mantissa : int , optional + The desired length of the mantissa. The default is 6 + Returns ------- dict @@ -1580,7 +1588,6 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset 'endcapB' """ - from topologicpy.Vertex import Vertex from topologicpy.Edge import Edge from topologicpy.Wire import Wire @@ -1596,12 +1603,12 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset endU = 1.0 - (endOffset / length) sv = Edge.VertexByParameter(edge, startU) ev = Edge.VertexByParameter(edge, endU) - x1 = sv.X() - y1 = sv.Y() - z1 = sv.Z() - x2 = ev.X() - y2 = ev.Y() - z2 = ev.Z() + x1 = Vertex.X(sv, mantissa=mantissa) + y1 = Vertex.Y(sv, mantissa=mantissa) + z1 = Vertex.Z(sv, mantissa=mantissa) + x2 = Vertex.X(ev, mantissa=mantissa) + y2 = Vertex.Y(ev, mantissa=mantissa) + z2 = Vertex.Z(ev, mantissa=mantissa) dx = x2 - x1 dy = y2 - y1 dz = z2 - z1 @@ -1615,9 +1622,9 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset else: for i in range(sides): angle = math.radians(360/sides)*i - x = math.sin(angle)*radius + sv.X() - y = math.cos(angle)*radius + sv.Y() - z = sv.Z() + x = math.sin(angle)*radius + Vertex.X(sv, mantissa=mantissa) + y = math.cos(angle)*radius + Vertex.Y(sv, mantissa=mantissa) + z = Vertex.Z(sv, mantissa=mantissa) baseV.append(Vertex.ByCoordinates(x, y, z)) topV.append(Vertex.ByCoordinates(x, y, z+dist)) @@ -1635,12 +1642,12 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset zzz = Vertex.ByCoordinates(0, 0, 0) if endcapA: origin = edge.StartVertex() - x1 = origin.X() - y1 = origin.Y() - z1 = origin.Z() - x2 = edge.EndVertex().X() - y2 = edge.EndVertex().Y() - z2 = edge.EndVertex().Z() + x1 = Vertex.X(origin, mantissa=mantissa) + y1 = Vertex.Y(origin, mantissa=mantissa) + z1 = Vertex.Z(origin, mantissa=mantissa) + x2 = Vertex.X(edge.EndVertex(), mantissa=mantissa) + y2 = Vertex.Y(edge.EndVertex(), mantissa=mantissa) + z2 = Vertex.Z(edge.EndVertex(), mantissa=mantissa) dx = x2 - x1 dy = y2 - y1 dz = z2 - z1 @@ -1653,15 +1660,15 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset endcapA = Topology.Copy(endcapA) endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 1, 0], angle=theta) endcapA = Topology.Rotate(endcapA, origin=zzz, axis=[0, 0, 1], angle=phi+180) - endcapA = Topology.Translate(endcapA, origin.X(), origin.Y(), origin.Z()) + endcapA = Topology.Translate(endcapA, Vertex.X(origin, mantissa=mantissa), Vertex.Y(origin, mantissa=mantissa), Vertex.Z(origin, mantissa=mantissa)) if endcapB: origin = edge.EndVertex() - x1 = origin.X() - y1 = origin.Y() - z1 = origin.Z() - x2 = edge.StartVertex().X() - y2 = edge.StartVertex().Y() - z2 = edge.StartVertex().Z() + x1 = Vertex.X(origin, mantissa=mantissa) + y1 = Vertex.Y(origin, mantissa=mantissa) + z1 = Vertex.Z(origin, mantissa=mantissa) + x2 = Vertex.X(edge.StartVertex(), mantissa=mantissa) + y2 = Vertex.Y(edge.StartVertex(), mantissa=mantissa) + z2 = Vertex.Z(edge.StartVertex(), mantissa=mantissa) dx = x2 - x1 dy = y2 - y1 dz = z2 - z1 @@ -1674,12 +1681,12 @@ def Pipe(edge, profile = None, radius: float = 0.5, sides: int = 16, startOffset endcapB = Topology.Copy(endcapB) endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 1, 0], angle=theta) endcapB = Topology.Rotate(endcapB, origin=zzz, axis=[0, 0, 1], angle=phi+180) - endcapB = Topology.Translate(endcapB, origin.X(), origin.Y(), origin.Z()) + endcapB = Topology.Translate(endcapB, Vertex.X(origin, mantissa=mantissa), Vertex.Y(origin, mantissa=mantissa), Vertex.Z(origin, mantissa=mantissa)) return {'pipe': pipe, 'endcapA': endcapA, 'endcapB': endcapB} @staticmethod def Prism(origin= None, width: float = 1, length: float = 1, height: float = 1, uSides: int = 1, vSides: int = 1, wSides: int = 1, - direction: list = [0, 0, 1], placement: str ="center", tolerance: float = 0.0001): + direction: list = [0, 0, 1], placement: str ="center", mantissa: int = 6, tolerance: float = 0.0001): """ Description ---------- @@ -1705,6 +1712,8 @@ def Prism(origin= None, width: float = 1, length: float = 1, height: float = 1, The vector representing the up direction of the prism. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the prism. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center". + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -1755,10 +1764,10 @@ def sliceCell(cell, width, length, height, uSides, vSides, wSides): elif placement.lower() == "lowerleft": xOffset = width*0.5 yOffset = length*0.5 - vb1 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset) - vb2 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()-length*0.5+yOffset,origin.Z()+zOffset) - vb3 = Vertex.ByCoordinates(origin.X()+width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset) - vb4 = Vertex.ByCoordinates(origin.X()-width*0.5+xOffset,origin.Y()+length*0.5+yOffset,origin.Z()+zOffset) + vb1 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset) + vb2 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset) + vb3 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset) + vb4 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset) baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True) baseFace = Face.ByWire(baseWire, tolerance=tolerance) diff --git a/src/topologicpy/CellComplex.py b/src/topologicpy/CellComplex.py index ea9b4cf..ada9a98 100644 --- a/src/topologicpy/CellComplex.py +++ b/src/topologicpy/CellComplex.py @@ -831,7 +831,7 @@ def Octahedron(origin= None, radius: float = 0.5, def Prism(origin= None, width: float = 1.0, length: float = 1.0, height: float = 1.0, uSides: int = 2, vSides: int = 2, wSides: int = 2, - direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): + direction: list = [0, 0, 1], placement: str = "center", mantissa: int = 6, tolerance: float = 0.0001): """ Creates a prismatic cellComplex with internal cells. @@ -855,6 +855,8 @@ def Prism(origin= None, The vector representing the up direction of the prism. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the prism. This can be "bottom", "center", or "lowerleft". It is case insensitive. The default is "center". + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -876,9 +878,9 @@ def bb(topology): y = [] z = [] for aVertex in vertices: - x.append(aVertex.X()) - y.append(aVertex.Y()) - z.append(aVertex.Z()) + x.append(Vertex.X(aVertex, mantissa=mantissa)) + y.append(Vertex.Y(aVertex, mantissa=mantissa)) + z.append(Vertex.Z(aVertex, mantissa=mantissa)) minX = min(x) minY = min(y) minZ = min(z) @@ -890,19 +892,19 @@ def bb(topology): def slice(topology, uSides, vSides, wSides): minX, minY, minZ, maxX, maxY, maxZ = bb(topology) centroid = Vertex.ByCoordinates(minX+(maxX-minX)*0.5, minY+(maxY-minY)*0.5, minZ+(maxZ-minZ)*0.5) - wOrigin = Vertex.ByCoordinates(Vertex.X(centroid), Vertex.Y(centroid), minZ) + wOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), Vertex.Y(centroid, mantissa=mantissa), minZ) wFace = Face.Rectangle(origin=wOrigin, width=(maxX-minX)*1.1, length=(maxY-minY)*1.1) wFaces = [] wOffset = (maxZ-minZ)/wSides for i in range(wSides-1): wFaces.append(Topology.Translate(wFace, 0,0,wOffset*(i+1))) - uOrigin = Vertex.ByCoordinates(minX, Vertex.Y(centroid), Vertex.Z(centroid)) + uOrigin = Vertex.ByCoordinates(minX, Vertex.Y(centroid, mantissa=mantissa), Vertex.Z(centroid, mantissa=mantissa)) uFace = Face.Rectangle(origin=uOrigin, width=(maxZ-minZ)*1.1, length=(maxY-minY)*1.1, direction=[1,0,0]) uFaces = [] uOffset = (maxX-minX)/uSides for i in range(uSides-1): uFaces.append(Topology.Translate(uFace, uOffset*(i+1),0,0)) - vOrigin = Vertex.ByCoordinates(Vertex.X(centroid), minY, Vertex.Z(centroid)) + vOrigin = Vertex.ByCoordinates(Vertex.X(centroid, mantissa=mantissa), minY, Vertex.Z(centroid, mantissa=mantissa)) vFace = Face.Rectangle(origin=vOrigin, width=(maxX-minX)*1.1, length=(maxZ-minZ)*1.1, direction=[0,1,0]) vFaces = [] vOffset = (maxY-minY)/vSides @@ -917,7 +919,7 @@ def slice(topology, uSides, vSides, wSides): if not Topology.IsInstance(origin, "Vertex"): origin = Vertex.ByCoordinates(0, 0, 0) - c = Cell.Prism(origin=origin, width=width, length=length, height=height, uSides=1, vSides=1, wSides=1, placement=placement, tolerance=tolerance) + c = Cell.Prism(origin=origin, width=width, length=length, height=height, uSides=1, vSides=1, wSides=1, placement=placement, mantissa=mantissa, tolerance=tolerance) prism = slice(c, uSides=uSides, vSides=vSides, wSides=wSides) if prism: prism = Topology.Orient(prism, origin=origin, dirA=[0, 0, 1], dirB=direction) diff --git a/src/topologicpy/Edge.py b/src/topologicpy/Edge.py index 38d0fa0..8308613 100644 --- a/src/topologicpy/Edge.py +++ b/src/topologicpy/Edge.py @@ -348,6 +348,7 @@ def Direction(edge, mantissa: int = 6) -> list: The direction of the input edge. """ + from topologicpy.Vertex import Vertex from topologicpy.Vector import Vector from topologicpy.Topology import Topology @@ -356,9 +357,9 @@ def Direction(edge, mantissa: int = 6) -> list: return None ev = edge.EndVertex() sv = edge.StartVertex() - x = ev.X() - sv.X() - y = ev.Y() - sv.Y() - z = ev.Z() - sv.Z() + x = Vertex.X(ev, mantissa=mantissa) - Vertex.X(sv, mantissa=mantissa) + y = Vertex.Y(ev, mantissa=mantissa) - Vertex.Y(sv, mantissa=mantissa) + z = Vertex.Z(ev, mantissa=mantissa) - Vertex.Z(sv, mantissa=mantissa) uvec = Vector.Normalize([x,y,z]) x = round(uvec[0], mantissa) y = round(uvec[1], mantissa) @@ -538,7 +539,7 @@ def Index(edge, edges: list, strict: bool = False, tolerance: float = 0.0001) -> return None @staticmethod - def Intersect2D(edgeA, edgeB, silent: bool = False): + def Intersect2D(edgeA, edgeB, mantissa: int = 6, silent: bool = False): """ Returns the intersection of the two input edges as a topologic_core.Vertex. This works only in the XY plane. Z coordinates are ignored. @@ -548,6 +549,8 @@ def Intersect2D(edgeA, edgeB, silent: bool = False): The first input edge. edgeB : topologic_core.Edge The second input edge. + mantissa : int , optional + The desired length of the mantissa. The default is 6. silent : bool , optional If set to False, error and warning messages are displayed. Otherwise they are not. The default is False. @@ -573,14 +576,14 @@ def Intersect2D(edgeA, edgeB, silent: bool = False): svb = Edge.StartVertex(edgeB) evb = Edge.EndVertex(edgeB) # Line AB represented as a1x + b1y = c1 - a1 = Vertex.Y(eva) - Vertex.Y(sva) - b1 = Vertex.X(sva) - Vertex.X(eva) - c1 = a1*(Vertex.X(sva)) + b1*(Vertex.Y(sva)) + a1 = Vertex.Y(eva, mantissa=mantissa) - Vertex.Y(sva, mantissa=mantissa) + b1 = Vertex.X(sva, mantissa=mantissa) - Vertex.X(eva, mantissa=mantissa) + c1 = a1*(Vertex.X(sva, mantissa=mantissa)) + b1*(Vertex.Y(sva, mantissa=mantissa)) # Line CD represented as a2x + b2y = c2 - a2 = Vertex.Y(evb) - Vertex.Y(svb) - b2 = Vertex.X(svb) - Vertex.X(evb) - c2 = a2*(Vertex.X(svb)) + b2*(Vertex.Y(svb)) + a2 = Vertex.Y(evb, mantissa=mantissa) - Vertex.Y(svb, mantissa=mantissa) + b2 = Vertex.X(svb, mantissa=mantissa) - Vertex.X(evb, mantissa=mantissa) + c2 = a2*(Vertex.X(svb, mantissa=mantissa)) + b2*(Vertex.Y(svb, mantissa=mantissa)) determinant = a1*b2 - a2*b1 @@ -639,7 +642,7 @@ def IsCollinear(edgeA, edgeB, mantissa: int = 6, tolerance: float = 0.0001) -> b # Get start and end points of the first edge start_a = Edge.StartVertex(edgeA) end_a = Edge.EndVertex(edgeA) - start_a_coords = np.array([Vertex.X(start_a), Vertex.Y(start_a), Vertex.Z(start_a)]) + start_a_coords = np.array([Vertex.X(start_a, mantissa=mantissa), Vertex.Y(start_a, mantissa=mantissa), Vertex.Z(start_a, mantissa=mantissa)]) end_a_coords = np.array( [Vertex.X(end_a, mantissa=mantissa), Vertex.Y(end_a, mantissa=mantissa), Vertex.Z(end_a, mantissa=mantissa)]) @@ -1158,7 +1161,7 @@ def TrimByEdge2D(edgeA, edgeB, reverse: bool = False, tolerance: float = 0.0001) return edgeA @staticmethod - def VertexByDistance(edge, distance: float = 0.0, origin= None, tolerance: float = 0.0001): + def VertexByDistance(edge, distance: float = 0.0, origin= None, mantissa: int = 6, tolerance: float = 0.0001): """ Creates a vertex along the input edge offset by the input distance from the input origin. @@ -1170,6 +1173,8 @@ def VertexByDistance(edge, distance: float = 0.0, origin= None, tolerance: float The offset distance. The default is 0. origin : topologic_core.Vertex , optional The origin of the offset distance. If set to None, the origin will be set to the start vertex of the input edge. 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. @@ -1193,12 +1198,12 @@ def VertexByDistance(edge, distance: float = 0.0, origin= None, tolerance: float return None sv = edge.StartVertex() ev = edge.EndVertex() - vx = ev.X() - sv.X() - vy = ev.Y() - sv.Y() - vz = ev.Z() - sv.Z() + vx = Vertex.X(ev, mantissa=mantissa) - Vertex.X(sv, mantissa=mantissa) + vy = Vertex.Y(ev, mantissa=mantissa) - Vertex.Y(sv, mantissa=mantissa) + vz = Vertex.Z(ev, mantissa=mantissa) - Vertex.Z(sv, mantissa=mantissa) vector = Vector.Normalize([vx, vy, vz]) vector = Vector.Multiply(vector, distance, tolerance) - return Vertex.ByCoordinates(origin.X()+vector[0], origin.Y()+vector[1], origin.Z()+vector[2]) + return Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+vector[0], Vertex.Y(origin, mantissa=mantissa)+vector[1], Vertex.Z(origin, mantissa=mantissa)+vector[2]) @staticmethod def VertexByParameter(edge, u: float = 0.0): diff --git a/src/topologicpy/EnergyModel.py b/src/topologicpy/EnergyModel.py index c93228c..a9321ba 100644 --- a/src/topologicpy/EnergyModel.py +++ b/src/topologicpy/EnergyModel.py @@ -128,7 +128,9 @@ def ByTopology(building, heatingTemp : float = 20.0, defaultSpaceType : str = "189.1-2009 - Office - WholeBuilding - Lg Office - CZ4-8", spaceNameKey : str = "TOPOLOGIC_name", - spaceTypeKey : str = "TOPOLOGIC_type"): + spaceTypeKey : str = "TOPOLOGIC_type", + mantissa : int = 6, + tolerance : float = 0.0001): """ Creates an EnergyModel from the input topology and parameters. @@ -165,6 +167,10 @@ def ByTopology(building, The dictionary key to use to find the space name value. The default is "Name". spaceTypeKey : str , optional The dictionary key to use to find the space type value. The default is "Type". + 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 ------- @@ -172,6 +178,7 @@ def ByTopology(building, The created OSM model. """ + from topologicpy.Vertex import Vertex from topologicpy.Face import Face from topologicpy.Cell import Cell from topologicpy.Topology import Topology @@ -229,7 +236,7 @@ def getFloorLevels(building): hf = bhf+thf else: return None - floorLevels = [Vertex.Z(Topology.Centroid(f)) for f in hf] + floorLevels = [Vertex.Z(Topology.Centroid(f), mantissa=mantissa) for f in hf] floorLevels = list(set(floorLevels)) floorLevels.sort() return floorLevels @@ -294,7 +301,7 @@ def getFloorLevels(building): building_cells = [building] for spaceNumber, buildingCell in enumerate(building_cells): osSpace = openstudio.model.Space(osModel) - osSpaceZ = buildingCell.CenterOfMass().Z() + osSpaceZ = Vertex.Z(buildingCell.CenterOfMass(), mantissa=mantissa) osBuildingStory = osBuildingStorys[0] for x in osBuildingStorys: osBuildingStoryZ = x.nominalZCoordinate().get() @@ -344,9 +351,9 @@ def getFloorLevels(building): for faceNumber, buildingFace in enumerate(cellFaces): osFacePoints = [] for vertex in Topology.SubTopologies(buildingFace.ExternalBoundary(), "Vertex"): - osFacePoints.append(openstudio.Point3d(vertex.X(), vertex.Y(), vertex.Z())) + osFacePoints.append(openstudio.Point3d(Vertex.X(vertex, mantissa=mantissa), Vertex.Y(vertex, mantissa=mantissa), Vertex.Z(vertex, mantissa=mantissa))) osSurface = openstudio.model.Surface(osFacePoints, osModel) - faceNormal = Face.Normal(buildingFace) + faceNormal = Face.Normal(buildingFace, mantissa=mantissa) osFaceNormal = openstudio.Vector3d(faceNormal[0], faceNormal[1], faceNormal[2]) osFaceNormal.normalize() if osFaceNormal.dot(osSurface.outwardNormal()) < 1e-6: @@ -376,9 +383,9 @@ def getFloorLevels(building): osSubSurfacePoints = [] apertureFace = Aperture.Topology(aperture) for vertex in Topology.SubTopologies(apertureFace.ExternalBoundary(), "Vertex"): - osSubSurfacePoints.append(openstudio.Point3d(vertex.X(), vertex.Y(), vertex.Z())) + osSubSurfacePoints.append(openstudio.Point3d(Vertex.X(vertex, mantissa=mantissa), Vertex.Y(vertex, mantissa=mantissa), Vertex.Z(vertex, mantissa=mantissa))) osSubSurface = openstudio.model.SubSurface(osSubSurfacePoints, osModel) - apertureFaceNormal = Face.Normal(apertureFace) + apertureFaceNormal = Face.Normal(apertureFace, mantissa=mantissa) osSubSurfaceNormal = openstudio.Vector3d(apertureFaceNormal[0], apertureFaceNormal[1], apertureFaceNormal[2]) osSubSurfaceNormal.normalize() if osSubSurfaceNormal.dot(osSubSurface.outwardNormal()) < 1e-6: @@ -414,9 +421,9 @@ def getFloorLevels(building): osSubSurfacePoints = [] apertureFace = Aperture.Topology(aperture) for vertex in Topology.SubTopologies(apertureFace.ExternalBoundary(), "Vertex"): - osSubSurfacePoints.append(openstudio.Point3d(vertex.X(), vertex.Y(), vertex.Z())) + osSubSurfacePoints.append(openstudio.Point3d(Vertex.X(vertex, mantissa=mantissa), Vertex.Y(vertex, mantissa=mantissa), Vertex.Z(vertex.Z, mantissa=mantissa))) osSubSurface = openstudio.model.SubSurface(osSubSurfacePoints, osModel) - apertureFaceNormal = Face.Normal(apertureFace) + apertureFaceNormal = Face.Normal(apertureFace, mantissa=mantissa) osSubSurfaceNormal = openstudio.Vector3d(apertureFaceNormal[0], apertureFaceNormal[1], apertureFaceNormal[2]) osSubSurfaceNormal.normalize() if osSubSurfaceNormal.dot(osSubSurface.outwardNormal()) < 1e-6: @@ -425,10 +432,10 @@ def getFloorLevels(building): osSubSurface.setSurface(osSurface) osThermalZone = openstudio.model.ThermalZone(osModel) - osThermalZone.setVolume(Cell.Volume(buildingCell)) + osThermalZone.setVolume(Cell.Volume(buildingCell, mantissa=mantissa)) osThermalZone.setName(osSpace.name().get() + "_THERMAL_ZONE") osThermalZone.setUseIdealAirLoads(True) - osThermalZone.setVolume(Cell.Volume(buildingCell)) + osThermalZone.setVolume(Cell.Volume(buildingCell, mantissa=mantissa)) osThermalZone.setThermostatSetpointDualSetpoint(osThermostat) osSpace.setThermalZone(osThermalZone) @@ -443,9 +450,9 @@ def getFloorLevels(building): for faceIndex, shadingFace in enumerate(Topology.SubTopologies(shadingSurfaces, "Face")): facePoints = [] for aVertex in Topology.SubTopologies(shadingFace.ExternalBoundary(), "Vertex"): - facePoints.append(openstudio.Point3d(aVertex.X(), aVertex.Y(), aVertex.Z())) + facePoints.append(openstudio.Point3d(Vertex.X(aVertex, mantissa=mantissa), Vertex.Y(aVertex, mantissa=mantissa), Vertex.Z(aVertex, mantissa=mantissa))) aShadingSurface = openstudio.model.ShadingSurface(facePoints, osModel) - faceNormal = Face.Normal(shadingFace) + faceNormal = Face.Normal(shadingFace, mantissa=mantissa) osFaceNormal = openstudio.Vector3d(faceNormal[0], faceNormal[1], faceNormal[2]) osFaceNormal.normalize() if osFaceNormal.dot(aShadingSurface.outwardNormal()) < 0: diff --git a/src/topologicpy/Face.py b/src/topologicpy/Face.py index e0f2186..d6fb9da 100644 --- a/src/topologicpy/Face.py +++ b/src/topologicpy/Face.py @@ -942,7 +942,7 @@ def ExternalBoundary(face): return eb @staticmethod - def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, tolerance: float = 0.0001) -> bool: + def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, mantissa: int = 6, tolerance: float = 0.0001) -> bool: """ Returns True if the input face is facing toward the input direction. @@ -954,6 +954,8 @@ def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, toler 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. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -963,22 +965,16 @@ def FacingToward(face, direction: list = [0,0,-1], asVertex: bool = False, toler True if the face is facing toward the direction. False otherwise. """ + from topologicpy.Vertex import Vertex from topologicpy.Vector import Vector - faceNormal = Face.Normal(face) + faceNormal = Face.Normal(face, mantissa=mantissa) 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") + cList = [Vertex.X(faceCenter, mantissa=mantissa), Vertex.Y(faceCenter, mantissa=mantissa), Vertex.Z(faceCenter, mantissa=mantissa)] if asVertex: - dV = [vList[0]-cList[0], vList[1]-cList[1], vList[2]-cList[2]] + dV = [direction[0]-cList[0], direction[1]-cList[1], direction[2]-cList[2]] else: - dV = vList + dV = direction uV = Vector.Normalize(dV) dot = sum([i*j for (i, j) in zip(uV, faceNormal)]) if dot < tolerance: @@ -1322,9 +1318,10 @@ def vertexPartofFace(vertex, face, tolerance): 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) + if Vertex.Distance(vertex, target) > tolerance: + 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 = [] @@ -1987,7 +1984,7 @@ def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: return Face.ByWire(wire, tolerance=tolerance) @staticmethod - def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001) -> list: + def Triangulate(face, mode: int = 0, meshSize: float = None, mantissa: int = 6, tolerance: float = 0.0001) -> list: """ Triangulates the input face and returns a list of faces. @@ -1995,8 +1992,6 @@ def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = ---------- 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 @@ -2012,6 +2007,10 @@ def Triangulate(face, mode: int = 0, meshSize: float = None, tolerance: float = 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. + 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 ------- @@ -2073,7 +2072,6 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): 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 @@ -2084,8 +2082,8 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): 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] + bounding_face_vertices_x = [Vertex.X(i, mantissa=mantissa) for i in bounding_face_vertices] + bounding_face_vertices_y = [Vertex.Y(i, mantissa=mantissa) 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 @@ -2095,7 +2093,7 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): 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) + gmsh.model.geo.addPoint(Vertex.X(external_vertices[i], mantissa=mantissa), Vertex.Y(external_vertices[i], mantissa=mantissa), Vertex.Z(external_vertices[i], mantissa=mantissa), 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) @@ -2114,7 +2112,7 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): 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) + gmsh.model.geo.addPoint(Vertex.X(internal_vertices[j]), Vertex.Y(internal_vertices[j], mantissa=mantissa), Vertex.Z(internal_vertices[j], mantissa=mantissa), 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) @@ -2160,7 +2158,7 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): if len(vertices) == 3: # Already a triangle return [face] origin = Topology.Centroid(face) - normal = Face.Normal(face) + normal = Face.Normal(face, mantissa=mantissa) flatFace = Topology.Flatten(face, origin=origin, direction=normal) if mode == 0: @@ -2179,7 +2177,7 @@ def generate_gmsh(face, mode="mesh", meshSize = None, tolerance = 0.0001): finalFaces = [] for f in shell_faces: f = Topology.Unflatten(f, origin=origin, direction=normal) - if Face.Angle(face, f) > 90: + if Face.Angle(face, f, mantissa=mantissa) > 90: wire = Face.ExternalBoundary(f) wire = Wire.Invert(wire) f = Face.ByWire(wire) diff --git a/src/topologicpy/Graph.py b/src/topologicpy/Graph.py index 2f3573a..e55862e 100644 --- a/src/topologicpy/Graph.py +++ b/src/topologicpy/Graph.py @@ -62,8 +62,6 @@ except: warnings.warn("Graph - Error: Could not import tqdm.") - - class _Tree: def __init__(self, node="", *children): self.node = node @@ -94,7 +92,6 @@ def __len__(self): return len(self.children) - class _DrawTree(object): def __init__(self, tree, parent=None, depth=0, number=1): self.x = -1.0 @@ -557,7 +554,8 @@ def BOTGraph(graph, slabType = "slab", doorType = "door", windowType = "window", - contentType = "content" + contentType = "content", + namespace = "http://github.com/wassimj/topologicpy/resources" ): """ Creates an RDF graph according to the BOT ontology. See https://w3c-lbd-cg.github.io/bot/. @@ -621,7 +619,7 @@ def BOTGraph(graph, try: from rdflib import Graph as RDFGraph from rdflib import URIRef, Literal, Namespace - from rdflib.namespace import RDF, RDFS + from rdflib.namespace import RDF, RDFS, XSD except: print("Graph.BOTGraph - Information: Installing required rdflib library.") try: @@ -646,9 +644,12 @@ def BOTGraph(graph, # Define namespaces rdf = Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#") bot = Namespace("https://w3id.org/bot#") + top = Namespace(namespace) # Define a custom prefix mapping bot_graph.namespace_manager.bind("bot", bot) + bot_graph.namespace_manager.bind("xsd", XSD) + bot_graph.namespace_manager.bind("top", top) # Add site site_uri = URIRef(siteLabel) @@ -705,7 +706,7 @@ def BOTGraph(graph, # Add vertices as RDF resources for node, attributes in json_data['vertices'].items(): - node_uri = URIRef(node) + node_uri = URIRef(top[node]) if spaceType.lower() in attributes[typeKey].lower(): bot_graph.add((node_uri, rdf.type, bot.Space)) # Find the storey it is on @@ -730,6 +731,8 @@ def BOTGraph(graph, if includeAttributes: for key, value in attributes.items(): + if key == "brepType": + print("Key = brepType, Value =", value, value.__class__) if key == geometryKey: if includeGeometry: bot_graph.add((node_uri, bot.hasSimpleGeometry, Literal(value))) @@ -737,7 +740,17 @@ def BOTGraph(graph, if includeLabel: bot_graph.add((node_uri, RDFS.label, Literal(value))) elif key != typeKey and key != geometryKey: - bot_graph.add((node_uri, bot[key], Literal(value))) + if isinstance(value, float): + datatype = XSD.float + elif isinstance(value, bool): + print("got boolean") + datatype = XSD.boolean + elif isinstance(value, int): + print("got integer") + datatype = XSD.integer + elif isinstance(value, str): + datatype = XSD.string + bot_graph.add((node_uri, top[key], Literal(value, datatype=datatype))) if includeLabel: for key, value in attributes.items(): if key == labelKey: @@ -747,8 +760,8 @@ def BOTGraph(graph, for edge, attributes in json_data['edges'].items(): source = attributes["source"] target = attributes["target"] - source_uri = URIRef(source) - target_uri = URIRef(target) + source_uri = URIRef(top[source]) + target_uri = URIRef(top[target]) if spaceType.lower() in json_data['vertices'][source][typeKey].lower() and spaceType.lower() in json_data['vertices'][target][typeKey].lower(): bot_graph.add((source_uri, bot.adjacentTo, target_uri)) if bidirectional: @@ -4347,6 +4360,7 @@ def ExportToAdjacencyMatrixCSV(adjacencyMatrix, path): def ExportToBOT(graph, path, format="turtle", + namespace = "http://github.com/wassimj/topologicpy/resources", overwrite = False, bidirectional=False, includeAttributes=False, @@ -4386,6 +4400,8 @@ def ExportToBOT(graph, trig : Trig , Turtle-like format for RDF triples + context (RDF quads) and thus multiple graphs trix : Trix , RDF/XML-like format for RDF quads nquads : N-Quads , N-Triples-like format for RDF quads + namespace : str , optional + The desired namespace for creating IRIs for entities. The default is "http://github.com/wassimj/topologicpy/resources". path : str The desired path to where the RDF/BOT file will be saved. overwrite : bool , optional @@ -4465,7 +4481,8 @@ def ExportToBOT(graph, slabType = slabType, doorType = doorType, windowType = windowType, - contentType = contentType + contentType = contentType, + namespace = namespace ) if "turtle" in format.lower() or "ttl" in format.lower() or "turtle2" in format.lower(): ext = ".ttl" @@ -4759,7 +4776,7 @@ def ExportToCSV(graph, path, graphLabel, graphFeatures="", node_features = node_features + ","+ str(round(float(Dictionary.ValueAtKey(nd, node_feature_key)),mantissa)) else: node_features = str(round(float(Dictionary.ValueAtKey(nd, node_feature_key)),mantissa)) - single_node_data = [graph_id, i, vLabel, train_mask, validate_mask, test_mask, node_features, round(float(Vertex.X(v)),mantissa), round(float(Vertex.Y(v)),mantissa), round(float(Vertex.Z(v)),mantissa)] + single_node_data = [graph_id, i, vLabel, train_mask, validate_mask, test_mask, node_features, float(Vertex.X(v, mantissa=mantissa)), float(Vertex.Y(v,mantissa)), float(Vertex.Z(v,mantissa))] node_data.append(single_node_data) # Write Node Data to CSV file @@ -4871,11 +4888,11 @@ def ExportToCSV(graph, path, graphLabel, graphFeatures="", return True @staticmethod - def ExportToGEXF(graph, path, graphWidth=20, graphLength=20, graphHeight=20, - defaultVertexColor="black", defaultVertexSize=3, - vertexLabelKey=None, vertexColorKey=None, vertexSizeKey=None, - defaultEdgeColor="black", defaultEdgeWeight=1, defaultEdgeType="undirected", - edgeLabelKey=None, edgeColorKey=None, edgeWeightKey=None, overwrite=False): + def ExportToGEXF(graph, path: str = None, graphWidth: float = 20, graphLength: float = 20, graphHeight: float = 20, + defaultVertexColor: str = "black", defaultVertexSize: float = 3, + vertexLabelKey: str = None, vertexColorKey: str = None, vertexSizeKey: str = None, + defaultEdgeColor: str = "black", defaultEdgeWeight: float = 1, defaultEdgeType: str = "undirected", + edgeLabelKey: str = None, edgeColorKey: str = None, edgeWeightKey: str = None, overwrite: bool = False, mantissa: int = 6): """ Exports the input graph to a Graph Exchange XML (GEXF) file format. See https://gexf.net/ @@ -4921,6 +4938,8 @@ def ExportToGEXF(graph, path, graphWidth=20, graphLength=20, graphHeight=20, the edge weight is set to the value defined by defaultEdgeWeight parameter. The default is None. overwrite : bool , optional If set to True, any existing file is overwritten. Otherwise, it is not. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. Returns ------- @@ -5049,9 +5068,9 @@ def valueType(value): 'size': 'double'} nodes = {} # Resize the graph - xList = [Vertex.X(v) for v in g_vertices] - yList = [Vertex.Y(v) for v in g_vertices] - zList = [Vertex.Z(v) for v in g_vertices] + xList = [Vertex.X(v, mantissa=mantissa) for v in g_vertices] + yList = [Vertex.Y(v, mantissa=mantissa) for v in g_vertices] + zList = [Vertex.Z(v, mantissa=mantissa) for v in g_vertices] xMin = min(xList) xMax = max(xList) yMin = min(yList) @@ -5073,9 +5092,9 @@ def valueType(value): d = Topology.Dictionary(v) keys = Dictionary.Keys(d) values = Dictionary.Values(d) - x = (Vertex.X(v) - x_avg)*x_sf + x_avg - y = (Vertex.Y(v) - y_avg)*y_sf + y_avg - z = (Vertex.Z(v) - z_avg)*z_sf + z_avg + x = (Vertex.X(v, mantissa=mantissa) - x_avg)*x_sf + x_avg + y = (Vertex.Y(v, mantissa=mantissa) - y_avg)*y_sf + y_avg + z = (Vertex.Z(v, mantissa=mantissa) - z_avg)*z_sf + z_avg node_dict['x'] = x node_dict['y'] = y node_dict['z'] = z @@ -5944,17 +5963,17 @@ def IsolatedVertices(graph): @staticmethod def JSONData(graph, - verticesKey="vertices", - edgesKey="edges", - vertexLabelKey="", - edgeLabelKey="", - sourceKey="source", - targetKey="target", - xKey="x", - yKey="y", - zKey="z", - geometryKey="brep", - mantissa=6): + verticesKey: str = "vertices", + edgesKey: str = "edges", + vertexLabelKey: str = "", + edgeLabelKey: str = "", + sourceKey: str = "source", + targetKey: str = "target", + xKey: str = "x", + yKey: str = "y", + zKey: str = "z", + geometryKey: str = "brep", + mantissa: int = 6): """ Converts the input graph into JSON data. @@ -5975,7 +5994,7 @@ def JSONData(graph, sourceKey : str , optional The dictionary key used to store the source vertex. The default is "source". targetKey : str , optional - The dictionary key used to store the target vertex. The default is "source". + The dictionary key used to store the target vertex. The default is "target". xKey : str , optional The desired key name to use for x-coordinates. The default is "x". yKey : str , optional @@ -6709,7 +6728,7 @@ def NearestVertex(graph, vertex): return nearestVertex @staticmethod - def NetworkXGraph(graph, tolerance=0.0001): + def NetworkXGraph(graph, mantissa: int = 6, tolerance: float = 0.0001): """ converts the input graph into a NetworkX Graph. See http://networkx.org @@ -6717,6 +6736,10 @@ def NetworkXGraph(graph, tolerance=0.0001): ---------- graph : topologic_core.Graph The input graph. + 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 ------- @@ -6763,7 +6786,7 @@ def NetworkXGraph(graph, tolerance=0.0001): values = [] keys += ["x","y","z"] import random - values += [Vertex.X(v), Vertex.Y(v), Vertex.Z(v)] + values += [Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)] d = Dictionary.ByKeysValues(keys,values) pythonD = Dictionary.PythonDictionary(d) nodes.append((i, pythonD)) @@ -6776,7 +6799,7 @@ def NetworkXGraph(graph, tolerance=0.0001): for adjVertex in adjVertices: adjIndex = Vertex.Index(vertex=adjVertex, vertices=vertices, strict=True, tolerance=tolerance) if not adjIndex == None: - nxGraph.add_edge(i,adjIndex, length=(Vertex.Distance(v, adjVertex))) + nxGraph.add_edge(i,adjIndex, length=(Vertex.Distance(v, adjVertex, mantissa=mantissa))) pos=nx.spring_layout(nxGraph, k=0.2) nx.set_node_attributes(nxGraph, pos, "pos") diff --git a/src/topologicpy/Grid.py b/src/topologicpy/Grid.py index 573b403..4e0001e 100644 --- a/src/topologicpy/Grid.py +++ b/src/topologicpy/Grid.py @@ -18,7 +18,7 @@ class Grid(): @staticmethod - def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0, 0.25,0.5], vRange=[-0.5,-0.25,0, 0.25,0.5], clip=False, tolerance=0.0001): + def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0, 0.25,0.5], vRange=[-0.5,-0.25,0, 0.25,0.5], clip=False, mantissa: int = 6, tolerance=0.0001): """ Creates a grid (cluster of edges). @@ -36,6 +36,8 @@ def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0 A list of distances for the *v* grid lines from the vOrigin. The default is [-0.5,-0.25,0, 0.25,0.5]. clip : bool , optional If True the grid will be clipped by the shape of the input face. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -52,6 +54,7 @@ def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0 from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary from topologicpy.Vector import Vector + if len(uRange) < 1 or len(vRange) < 1: return None if not uOrigin: @@ -76,16 +79,16 @@ def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0 v3 = Vertex.ByCoordinates(0, 0, 0) v4 = Vertex.ByCoordinates(0,max(vRange),0) - uVector = [v2.X()-v1.X(), v2.Y()-v1.Y(),v2.Z()-v1.Z()] - vVector = [v4.X()-v3.X(), v4.Y()-v3.Y(),v4.Z()-v3.Z()] + uVector = [Vertex.X(v2, mantissa=mantissa)-Vertex.X(v1, mantissa=mantissa), Vertex.Y(v2, mantissa=mantissa)-Vertex.Y(v1, mantissa=mantissa),Vertex.Z(v2, mantissa=mantissa)-Vertex.Z(v1, mantissa=mantissa)] + vVector = [Vertex.X(v4, mantissa=mantissa)-Vertex.X(v3, mantissa=mantissa), Vertex.Y(v4, mantissa=mantissa)-Vertex.Y(v3, mantissa=mantissa),Vertex.Z(v4, mantissa=mantissa)-Vertex.Z(v3, mantissa=mantissa)] gridEdges = [] if len(uRange) > 0: uRange.sort() uuVector = Vector.Normalize(uVector) for u in uRange: tempVec = Vector.Multiply(uuVector, u, tolerance) - v1 = Vertex.ByCoordinates(uOrigin.X()+tempVec[0], uOrigin.Y()+tempVec[1], uOrigin.Z()+tempVec[2]) - v2 = Vertex.ByCoordinates(v1.X()+vVector[0], v1.Y()+vVector[1], v1.Z()+vVector[2]) + v1 = Vertex.ByCoordinates(Vertex.X(uOrigin, mantissa=mantissa)+tempVec[0], Vertex.Y(uOrigin, mantissa=mantissa)+tempVec[1], Vertex.Z(uOrigin, mantissa=mantissa)+tempVec[2]) + v2 = Vertex.ByCoordinates(Vertex.X(v1, mantissa=mantissa)+vVector[0], Vertex.Y(v1, mantissa=mantissa)+vVector[1], Vertex.Z(v1, mantissa=mantissa)+vVector[2]) e = Edge.ByVertices([v1, v2], tolerance=tolerance) if clip and Topology.IsInstance(face, "Face"): e = e.Intersect(face, False) @@ -105,8 +108,8 @@ def EdgesByDistances(face=None, uOrigin=None, vOrigin=None, uRange=[-0.5,-0.25,0 uvVector = Vector.Normalize(vVector) for v in vRange: tempVec = Vector.Multiply(uvVector, v, tolerance) - v1 = Vertex.ByCoordinates(vOrigin.X()+tempVec[0], vOrigin.Y()+tempVec[1], vOrigin.Z()+tempVec[2]) - v2 = Vertex.ByCoordinates(v1.X()+uVector[0], v1.Y()+uVector[1], v1.Z()+uVector[2]) + v1 = Vertex.ByCoordinates(Vertex.X(vOrigin, mantissa=mantissa)+tempVec[0], Vertex.Y(vOrigin, mantissa=mantissa)+tempVec[1], Vertex.Z(vOrigin, mantissa=mantissa)+tempVec[2]) + v2 = Vertex.ByCoordinates(Vertex.X(v1, mantissa=mantissa)+uVector[0], Vertex.Y(v1, mantissa=mantissa)+uVector[1], Vertex.Z(v1, mantissa=mantissa)+uVector[2]) e = Edge.ByVertices([v1, v2], tolerance=tolerance) if clip and Topology.IsInstance(face, "Face"): e = e.Intersect(face, False) diff --git a/src/topologicpy/Honeybee.py b/src/topologicpy/Honeybee.py index ef143c2..4378f53 100644 --- a/src/topologicpy/Honeybee.py +++ b/src/topologicpy/Honeybee.py @@ -195,17 +195,18 @@ def ExportToHBJSON(model, path, overwrite=False): @staticmethod def ModelByTopology(tpBuilding, tpShadingFacesCluster = None, - buildingName = "Generic_Building", - defaultProgramIdentifier = "Generic Office Program", - defaultConstructionSetIdentifier = "Default Generic Construction Set", - coolingSetpoint = 25.0, - heatingSetpoint = 20.0, - humidifyingSetpoint = 30.0, - dehumidifyingSetpoint = 55.0, - roomNameKey = "TOPOLOGIC_name", - roomTypeKey = "TOPOLOGIC_type", - apertureTypeKey = "TOPOLOGIC_type", - addSensorGrid = False): + buildingName: str = "Generic_Building", + defaultProgramIdentifier: str = "Generic Office Program", + defaultConstructionSetIdentifier: str = "Default Generic Construction Set", + coolingSetpoint: float = 25.0, + heatingSetpoint: float = 20.0, + humidifyingSetpoint: float = 30.0, + dehumidifyingSetpoint: float = 55.0, + roomNameKey: str = "TOPOLOGIC_name", + roomTypeKey: str = "TOPOLOGIC_type", + apertureTypeKey: str = "TOPOLOGIC_type", + addSensorGrid: bool = False, + mantissa: int = 6): """ Creates an HB Model from the input Topology. @@ -330,11 +331,11 @@ def createUniqueName(name, nameList, number): if tpCellFaces: hbRoomFaces = [] for tpFaceNumber, tpCellFace in enumerate(tpCellFaces): - tpCellFaceNormal = Face.NormalAtParameters(tpCellFace, 0.5, 0.5) + tpCellFaceNormal = Face.Normal(tpCellFace, mantissa=mantissa) hbRoomFacePoints = [] tpFaceVertices = Wire.Vertices(Face.ExternalBoundary(tpCellFace)) for tpVertex in tpFaceVertices: - hbRoomFacePoints.append(Point3D(tpVertex.X(), tpVertex.Y(), tpVertex.Z())) + hbRoomFacePoints.append(Point3D(Vertex.X(tpVertex, mantissa=mantissa), Vertex.Y(tpVertex, mantissa=mantissa), Vertex.Z(tpVertex, mantissa=mantissa))) hbRoomFace = HBFace(tpCellName+'_Face_'+str(tpFaceNumber+1), Face3D(hbRoomFacePoints)) tpFaceApertures = [] _ = tpCellFace.Apertures(tpFaceApertures) @@ -349,7 +350,7 @@ def createUniqueName(name, nameList, number): tpFaceApertureVertices = [] tpFaceApertureVertices = Wire.Vertices(Face.ExternalBoundary(apertureTopology)) for tpFaceApertureVertex in tpFaceApertureVertices: - hbFaceAperturePoints.append(Point3D(tpFaceApertureVertex.X(), tpFaceApertureVertex.Y(), tpFaceApertureVertex.Z())) + hbFaceAperturePoints.append(Point3D(Vertex.X(tpFaceApertureVertex, mantissa=mantissa), Vertex.Y(tpFaceApertureVertex, mantissa=mantissa), Vertex.Z(tpFaceApertureVertex, mantissa=mantissa))) if(tpFaceApertureType): if ("door" in tpFaceApertureType.lower()): hbFaceAperture = HBDoor(tpCellName+'_Face_'+str(tpFaceNumber+1)+'_Door_'+str(tpFaceApertureNumber), Face3D(hbFaceAperturePoints)) @@ -398,7 +399,7 @@ def createUniqueName(name, nameList, number): faceVertices = Wire.Vertices(Face.ExternalBoundary(tpShadingFace)) facePoints = [] for aVertex in faceVertices: - facePoints.append(Point3D(aVertex.X(), aVertex.Y(), aVertex.Z())) + facePoints.append(Point3D(Vertex.X(aVertex, mantissa=mantissa), Vertex.Y(aVertex, mantissa=mantissa), Vertex.Z(aVertex, mantissa=mantissa))) hbShadingFace = Face3D(facePoints, None, []) hbShade = HBShade("SHADINGSURFACE_" + str(faceIndex+1), hbShadingFace) hbShades.append(hbShade) diff --git a/src/topologicpy/Neo4j.py b/src/topologicpy/Neo4j.py index 4a3e062..8a60cb5 100644 --- a/src/topologicpy/Neo4j.py +++ b/src/topologicpy/Neo4j.py @@ -276,7 +276,7 @@ def randomVertex(vertices, minDistance): return Graph.ByVerticesEdges(vertices,edges) @staticmethod - def AddGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirectional=True, deleteAll=True, tolerance=0.0001): + def AddGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirectional=True, deleteAll=True, mantissa: int = 6, tolerance: float = 0.0001): """ Adds the input topologic graph to the input neo4j graph @@ -288,6 +288,8 @@ def AddGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirection The input topologic graph. categoryKey : str The category key in the dictionary under which to look for the category value. + mantissa : int, optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -301,6 +303,7 @@ def AddGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirection from topologicpy.Topology import Topology from topologicpy.Graph import Graph from topologicpy.Dictionary import Dictionary + gmt = time.gmtime() timestamp = str(gmt.tm_zone)+"_"+str(gmt.tm_year)+"_"+str(gmt.tm_mon)+"_"+str(gmt.tm_wday)+"_"+str(gmt.tm_hour)+"_"+str(gmt.tm_min)+"_"+str(gmt.tm_sec) vertices = Graph.Vertices(graph) @@ -316,11 +319,11 @@ def AddGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirection keys.append("z") keys.append("timestamp") keys.append("location") - values.append(vertices[i].X()) - values.append(vertices[i].Y()) - values.append(vertices[i].Z()) + values.append(Vertex.X(vertices[i], mantissa=mantissa)) + values.append(Vertex.Y(vertices[i], mantissa=mantissa)) + values.append(Vertex.Z(vertices[i], mantissa=mantissa)) values.append(timestamp) - values.append(sp.CartesianPoint([vertices[i].X(),vertices[i].Y(),vertices[i].Z()])) + values.append(sp.CartesianPoint([Vertex.X(vertices[i], mantissa=mantissa), Vertex.Y(vertices[i], mantissa=mantissa), Vertex.Z(vertices[i], mantissa=mantissa)])) zip_iterator = zip(keys, values) pydict = dict(zip_iterator) if labelKey == 'None': @@ -427,7 +430,14 @@ def RelationshipTypes(neo4jGraph): return list(neo4jGraph.schema.relationship_types) @staticmethod - def SetGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirectional=True, deleteAll=True, tolerance=0.0001): + def SetGraph(neo4jGraph, + graph, + labelKey: str = None, + relationshipKey: str = None, + bidirectional: bool = True, + deleteAll: bool = True, + mantissa: int = 6, + tolerance: float = 0.0001): """ Sets the input topologic graph to the input neo4jGraph. @@ -445,6 +455,8 @@ def SetGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirection If set to True, the edges in the neo4j graph are set to be bi-drectional. deleteAll : bool , optional If set to True, all previous entities are deleted before adding the new entities. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -484,11 +496,11 @@ def SetGraph(neo4jGraph, graph, labelKey=None, relationshipKey=None, bidirection keys.append("z") keys.append("timestamp") keys.append("location") - values.append(vertices[i].X()) - values.append(vertices[i].Y()) - values.append(vertices[i].Z()) + values.append(Vertex.X(vertices[i], mantissa=mantissa)) + values.append(Vertex.Y(vertices[i], mantissa=mantissa)) + values.append(Vertex.Z(vertices[i], mantissa=mantissa)) values.append(timestamp) - values.append(sp.CartesianPoint([vertices[i].X(),vertices[i].Y(),vertices[i].Z()])) + values.append(sp.CartesianPoint([Vertex.X(vertices[i], mantissa=mantissa), Vertex.Y(vertices[i], mantissa=mantissa), Vertex.Z(vertices[i], mantissa=mantissa)])) zip_iterator = zip(keys, values) pydict = dict(zip_iterator) if (labelKey == 'None') or (not (labelKey)): diff --git a/src/topologicpy/Plotly.py b/src/topologicpy/Plotly.py index 5b2b5df..9f77ca7 100644 --- a/src/topologicpy/Plotly.py +++ b/src/topologicpy/Plotly.py @@ -272,7 +272,23 @@ def DataByDGL(data, labels): return df @staticmethod - def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, vertexGroupKey=None, vertexGroups=[], showVertices=True, showVertexLegend=False, edgeColor="black", edgeWidth=1, edgeLabelKey=None, edgeGroupKey=None, edgeGroups=[], showEdges=True, showEdgeLegend=False, colorScale="viridis"): + def DataByGraph(graph, + vertexColor: str = "black", + vertexSize: float = 6, + vertexLabelKey: str = None, + vertexGroupKey: str = None, + vertexGroups: list = [], + showVertices: bool = True, + showVertexLegend: bool = False, + edgeColor: str = "black", + edgeWidth: float = 1, + edgeLabelKey: str = None, + edgeGroupKey: str = None, + edgeGroups: list = [], + showEdges: bool = True, + showEdgeLegend: bool = False, + colorScale: str = "viridis", + mantissa: int = 6): """ Creates plotly vertex and edge data from the input graph. @@ -344,9 +360,9 @@ def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, v vertices = Graph.Vertices(graph) if vertexLabelKey or vertexGroupKey: for v in vertices: - Xn=[round(Vertex.X(v), 4) for v in vertices] # x-coordinates of nodes - Yn=[round(Vertex.Y(v), 4) for v in vertices] # y-coordinates of nodes - Zn=[round(Vertex.Z(v), 4) for v in vertices] # x-coordinates of nodes + Xn=[Vertex.X(v, mantissa=mantissa) for v in vertices] # x-coordinates of nodes + Yn=[Vertex.Y(v, mantissa=mantissa) for v in vertices] # y-coordinates of nodes + Zn=[Vertex.Z(v, mantissa=mantissa) for v in vertices] # x-coordinates of nodes v_label = "" v_group = "" d = Topology.Dictionary(v) @@ -371,9 +387,9 @@ def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, v v_labels.append(v_label) else: for v in vertices: - Xn=[round(Vertex.X(v), 4) for v in vertices] # x-coordinates of nodes - Yn=[round(Vertex.Y(v), 4) for v in vertices] # y-coordinates of nodes - Zn=[round(Vertex.Z(v), 4) for v in vertices] # x-coordinates of nodes + Xn=[Vertex.X(v, mantissa=mantissa) for v in vertices] # x-coordinates of nodes + Yn=[Vertex.Y(v, mantissa=mantissa) for v in vertices] # y-coordinates of nodes + Zn=[Vertex.Z(v, mantissa=mantissa) for v in vertices] # x-coordinates of nodes if len(list(set(v_groupList))) < 2: v_groupList = vertexColor if len(v_labels) < 1: @@ -409,9 +425,9 @@ def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, v for e in edges: sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) - Xe+=[round(Vertex.X(sv), 4), round(Vertex.X(ev), 4), None] # x-coordinates of edge ends - Ye+=[round(Vertex.Y(sv), 4), round(Vertex.Y(ev), 4), None] # y-coordinates of edge ends - Ze+=[round(Vertex.Z(sv), 4), round(Vertex.Z(ev), 4), None] # z-coordinates of edge ends + Xe+=[Vertex.X(sv, mantissa=mantissa), Vertex.X(ev, mantissa=mantissa), None] # x-coordinates of edge ends + Ye+=[Vertex.Y(sv, mantissa=mantissa), Vertex.Y(ev, mantissa=mantissa), None] # y-coordinates of edge ends + Ze+=[Vertex.Z(sv, mantissa=mantissa), Vertex.Z(ev, mantissa=mantissa), None] # z-coordinates of edge ends e_label = "" e_group = "" d = Topology.Dictionary(e) @@ -435,9 +451,9 @@ def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, v for e in edges: sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) - Xe+=[round(Vertex.X(sv), 4), round(Vertex.X(ev), 4), None] # x-coordinates of edge ends - Ye+=[round(Vertex.Y(sv), 4), round(Vertex.Y(ev), 4), None] # y-coordinates of edge ends - Ze+=[round(Vertex.Z(sv), 4), round(Vertex.Z(ev), 4), None] # z-coordinates of edge ends + Xe+=[Vertex.X(sv, mantissa=mantissa), Vertex.X(ev, mantissa=mantissa), None] # x-coordinates of edge ends + Ye+=[Vertex.Y(sv, mantissa=mantissa), Vertex.Y(ev, mantissa=mantissa), None] # y-coordinates of edge ends + Ze+=[Vertex.Z(sv, mantissa=mantissa), Vertex.Z(ev, mantissa=mantissa), None] # z-coordinates of edge ends if len(list(set(e_groupList))) < 2: e_groupList = edgeColor @@ -460,14 +476,6 @@ def DataByGraph(graph, vertexColor="black", vertexSize=6, vertexLabelKey=None, v return data - - - - - - - - @staticmethod def DataByTopology(topology, showVertices=True, vertexSize=1.1, vertexColor="black", @@ -603,6 +611,9 @@ def DataByTopology(topology, The vertex, edge, and face data list. """ + from topologicpy.Vertex import Vertex + from topologicpy.Face import Face + from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology from topologicpy.Dictionary import Dictionary from topologicpy.Color import Color @@ -634,9 +645,9 @@ def vertexData(vertices, dictionaries=[], color="black", size=1.1, labelKey=None minGroup = 0 maxGroup = 1 for m, v in enumerate(vertices): - x.append(round(v[0], mantissa)) - y.append(round(v[1], mantissa)) - z.append(round(v[2], mantissa)) + x.append(v[0]) + y.append(v[1]) + z.append(v[2]) label = "" group = "" if len(dictionaries) > 0: @@ -666,9 +677,9 @@ def vertexData(vertices, dictionaries=[], color="black", size=1.1, labelKey=None labels.append(label) else: for v in vertices: - x.append(round(v[0], mantissa)) - y.append(round(v[1], mantissa)) - z.append(round(v[2], mantissa)) + x.append(v[0]) + y.append(v[1]) + z.append(v[2]) if len(list(set(groupList))) < 2: groupList = color @@ -714,9 +725,9 @@ def edgeData(vertices, edges, dictionaries=None, color="black", width=1, labelKe for m, e in enumerate(edges): sv = vertices[e[0]] ev = vertices[e[1]] - x+=[round(sv[0], mantissa),round(ev[0], mantissa), None] # x-coordinates of edge ends - y+=[round(sv[1], mantissa),round(ev[1], mantissa), None] # y-coordinates of edge ends - z+=[round(sv[2], mantissa),round(ev[2], mantissa), None] # z-coordinates of edge ends + x+=[sv[0], ev[0], None] # x-coordinates of edge ends + y+=[sv[1], ev[1], None] # y-coordinates of edge ends + z+=[sv[2], ev[2], None] # z-coordinates of edge ends label = "" group = "" if len(dictionaries) > 0: @@ -748,9 +759,9 @@ def edgeData(vertices, edges, dictionaries=None, color="black", width=1, labelKe for e in edges: sv = vertices[e[0]] ev = vertices[e[1]] - x+=[round(sv[0],mantissa),round(ev[0],mantissa), None] # x-coordinates of edge ends - y+=[round(sv[1],mantissa),round(ev[1],mantissa), None] # y-coordinates of edge ends - z+=[round(sv[2],mantissa),round(ev[2],mantissa), None] # z-coordinates of edge ends + x+=[sv[0], ev[0], None] # x-coordinates of edge ends + y+=[sv[1], ev[1], None] # y-coordinates of edge ends + z+=[sv[2], ev[2], None] # z-coordinates of edge ends if len(list(set(groupList))) < 2: groupList = color @@ -779,9 +790,9 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", y = [] z = [] for v in vertices: - x.append(round(v[0], mantissa)) - y.append(round(v[1], mantissa)) - z.append(round(v[2], mantissa)) + x.append(v[0]) + y.append(v[1]) + z.append(v[2]) i = [] j = [] k = [] @@ -870,12 +881,6 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", lighting = {"facenormalsepsilon": 0}, ) return fData - - from topologicpy.Face import Face - from topologicpy.Cluster import Cluster - from topologicpy.Topology import Topology - from topologicpy.Dictionary import Dictionary - from time import time if not Topology.IsInstance(topology, "Topology"): return None @@ -901,7 +906,7 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", if intensityKey: for i, tp_v in enumerate(tp_vertices): - vertices.append([tp_v.X(), tp_v.Y(), tp_v.Z()]) + vertices.append([Vertex.X(tp_v, mantissa=mantissa), Vertex.Y(tp_v, mantissa=mantissa), Vertex.Z(tp_v, mantissa=mantissa)]) d = Topology.Dictionary(tp_v) if d: v = Dictionary.ValueAtKey(d, key=intensityKey) @@ -938,7 +943,7 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", if vertexLabelKey or vertexGroupKey: d = Topology.Dictionary(tp_v) v_dictionaries.append(d) - vertices.append([tp_v.X(), tp_v.Y(), tp_v.Z()]) + vertices.append([Vertex.X(tp_v, mantissa=mantissa), Vertex.Y(tp_v, mantissa=mantissa), Vertex.Z(tp_v, mantissa=mantissa)]) data.append(vertexData(vertices, dictionaries=v_dictionaries, color=vertexColor, size=vertexSize, labelKey=vertexLabelKey, groupKey=vertexGroupKey, minGroup=vertexMinGroup, maxGroup=vertexMaxGroup, groups=vertexGroups, legendLabel=vertexLegendLabel, legendGroup=vertexLegendGroup, legendRank=vertexLegendRank, showLegend=showVertexLegend, colorScale=colorScale)) if showEdges and Topology.Type(topology) > Topology.TypeID("Vertex"): @@ -963,24 +968,6 @@ def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", else: tp_faces = Topology.Faces(topology) if not(tp_faces == None or tp_faces == []): - # rebuild faces to remove any degenerate faces - #new_faces = [] - #for i, f in enumerate(tp_faces): - #eb = Face.ExternalBoundary(f) - #eb = Topology.RemoveCollinearEdges(eb) - #if not eb == None: - #ibList = Face.InternalBoundaries(f) - #ibList = [Wire.RemoveCollinearEdges(ib) for ib in ibList] - #ibList = [ib for ib in ibList if not ib == None] - #new_f = Face.ByWires(eb, ibList, silent=False) - #if Topology.IsInstance(new_f, "Face"): - #if faceLabelKey or faceGroupKey: - #d = Topology.Dictionary(tp_faces[i]) - #keys = Dictionary.Keys(d) - #if len(keys) > 0: - #new_f = Topology.SetDictionary(new_f, d) - #new_faces.append(new_f) - f_dictionaries = [] all_triangles = [] for tp_face in tp_faces: diff --git a/src/topologicpy/Shell.py b/src/topologicpy/Shell.py index 3c365f4..ff97a1e 100644 --- a/src/topologicpy/Shell.py +++ b/src/topologicpy/Shell.py @@ -50,7 +50,16 @@ class Shell(): @staticmethod - def ByDisjointFaces(externalBoundary, faces, maximumGap=0.5, mergeJunctions=False, threshold=0.5, uSides=1, vSides=1, transferDictionaries=False, tolerance=0.0001): + def ByDisjointFaces(externalBoundary, + faces, + maximumGap: float = 0.5, + mergeJunctions: bool = False, + threshold: float = 0.5, + uSides: int = 1, + vSides: int = 1, + transferDictionaries: bool = False, + mantissa: int = 6, + tolerance: float = 0.0001): """ Creates a shell from an input list of disjointed faces. THIS IS STILL EXPERIMENTAL @@ -72,6 +81,8 @@ def ByDisjointFaces(externalBoundary, faces, maximumGap=0.5, mergeJunctions=Fals The desired number of sides along the Y axis for the grid that subdivides the input faces to aid in processing. The default is 1. transferDictionaries : bool, optional. If set to True, the dictionaries in the input list of faces are transfered to the faces of the resulting shell. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -177,7 +188,7 @@ def extendEdges(edges, hostTopology, maximumGap=0.5): for v in vertices: for w in vertices: if not Topology.IsSame(v, w) and not w in used: - if Vertex.Distance(v, w) < threshold: + if Vertex.Distance(v, w, mantissa=mantissa) < threshold: centers.append(v) used.append(w) edges = Shell.Edges(shell) @@ -186,9 +197,9 @@ def extendEdges(edges, hostTopology, maximumGap=0.5): sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) for v in centers: - if Vertex.Distance(sv, v) < threshold: + if Vertex.Distance(sv, v, mantissa=mantissa) < threshold: sv = v - if Vertex.Distance(ev, v) < threshold: + if Vertex.Distance(ev, v, mantissa=mantissa) < threshold: ev = v new_edges.append(Edge.ByVertices([sv,ev], tolerance=tolerance)) cluster = Cluster.ByTopologies(new_edges) @@ -196,10 +207,10 @@ def extendEdges(edges, hostTopology, maximumGap=0.5): vertices = Topology.Vertices(cluster) edges = Topology.Edges(shell) - xList = list(set([Vertex.X(v) for v in vertices])) + xList = list(set([Vertex.X(v, mantissa=mantissa) for v in vertices])) xList.sort() xList = Helper.MergeByThreshold(xList, 0.5) - yList = list(set([Vertex.Y(v) for v in vertices])) + yList = list(set([Vertex.Y(v, mantissa=mantissa) for v in vertices])) yList.sort() yList = Helper.MergeByThreshold(yList, 0.5) yList.sort() @@ -211,10 +222,10 @@ def extendEdges(edges, hostTopology, maximumGap=0.5): for e in edges: sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) - svx = Vertex.X(sv) - svy = Vertex.Y(sv) - evx = Vertex.X(ev) - evy = Vertex.Y(ev) + svx = Vertex.X(sv, mantissa=mantissa) + svy = Vertex.Y(sv, mantissa=mantissa) + evx = Vertex.X(ev, mantissa=mantissa) + evy = Vertex.Y(ev, mantissa=mantissa) for x in xList: if abs(svx-x) < threshold: svx = x @@ -479,7 +490,7 @@ def Circle(origin= None, radius: float = 0.5, sides: int = 32, fromAngle: float return Shell.Pie(origin=origin, radiusA=radius, radiusB=0, sides=sides, rings=1, fromAngle=fromAngle, toAngle=toAngle, direction=direction, placement=placement, tolerance=tolerance) @staticmethod - def Delaunay(vertices: list, face= None, tolerance: float = 0.0001): + def Delaunay(vertices: list, face= None, mantissa: int = 6, tolerance: float = 0.0001): """ Returns a delaunay partitioning of the input vertices. The vertices must be coplanar. See https://en.wikipedia.org/wiki/Delaunay_triangulation. @@ -489,6 +500,8 @@ def Delaunay(vertices: list, face= None, tolerance: float = 0.0001): The input list of vertices. face : topologic_core.Face , optional The input face. If specified, the delaunay triangulation is clipped to the face. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -503,10 +516,8 @@ def Delaunay(vertices: list, face= None, tolerance: float = 0.0001): from topologicpy.Face import Face from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology - from topologicpy.Dictionary import Dictionary from random import sample from scipy.spatial import Delaunay as SCIDelaunay - import numpy as np if not isinstance(vertices, list): return None @@ -514,13 +525,11 @@ def Delaunay(vertices: list, face= None, tolerance: float = 0.0001): if len(vertices) < 3: return None - # Create a Vertex at the world's origin (0, 0, 0) - world_origin = Vertex.Origin() if Topology.IsInstance(face, "Face"): # Flatten the face origin = Topology.Centroid(face) - normal = Face.Normal(face) + normal = Face.Normal(face, mantissa=mantissa) flatFace = Topology.Flatten(face, origin=origin, direction=normal) faceVertices = Face.Vertices(face) vertices += faceVertices @@ -534,7 +543,7 @@ def Delaunay(vertices: list, face= None, tolerance: float = 0.0001): vertices = Cluster.Vertices(verticesCluster) points = [] for v in vertices: - points.append([Vertex.X(v), Vertex.Y(v)]) + points.append([Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa)]) delaunay = SCIDelaunay(points) simplices = delaunay.simplices @@ -682,8 +691,17 @@ def IsOnBoundary(shell, vertex, tolerance: float = 0.0001) -> bool: return False @staticmethod - def HyperbolicParaboloidRectangularDomain(origin= None, llVertex= None, lrVertex= None, ulVertex= None, urVertex= None, - uSides: int = 10, vSides: int = 10, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001): + def HyperbolicParaboloidRectangularDomain(origin= None, + llVertex= None, + lrVertex= None, + ulVertex= None, + urVertex= None, + uSides: int = 10, + vSides: int = 10, + direction: list = [0, 0, 1], + placement: str = "center", + mantissa: int = 6, + tolerance: float = 0.0001): """ Creates a hyperbolic paraboloid with a rectangular domain. @@ -707,6 +725,8 @@ def HyperbolicParaboloidRectangularDomain(origin= None, llVertex= None, lrVertex The vector representing the up direction of the hyperbolic parabolid. The default is [0, 0, 1]. placement : str , optional The description of the placement of the origin of the hyperbolic parabolid. This can be "center", "lowerleft", "bottom". It is case insensitive. The default is "center". + 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 @@ -751,12 +771,13 @@ def HyperbolicParaboloidRectangularDomain(origin= None, llVertex= None, lrVertex xOffset = 0 yOffset = 0 zOffset = 0 - minX = min([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()]) - maxX = max([llVertex.X(), lrVertex.X(), ulVertex.X(), urVertex.X()]) - minY = min([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()]) - maxY = max([llVertex.Y(), lrVertex.Y(), ulVertex.Y(), urVertex.Y()]) - minZ = min([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()]) - maxZ = max([llVertex.Z(), lrVertex.Z(), ulVertex.Z(), urVertex.Z()]) + minX = min([Vertex.X(llVertex, mantissa=mantissa), Vertex.X(lrVertex, mantissa=mantissa), Vertex.X(ulVertex, mantissa=mantissa), Vertex.X(urVertex, mantissa=mantissa)]) + maxX = max([Vertex.X(llVertex, mantissa=mantissa), Vertex.X(lrVertex, mantissa=mantissa), Vertex.X(ulVertex, mantissa=mantissa), Vertex.X(urVertex, mantissa=mantissa)]) + minY = min([Vertex.Y(llVertex, mantissa=mantissa), Vertex.Y(lrVertex, mantissa=mantissa), Vertex.Y(ulVertex, mantissa=mantissa), Vertex.Y(urVertex, mantissa=mantissa)]) + maxY = max([Vertex.Y(llVertex, mantissa=mantissa), Vertex.Y(lrVertex, mantissa=mantissa), Vertex.Y(ulVertex, mantissa=mantissa), Vertex.Y(urVertex, mantissa=mantissa)]) + minZ = min([Vertex.Z(llVertex, mantissa=mantissa), Vertex.Z(lrVertex, mantissa=mantissa), Vertex.Z(ulVertex, mantissa=mantissa), Vertex.Z(urVertex, mantissa=mantissa)]) + maxZ = max([Vertex.Z(llVertex, mantissa=mantissa), Vertex.Z(lrVertex, mantissa=mantissa), Vertex.Z(ulVertex, mantissa=mantissa), Vertex.Z(urVertex, mantissa=mantissa)]) + if placement.lower() == "lowerleft": xOffset = -minX yOffset = -minY @@ -777,7 +798,7 @@ def HyperbolicParaboloidRectangularDomain(origin= None, llVertex= None, lrVertex @staticmethod def HyperbolicParaboloidCircularDomain(origin= None, radius: float = 0.5, sides: int = 36, rings: int = 10, A: float = 2.0, B: float = -2.0, direction: list = [0, 0, 1], - placement: str = "center", tolerance: float = 0.0001): + placement: str = "center", mantissa: int = 6, tolerance: float = 0.0001): """ Creates a hyperbolic paraboloid with a circular domain. See https://en.wikipedia.org/wiki/Compactness_measure_of_a_shape @@ -799,6 +820,8 @@ def HyperbolicParaboloidCircularDomain(origin= None, radius: float = 0.5, sides: The vector representing the up direction of the hyperbolic paraboloid. 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", "bottom". It is case insensitive. The default is "center". + 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 @@ -907,9 +930,9 @@ def HyperbolicParaboloidCircularDomain(origin= None, radius: float = 0.5, sides: yList = [] zList = [] for aVertex in vertices: - xList.append(aVertex.X()) - yList.append(aVertex.Y()) - zList.append(aVertex.Z()) + xList.append(Vertex.X(aVertex, mantissa=mantissa)) + yList.append(Vertex.Y(aVertex, mantissa=mantissa)) + zList.append(Vertex.Z(aVertex, mantissa=mantissa)) minX = min(xList) maxX = max(xList) minY = min(yList) @@ -1251,7 +1274,7 @@ def RemoveCollinearEdges(shell, angTolerance: float = 0.1, tolerance: float = 0. return Shell.ByFaces(clean_faces, tolerance=tolerance) @staticmethod - def Roof(face, angle: float = 45, epsilon: float = 0.01, tolerance: float = 0.001): + def Roof(face, angle: float = 45, epsilon: float = 0.01, mantissa: int = 6, tolerance: float = 0.001): """ Creates a hipped roof through a straight skeleton. This method is contributed by 高熙鹏 xipeng gao This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel @@ -1264,6 +1287,8 @@ def Roof(face, angle: float = 45, epsilon: float = 0.01, tolerance: float = 0.00 The desired angle in degrees of the roof. The default is 45. epsilon : float , optional The desired epsilon (another form of tolerance for distance from plane). The default is 0.01. (This is set to a larger number as it was found to work better) + manitssa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.001. (This is set to a larger number as it was found to work better) @@ -1277,19 +1302,15 @@ def Roof(face, angle: float = 45, epsilon: float = 0.01, tolerance: float = 0.00 from topologicpy.Wire import Wire from topologicpy.Face import Face from topologicpy.Shell import Shell - from topologicpy.Cell import Cell from topologicpy.Cluster import Cluster from topologicpy.Topology import Topology - from topologicpy.Dictionary import Dictionary - import topologic_core as topologic - import math def nearest_vertex_2d(v, vertices, tolerance=0.001): for vertex in vertices: - x2 = Vertex.X(vertex) - y2 = Vertex.Y(vertex) - temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v)) - if Vertex.Distance(v, temp_v) <= tolerance: + x2 = Vertex.X(vertex, mantissa=mantissa) + y2 = Vertex.Y(vertex, mantissa=mantissa) + temp_v = Vertex.ByCoordinates(x2, y2, Vertex.Z(v, mantissa=mantissa)) + if Vertex.Distance(v, temp_v, mantissa=mantissa) <= tolerance: return vertex return None @@ -1301,7 +1322,7 @@ def nearest_vertex_2d(v, vertices, tolerance=0.001): if angle < tolerance: return None origin = Topology.Centroid(face) - normal = Face.Normal(face) + normal = Face.Normal(face, mantissa=mantissa) flat_face = Topology.Flatten(face, origin=origin, direction=normal) roof = Wire.Roof(flat_face, angle=angle, tolerance=tolerance) if not roof: @@ -1323,7 +1344,7 @@ def nearest_vertex_2d(v, vertices, tolerance=0.001): roof_vertices = Topology.Vertices(roof) flat_vertices = [] for rv in roof_vertices: - flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv), Vertex.Y(rv), 0)) + flat_vertices.append(Vertex.ByCoordinates(Vertex.X(rv, mantissa=mantissa), Vertex.Y(rv, mantissa=mantissa), 0)) final_triangles = [] for triangle in triangles: @@ -1473,7 +1494,7 @@ def Skeleton(face, tolerance: float = 0.001): return shell @staticmethod - def Simplify(shell, simplifyBoundary=True, tolerance=0.0001): + def Simplify(shell, simplifyBoundary: bool = True, mantissa: int = 6, tolerance: float = 0.0001): """ Simplifies the input shell 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 @@ -1484,6 +1505,8 @@ def Simplify(shell, simplifyBoundary=True, tolerance=0.0001): The input shell. simplifyBoundary : bool , optional If set to True, the external boundary of the shell will be simplified as well. Otherwise, it will not be simplified. The default is True. + mantissa : int , optional + The desired length of the mantissa. The default is 6 tolerance : float , optional The desired tolerance. The default is 0.0001. Edges shorter than this length will be removed. @@ -1503,12 +1526,12 @@ def Simplify(shell, simplifyBoundary=True, tolerance=0.0001): def perpendicular_distance(point, line_start, line_end): # Calculate the perpendicular distance from a point to a line segment - x0 = point.X() - y0 = point.Y() - x1 = line_start.X() - y1 = line_start.Y() - x2 = line_end.X() - y2 = line_end.Y() + x0 = Vertex.X(point, mantissa=mantissa) + y0 = Vertex.Y(point, mantissa=mantissa) + x1 = Vertex.X(line_start, mantissa=mantissa) + y1 = Vertex.Y(line_start, mantissa=mantissa) + x2 = Vertex.X(line_end, mantissa=mantissa) + y2 = Vertex.Y(line_end, mantissa=mantissa) numerator = abs((y2 - y1) * x0 - (x2 - x1) * y0 + x2 * y1 - y2 * x1) denominator = Vertex.Distance(line_start, line_end) @@ -1642,7 +1665,7 @@ def Vertices(shell) -> list: return vertices @staticmethod - def Voronoi(vertices: list, face= None, tolerance: float = 0.0001): + def Voronoi(vertices: list, face= None, mantissa: int = 6, tolerance: float = 0.0001): """ Returns a voronoi partitioning of the input face based on the input vertices. The vertices must be coplanar and within the face. See https://en.wikipedia.org/wiki/Voronoi_diagram. @@ -1652,6 +1675,8 @@ def Voronoi(vertices: list, face= None, tolerance: float = 0.0001): The input list of vertices. face : topologic_core.Face , optional The input face. If the face is not set an optimised bounding rectangle of the input vertices is used instead. 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. @@ -1681,17 +1706,17 @@ def Voronoi(vertices: list, face= None, tolerance: float = 0.0001): # Flatten the input face origin = Topology.Centroid(face) - normal = Face.Normal(face) + normal = Face.Normal(face, mantissa=mantissa) flatFace = Topology.Flatten(face, origin=origin, direction=normal) eb = Face.ExternalBoundary(flatFace) ibList = Face.InternalBoundaries(flatFace) temp_verts = Topology.Vertices(eb) - new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts] + new_verts = [Vertex.ByCoordinates(Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), 0) for v in temp_verts] eb = Wire.ByVertices(new_verts, close=True) new_ibList = [] for ib in ibList: temp_verts = Topology.Vertices(ib) - new_verts = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in temp_verts] + new_verts = [Vertex.ByCoordinates(Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), 0) for v in temp_verts] new_ibList.append(Wire.ByVertices(new_verts, close=True)) flatFace = Face.ByWires(eb, new_ibList) @@ -1701,10 +1726,10 @@ def Voronoi(vertices: list, face= None, tolerance: float = 0.0001): # Flatten the cluster using the same transformations verticesCluster = Topology.Flatten(verticesCluster, origin=origin, direction=normal) flatVertices = Topology.Vertices(verticesCluster) - flatVertices = [Vertex.ByCoordinates(Vertex.X(v), Vertex.Y(v), 0) for v in flatVertices] + flatVertices = [Vertex.ByCoordinates(Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), 0) for v in flatVertices] points = [] for flatVertex in flatVertices: - points.append([flatVertex.X(), flatVertex.Y()]) + points.append([Vertex.X(flatVertex, mantissa=mantissa), Vertex.Y(flatVertex, mantissa=mantissa)]) br = Wire.BoundingRectangle(flatFace) br_vertices = Wire.Vertices(br) @@ -1736,7 +1761,7 @@ def Voronoi(vertices: list, face= None, tolerance: float = 0.0001): tempWire = [] if len(region) > 1 and not -1 in region: for v in region: - tempWire.append(Vertex.ByCoordinates(voronoiVertices[v].X(), voronoiVertices[v].Y(), 0)) + tempWire.append(Vertex.ByCoordinates(Vertex.X(voronoiVertices[v], mantissa=mantissa), Vertex.Y(voronoiVertices[v], mantissa=mantissa), 0)) temp_verts = [] for v in tempWire: if len(temp_verts) == 0: diff --git a/src/topologicpy/Topology.py b/src/topologicpy/Topology.py index 99aca4e..a1e651c 100644 --- a/src/topologicpy/Topology.py +++ b/src/topologicpy/Topology.py @@ -1111,7 +1111,7 @@ def Boolean(topologyA, topologyB, operation="union", tranDict=False, tolerance=0 @staticmethod - def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001): + def BoundingBox(topology, optimize: int = 0, axes: str ="xyz", mantissa: int = 6, tolerance: float = 0.0001): """ Returns a cell representing a bounding box of the input topology. The returned cell contains a dictionary with keys "xrot", "yrot", and "zrot" that represents rotations around the X, Y, and Z axes. If applied in the order of Z, Y, X, the resulting box will become axis-aligned. @@ -1123,6 +1123,8 @@ def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001): If set to an integer from 1 (low optimization) to 10 (high optimization), the method will attempt to optimize the bounding box so that it reduces its surface area. The default is 0 which will result in an axis-aligned bounding box. The default is 0. axes : str , optional Sets what axes are to be used for rotating the bounding box. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz". + mantissa : int , optional + The desired length of the mantissa. The default is 6 tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -1138,6 +1140,7 @@ def BoundingBox(topology, optimize=0, axes="xyz", tolerance=0.0001): from topologicpy.Cell import Cell from topologicpy.Cluster import Cluster from topologicpy.Dictionary import Dictionary + def bb(topology): vertices = [] _ = topology.Vertices(None, vertices) @@ -1145,9 +1148,9 @@ def bb(topology): y = [] z = [] for aVertex in vertices: - x.append(aVertex.X()) - y.append(aVertex.Y()) - z.append(aVertex.Z()) + x.append(Vertex.X(aVertex, mantissa=mantissa)) + y.append(Vertex.Y(aVertex, mantissa=mantissa)) + z.append(Vertex.Z(aVertex, mantissa=mantissa)) minX = min(x) minY = min(y) minZ = min(z) @@ -2954,7 +2957,7 @@ def Contexts(topology): return contexts @staticmethod - def ConvexHull(topology, tolerance=0.0001): + def ConvexHull(topology, mantissa: int = 6, tolerance: float = 0.0001): """ Creates a convex hull @@ -2962,6 +2965,8 @@ def ConvexHull(topology, tolerance=0.0001): ---------- topology : topologic_core.Topology The input Topology. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -2985,7 +2990,7 @@ def convexHull3D(item, tolerance, option): _ = item.Vertices(None, vertices) pointList = [] for v in vertices: - pointList.append([v.X(), v.Y(), v.Z()]) + pointList.append(Vertex.Coordinates(v, mantissa=mantissa)) points = np.array(pointList) if option: hull = ConvexHull(points, qhull_options=option) @@ -3118,8 +3123,8 @@ def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False The input topology with the divided topologies added to it as contents. """ - from topologicpy.Dictionary import Dictionary + if not Topology.IsInstance(topologyA, "Topology"): print("Topology.Divide - Error: the input topologyA parameter is not a valid topology. Returning None.") return None @@ -3186,7 +3191,7 @@ def Divide(topologyA, topologyB, transferDictionary=False, addNestingDepth=False return topologyA @staticmethod - def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes="xyz", tolerance=0.0001): + def Explode(topology, origin=None, scale: float = 1.25, typeFilter: str = None, axes: str = "xyz", mantissa: int = 6, tolerance: float = 0.0001): """ Explodes the input topology. See https://en.wikipedia.org/wiki/Exploded-view_drawing. @@ -3202,6 +3207,8 @@ def Explode(topology, origin=None, scale=1.25, typeFilter=None, axes="xyz", tole The type of the subtopologies to explode. This can be any of "vertex", "edge", "face", or "cell". If set to None, a subtopology one level below the type of the input topology will be used. The default is None. axes : str , optional Sets what axes are to be used for exploding the topology. This can be any permutation or substring of "xyz". It is not case sensitive. The default is "xyz". + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -3288,19 +3295,19 @@ def getTypeFilter(topology): topologies = Topology.SubTopologies(topology, subTopologyType=typeFilter.lower()) for aTopology in topologies: c = Topology.InternalVertex(aTopology, tolerance=tolerance) - oldX = c.X() - oldY = c.Y() - oldZ = c.Z() + oldX = Vertex.X(c, mantissa=mantissa) + oldY = Vertex.Y(c, mantissa=mantissa) + oldZ = Vertex.Z(c, mantissa=mantissa) if x_flag: - newX = (oldX - origin.X())*scale + origin.X() + newX = (oldX - Vertex.X(origin, mantissa=mantissa))*scale + Vertex.X(origin, mantissa=mantissa) else: newX = oldX if y_flag: - newY = (oldY - origin.Y())*scale + origin.Y() + newY = (oldY - Vertex.Y(origin, mantissa=mantissa))*scale + Vertex.Y(origin, mantissa=mantissa) else: newY = oldY if z_flag: - newZ = (oldZ - origin.Z())*scale + origin.Z() + newZ = (oldZ - Vertex.Z(origin, mantissa=mantissa))*scale + Vertex.Z(origin, mantissa=mantissa) else: newZ = oldZ xT = newX - oldX @@ -3362,7 +3369,7 @@ def ExportToBREP(topology, path, overwrite=False, version=3): return False - def ExportToDXF(topologies, path, overwrite=False): + def ExportToDXF(topologies, path: str, overwrite: bool = False, mantissa: int = 6): """ Exports the input topology to a DXF file. See https://en.wikipedia.org/wiki/AutoCAD_DXF. THe DXF version is 'R2010' @@ -3376,6 +3383,8 @@ def ExportToDXF(topologies, path, overwrite=False): The input file path. overwrite : bool , optional If set to True the ouptut file will overwrite any pre-existing file. Otherwise, it won't. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. Returns ------- @@ -3423,14 +3432,14 @@ def ExportToDXF(topologies, path, overwrite=False): def add_vertices(vertices, msp): for v in vertices: if Topology.IsInstance(v, "Vertex"): - msp.add_point((Vertex.X(v), Vertex.Y(v), Vertex.Z(v))) + msp.add_point((Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa))) def add_edges(edges, msp): for e in edges: if Topology.IsInstance(e, "Edge"): sv = Edge.StartVertex(e) ev = Edge.EndVertex(e) - start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv)) - end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev)) + start = (Vertex.X(sv, mantissa=mantissa), Vertex.Y(sv, mantissa=mantissa), Vertex.Z(sv, mantissa=mantissa)) + end = (Vertex.X(ev, mantissa=mantissa), Vertex.Y(ev, mantissa=mantissa), Vertex.Z(ev, mantissa=mantissa)) msp.add_line(start, end) def add_wires(wires, msp): for i, w in enumerate(wires): @@ -3442,8 +3451,8 @@ def add_wires(wires, msp): for edge in edges: sv = Edge.StartVertex(edge) ev = Edge.EndVertex(edge) - start = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv)) - end = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev)) + start = (Vertex.X(sv, mantissa=mantissa), Vertex.Y(sv, mantissa=mantissa), Vertex.Z(sv, mantissa=mantissa)) + end = (Vertex.X(ev, mantissa=mantissa), Vertex.Y(ev, mantissa=mantissa), Vertex.Z(ev, mantissa=mantissa)) block.add_line(start, end) # Insert the block into the model space msp.add_blockref(block_name, insert=(0, 0, 0)) @@ -4229,7 +4238,7 @@ def listToString(item): return {"filtered": filteredTopologies, "other": otherTopologies} @staticmethod - def Flatten(topology, origin=None, direction=[0, 0, 1]): + def Flatten(topology, origin=None, direction: list = [0, 0, 1], mantissa: int = 6): """ Flattens the input topology such that the input origin is located at the world origin and the input topology is rotated such that the input vector is pointed in the Up direction (see Vector.Up()). @@ -4239,8 +4248,10 @@ def Flatten(topology, origin=None, direction=[0, 0, 1]): The input topology. origin : topologic_core.Vertex , optional The input origin. If set to None, The object's centroid will be used to place the world origin. The default is None. - vector : list , optional + direction : list , optional The input direction vector. The input topology will be rotated such that this vector is pointed in the positive Z axis. + mantissa : int , optional + The desired length of the mantissa. The default is 6. Returns ------- @@ -4257,13 +4268,13 @@ def Flatten(topology, origin=None, direction=[0, 0, 1]): if origin == None: origin = Topology.Centroid(topology) up = Vector.Up() - flat_topology = Topology.Translate(topology, -Vertex.X(origin), -Vertex.Y(origin), -Vertex.Z(origin)) + flat_topology = Topology.Translate(topology, -Vertex.X(origin, mantissa=mantissa), -Vertex.Y(origin, mantissa=mantissa), -Vertex.Z(origin, mantissa=mantissa)) tran_mat = Vector.TransformationMatrix(direction, up) flat_topology = Topology.Transform(flat_topology, tran_mat) return flat_topology @staticmethod - def Geometry(topology, mantissa=6): + def Geometry(topology, mantissa: int = 6): """ Returns the geometry (mesh data format) of the input topology as a dictionary of vertices, edges, and faces. @@ -4543,7 +4554,7 @@ def IsInstance(topology, type: str): return None @staticmethod - def IsPlanar(topology, tolerance=0.0001): + def IsPlanar(topology, mantissa: int = 6, tolerance: float = 0.0001): """ Returns True if all the vertices of the input topology are co-planar. Returns False otherwise. @@ -4551,6 +4562,8 @@ def IsPlanar(topology, tolerance=0.0001): ---------- topology : topologic_core.Topology The input topology. + mantissa : int , optional + The desired length of the mantissa. The default is 6 tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -4560,7 +4573,8 @@ def IsPlanar(topology, tolerance=0.0001): True if all the vertices of the input topology are co-planar. False otherwise. """ - + from topologicpy.Vertex import Vertex + def isOnPlane(v, plane, tolerance): x, y, z = v a, b, c, d = plane @@ -4569,16 +4583,16 @@ def isOnPlane(v, plane, tolerance): return False def plane(v1, v2, v3): - a1 = v2.X() - v1.X() - b1 = v2.Y() - v1.Y() - c1 = v2.Z() - v1.Z() - a2 = v3.X() - v1.X() - b2 = v3.Y() - v1.Y() - c2 = v3.Z() - v1.Z() + a1 = Vertex.X(v2, mantissa=mantissa) - Vertex.X(v1, mantissa=mantissa) + b1 = Vertex.Y(v2, mantissa=mantissa) - Vertex.Y(v1, mantissa=mantissa) + c1 = Vertex.Z(v2, mantissa=mantissa) - Vertex.Z(v1, mantissa=mantissa) + a2 = Vertex.X(v3, mantissa=mantissa) - Vertex.X(v1, mantissa=mantissa) + b2 = Vertex.Y(v3, mantissa=mantissa) - Vertex.Y(v1, mantissa=mantissa) + c2 = Vertex.Z(v3, mantissa=mantissa) - Vertex.Z(v1, mantissa=mantissa) a = b1 * c2 - b2 * c1 b = a2 * c1 - a1 * c2 c = a1 * b2 - b1 * a2 - d = (- a * v1.X() - b * v1.Y() - c * v1.Z()) + d = (- a * Vertex.X(v1, mantissa=mantissa) - b * Vertex.Y(v1, mantissa=mantissa) - c * Vertex.Z(v1, mantissa=mantissa)) return [a, b, c, d] if not Topology.IsInstance(topology, "Topology"): @@ -4592,7 +4606,7 @@ def plane(v1, v2, v3): else: p = plane(vertices[0], vertices[1], vertices[2]) for i in range(len(vertices)): - if isOnPlane([vertices[i].X(), vertices[i].Y(), vertices[i].Z()], p, tolerance) == False: + if isOnPlane([Vertex.X(vertices[i], mantissa=mantissa), Vertex.Y(vertices[i], mantissa=mantissa), Vertex.Z(vertices[i], mantissa=mantissa)], p, tolerance) == False: result = False break return result @@ -4846,7 +4860,7 @@ def Orient(topology, origin=None, dirA=[0, 0, 1], dirB=[0, 0, 1], tolerance=0.00 return return_topology @staticmethod - def Place(topology, originA=None, originB=None): + def Place(topology, originA=None, originB=None, mantissa: int = 6): """ Places the input topology at the specified location. @@ -4858,6 +4872,8 @@ def Place(topology, originA=None, originB=None): The old location to use as the origin of the movement. If set to None, the centroid of the input topology is used. The default is None. originB : topologic_core.Vertex , optional The new location at which to place the topology. If set to None, the world origin (0, 0, 0) is used. The default is None. + mantissa : int , optional + The desired length of the mantissa. The default is 6 Returns ------- @@ -4873,9 +4889,9 @@ def Place(topology, originA=None, originB=None): if not Topology.IsInstance(originA, "Vertex"): originA = Vertex.ByCoordinates(0, 0, 0) - x = originB.X() - originA.X() - y = originB.Y() - originA.Y() - z = originB.Z() - originA.Z() + x = Vertex.X(originB, mantissa=mantissa) - Vertex.X(originA, mantissa=mantissa) + y = Vertex.Y(originB, mantissa=mantissa) - Vertex.Y(originA, mantissa=mantissa) + z = Vertex.Z(originB, mantissa=mantissa) - Vertex.Z(originA, mantissa=mantissa) newTopology = None try: newTopology = Topology.Translate(topology, x, y, z) @@ -5304,7 +5320,7 @@ def Cleanup(topology=None): return topology @staticmethod - def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance=0.0001): + def ReplaceVertices(topology, verticesA: list = [], verticesB: list = [], mantissa: int = 6, tolerance: float = 0.0001): """ Replaces the vertices in the first input list with the vertices in the second input list and rebuilds the input topology. The two lists must be of the same length. @@ -5341,7 +5357,7 @@ def ReplaceVertices(topology, verticesA=[], verticesB=[], mantissa=6, tolerance= n = Vertex.Index(v, verts, tolerance=tolerance) if not n == None: new_verts[n] = verticesB[i] - new_g_verts = [[Vertex.X(v),Vertex.Y(v),Vertex.Z(v)] for v in new_verts] + new_g_verts = [[Vertex.X(v, mantissa=mantissa),Vertex.Y(v, mantissa=mantissa),Vertex.Z(v, mantissa=mantissa)] for v in new_verts] new_topology = Topology.ByGeometry(vertices=new_g_verts, edges=g_edges, faces=g_faces) return new_topology @@ -6286,7 +6302,7 @@ def Spin(topology, origin=None, triangulate: bool = True, direction: list = [0, return returnTopology @staticmethod - def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance=0.0001): + def Taper(topology, origin=None, ratioRange: list = [0, 1], triangulate: bool = False, mantissa: int = 6, tolerance: float = 0.0001): """ Tapers the input topology. This method tapers the input geometry along its Z-axis based on the ratio range input. @@ -6300,6 +6316,8 @@ def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance The desired ratio range. This will specify a linear range from bottom to top for tapering the vertices. 0 means no tapering, and 1 means maximum (inward) tapering. Negative numbers mean that tapering will be outwards. triangulate : bool , optional If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. Vertices will not be moved if the calculated distance is at or less than this tolerance. @@ -6321,17 +6339,17 @@ def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance if origin == None: origin = Topology.Centroid(topology) vertices = Topology.Vertices(topology) - zList = [Vertex.Z(v) for v in vertices] + zList = [Vertex.Z(v, mantissa=mantissa) for v in vertices] minZ = min(zList) maxZ = max(zList) new_vertices = [] for v in vertices: ht = (Vertex.Z(v)-minZ)/(maxZ - minZ) rt = ratioRange[0] + ht*(ratioRange[1] - ratioRange[0]) - new_origin = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v)) - new_dist = Vertex.Distance(new_origin, v)*rt - c_a = Vertex.Coordinates(new_origin) - c_b = Vertex.Coordinates(v) + new_origin = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa), Vertex.Y(origin, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)) + new_dist = Vertex.Distance(new_origin, v, mantissa=mantissa)*rt + c_a = Vertex.Coordinates(new_origin, mantissa=mantissa) + c_b = Vertex.Coordinates(v, mantissa=mantissa) new_dir = [(c_a[0]-c_b[0]), (c_a[1]-c_b[1]), 0] if abs(new_dist) > tolerance: new_v = Topology.TranslateByDirectionDistance(v, direction=new_dir, distance=new_dist) @@ -6342,7 +6360,7 @@ def Taper(topology, origin=None, ratioRange=[0, 1], triangulate=False, tolerance return return_topology @staticmethod - def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False): + def Twist(topology, origin=None, angleRange: list = [45, 90], triangulate: bool = False, mantissa: int = 6): """ Twists the input topology. This method twists the input geometry along its Z-axis based on the degree range input. @@ -6356,6 +6374,8 @@ def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False): The desired angle range in degrees. This will specify a linear range from bottom to top for twisting the vertices. positive numbers mean a clockwise rotation. triangulate : bool , optional If set to true, the input topology is triangulated before tapering. Otherwise, it will not be traingulated. The default is False. + mantissa : int , optional + The desired length of the mantissa. The default is 6. Returns ------- @@ -6373,7 +6393,7 @@ def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False): origin = Topology.Centroid(topology) vertices = Topology.Vertices(topology) - zList = [Vertex.Z(v) for v in vertices] + zList = [Vertex.Z(v, mantissa=mantissa) for v in vertices] minZ = min(zList) maxZ = max(zList) h = maxZ - minZ @@ -6381,7 +6401,7 @@ def Twist(topology, origin=None, angleRange=[45, 90], triangulate=False): for v in vertices: ht = (Vertex.Z(v)-minZ)/(maxZ - minZ) new_rot = angleRange[0] + ht*(angleRange[1] - angleRange[0]) - orig = Vertex.ByCoordinates(Vertex.X(origin), Vertex.Y(origin), Vertex.Z(v)) + orig = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa), Vertex.Y(origin, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa)) new_vertices.append(Topology.Rotate(v, origin=orig, axis=[0, 0, 1], angle=new_rot)) return_topology = Topology.ReplaceVertices(topology, vertices, new_vertices) return_topology = Topology.Fix(return_topology, topologyType=Topology.TypeAsString(topology)) @@ -7141,4 +7161,29 @@ def TypeID(name : str = None) -> int: typeID = 2048 elif name == "topology": typeID = 4096 - return typeID \ No newline at end of file + return typeID + + @staticmethod + def UUID(topology, namespace="topologicpy"): + """ + Generate a UUID v5 based on the provided content and a fixed namespace. + + Parameters + ---------- + topology : topologic_core.Topology + The input topology + namespace : str , optional + The base namescape to use for generating the UUID + + Returns + ------- + UUID + The uuid of the input topology. + + """ + import uuid + + predefined_namespace_dns = uuid.UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') + namespace_uuid = uuid.uuid5(predefined_namespace_dns, namespace) + brep_string = Topology.BREPString(topology) + return uuid.uuid5(namespace_uuid, brep_string) \ No newline at end of file diff --git a/src/topologicpy/Vector.py b/src/topologicpy/Vector.py index 431f1cd..9810214 100644 --- a/src/topologicpy/Vector.py +++ b/src/topologicpy/Vector.py @@ -248,7 +248,7 @@ def ByCoordinates(x, y, z): return [x, y, z] @staticmethod - def ByVertices(vertices, normalize=True): + def ByVertices(vertices, normalize: bool = True, mantissa: int = 6): """ Creates a vector by the specified input list of vertices. @@ -258,6 +258,8 @@ def ByVertices(vertices, normalize=True): The the input list of topologic vertices. The first element in the list is considered the start vertex. The last element in the list is considered the end vertex. normalize : bool , optional If set to True, the resulting vector is normalized (i.e. its length is set to 1) + mantissa : int , optional + The desired length of the mantissa. The default is 6. Returns ------- @@ -278,7 +280,7 @@ def ByVertices(vertices, normalize=True): return None v1 = vertices[0] v2 = vertices[-1] - vector = [Vertex.X(v2)-Vertex.X(v1), Vertex.Y(v2)-Vertex.Y(v1), Vertex.Z(v2)-Vertex.Z(v1)] + vector = [Vertex.X(v2, mantissa=mantissa)-Vertex.X(v1, mantissa=mantissa), Vertex.Y(v2, mantissa=mantissa)-Vertex.Y(v1, mantissa=mantissa), Vertex.Z(v2, mantissa=mantissa)-Vertex.Z(v1, mantissa=mantissa)] if normalize: vector = Vector.Normalize(vector) return vector diff --git a/src/topologicpy/Vertex.py b/src/topologicpy/Vertex.py index 65c5959..644a336 100644 --- a/src/topologicpy/Vertex.py +++ b/src/topologicpy/Vertex.py @@ -35,7 +35,7 @@ class Vertex(): @staticmethod - def AreCollinear(vertices: list, tolerance: float = 0.0001): + def AreCollinear(vertices: list, mantissa: int = 6, tolerance: float = 0.0001): """ Returns True if the input list of vertices form a straight line. Returns False otherwise. @@ -43,6 +43,8 @@ def AreCollinear(vertices: list, tolerance: float = 0.0001): ---------- vertices : list The input list of vertices. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float, optional The desired tolerance. The default is 0.0001. @@ -57,9 +59,9 @@ def AreCollinear(vertices: list, tolerance: float = 0.0001): from topologicpy.Vector import Vector import sys def areCollinear(vertices, tolerance=0.0001): - point1 = [Vertex.X(vertices[0]), Vertex.Y(vertices[0]), Vertex.Z(vertices[0])] - point2 = [Vertex.X(vertices[1]), Vertex.Y(vertices[1]), Vertex.Z(vertices[1])] - point3 = [Vertex.X(vertices[2]), Vertex.Y(vertices[2]), Vertex.Z(vertices[2])] + point1 = Vertex.Coordinates(vertices[0], mantissa=mantissa) + point2 = Vertex.Coordinates(vertices[1], mantissa=mantissa) + point3 = Vertex.Coordinates(vertices[2], mantissa=mantissa) vector1 = [point2[0] - point1[0], point2[1] - point1[1], point2[2] - point1[2]] vector2 = [point3[0] - point1[0], point3[1] - point1[1], point3[2] - point1[2]] @@ -306,7 +308,7 @@ def ByCoordinates(*args, **kwargs): return vertex @staticmethod - def Centroid(vertices): + def Centroid(vertices: list, mantissa: int = 6): """ Returns the centroid of the input list of vertices. @@ -314,6 +316,8 @@ def Centroid(vertices): ----------- vertices : list The input list of vertices + mantissa : int , optional + The desired length of the mantissa. The default is 6. Return ---------- @@ -332,9 +336,9 @@ def Centroid(vertices): return None if len(vertices) == 1: return vertices[0] - cx = sum(Vertex.X(v) for v in vertices) / len(vertices) - cy = sum(Vertex.Y(v) for v in vertices) / len(vertices) - cz = sum(Vertex.Z(v) for v in vertices) / len(vertices) + cx = sum(Vertex.X(v, mantissa=mantissa) for v in vertices) / len(vertices) + cy = sum(Vertex.Y(v, mantissa=mantissa) for v in vertices) / len(vertices) + cz = sum(Vertex.Z(v, mantissa=mantissa) for v in vertices) / len(vertices) return Vertex.ByCoordinates(cx, cy, cz) @staticmethod @@ -379,9 +383,9 @@ def Coordinates(vertex, outputType: str = "xyz", mantissa: int = 6) -> list: if not Topology.IsInstance(vertex, "Vertex"): return None - x = round(vertex.X(), mantissa) - y = round(vertex.Y(), mantissa) - z = round(vertex.Z(), mantissa) + x = Vertex.X(vertex, mantissa) + y = Vertex.Y(vertex, mantissa) + z = Vertex.Z(vertex, mantissa) matrix = [[1, 0, 0, x], [0, 1, 0, y], [0, 0, 1, z], @@ -402,7 +406,7 @@ def Coordinates(vertex, outputType: str = "xyz", mantissa: int = 6) -> list: return output @staticmethod - def CounterClockwise2D(vertices): + def CounterClockwise2D(vertices: list, mantissa: int = 6): """ Sorts the input list of vertices in a counterclockwise fashion. This method assumes that the vertices are on the XY plane. The Z coordinate is ignored. @@ -410,6 +414,8 @@ def CounterClockwise2D(vertices): ----------- vertices : list The input list of vertices + mantissa : int , optional + The desired length of the mantissa. The default is 6. Return ----------- @@ -417,10 +423,22 @@ def CounterClockwise2D(vertices): The input list of vertices sorted in a counter clockwise fashion """ + from topologicpy.Topology import Topology import math + + if not isinstance(vertices, list): + print("Vertex.CounterClockwise2D - Error: The input vertices parameter is not a valid list. Returning None.") + return None + vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")] + if len(vertices) < 1: + print("Vertex.CounterClockwise2D - Error: The input vertices parameter does not contain any valid vertices. Returning None.") + return None + if len(vertices) == 1: + return vertices[0] + # find the centroid of the points - cx = sum(Vertex.X(v) for v in vertices) / len(vertices) - cy = sum(Vertex.Y(v) for v in vertices) / len(vertices) + cx = sum(Vertex.X(v, mantissa=mantissa) for v in vertices) / len(vertices) + cy = sum(Vertex.Y(v, mantissa=mantissa) for v in vertices) / len(vertices) # sort the points based on their angle with respect to the centroid vertices.sort(key=lambda v: (math.atan2(Vertex.Y(v) - cy, Vertex.X(v) - cx) + 2 * math.pi) % (2 * math.pi)) @@ -457,8 +475,7 @@ def Degree(vertex, hostTopology, topologyType: str = "edge"): @staticmethod - def Distance(vertex, topology, includeCentroid: bool =True, - mantissa: int = 6) -> float: + def Distance(vertex, topology, includeCentroid: bool =True, mantissa: int = 6) -> float: """ Returns the distance between the input vertex and the input topology. This method returns the distance to the closest sub-topology in the input topology, optionally including its centroid. @@ -527,16 +544,16 @@ def distance_point_to_line(point, line_start, line_end): return distance def distance_to_vertex(vertexA, vertexB): - a = (Vertex.X(vertexA), Vertex.Y(vertexA), Vertex.Z(vertexA)) - b = (Vertex.X(vertexB), Vertex.Y(vertexB), Vertex.Z(vertexB)) + a = (Vertex.X(vertexA, mantissa=mantissa), Vertex.Y(vertexA, mantissa=mantissa), Vertex.Z(vertexA, mantissa=mantissa)) + b = (Vertex.X(vertexB, mantissa=mantissa), Vertex.Y(vertexB, mantissa=mantissa), Vertex.Z(vertexB, mantissa=mantissa)) return distance_point_to_point(a, b) def distance_to_edge(vertex, edge): - a = (Vertex.X(vertex), Vertex.Y(vertex), Vertex.Z(vertex)) + a = (Vertex.X(vertex, mantissa=mantissa), Vertex.Y(vertex, mantissa=mantissa), Vertex.Z(vertex, mantissa=mantissa)) sv = Edge.StartVertex(edge) ev = Edge.EndVertex(edge) - svp = (Vertex.X(sv), Vertex.Y(sv), Vertex.Z(sv)) - evp = (Vertex.X(ev), Vertex.Y(ev), Vertex.Z(ev)) + svp = (Vertex.X(sv, mantissa=mantissa), Vertex.Y(sv, mantissa=mantissa), Vertex.Z(sv, mantissa=mantissa)) + evp = (Vertex.X(ev, mantissa=mantissa), Vertex.Y(ev, mantissa=mantissa), Vertex.Z(ev, mantissa=mantissa)) return distance_point_to_line(a,svp, evp) def distance_to_face(vertex, face, includeCentroid): @@ -554,7 +571,7 @@ def distance_to_face(vertex, face, includeCentroid): b = dic["b"] c = dic["c"] d = dic["d"] - x1, y1, z1 = Vertex.Coordinates(vertex) + x1, y1, z1 = Vertex.Coordinates(vertex, mantissa=mantissa) d = abs((a * x1 + b * y1 + c * z1 + d)) e = (math.sqrt(a * a + b * b + c * c)) if e == 0: @@ -598,7 +615,7 @@ def distance_to_face(vertex, face, includeCentroid): return None @staticmethod - def EnclosingCell(vertex, topology, exclusive: bool = True, tolerance: float = 0.0001) -> list: + def EnclosingCell(vertex, topology, exclusive: bool = True, mantissa: int = 6, tolerance: float = 0.0001) -> list: """ Returns the list of Cells found in the input topology that enclose the input vertex. @@ -610,6 +627,8 @@ def EnclosingCell(vertex, topology, exclusive: bool = True, tolerance: float = 0 The input topology. exclusive : bool , optional If set to True, return only the first found enclosing cell. The default is True. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The tolerance for computing if the input vertex is enclosed in a cell. The default is 0.0001. @@ -629,16 +648,15 @@ def boundingBox(cell): y = [] z = [] for aVertex in vertices: - x.append(aVertex.X()) - y.append(aVertex.Y()) - z.append(aVertex.Z()) + x.append(Vertex.X(aVertex, mantissa=mantissa)) + y.append(Vertex.Y(aVertex, mantissa=mantissa)) + z.append(Vertex.Z(aVertex, mantissa=mantissa)) return ([min(x), min(y), min(z), max(x), max(y), max(z)]) if Topology.IsInstance(topology, "Cell"): cells = [topology] elif Topology.IsInstance(topology, "Cluster") or Topology.IsInstance(topology, "CellComplex"): - cells = [] - _ = topology.Cells(None, cells) + cells = Topology.Cells(topology) else: return None if len(cells) < 1: @@ -646,7 +664,7 @@ def boundingBox(cell): enclosingCells = [] for i in range(len(cells)): bbox = boundingBox(cells[i]) - if ((vertex.X() < bbox[0]) or (vertex.Y() < bbox[1]) or (vertex.Z() < bbox[2]) or (vertex.X() > bbox[3]) or (vertex.Y() > bbox[4]) or (vertex.Z() > bbox[5])) == False: + if ((Vertex.X(vertex, mantissa=mantissa) < bbox[0]) or (Vertex.Y(vertex, mantissa=mantissa) < bbox[1]) or (Vertex.Z(vertex, mantissa=mantissa) < bbox[2]) or (Vertex.X(vertex, mantissa=mantissa) > bbox[3]) or (Vertex.Y(vertex, mantissa=mantissa) > bbox[4]) or (Vertex.Z(vertex, mantissa=mantissa) > bbox[5])) == False: if Vertex.IsInternal(vertex, cells[i], tolerance=tolerance): if exclusive: return([cells[i]]) @@ -770,7 +788,7 @@ def Index(vertex, vertices: list, strict: bool = False, tolerance: float = 0.000 return None @staticmethod - def InterpolateValue(vertex, vertices, n=3, key="intensity", tolerance=0.0001): + def InterpolateValue(vertex, vertices: list, n: int = 3, key: str = "intensity", mantissa: int = 6, tolerance: float = 0.0001): """ Interpolates the value of the input vertex based on the values of the *n* nearest vertices. @@ -784,6 +802,8 @@ def InterpolateValue(vertex, vertices, n=3, key="intensity", tolerance=0.0001): The maximum number of nearest vertices to consider. The default is 3. key : str , optional The key that holds the value to be interpolated in the dictionaries of the vertices. The default is "intensity". + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The tolerance for computing if the input vertex is coincident with another vertex in the input list of vertices. The default is 0.0001. @@ -860,14 +880,14 @@ def distance(point1, point2): if len(vertices) == 0: return None - point = (Vertex.X(vertex), Vertex.Y(vertex), Vertex.Z(vertex)) + point = (Vertex.X(vertex, mantissa=mantissa), Vertex.Y(vertex, mantissa=mantissa), Vertex.Z(vertex, mantissa=mantissa)) data_points = [] for v in vertices: d = Topology.Dictionary(v) value = Dictionary.ValueAtKey(d, key) if not value == None: if type(value) == int or type(value) == float: - data_points.append((Vertex.X(v), Vertex.Y(v), Vertex.Z(v), value)) + data_points.append((Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa), Vertex.Z(v, mantissa=mantissa), value)) if len(data_points) == 0: return None if n > len(data_points): @@ -1155,7 +1175,7 @@ def IsPeripheral(vertex, topology, tolerance: float = 0.0001, silent: bool = Fal return False @staticmethod - def NearestVertex(vertex, topology, useKDTree: bool = True): + def NearestVertex(vertex, topology, useKDTree: bool = True, mantissa: int = 6): """ Returns the vertex found in the input topology that is the nearest to the input vertex. @@ -1167,7 +1187,9 @@ def NearestVertex(vertex, topology, useKDTree: bool = True): The input topology to be searched for the nearest vertex. useKDTree : bool , optional if set to True, the algorithm will use a KDTree method to search for the nearest vertex. The default is True. - + mantissa : int , optional + The desired length of the mantissa. The default is 6. + Returns ------- topologic_core.Vertex @@ -1178,8 +1200,8 @@ def NearestVertex(vertex, topology, useKDTree: bool = True): def SED(a, b): """Compute the squared Euclidean distance between X and Y.""" - p1 = (a.X(), a.Y(), a.Z()) - p2 = (b.X(), b.Y(), b.Z()) + p1 = (Vertex.X(a, mantissa=mantissa), Vertex.Y(a, mantissa=mantissa), Vertex.Z(a, mantissa=mantissa)) + p2 = (Vertex.X(b, mantissa=mantissa), Vertex.Y(b, mantissa=mantissa), Vertex.Z(b, mantissa=mantissa)) return sum((i-j)**2 for i, j in zip(p1, p2)) BT = collections.namedtuple("BT", ["value", "left", "right"]) @@ -1188,19 +1210,19 @@ def SED(a, b): right-subtrees. """ def firstItem(v): - return v.X() + return Vertex.X(v, mantissa=mantissa) def secondItem(v): - return v.Y() + return Vertex.Y(v, mantissa=mantissa) def thirdItem(v): - return v.Z() + return Vertex.Z(v, mantissa=mantissa) def itemAtIndex(v, index): if index == 0: - return v.X() + return Vertex.X(v, mantissa=mantissa) elif index == 1: - return v.Y() + return Vertex.Y(v, mantissa=mantissa) elif index == 2: - return v.Z() + return Vertex.Z(v, mantissa=mantissa) def sortList(vertices, index): if index == 0: diff --git a/src/topologicpy/Wire.py b/src/topologicpy/Wire.py index 97176b3..0106379 100644 --- a/src/topologicpy/Wire.py +++ b/src/topologicpy/Wire.py @@ -143,7 +143,7 @@ def segmented_arc(x1, y1, x2, y2, x3, y3, sides): return arc @staticmethod - def BoundingRectangle(topology, optimize: int = 0, tolerance=0.0001): + def BoundingRectangle(topology, optimize: int = 0, mantissa: int = 6, tolerance=0.0001): """ Returns a wire representing a bounding rectangle of the input topology. The returned wire contains a dictionary with key "zrot" that represents rotations around the Z axis. If applied the resulting wire will become axis-aligned. @@ -153,6 +153,8 @@ def BoundingRectangle(topology, optimize: int = 0, tolerance=0.0001): 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. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -177,8 +179,8 @@ def br(topology): x = [] y = [] for aVertex in vertices: - x.append(aVertex.X()) - y.append(aVertex.Y()) + x.append(Vertex.X(aVertex, mantissa=mantissa)) + y.append(Vertex.Y(aVertex, mantissa=mantissa)) minX = min(x) minY = min(y) maxX = max(x) @@ -205,7 +207,7 @@ def br(topology): w = Wire.ByVertices(vList) f = Face.ByWire(w, tolerance=tolerance) f_origin = Topology.Centroid(f) - normal = Face.Normal(f) + normal = Face.Normal(f, mantissa=mantissa) topology = Topology.Flatten(topology, origin=f_origin, direction=normal) boundingRectangle = br(topology) @@ -757,7 +759,7 @@ def nearest_vertex(vertex, vertices): return new_wire @staticmethod - def ConvexHull(topology, tolerance: float = 0.0001): + def ConvexHull(topology, mantissa: int = 6, tolerance: float = 0.0001): """ Returns a wire representing the 2D convex hull of the input topology. The vertices of the topology are assumed to be coplanar. @@ -765,6 +767,8 @@ def ConvexHull(topology, tolerance: float = 0.0001): ---------- topology : topologic_core.Topology The input topology. + mantissa : int , optional + The desired length of the mantissa. The default is 6. tolerance : float , optional The desired tolerance. The default is 0.0001. @@ -875,13 +879,13 @@ def convex_hull(points, n): w = Wire.ByVertices(v) f = Face.ByWire(w, tolerance=tolerance) origin = Topology.Centroid(f) - normal = Face.Normal(f) + normal = Face.Normal(f, mantissa=mantissa) f = Topology.Flatten(f, origin=origin, direction=normal) topology = Topology.Flatten(topology, origin=origin, direction=normal) vertices = Topology.Vertices(topology) points = [] for v in vertices: - points.append((Vertex.X(v), Vertex.Y(v))) + points.append((Vertex.X(v, mantissa=mantissa), Vertex.Y(v, mantissa=mantissa))) hull = convex_hull(points, len(points)) hull_vertices = [] for p in hull: @@ -1031,7 +1035,7 @@ def Edges(wire) -> list: return edges @staticmethod - def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = "center"): + def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1], placement: str = "center", mantissa: int = 6): """ 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 @@ -1045,11 +1049,18 @@ def Einstein(origin= None, radius: float = 0.5, direction: list = [0, 0, 1], pla 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". - + mantissa : int , optional + The desired length of the mantissa. The default is 6. + Returns + ------- + topologic_core.Wire + The created wire. + """ from topologicpy.Vertex import Vertex from topologicpy.Topology import Topology import math + def cos(angle): return math.cos(math.radians(angle)) def sin(angle): @@ -1074,9 +1085,9 @@ def sin(angle): if placement.lower() == "lowerleft": einstein = Topology.Translate(einstein, radius, d, 0) - dx = Vertex.X(origin) - dy = Vertex.Y(origin) - dz = Vertex.Z(origin) + dx = Vertex.X(origin, mantissa=mantissa) + dy = Vertex.Y(origin, mantissa=mantissa) + dz = Vertex.Z(origin, mantissa=mantissa) einstein = Topology.Translate(einstein, dx, dy, dz) if direction != [0, 0, 1]: einstein = Topology.Orient(einstein, origin=origin, dirA=[0, 0, 1], dirB=direction) diff --git a/src/topologicpy/version.py b/src/topologicpy/version.py index 7dfe66c..79866ab 100644 --- a/src/topologicpy/version.py +++ b/src/topologicpy/version.py @@ -1 +1 @@ -__version__ = '0.7.5' +__version__ = '0.7.6' diff --git a/tests/test_04Face.py b/tests/test_04Face.py index 287b434..014e6d6 100644 --- a/tests/test_04Face.py +++ b/tests/test_04Face.py @@ -278,7 +278,7 @@ def test_main(): # Case 19 - FacingToward print("Case 19") # test 1 - ft1 = Face.FacingToward(f1, [0, 0, 1], True, 0.005) # with optional inputs + ft1 = Face.FacingToward(f1, [0, 0, 1], True, tolerance=0.005) # with optional inputs assert isinstance(ft1, bool), "Face.FacingToward. Should be Boolean" # test 2 ft2 = Face.FacingToward(cF1) # without optional inputs diff --git a/tests/test_09Topology.py b/tests/test_09Topology.py index d575138..7558483 100644 --- a/tests/test_09Topology.py +++ b/tests/test_09Topology.py @@ -512,7 +512,7 @@ def test_main(): assert topology_ip == True, "Topology.IsPlanar. Should be True" # test 2 cluster_pts2 = Cluster.ByTopologies([v00,v01,v02,v04]) - topology_ip2 = Topology.IsPlanar(cluster_pts2,0.01) # with optional inputs + topology_ip2 = Topology.IsPlanar(cluster_pts2, tolerance=0.01) # with optional inputs assert isinstance(topology_ip2, bool), "Topology.IsPlanar. Should be bool" assert topology_ip2 == False, "Topology.IsPlanar. Should be False"