topologicpy 0.8.29__py3-none-any.whl → 0.8.31__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
topologicpy/Topology.py CHANGED
@@ -1209,7 +1209,7 @@ class Topology():
1209
1209
 
1210
1210
 
1211
1211
  @staticmethod
1212
- def BoundingBox(topology, optimize: int = 0, axes: str ="xyz", mantissa: int = 6, tolerance: float = 0.0001):
1212
+ def BoundingBox(topology, optimize: int = 0, axes: str ="xyz", mantissa: int = 6, tolerance: float = 0.0001, silent: bool = False):
1213
1213
  """
1214
1214
  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.
1215
1215
 
@@ -1225,7 +1225,9 @@ class Topology():
1225
1225
  The desired length of the mantissa. The default is 6.
1226
1226
  tolerance : float , optional
1227
1227
  The desired tolerance. The default is 0.0001.
1228
-
1228
+ silent : bool , optional
1229
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
1230
+
1229
1231
  Returns
1230
1232
  -------
1231
1233
  topologic_core.Cell or topologic_core.Face
@@ -1376,7 +1378,7 @@ class Topology():
1376
1378
  vb3 = Vertex.ByCoordinates(x_max, y_max, z_min)
1377
1379
  vb4 = Vertex.ByCoordinates(x_min, y_max, z_min)
1378
1380
 
1379
- baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
1381
+ baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True, tolerance=tolerance, silent=silent)
1380
1382
  baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1381
1383
  if abs(z_max - z_min) <= tolerance:
1382
1384
  box = baseFace
@@ -1853,7 +1855,7 @@ class Topology():
1853
1855
  return Topology.ByBREPFile(file)
1854
1856
 
1855
1857
  @staticmethod
1856
- def ByDXFFile(file, sides: int = 16):
1858
+ def ByDXFFile(file, sides: int = 16, tolerance: float = 0.0001, silent: bool = False):
1857
1859
  """
1858
1860
  Imports a list of topologies from a DXF file.
1859
1861
  This is an experimental method with limited capabilities.
@@ -1864,6 +1866,10 @@ class Topology():
1864
1866
  The DXF file object.
1865
1867
  sides : int , optional
1866
1868
  The desired number of sides of splines. The default is 16.
1869
+ tolerance : float , optional
1870
+ The desired tolerance. The default is 0.0001.
1871
+ silent : bool , optional
1872
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
1867
1873
 
1868
1874
  Returns
1869
1875
  -------
@@ -1955,7 +1961,7 @@ class Topology():
1955
1961
  vertices.append(Vertex.ByCoordinates(point[0], point[1], point[2]))
1956
1962
  if entity.dxf.hasattr("closed"):
1957
1963
  closed = entity.closed
1958
- e = Wire.ByVertices(vertices, close=closed)
1964
+ e = Wire.ByVertices(vertices, close=closed, tolerance=tolerance, silent=silent)
1959
1965
  e = Topology.SetDictionary(e, d)
1960
1966
 
1961
1967
  elif entity_type == 'LWPOLYLINE':
@@ -1966,7 +1972,7 @@ class Topology():
1966
1972
  close = entity.closed
1967
1973
  else:
1968
1974
  close = False
1969
- e = Wire.ByVertices(vertices, close=close)
1975
+ e = Wire.ByVertices(vertices, close=close, tolerance=tolerance, silent=silent)
1970
1976
  e = Topology.SetDictionary(e, d)
1971
1977
 
1972
1978
  elif entity_type == 'CIRCLE':
@@ -1980,7 +1986,7 @@ class Topology():
1980
1986
  y = center[1] + radius * np.sin(angle)
1981
1987
  z = center[2]
1982
1988
  vertices.append(Vertex.ByCoordinates(x,y,z))
1983
- e = Wire.ByVertices(vertices, close=True)
1989
+ e = Wire.ByVertices(vertices, close=True, tolerance=tolerance, silent=silent)
1984
1990
  e = Topology.SetDictionary(e, d)
1985
1991
 
1986
1992
  elif entity_type == 'ARC':
@@ -1995,7 +2001,7 @@ class Topology():
1995
2001
  y = center[1] + radius * np.sin(angle)
1996
2002
  z = center[2]
1997
2003
  vertices.append(Vertex.ByCoordinates(x,y,z))
1998
- e = Wire.ByVertices(vertices, close=False)
2004
+ e = Wire.ByVertices(vertices, close=False, tolerance=tolerance, silent=silent)
1999
2005
  e = Topology.SetDictionary(e, d)
2000
2006
 
2001
2007
  elif entity_type == 'SPLINE':
@@ -2005,7 +2011,7 @@ class Topology():
2005
2011
  for t in np.linspace(0, ct.max_t, 64):
2006
2012
  point, derivative = ct.derivative(t, 1)
2007
2013
  vertices.append(Vertex.ByCoordinates(list(point)))
2008
- converted_entity = Wire.ByVertices(vertices, close=entity.closed)
2014
+ converted_entity = Wire.ByVertices(vertices, close=entity.closed, tolerance=tolerance, silent=silent)
2009
2015
  vertices = []
2010
2016
  for i in range(sides+1):
2011
2017
  if i == 0:
@@ -2016,7 +2022,7 @@ class Topology():
2016
2022
  u = float(i)/float(sides)
2017
2023
  vertices.append(Wire.VertexByParameter(converted_entity, u))
2018
2024
 
2019
- e = Wire.ByVertices(vertices, close=entity.closed)
2025
+ e = Wire.ByVertices(vertices, close=entity.closed, tolerance=tolerance, silent=silent)
2020
2026
  e = Topology.SetDictionary(e, d)
2021
2027
 
2022
2028
  elif entity_type == 'MESH':
@@ -2073,7 +2079,7 @@ class Topology():
2073
2079
  return converted_entities
2074
2080
 
2075
2081
  @staticmethod
2076
- def ByDXFPath(path, sides: int = 16):
2082
+ def ByDXFPath(path, sides: int = 16, tolerance: float = 0.0001, silent: bool = False):
2077
2083
  """
2078
2084
  Imports a list of topologies from a DXF file path.
2079
2085
  This is an experimental method with limited capabilities.
@@ -2084,6 +2090,10 @@ class Topology():
2084
2090
  The path to the DXF file.
2085
2091
  sides : int , optional
2086
2092
  The desired number of sides of splines. The default is 16.
2093
+ tolerance : float , optional
2094
+ The desired tolerance. The default is 0.0001.
2095
+ silent : bool , optional
2096
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
2087
2097
 
2088
2098
  Returns
2089
2099
  -------
@@ -2106,29 +2116,29 @@ class Topology():
2106
2116
  warnings.warn("Topology.ByDXFPath - Error: Could not import ezdxf library. Please install it manually. Returning None.")
2107
2117
  return None
2108
2118
  if not path:
2109
- print("Topology.ByDXFPath - Error: the input path parameter is not a valid path. Returning None.")
2119
+ if not silent:
2120
+ print("Topology.ByDXFPath - Error: the input path parameter is not a valid path. Returning None.")
2110
2121
  return None
2111
2122
  try:
2112
2123
  file = ezdxf.readfile(path)
2113
2124
  except:
2114
2125
  file = None
2115
2126
  if not file:
2116
- print("Topology.ByDXFPath - Error: the input file parameter is not a valid file. Returning None.")
2127
+ if not silent:
2128
+ print("Topology.ByDXFPath - Error: the input file parameter is not a valid file. Returning None.")
2117
2129
  return None
2118
- return Topology.ByDXFFile(file, sides=sides)
2130
+ return Topology.ByDXFFile(file, sides=sides, tolerance=tolerance, silent=silent)
2119
2131
 
2120
2132
  @staticmethod
2121
- def ByIFCFile(file, includeTypes=[], excludeTypes=[], transferDictionaries=False,
2133
+ def ByIFCFile(ifc_file, includeTypes=[], excludeTypes=[], transferDictionaries=False,
2122
2134
  removeCoplanarFaces=False,
2123
- xMin: float = -0.5, yMin: float = -0.5, zMin: float = -0.5,
2124
- xMax: float = 0.5, yMax: float = 0.5, zMax: float = 0.5,
2125
2135
  epsilon=0.0001, tolerance=0.0001, silent=False):
2126
2136
  """
2127
2137
  Create a topology by importing it from an IFC file.
2128
2138
 
2129
2139
  Parameters
2130
2140
  ----------
2131
- file : file object
2141
+ ifc_file : file object
2132
2142
  The input IFC file.
2133
2143
  includeTypes : list , optional
2134
2144
  The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
@@ -2138,18 +2148,6 @@ class Topology():
2138
2148
  If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. The default is False.
2139
2149
  removeCoplanarFaces : bool , optional
2140
2150
  If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
2141
- xMin : float, optional
2142
- The desired minimum value to assign for a vertex's X coordinate. The default is -0.5.
2143
- yMin : float, optional
2144
- The desired minimum value to assign for a vertex's Y coordinate. The default is -0.5.
2145
- zMin : float, optional
2146
- The desired minimum value to assign for a vertex's Z coordinate. The default is -0.5.
2147
- xMax : float, optional
2148
- The desired maximum value to assign for a vertex's X coordinate. The default is 0.5.
2149
- yMax : float, optional
2150
- The desired maximum value to assign for a vertex's Y coordinate. The default is 0.5.
2151
- zMax : float, optional
2152
- The desired maximum value to assign for a vertex's Z coordinate. The default is 0.5.
2153
2151
  epsilon : float , optional
2154
2152
  The desired epsilon (another form of tolerance) for finding if two faces are coplanar. The default is 0.01.
2155
2153
  tolerance : float , optional
@@ -2162,503 +2160,88 @@ class Topology():
2162
2160
  The created list of topologies.
2163
2161
 
2164
2162
  """
2163
+ import multiprocessing
2165
2164
  import ifcopenshell
2166
2165
  import ifcopenshell.geom
2167
- from topologicpy.Vertex import Vertex
2168
- from topologicpy.Wire import Wire
2169
2166
  from topologicpy.Face import Face
2170
2167
  from topologicpy.Cluster import Cluster
2171
2168
  from topologicpy.Topology import Topology
2172
2169
  from topologicpy.Dictionary import Dictionary
2173
- from concurrent.futures import ThreadPoolExecutor
2174
- import random
2175
-
2176
- # Early filtering in parallel (safe)
2177
- def is_valid(product):
2178
- is_a = product.is_a().lower()
2179
- include = [t.lower() for t in includeTypes]
2180
- exclude = [t.lower() for t in excludeTypes]
2181
- return (not include or is_a in include) and (is_a not in exclude)
2182
-
2183
- with ThreadPoolExecutor() as executor:
2184
- products = list(file.by_type("IfcProduct"))
2185
- valid_entities = list(executor.map(lambda p: p if is_valid(p) else None, products))
2186
- valid_entities = [e for e in valid_entities if e is not None]
2187
-
2170
+
2171
+ # 1 Guard against including and excluding the same types
2172
+ includeTypes = [s.lower() for s in includeTypes]
2173
+ excludeTypes = [s.lower() for s in excludeTypes]
2174
+ if len(includeTypes) > 0 and len(excludeTypes) > 0:
2175
+ excludeTypes = [s for s in excludeTypes if s not in includeTypes]
2176
+ # 2 Setup geometry settings
2188
2177
  settings = ifcopenshell.geom.settings()
2189
2178
  settings.set(settings.USE_WORLD_COORDS, True)
2179
+ # A string representation of the OCC representation
2180
+ settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.NATIVE)
2181
+ # A string representation of the OCC representation
2190
2182
  settings.set("iterator-output", ifcopenshell.ifcopenshell_wrapper.SERIALIZED)
2183
+
2184
+ # 3 Create iterator
2185
+ it = ifcopenshell.geom.iterator(settings, ifc_file, multiprocessing.cpu_count())
2186
+ if not it.initialize():
2187
+ if not silent:
2188
+ print("Topology.ByIFCFile")
2189
+ raise RuntimeError("Geometry iterator failed to initialize")
2191
2190
 
2191
+ # 4) Loop over shapes
2192
2192
  topologies = []
2193
- for entity in valid_entities:
2194
- if not hasattr(entity, "Representation") or not entity.Representation:
2195
- continue
2196
- shape = ifcopenshell.geom.create_shape(settings, entity)
2197
- topology = None
2198
- if hasattr(shape.geometry, 'brep_data'):
2199
- brep_string = shape.geometry.brep_data
2200
- topology = Topology.ByBREPString(brep_string)
2201
- if not topology:
2202
- if not silent:
2203
- print(f"Topology.ByIFCFile - Warning: Could not convert entity {getattr(entity, 'GlobalId', 0)} to a topology. Skipping.")
2204
- continue
2193
+
2194
+ while True:
2195
+ shape = it.get()
2196
+ element = ifc_file.by_guid(shape.guid)
2197
+ element_type = element.is_a().lower()
2198
+ if ((element_type in includeTypes) or (len(includeTypes) == 0)) and ((not element_type in excludeTypes) or (len(excludeTypes) == 0)):
2199
+ geom = shape.geometry
2200
+ topology = Topology.ByBREPString(geom.brep_data)
2201
+ faces = Topology.Faces(topology)
2202
+ new_faces = []
2203
+ for face in faces:
2204
+ eb = Face.ExternalBoundary(face)
2205
+ ib = Face.InternalBoundaries(face)
2206
+ f = Face.ByWires(eb, ib)
2207
+ new_faces.append(f)
2208
+ topology = Topology.SelfMerge(Cluster.ByTopologies(new_faces))
2205
2209
  if removeCoplanarFaces:
2206
2210
  topology = Topology.RemoveCoplanarFaces(topology, epsilon=epsilon, tolerance=tolerance)
2207
- else:
2208
- if not silent:
2209
- print(f"Topology.ByIFCFile - Warning: Entity {getattr(entity, 'GlobalId', 0)} does not have a BREP. Skipping.")
2210
- continue
2211
-
2212
-
2213
- if transferDictionaries:
2214
- entity_dict = {
2215
- "TOPOLOGIC_id": str(Topology.UUID(topology)),
2216
- "TOPOLOGIC_name": getattr(entity, 'Name', "Untitled"),
2217
- "TOPOLOGIC_type": Topology.TypeAsString(topology),
2218
- "IFC_global_id": getattr(entity, 'GlobalId', 0),
2219
- "IFC_name": getattr(entity, 'Name', "Untitled"),
2220
- "IFC_type": entity.is_a()
2221
- }
2222
-
2223
- # Optionally add property sets
2224
- psets = {}
2225
- if hasattr(entity, 'IsDefinedBy'):
2226
- for rel in entity.IsDefinedBy:
2227
- if rel.is_a('IfcRelDefinesByProperties'):
2228
- pdef = rel.RelatingPropertyDefinition
2229
- if pdef and pdef.is_a('IfcPropertySet'):
2230
- key = f"IFC_{pdef.Name}"
2231
- props = {}
2232
- for prop in pdef.HasProperties:
2233
- if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
2234
- props[f"IFC_{prop.Name}"] = prop.NominalValue.wrappedValue
2235
- psets[key] = props
2236
-
2237
- final_dict = Dictionary.ByPythonDictionary(entity_dict)
2238
- if psets:
2239
- pset_dict = Dictionary.ByPythonDictionary(psets)
2240
- final_dict = Dictionary.ByMergedDictionaries([final_dict, pset_dict])
2241
-
2242
- topology = Topology.SetDictionary(topology, final_dict)
2243
-
2244
- topologies.append(topology)
2245
-
2246
- final_topologies = []
2247
- for topology in topologies:
2248
- faces = []
2249
-
2250
- for w in Topology.Wires(topology):
2251
- # Skip trivial wires (e.g. edges)
2252
- if len(Topology.Vertices(w)) < 3:
2253
- continue
2254
-
2255
- # Only attempt face creation if the wire is closed
2256
- if Wire.IsClosed(w) and Wire.IsManifold(w):
2257
- f = Face.ByWire(w)
2258
- if f:
2259
- faces.append(f)
2260
- continue
2261
-
2262
- # fallback: keep wire
2263
- faces.append(w)
2264
-
2265
- # Avoid unnecessary Cluster/SelfMerge if there's only one face
2266
- if len(faces) == 1:
2267
- final_topology = faces[0]
2268
- else:
2269
- final_topology = Topology.SelfMerge(Cluster.ByTopologies(faces))
2270
- if final_topology == None:
2271
- final_topology = Cluster.ByTopologies(faces)
2272
- if transferDictionaries:
2273
- d = Topology.Dictionary(topology)
2274
- final_topology = Topology.SetDictionary(final_topology, d)
2275
-
2276
- final_topologies.append(final_topology)
2277
- return final_topologies
2278
-
2279
- @staticmethod
2280
- def _ByIFCFile_old(file, includeTypes=[], excludeTypes=[], transferDictionaries=False, removeCoplanarFaces=False):
2281
- """
2282
- Create a topology by importing it from an IFC file.
2283
-
2284
- Parameters
2285
- ----------
2286
- file : file object
2287
- The input IFC file.
2288
- includeTypes : list , optional
2289
- The list of IFC object types to include. It is case insensitive. If set to an empty list, all types are included. The default is [].
2290
- excludeTypes : list , optional
2291
- The list of IFC object types to exclude. It is case insensitive. If set to an empty list, no types are excluded. The default is [].
2292
- transferDictionaries : bool , optional
2293
- If set to True, the dictionaries from the IFC file will be transferred to the topology. Otherwise, they won't. The default is False.
2294
- removeCoplanarFaces : bool , optional
2295
- If set to True, coplanar faces are removed. Otherwise they are not. The default is False.
2296
- Returns
2297
- -------
2298
- list
2299
- The created list of topologies.
2300
-
2301
- """
2302
- import multiprocessing
2303
- from topologicpy.Cluster import Cluster
2304
- from topologicpy.Dictionary import Dictionary
2305
- import uuid
2306
- import random
2307
- import hashlib
2308
- import re
2309
- import numpy as np
2310
-
2311
- try:
2312
- import ifcopenshell
2313
- import ifcopenshell.geom
2314
- except:
2315
- print("Topology.ByIFCFile - Warning: Installing required ifcopenshell library.")
2316
- try:
2317
- os.system("pip install ifcopenshell")
2318
- except:
2319
- os.system("pip install ifcopenshell --user")
2320
- try:
2321
- import ifcopenshell
2322
- import ifcopenshell.geom
2323
- print("Topology.ByIFCFile - Warning: ifcopenshell library installed correctly.")
2324
- except:
2325
- warnings.warn("Topology.ByIFCFile - Error: Could not import ifcopenshell. Please try to install ifcopenshell manually. Returning None.")
2326
- return None
2327
- if not file:
2328
- print("Topology.ByIFCFile - Error: the input file parameter is not a valid file. Returning None.")
2329
- return None
2330
-
2331
- def clean_key(string):
2332
- # Replace any character that is not a letter, digit, or underscore with an underscore
2333
- cleaned_string = re.sub(r'[^a-zA-Z0-9_]', '_', string)
2334
- return cleaned_string
2335
-
2336
- def transform_wall_vertices(wall):
2337
-
2338
- # Relatives Placement abrufen und ausgeben
2339
- if wall.ObjectPlacement and wall.ObjectPlacement.RelativePlacement:
2340
- relative_placement = wall.ObjectPlacement.RelativePlacement
2341
- if relative_placement.is_a('IFCAXIS2PLACEMENT3D'):
2342
- location = relative_placement.Location
2343
- ref_direction = relative_placement.RefDirection
2344
- print("Relative Placement Location:", location.Coordinates)
2345
- if ref_direction:
2346
- print("Relative Placement RefDirection:", ref_direction.DirectionRatios)
2347
- else:
2348
- print("Relative Placement RefDirection: None")
2349
-
2350
- # IFCPRODUCTDEFINITIONSHAPE der Wand abrufen
2351
- product_definition_shape = wall.Representation
2352
- if not product_definition_shape:
2353
- print("Keine Repräsentation gefunden.")
2354
- return
2355
-
2356
- # Initialisieren von Variablen für Representation Type und Layer-Infos
2357
- representation_type = None
2358
- diverse_representation = False
2359
- layer_details = []
2360
-
2361
- if hasattr(product_definition_shape, 'HasShapeAspects'):
2362
- for aspect in product_definition_shape.HasShapeAspects:
2363
- for representation in aspect.ShapeRepresentations:
2364
- if representation.is_a('IFCSHAPEREPRESENTATION'):
2365
- for item in representation.Items:
2366
- if item.is_a('IFCEXTRUDEDAREASOLID'):
2367
- # Profilbeschreibung abrufen
2368
- profile = item.SweptArea
2369
- if profile.is_a('IFCARBITRARYCLOSEDPROFILEDEF'):
2370
- if not representation_type:
2371
- representation_type = "ArbitraryClosedProfil"
2372
- elif representation_type != "ArbitraryClosedProfil":
2373
- diverse_representation = True
2374
-
2375
- # Profilpunkte abrufen
2376
- if hasattr(profile, 'OuterCurve') and profile.OuterCurve.is_a('IFCINDEXEDPOLYCURVE'):
2377
- indexed_polycurve = profile.OuterCurve
2378
- if hasattr(indexed_polycurve, 'Points') and indexed_polycurve.Points.is_a('IFCCARTESIANPOINTLIST2D'):
2379
- point_list_2d = indexed_polycurve.Points
2380
- points = point_list_2d.CoordList
2381
- layer_info["Profilpunkte"] = points
2382
- else:
2383
- diverse_representation = True
2384
-
2385
- # Location und RefDirection abrufen
2386
- if item.Position.is_a('IFCAXIS2PLACEMENT3D'):
2387
- axis_placement = item.Position
2388
- location = axis_placement.Location
2389
- ref_direction = axis_placement.RefDirection
2390
- layer_info["Location"] = location.Coordinates
2391
- if ref_direction:
2392
- layer_info["RefDirection"] = ref_direction.DirectionRatios
2393
- else:
2394
- layer_info["RefDirection"] = None
2395
-
2396
- layer_details.append(layer_info)
2397
-
2398
- # Representation Type ausgeben
2399
- if diverse_representation:
2400
- representation_type = "divers"
2401
- print("Representation Type der Wand:", representation_type)
2402
-
2403
- # Layer-Details ausgeben
2404
- for index, layer in enumerate(layer_details):
2405
- print(f"\nLayer {index + 1} Details:")
2406
- print("Material:", layer.get("Material", "Nicht verfügbar"))
2407
- print("Extrusionsstärke:", layer.get("Extrusionsstärke", "Nicht verfügbar"))
2408
- print("Profilpunkte:", layer.get("Profilpunkte", "Nicht verfügbar"))
2409
- print("Location:", layer.get("Location", "Nicht verfügbar"))
2410
- print("RefDirection:", layer.get("RefDirection", "Nicht verfügbar"))
2411
-
2412
-
2413
-
2414
-
2415
-
2416
-
2417
- def extract_matrix_from_placement(placement):
2418
- """Constructs a transformation matrix from an IFC Local Placement."""
2419
- # Initialize identity matrix
2420
- matrix = np.identity(4)
2421
-
2422
- # Check if the placement is IfcLocalPlacement
2423
- if placement.is_a("IfcLocalPlacement"):
2424
- relative_placement = placement.RelativePlacement
2425
-
2426
- if relative_placement.is_a("IfcAxis2Placement3D"):
2427
- location = relative_placement.Location.Coordinates
2428
- z_dir = relative_placement.Axis.DirectionRatios if relative_placement.Axis else [0, 0, 1]
2429
- x_dir = relative_placement.RefDirection.DirectionRatios if relative_placement.RefDirection else [1, 0, 0]
2430
-
2431
- # Compute y direction (cross product of z and x)
2432
- y_dir = np.cross(z_dir, x_dir)
2211
+ if transferDictionaries:
2212
+ element_dict = {
2213
+ "TOPOLOGIC_id": str(Topology.UUID(topology)),
2214
+ "TOPOLOGIC_name": getattr(element, 'Name', "Untitled"),
2215
+ "TOPOLOGIC_type": Topology.TypeAsString(topology),
2216
+ "IFC_global_id": getattr(element, 'GlobalId', 0),
2217
+ "IFC_name": getattr(element, 'Name', "Untitled"),
2218
+ "IFC_type": element_type
2219
+ }
2220
+
2221
+ # Optionally add property sets
2222
+ psets = {}
2223
+ if hasattr(element, 'IsDefinedBy'):
2224
+ for rel in element.IsDefinedBy:
2225
+ if rel.is_a('IfcRelDefinesByProperties'):
2226
+ pdef = rel.RelatingPropertyDefinition
2227
+ if pdef and pdef.is_a('IfcPropertySet'):
2228
+ key = f"IFC_{pdef.Name}"
2229
+ props = {}
2230
+ for prop in pdef.HasProperties:
2231
+ if prop.is_a('IfcPropertySingleValue') and prop.NominalValue:
2232
+ props[f"IFC_{prop.Name}"] = prop.NominalValue.wrappedValue
2233
+ psets[key] = props
2234
+
2235
+ final_dict = Dictionary.ByPythonDictionary(element_dict)
2236
+ if psets:
2237
+ pset_dict = Dictionary.ByPythonDictionary(psets)
2238
+ final_dict = Dictionary.ByMergedDictionaries([final_dict, pset_dict])
2433
2239
 
2434
- # Construct the rotation matrix
2435
- rotation_matrix = np.array([
2436
- [x_dir[0], y_dir[0], z_dir[0], 0],
2437
- [x_dir[1], y_dir[1], z_dir[1], 0],
2438
- [x_dir[2], y_dir[2], z_dir[2], 0],
2439
- [0, 0, 0, 1]
2440
- ])
2441
-
2442
- # Translation vector
2443
- translation_vector = np.array([
2444
- [1, 0, 0, location[0]],
2445
- [0, 1, 0, location[1]],
2446
- [0, 0, 1, location[2]],
2447
- [0, 0, 0, 1]
2448
- ])
2449
-
2450
- # Combine the rotation matrix and the translation vector
2451
- matrix = np.dot(translation_vector, rotation_matrix)
2452
-
2453
- return matrix
2454
-
2455
- def apply_transformation(verts, matrix):
2456
- """Applies a 4x4 transformation matrix to a list of vertices."""
2457
- transformed_verts = []
2458
- for vert in verts:
2459
- print("vert:", vert)
2460
- v = np.array([vert[0], vert[1], vert[2], 1.0])
2461
- transformed_v = np.dot(matrix, v)
2462
- transformed_verts.append([transformed_v[0], transformed_v[1], transformed_v[2]])
2463
- return transformed_verts
2464
-
2465
- def get_entity_transformation_matrix(entity):
2466
- """Extracts the transformation matrix from an IFC entity."""
2467
- matrix = np.identity(4) # Default to an identity matrix
2468
- if hasattr(entity, "ObjectPlacement") and entity.ObjectPlacement:
2469
- placement = entity.ObjectPlacement
2470
- matrix = extract_matrix_from_placement(placement)
2471
- return matrix
2472
-
2473
- # Function to generate a unique random color based on material ID
2474
- def generate_color_for_material(material_id):
2475
- # Use a hash function to get a consistent "random" seed
2476
- hash_object = hashlib.sha1(material_id.encode())
2477
- seed = int(hash_object.hexdigest(), 16) % (10 ** 8)
2478
- random.seed(seed)
2479
- # Generate a random color
2480
- r = random.random()
2481
- g = random.random()
2482
- b = random.random()
2483
- return [r, g, b]
2484
-
2485
- # Function to get the material IDs associated with an entity
2486
- def get_material_ids_of_entity(entity):
2487
- return_dict = {}
2488
- material_names = []
2489
- material_ids = []
2490
- if hasattr(entity, "HasAssociations"):
2491
- for association in entity.HasAssociations:
2492
- if association.is_a("IfcRelAssociatesMaterial"):
2493
- material = association.RelatingMaterial
2494
- try:
2495
- material_name = material.Name
2496
- except:
2497
- material_name = material.to_string()
2498
- if material.is_a("IfcMaterial"):
2499
- material_ids.append(material.id())
2500
- material_names.append(material_name)
2501
- return_dict[clean_key(material_name)] = material.id
2502
- elif material.is_a("IfcMaterialList"):
2503
- for mat in material.Materials:
2504
- material_ids.append(mat.id())
2505
- try:
2506
- material_name = mat.Name
2507
- except:
2508
- material_name = mat.to_string()
2509
- material_names.append(material_name)
2510
- return_dict[clean_key(material_name)] = mat.id
2511
- elif material.is_a("IfcMaterialLayerSetUsage") or material.is_a("IfcMaterialLayerSet"):
2512
- for layer in material.ForLayerSet.MaterialLayers:
2513
- material_ids.append(layer.Material.id())
2514
- try:
2515
- material_name = layer.Name
2516
- except:
2517
- material_name = layer.to_string()
2518
- material_names.append(material_name)
2519
- return_dict[clean_key(material_name)] = layer.Material.id()
2520
- elif material.is_a("IfcMaterialConstituentSet"):
2521
- for constituent in material.MaterialConstituents:
2522
- material_ids.append(constituent.Material.id())
2523
- try:
2524
- material_name = constituent.Material.Name
2525
- except:
2526
- material_name = constituent.Material.to_string()
2527
- material_names.append(material_name)
2528
- return_dict[clean_key(material_name)] = constituent.Material.id()
2529
-
2530
- return return_dict
2531
-
2532
- def get_wall_layers(wall, matrix=None, transferDictionaries=False):
2533
- settings = ifcopenshell.geom.settings()
2534
- settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.CURVES_SURFACES_AND_SOLIDS)
2535
- settings.set(settings.USE_WORLD_COORDS, False)
2536
-
2537
- # IFCPRODUCTDEFINITIONSHAPE der Wand abrufen
2538
- product_definition_shape = wall.Representation
2539
- if not product_definition_shape:
2540
- print("Topology.ByIFCFile - Error: The object has no representation. Returning None")
2541
- return None
2240
+ topology = Topology.SetDictionary(topology, final_dict)
2241
+ topologies.append(topology)
2242
+ if not it.next():
2243
+ break
2542
2244
 
2543
- if hasattr(product_definition_shape, 'HasShapeAspects'):
2544
- for aspect in product_definition_shape.HasShapeAspects:
2545
- material_name = aspect.Name
2546
- for representation in aspect.ShapeRepresentations:
2547
- print(dir(representation))
2548
- axis_placement = representation.Position
2549
- location = axis_placement.Location
2550
- ref_direction = axis_placement.RefDirection
2551
- print("Location:", location)
2552
- print("Direction", ref_direction)
2553
- aspect_matrix = get_entity_transformation_matrix(representation)
2554
- print("Aspect Matrix:", aspect_matrix)
2555
- shape = ifcopenshell.geom.create_shape(settings, representation)
2556
- verts = shape.verts
2557
- edges = shape.edges
2558
- faces = shape.faces
2559
- grouped_verts = [ [verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
2560
- grouped_verts = apply_transformation(grouped_verts, aspect_matrix)
2561
- grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
2562
- grouped_faces = [ [faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]
2563
- topology = Topology.SelfMerge(Topology.ByGeometry(grouped_verts, grouped_edges, grouped_faces))
2564
- #matrix = shape.transformation.matrix
2565
- #topology = Topology.Transform(topology, matrix)
2566
- d = get_material_ids_of_entity(wall)
2567
- material_id = d.get(clean_key(material_name), 0)
2568
- if transferDictionaries:
2569
- keys = []
2570
- values = []
2571
- try:
2572
- entity_name = entity.Name
2573
- except:
2574
- entity_name = entity.to_str()
2575
- keys.append("TOPOLOGIC_id")
2576
- values.append(str(uuid.uuid4()))
2577
- keys.append("TOPOLOGIC_name")
2578
- values.append(entity_name)
2579
- keys.append("TOPOLOGIC_type")
2580
- values.append(Topology.TypeAsString(topology))
2581
- keys.append("IFC_id")
2582
- values.append(str(aspect.id))
2583
- #keys.append("IFC_guid")
2584
- #values.append(str(aspect.guid))
2585
- #keys.append("IFC_unique_id")
2586
- #values.append(str(aspect.unique_id))
2587
- keys.append("IFC_name")
2588
- values.append(entity_name)
2589
- #keys.append("IFC_type")
2590
- #values.append(aspect.type)
2591
- keys.append("IFC_material_id")
2592
- values.append(material_id)
2593
- keys.append("IFC_material_name")
2594
- values.append(material_name)
2595
- keys.append("TOPOLOGIC_color")
2596
- color = generate_color_for_material(str(material_id))
2597
- values.append(color)
2598
- d = Dictionary.ByKeysValues(keys, values)
2599
- topology = Topology.SetDictionary(topology, d)
2600
-
2601
- return topology
2602
-
2603
-
2604
- includeTypes = [s.lower() for s in includeTypes]
2605
- excludeTypes = [s.lower() for s in excludeTypes]
2606
- topologies = []
2607
- settings = ifcopenshell.geom.settings()
2608
- settings.set("dimensionality", ifcopenshell.ifcopenshell_wrapper.SOLID)
2609
- settings.set(settings.USE_WORLD_COORDS, True)
2610
- for entity in file.by_type('IfcProduct'): # You might want to refine the types you check
2611
- if hasattr(entity, "Representation") and entity.Representation:
2612
- print("Number of Representations:", len(entity.Representation.Representations))
2613
- for rep in entity.Representation.Representations:
2614
- print("Rep:", rep)
2615
- print(dir(rep))
2616
- matrix = get_entity_transformation_matrix(entity)
2617
- print(matrix)
2618
- if rep.is_a("IfcShapeRepresentation"):
2619
- # Generate the geometry for this entity
2620
- shape = ifcopenshell.geom.create_shape(settings, rep)
2621
- verts = shape.verts
2622
- edges = shape.edges
2623
- faces = shape.faces
2624
- grouped_verts = [ [verts[i], verts[i + 1], verts[i + 2]] for i in range(0, len(verts), 3)]
2625
- #grouped_verts = apply_transformation(grouped_verts, matrix)
2626
- grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)]
2627
- grouped_faces = [ [faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)]
2628
- topology = Topology.SelfMerge(Topology.ByGeometry(grouped_verts, grouped_edges, grouped_faces))
2629
- if removeCoplanarFaces:
2630
- topology = Topology.RemoveCoplanarFaces(topology)
2631
- if transferDictionaries:
2632
- keys = []
2633
- values = []
2634
- keys.append("TOPOLOGIC_id")
2635
- values.append(str(uuid.uuid4()))
2636
- keys.append("TOPOLOGIC_name")
2637
- values.append(shape.name)
2638
- keys.append("TOPOLOGIC_type")
2639
- values.append(Topology.TypeAsString(topology))
2640
- keys.append("IFC_id")
2641
- values.append(str(shape.id))
2642
- keys.append("IFC_guid")
2643
- values.append(str(shape.guid))
2644
- keys.append("IFC_unique_id")
2645
- values.append(str(shape.unique_id))
2646
- keys.append("IFC_name")
2647
- values.append(shape.name)
2648
- keys.append("IFC_type")
2649
- values.append(shape.type)
2650
- material_dict = get_material_ids_of_entity(entity)
2651
- keys.append("IFC_materials")
2652
- values.append(material_dict)
2653
- #keys.append("IFC_material_name")
2654
- #values.append(material_name)
2655
- #keys.append("TOPOLOGIC_color")
2656
- #color = generate_color_for_material(str(material_ids))
2657
- #values.append(color)
2658
- d = Dictionary.ByKeysValues(keys, values)
2659
- topology = Topology.SetDictionary(topology, d)
2660
- topology = Topology.Transform(topology, matrix)
2661
- topologies.append(topology)
2662
2245
  return topologies
2663
2246
 
2664
2247
  @staticmethod
@@ -3050,6 +2633,7 @@ class Topology():
3050
2633
  """
3051
2634
  from topologicpy.Vertex import Vertex
3052
2635
  from topologicpy.Edge import Edge
2636
+ from topologicpy.Wire import Wire
3053
2637
  from topologicpy.Face import Face
3054
2638
  from topologicpy.Cell import Cell
3055
2639
  from topologicpy.Cluster import Cluster
@@ -3061,19 +2645,19 @@ class Topology():
3061
2645
  print("Topology.ByMeshData - Error: The input dictionary parameter is not a valid dictionary. Returning None.")
3062
2646
  return None
3063
2647
 
3064
- vertices = dictionary['vertices']
3065
- edges = dictionary['edges']
3066
- faces = dictionary['faces']
3067
- cells = dictionary['cells']
2648
+ vertices = dictionary.get('vertices', [])
2649
+ edges = dictionary.get('edges', [])
2650
+ faces = dictionary.get('faces', [])
2651
+ cells = dictionary.get('cells', [])
3068
2652
  if not 'mode' in dictionary.keys():
3069
2653
  mode = 0
3070
2654
  else:
3071
- mode = dictionary['mode']
2655
+ mode = dictionary.get('mode', 0)
3072
2656
  if transferDictionaries == True:
3073
- vertex_dicts = dictionary['vertex_dicts']
3074
- edge_dicts = dictionary['edge_dicts']
3075
- face_dicts = dictionary['face_dicts']
3076
- cell_dicts = dictionary['cell_dicts']
2657
+ vertex_dicts = dictionary.get('vertex_dicts', [{}]*len(vertices))
2658
+ edge_dicts = dictionary.get('edge_dicts', [{}]*len(edges))
2659
+ face_dicts = dictionary. get('face_dicts', [{}]*len(faces))
2660
+ cell_dicts = dictionary.get('cell_dicts', [{}]*len(cells))
3077
2661
  else:
3078
2662
  vertex_dicts = [{}]*len(vertices)
3079
2663
  edge_dicts = [{}]*len(edges)
@@ -3103,6 +2687,15 @@ class Topology():
3103
2687
  if mode == 0:
3104
2688
  face_vertices = [top_verts[v] for v in face]
3105
2689
  top_face = Face.ByVertices(face_vertices, tolerance=tolerance, silent=silent)
2690
+ if not top_face:
2691
+ temp_wire = Wire.ByVertices(top_face, tolerance=tolerance, silent=silent)
2692
+ temp = Face.ByWire(temp_wire, tolerance=tolerance, silent=silent)
2693
+ if isinstance(temp, list):
2694
+ for temp_face in temp:
2695
+ if transferDictionaries:
2696
+ d = Dictionary.ByPythonDictionary(face_dicts[i])
2697
+ temp_face = Topology.SetDictionary(temp_face, d, silent=True)
2698
+ top_faces.append(temp_face)
3106
2699
  else:
3107
2700
  face_edges = [top_edges[e] for e in face]
3108
2701
  top_face = Face.ByEdges(face_edges, tolerance=tolerance, silent=silent)
@@ -6590,15 +6183,19 @@ class Topology():
6590
6183
  return False, None
6591
6184
  # Done with Exclusion Tests.
6592
6185
 
6593
- faces_a = Topology.Faces(topologyA)
6594
- faces_b = Topology.Faces(topologyB)
6595
- if len(faces_a) > 0 and len(faces_b) > 0:
6596
- largest_faces_a = Topology.LargestFaces(topologyA)
6597
- largest_faces_b = Topology.LargestFaces(topologyB)
6186
+ if Topology.IsInstance(topologyA, "face"):
6187
+ largest_faces_a = [topologyA]
6188
+ largest_faces_b = [topologyB]
6598
6189
  else:
6599
- if not silent:
6600
- print("Topology.IsSimilar - Error: The topologies do not have faces. Returning None.")
6601
- return False, None
6190
+ faces_a = Topology.Faces(topologyA)
6191
+ faces_b = Topology.Faces(topologyB)
6192
+ if len(faces_a) > 0 and len(faces_b) > 0:
6193
+ largest_faces_a = Topology.LargestFaces(topologyA)
6194
+ largest_faces_b = Topology.LargestFaces(topologyB)
6195
+ else:
6196
+ if not silent:
6197
+ print("Topology.IsSimilar - Error: The topologies do not have faces. Returning None.")
6198
+ return False, None
6602
6199
 
6603
6200
  # Process largest faces
6604
6201
  for face_a in largest_faces_a:
@@ -9162,6 +8759,9 @@ class Topology():
9162
8759
  The desired number of sides. The default is 16.
9163
8760
  tolerance : float , optional
9164
8761
  The desired tolerance. The default is 0.0001.
8762
+ silent : bool , optional
8763
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
8764
+
9165
8765
 
9166
8766
  Returns
9167
8767
  -------
@@ -9194,7 +8794,7 @@ class Topology():
9194
8794
  topologies.append(tempTopology)
9195
8795
  returnTopology = None
9196
8796
  if Topology.Type(topology) == Topology.TypeID("Vertex"):
9197
- returnTopology = Wire.ByVertices(topologies, False)
8797
+ returnTopology = Wire.ByVertices(topologies, close=False, tolerance=tolerance, silent=silent)
9198
8798
  elif Topology.Type(topology) == Topology.TypeID("Edge"):
9199
8799
  try:
9200
8800
  returnTopology = Shell.ByWires(topologies,triangulate=triangulate, tolerance=tolerance, silent=True)