topologicpy 0.8.97__py3-none-any.whl → 0.8.98__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/BVH.py CHANGED
@@ -342,12 +342,13 @@ class BVH:
342
342
  for topology in topologyList:
343
343
  if Topology.IsInstance(topology, "vertex"):
344
344
  x,y,z = Vertex.Coordinates(topology, mantissa=mantissa)
345
- points = [[x-tolerance, y-tolerance, z-tolerance], [x+tolerance, y+tolerance, z+tolerance]]
345
+ # points = [[x-tolerance, y-tolerance, z-tolerance], [x+tolerance, y+tolerance, z+tolerance]]
346
+ aabb_box = AABB(x-tolerance, y-tolerance, z-tolerance, x+tolerance, y+tolerance, z+tolerance)
346
347
  else:
347
348
  points = [Vertex.Coordinates(v, mantissa=mantissa) for v in Topology.Vertices(topology)]
348
- aabb_box = AABB.from_points(points, pad = tolerance)
349
+ aabb_box = AABB.from_points(points, pad = tolerance)
349
350
  return_topologies.extend([bvh.items[i] for i in BVH.QueryAABB(bvh, aabb_box)])
350
- return return_topologies
351
+ return return_topologies
351
352
 
352
353
  @staticmethod
353
354
  def Raycast(bvh, origin, direction: Tuple[float, float, float], mantissa: int = 6, silent: bool = False) -> List[int]:
topologicpy/Face.py CHANGED
@@ -1933,7 +1933,12 @@ class Face():
1933
1933
  return centroid
1934
1934
 
1935
1935
  shell = Topology.Triangulate(face)
1936
- ib = Shell.InternalBoundaries(shell)
1936
+ # ib = Shell.InternalEdges(shell)
1937
+ # centroids = [Topology.Centroid(t) for t in ib]
1938
+ faces = Topology.Faces(shell)
1939
+ centroids = [Topology.Centroid(t) for t in faces]
1940
+ cluster = Cluster.ByTopologies(centroids)
1941
+ return Vertex.NearestVertex(centroid, cluster)
1937
1942
  cluster = Cluster.ByTopologies(ib)
1938
1943
  edges = Topology.Edges(cluster)
1939
1944
  bvh = BVH.ByTopologies(edges, tolerance=tolerance, silent=True)
@@ -3356,7 +3361,7 @@ class Face():
3356
3361
  return Face.Rectangle(origin=origin, width=width, length=length, direction = direction, placement=placement, tolerance=tolerance)
3357
3362
 
3358
3363
  @staticmethod
3359
- def Rectangle(origin= None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
3364
+ def Rectangle(origin= None, width: float = 1.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001, silent: bool = True):
3360
3365
  """
3361
3366
  Creates a rectangle.
3362
3367
 
@@ -3374,6 +3379,8 @@ class Face():
3374
3379
  The description of the placement of the origin of the rectangle. This can be "center", "lowerleft", "upperleft", "lowerright", "upperright". It is case insensitive. Default is "center".
3375
3380
  tolerance : float , optional
3376
3381
  The desired tolerance. Default is 0.0001.
3382
+ silent : bool , optional
3383
+ If set to True, error and warning messages are suppressed. Default is False.
3377
3384
 
3378
3385
  Returns
3379
3386
  -------
@@ -3384,9 +3391,10 @@ class Face():
3384
3391
  from topologicpy.Wire import Wire
3385
3392
  from topologicpy.Topology import Topology
3386
3393
 
3387
- wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance)
3394
+ wire = Wire.Rectangle(origin=origin, width=width, length=length, direction=direction, placement=placement, tolerance=tolerance, silent=silent)
3388
3395
  if not Topology.IsInstance(wire, "Wire"):
3389
- print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.")
3396
+ if not silent:
3397
+ print("Face.Rectangle - Error: Could not create the base wire for the rectangle. Returning None.")
3390
3398
  return None
3391
3399
  return Face.ByWire(wire, tolerance=tolerance)
3392
3400
 
topologicpy/Plotly.py CHANGED
@@ -902,8 +902,22 @@ class Plotly:
902
902
  faceLegendLabel="Topology Faces",
903
903
  faceLegendRank=3,
904
904
  faceLegendGroup=3,
905
- intensityKey=None, intensities=[], colorScale="viridis",
906
- mantissa=6, tolerance=0.0001, silent=False):
905
+ intensityKey=None,
906
+ intensities=[],
907
+ material = "plastic",
908
+ materialKey=None,
909
+ ambient = None,
910
+ ambientKey=None,
911
+ diffuse = None,
912
+ diffuseKey=None,
913
+ specular = None,
914
+ specularKey=None,
915
+ roughness = None,
916
+ roughnessKey=None,
917
+ colorScale="viridis",
918
+ mantissa=6,
919
+ tolerance=0.0001,
920
+ silent=False):
907
921
  """
908
922
  Creates plotly face, edge, and vertex data.
909
923
 
@@ -1035,10 +1049,50 @@ class Plotly:
1035
1049
  The legend rank order of the faces of this topology. Default is 3.
1036
1050
  faceLegendGroup : int , optional
1037
1051
  The number of the face legend group to which the faces of this topology belong. Default is 3.
1038
- intensityKey: str, optional
1052
+ intensityKey : str, optional
1039
1053
  If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. Default is None.
1040
1054
  intensities : list , optional
1041
1055
  The list of intensities against which to index the intensity of the vertex. Default is [].
1056
+ material : str , optional
1057
+ The type of object material. Supported pre-built materials are:
1058
+ Preset Ambient Diffuse Specular Roughness Description
1059
+ --------------------------------------------------------------
1060
+ chalk 1.0 0.4 0.0 1.0 Very soft shading, low contrast
1061
+ concrete 0.85 0.75 0.05 0.9 Highly matte, micro-rough surface, minimal specular reflection
1062
+ eggshell 0.65 0.85 0.25 0.45 Slight sheen, soft highlights without gloss
1063
+ glossy 0.5 0.9 0.6 0.1 Highly polished appearance
1064
+ matte 0.9 0.7 0.0 1.0 Flat, non-reflective surfaces
1065
+ metallic 0.3 0.8 0.9 0.2 Strong, sharp reflections
1066
+ plastic 0.6 0.9 0.2 0.4 Soft highlights, good shape readability
1067
+ Default is plastic.
1068
+ materialKey : str , optional
1069
+ The dictionary key under which the material string is stored. Default is None.
1070
+ ambient : float , optional
1071
+ Controls the strength of ambient light applied uniformly to the surface.
1072
+ Higher values reduce shading contrast by increasing overall brightness.
1073
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is 0.6.
1074
+ ambientKey : str , optional
1075
+ The dictionary key under which the ambient value (float) is stored. Default is None.
1076
+ diffuse : float , optional
1077
+ Controls the strength of diffuse (Lambertian) lighting based on the angle
1078
+ between the light direction and the surface normal.
1079
+ Higher values enhance shape perception through shading.
1080
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
1081
+ diffuseKey : str , optional
1082
+ The dictionary key under which the diffuse value (float) is stored. Default is None.
1083
+ specular : float , optional
1084
+ Controls the intensity of specular (mirror-like) highlights on the surface.
1085
+ Higher values produce sharper and brighter highlights, giving a glossy appearance.
1086
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
1087
+ specularKey : str , optional
1088
+ The dictionary key under which the specular value (float) is stored. Default is None.
1089
+ roughness : float , optional
1090
+ Controls the spread of specular highlights on the surface.
1091
+ Lower values result in sharp, concentrated highlights (smooth surfaces),
1092
+ while higher values produce broader, softer highlights (rough surfaces).
1093
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
1094
+ roughnessKey : str , optional
1095
+ The dictionary key under which the roughness value (float) is stored. Default is None.
1042
1096
  colorScale : str , optional
1043
1097
  The desired type of plotly color scales to use (e.g. "Viridis", "Plasma"). Default is "Viridis". For a full list of names, see https://plotly.com/python/builtin-colorscales/.
1044
1098
  mantissa : int , optional
@@ -1061,12 +1115,29 @@ class Plotly:
1061
1115
  from topologicpy.Helper import Helper
1062
1116
  from time import time
1063
1117
 
1118
+ materials = {
1119
+ "chalk": {"ambient":1.0, "diffuse":0.4, "specular":0.0, "roughness":1.0},
1120
+ "concrete": {"ambient":0.85, "diffuse":0.75, "specular":0.05, "roughness":0.9},
1121
+ "eggshell": {"ambient":0.65, "diffuse":0.85, "specular":0.25, "roughness":0.45},
1122
+ "glossy": {"ambient":0.5, "diffuse":0.9, "specular":0.6, "roughness":0.1},
1123
+ "matte": {"ambient":0.9, "diffuse":0.7, "specular":0.0, "roughness":1.0},
1124
+ "metallic": {"ambient":0.3, "diffuse":0.8, "specular":0.9, "roughness":0.2},
1125
+ "plastic": {"ambient":0.6, "diffuse":0.9, "specular":0.2, "roughness":0.4}
1126
+ }
1064
1127
  def closest_index(input_value, values):
1065
1128
  return int(min(range(len(values)), key=lambda i: abs(values[i] - input_value)))
1066
1129
 
1067
1130
 
1068
- def faceData(vertices, faces, dictionaries=None, color="#FAFAFA", colorKey=None,
1069
- opacity=0.5, opacityKey=None, labelKey=None, groupKey=None,
1131
+ def faceData(vertices, faces, dictionaries=None,
1132
+ color="#FAFAFA",
1133
+ colorKey=None,
1134
+ opacity=0.5,
1135
+ opacityKey=None,
1136
+ ambient=0.6,
1137
+ diffuse=0.9,
1138
+ specular=0.2,
1139
+ roughness=0.4,
1140
+ labelKey=None, groupKey=None,
1070
1141
  minGroup=None, maxGroup=None, groups=[], legendLabel="Topology Faces",
1071
1142
  legendGroup=3, legendRank=3, showLegend=True, intensities=None, colorScale="viridis"):
1072
1143
  x = []
@@ -1163,9 +1234,8 @@ class Plotly:
1163
1234
  hoverinfo = 'text',
1164
1235
  text = labels,
1165
1236
  hovertext = labels,
1166
- flatshading = True,
1167
1237
  showscale = False,
1168
- lighting = {"facenormalsepsilon": 0},
1238
+ lighting=dict(ambient=ambient, diffuse=diffuse, specular=specular, roughness=roughness)
1169
1239
  )
1170
1240
  return fData
1171
1241
 
@@ -1274,15 +1344,52 @@ class Plotly:
1274
1344
  data.extend(Plotly.edgeData(vertices, edges, dictionaries=e_dictionaries, color=edgeColor, colorKey=edgeColorKey, width=edgeWidth, widthKey=edgeWidthKey, directed=directed, arrowSize=arrowSize, arrowSizeKey=arrowSizeKey, labelKey=edgeLabelKey, showEdgeLabel=showEdgeLabel, groupKey=edgeGroupKey, minGroup=edgeMinGroup, maxGroup=edgeMaxGroup, groups=edgeGroups, legendLabel=edgeLegendLabel, legendGroup=edgeLegendGroup, legendRank=edgeLegendRank, showLegend=showEdgeLegend, colorScale=colorScale))
1275
1345
 
1276
1346
  if showFaces and Topology.Type(topology) >= Topology.TypeID("Face"):
1347
+ d = Topology.Dictionary(topology)
1277
1348
  if not faceColorKey == None:
1278
- d = Topology.Dictionary(topology)
1279
1349
  faceColor = Dictionary.ValueAtKey(d, faceColorKey, faceColor)
1280
1350
  if not faceOpacityKey == None:
1281
- d = Topology.Dictionary(topology)
1282
1351
  d_opacity = Dictionary.ValueAtKey(d, key=faceOpacityKey)
1283
1352
  if not d_opacity == None:
1284
1353
  if 0 <= d_opacity <= 1:
1285
1354
  faceOpacity = d_opacity
1355
+
1356
+ if not materialKey == None:
1357
+ d_material = Dictionary.ValueAtKey(d, key=materialKey)
1358
+ if not d_material == None and isinstance(d_material, str):
1359
+ if material in list(materials.keys()):
1360
+ material = d_material
1361
+ if not material == None and isinstance(material, str):
1362
+ material = material.lower()
1363
+ if not material in list(materials.keys()):
1364
+ material = "plastic"
1365
+ if not ambientKey == None:
1366
+ d_ambient = Dictionary.ValueAtKey(d, key=ambientKey)
1367
+ if not d_ambient == None:
1368
+ if 0 <= d_ambient <= 1:
1369
+ ambient = d_ambient
1370
+ if not diffuseKey == None:
1371
+ d_diffuse = Dictionary.ValueAtKey(d, key=diffuseKey)
1372
+ if not d_diffuse == None:
1373
+ if 0 <= d_diffuse <= 1:
1374
+ diffuse = d_diffuse
1375
+ if not specularKey == None:
1376
+ d_specular = Dictionary.ValueAtKey(d, key=specularKey)
1377
+ if not d_specular == None:
1378
+ if 0 <= d_specular <= 1:
1379
+ specular = d_specular
1380
+ if not roughnessKey == None:
1381
+ d_roughness = Dictionary.ValueAtKey(d, key=roughnessKey)
1382
+ if not d_roughness == None:
1383
+ if 0 <= d_roughness <= 1:
1384
+ roughness = d_roughness
1385
+ if ambient == None:
1386
+ ambient = materials[material]['ambient']
1387
+ if diffuse == None:
1388
+ diffuse = materials[material]['diffuse']
1389
+ if specular == None:
1390
+ specular = materials[material]['specular']
1391
+ if roughness == None:
1392
+ roughness = materials[material]['roughness']
1286
1393
  if Topology.IsInstance(topology, "Face"):
1287
1394
  tp_faces = [topology]
1288
1395
  else:
@@ -1305,7 +1412,9 @@ class Plotly:
1305
1412
  vertices = geo['vertices']
1306
1413
  faces = geo['faces']
1307
1414
  if len(faces) > 0:
1308
- data.append(faceData(vertices, faces, dictionaries=f_dictionaries, color=faceColor, colorKey=faceColorKey, opacity=faceOpacity, opacityKey=faceOpacityKey, labelKey=faceLabelKey, groupKey=faceGroupKey, minGroup=faceMinGroup, maxGroup=faceMaxGroup, groups=faceGroups, legendLabel=faceLegendLabel, legendGroup=faceLegendGroup, legendRank=faceLegendRank, showLegend=showFaceLegend, intensities=intensityList, colorScale=colorScale))
1415
+ data.append(faceData(vertices, faces, dictionaries=f_dictionaries, color=faceColor, colorKey=faceColorKey, opacity=faceOpacity, opacityKey=faceOpacityKey,
1416
+ ambient=ambient, diffuse=diffuse, specular=specular, roughness=roughness,
1417
+ labelKey=faceLabelKey, groupKey=faceGroupKey, minGroup=faceMinGroup, maxGroup=faceMaxGroup, groups=faceGroups, legendLabel=faceLegendLabel, legendGroup=faceLegendGroup, legendRank=faceLegendRank, showLegend=showFaceLegend, intensities=intensityList, colorScale=colorScale))
1309
1418
  return data
1310
1419
 
1311
1420
  @staticmethod
topologicpy/Topology.py CHANGED
@@ -1476,7 +1476,7 @@ class Topology():
1476
1476
  if Topology.Type(topologyC) == Topology.TypeID("Vertex"):
1477
1477
  sinkVertices = [topologyC]
1478
1478
  elif hidimC >= Topology.TypeID("Vertex"):
1479
- sinkVertices = Topology.Vertices(topologyC)
1479
+ sinkVertices = Topology.Vertices(topologyC, silent=True)
1480
1480
  if len(sourceVertices) > 0 and len(sinkVertices) > 0:
1481
1481
  _ = Topology.TransferDictionaries(sourceVertices, sinkVertices, tolerance=tolerance)
1482
1482
 
@@ -1561,7 +1561,7 @@ class Topology():
1561
1561
  from topologicpy.Dictionary import Dictionary
1562
1562
 
1563
1563
  def bb(topology):
1564
- vertices = Topology.Vertices(topology)
1564
+ vertices = Topology.Vertices(topology, silent=True)
1565
1565
  x = []
1566
1566
  y = []
1567
1567
  z = []
@@ -3207,191 +3207,326 @@ class Topology():
3207
3207
 
3208
3208
  @staticmethod
3209
3209
  def ByOBJString(objString: str, mtlString: str = None,
3210
- defaultColor: list = [255,255,255], defaultOpacity: float = 1.0,
3210
+ defaultColor=None, defaultOpacity: float = 1.0,
3211
3211
  transposeAxes: bool = True, removeCoplanarFaces: bool = False,
3212
3212
  selfMerge: bool = False,
3213
- mantissa = 6, tolerance = 0.0001):
3213
+ mantissa: int = 6, tolerance: float = 0.0001):
3214
3214
  """
3215
- Imports a topology from OBJ and MTL strings.
3215
+ Imports a TopologicPy hierarchy from OBJ and optional MTL strings.
3216
3216
 
3217
- Parameters
3218
- ----------
3219
- objString : str
3220
- The string of the OBJ file.
3221
- mtlString : str , optional
3222
- The string of the MTL file. Default is None.
3223
- defaultColor : list , optional
3224
- The default color to use if none is specified in the string. Default is [255, 255, 255] (white).
3225
- defaultOpacity : float , optional
3226
- The default opacity to use if none is specified in the string. Default is 1.0 (fully opaque).
3227
- transposeAxes : bool , optional
3228
- If set to True the Z and Y axes are transposed. Otherwise, they are not. Default is True.
3229
- removeCoplanarFaces : bool , optional
3230
- If set to True, coplanar faces are merged. Default is True.
3231
- selfMerge : bool , optional
3232
- If set to True, the faces of the imported topologies will each be self-merged to create higher-dimensional objects. Otherwise, they remain a cluster of faces. Default is False.
3233
- mantissa : int , optional
3234
- The number of decimal places to round the result to. Default is 6.
3235
- tolerance : float , optional
3236
- The desired tolerance. Default is 0.0001
3217
+ Supported OBJ primitives
3218
+ ------------------------
3219
+ - v : vertices
3220
+ - l : polylines (edges-only / wire-only models)
3221
+ - f : faces (tri/quad/ngon; may be self-merged)
3222
+
3223
+ Grouping
3224
+ --------
3225
+ - g / o : groups/objects become separate returned topologies (one per group/object)
3226
+
3227
+ Materials
3228
+ ---------
3229
+ - usemtl + MTL Kd/d/Tr are used to set:
3230
+ color : [R,G,B] in 0..255
3231
+ opacity : 0..1
3237
3232
 
3238
3233
  Returns
3239
3234
  -------
3240
3235
  list
3241
- The imported topologies.
3242
-
3236
+ One TopologicPy topology per OBJ group/object:
3237
+ - If a group has faces: returns (best effort) a merged topology (or a Cluster of faces).
3238
+ - Else if a group has polylines/edges: returns a Cluster of Wires/Edges.
3239
+ - Else if a group has only points: returns a Cluster of Vertices.
3240
+ - If mixed: returns a Cluster containing the appropriate mix.
3243
3241
  """
3244
3242
  from topologicpy.Vertex import Vertex
3245
3243
  from topologicpy.Edge import Edge
3246
3244
  from topologicpy.Wire import Wire
3247
- from topologicpy.Face import Face
3248
- from topologicpy.Shell import Shell
3249
- from topologicpy.Cell import Cell
3250
3245
  from topologicpy.Cluster import Cluster
3251
- from topologicpy.Topology import Topology
3252
3246
  from topologicpy.Dictionary import Dictionary
3253
- from topologicpy.Helper import Helper
3247
+ from topologicpy.Topology import Topology
3248
+
3249
+ if defaultColor is None:
3250
+ defaultColor = [255, 255, 255]
3254
3251
 
3255
- def load_materials(mtl_string):
3252
+ # -----------------------------
3253
+ # MTL parsing (robust)
3254
+ # -----------------------------
3255
+ def load_materials(mtl_string: str):
3256
3256
  materials = {}
3257
3257
  if not mtl_string:
3258
3258
  return materials
3259
- current_material = None
3260
- lines = mtlString.split('\n')
3261
- for line in lines:
3262
- line = line.strip()
3263
- if line.startswith('#') or not line:
3259
+
3260
+ current = None
3261
+ for raw in mtl_string.splitlines():
3262
+ line = raw.strip()
3263
+ if not line or line.startswith("#"):
3264
3264
  continue
3265
3265
  parts = line.split()
3266
3266
  if not parts:
3267
3267
  continue
3268
- if parts[0] == 'newmtl':
3269
- current_material = parts[1]
3270
- materials[current_material] = {}
3271
- elif current_material:
3272
- if parts[0] == 'Kd': # Diffuse color
3273
- materials[current_material]['Kd'] = list(map(float, parts[1:4]))
3274
- elif parts[0] == 'Ka': # Ambient color
3275
- materials[current_material]['Ka'] = list(map(float, parts[1:4]))
3276
- elif parts[0] == 'Ks': # Specular color
3277
- materials[current_material]['Ks'] = list(map(float, parts[1:4]))
3278
- elif parts[0] == 'Ns': # Specular exponent
3279
- materials[current_material]['Ns'] = float(parts[1])
3280
- elif parts[0] == 'd': # Transparency
3281
- materials[current_material]['d'] = float(parts[1])
3282
- elif parts[0] == 'map_Kd': # Diffuse texture map
3283
- materials[current_material]['map_Kd'] = parts[1]
3284
- # Add more properties as needed
3268
+
3269
+ tag = parts[0]
3270
+ if tag == "newmtl" and len(parts) > 1:
3271
+ current = parts[1]
3272
+ materials[current] = {}
3273
+ elif current:
3274
+ if tag in ("Kd", "Ka", "Ks") and len(parts) >= 4:
3275
+ materials[current][tag] = list(map(float, parts[1:4]))
3276
+ elif tag == "Ns" and len(parts) >= 2:
3277
+ materials[current]["Ns"] = float(parts[1])
3278
+ elif tag in ("d", "Tr") and len(parts) >= 2:
3279
+ # d is opacity; Tr sometimes is transparency (inverse)
3280
+ val = float(parts[1])
3281
+ if tag == "Tr":
3282
+ val = 1.0 - val
3283
+ materials[current]["d"] = val
3284
+ elif tag == "map_Kd" and len(parts) >= 2:
3285
+ materials[current]["map_Kd"] = " ".join(parts[1:]) # allow spaces
3285
3286
  return materials
3286
3287
 
3287
3288
  materials = load_materials(mtlString)
3288
- vertices = []
3289
- textures = []
3290
- normals = []
3291
- groups = {}
3292
- current_group = None
3289
+
3290
+ def clamp01(x: float) -> float:
3291
+ return max(0.0, min(1.0, x))
3292
+
3293
+ def material_to_color_opacity(mat_name):
3294
+ color = defaultColor
3295
+ opacity = defaultOpacity
3296
+ if mat_name and mat_name in materials:
3297
+ m = materials[mat_name]
3298
+ if "Kd" in m and isinstance(m["Kd"], list) and len(m["Kd"]) >= 3:
3299
+ color = [int(round(clamp01(c) * 255.0, 0)) for c in m["Kd"][:3]]
3300
+ if "d" in m:
3301
+ try:
3302
+ opacity = float(m["d"])
3303
+ except Exception:
3304
+ opacity = defaultOpacity
3305
+ return color, opacity
3306
+
3307
+ # -----------------------------
3308
+ # OBJ parsing
3309
+ # -----------------------------
3310
+ verts_xyz = []
3311
+ groups = {} # name -> {"faces":[(triplets,mat)], "lines":[(indices,mat)], "points":[(idx,mat)]}
3312
+ current_group = "default"
3293
3313
  current_material = None
3294
- lines = objString.split('\n')
3295
- for line in lines:
3296
- line = line.strip()
3297
- if line.startswith('#'):
3314
+
3315
+ def ensure_group(name: str):
3316
+ if not name:
3317
+ name = "default"
3318
+ if name not in groups:
3319
+ groups[name] = {"faces": [], "lines": [], "points": []}
3320
+ return name
3321
+
3322
+ def resolve_index(idx: int, n: int):
3323
+ """
3324
+ OBJ indices are 1-based; negative indices are relative to the end.
3325
+ Returns a 0-based index or None.
3326
+ """
3327
+ if idx is None:
3328
+ return None
3329
+ if idx > 0:
3330
+ z = idx - 1
3331
+ else:
3332
+ z = n + idx # idx is negative
3333
+ if z < 0 or z >= n:
3334
+ return None
3335
+ return z
3336
+
3337
+ for raw in objString.splitlines():
3338
+ line = raw.strip()
3339
+ if not line or line.startswith("#"):
3298
3340
  continue
3299
3341
 
3300
3342
  parts = line.split()
3301
3343
  if not parts:
3302
3344
  continue
3303
3345
 
3304
- if parts[0] == 'v':
3305
- vertex = list(map(float, parts[1:4]))
3306
- vertex = [round(coord, mantissa) for coord in vertex]
3307
- if transposeAxes == True:
3308
- vertex = [vertex[0], -vertex[2], vertex[1]]
3309
- vertices.append(vertex)
3310
- elif parts[0] == 'vt':
3311
- texture = list(map(float, parts[1:3]))
3312
- textures.append(texture)
3313
- elif parts[0] == 'vn':
3314
- normal = list(map(float, parts[1:4]))
3315
- normals.append(normal)
3316
- elif parts[0] == 'f':
3317
- face = []
3318
- for part in parts[1:]:
3319
- indices = part.split('/')
3320
- vertex_index = int(indices[0]) - 1 if indices[0] else None
3321
- texture_index = int(indices[1]) - 1 if len(indices) > 1 and indices[1] else None
3322
- normal_index = int(indices[2]) - 1 if len(indices) > 2 and indices[2] else None
3323
- face.append((vertex_index, texture_index, normal_index))
3324
-
3325
- if current_group not in groups:
3326
- groups[current_group] = []
3327
- groups[current_group].append((face, current_material))
3328
- elif parts[0] == 'usemtl':
3346
+ tag = parts[0]
3347
+
3348
+ if tag == "v" and len(parts) >= 4:
3349
+ v = list(map(float, parts[1:4]))
3350
+ v = [round(c, mantissa) for c in v]
3351
+ if transposeAxes:
3352
+ v = [v[0], -v[2], v[1]]
3353
+ verts_xyz.append(v)
3354
+
3355
+ elif tag == "usemtl" and len(parts) >= 2:
3329
3356
  current_material = parts[1]
3330
- elif parts[0] == 'g' or parts[0] == 'o':
3331
- current_group = ' '.join(parts[1:]) if len(parts) > 1 else None
3332
-
3333
- obj_data = {
3334
- 'vertices': vertices,
3335
- 'textures': textures,
3336
- 'normals': normals,
3337
- 'materials': materials,
3338
- 'groups': groups
3339
- }
3340
- print(obj_data.keys())
3341
- vertices = obj_data['vertices']
3342
- groups = obj_data['groups']
3343
- materials = obj_data['materials']
3344
- names = list(groups.keys())
3345
- return_topologies = []
3346
- for i in range(len(names)):
3347
- object_faces = []
3348
- face_selectors = []
3349
- object_name = names[i]
3350
- faces = groups[object_name]
3351
- print("Number of faces:", len(faces))
3352
- f = faces[0] # Get object material from first face. Assume it is the material of the group
3353
- object_color = defaultColor
3354
- object_opacity = defaultOpacity
3355
- object_material = None
3356
- if len(f) >= 2:
3357
- object_material = f[1]
3358
- if object_material in materials.keys():
3359
- object_color = materials[object_material]['Kd']
3360
- object_color = [int(round(c*255,0)) for c in object_color]
3361
- object_opacity = materials[object_material]['d']
3362
- for f in faces:
3363
- indices = f[0]
3364
- face_material = f[1]
3365
- face_indices = []
3366
- for coordinate in indices:
3367
- face_indices.append(coordinate[0])
3368
- face = Topology.ByGeometry(vertices=vertices, faces=[face_indices])
3369
- object_faces.append(face)
3370
- if not face_material == object_material:
3371
- if face_material in materials.keys():
3372
- face_color = materials[face_material]['Kd']
3373
- face_color = [int(round(c*255,0)) for c in face_color]
3374
- face_opacity = materials[face_material]['d']
3375
- selector = Face.InternalVertex(face)
3376
- d = Dictionary.ByKeysValues(['color', 'opacity'], [face_color, face_opacity])
3377
- selector = Topology.SetDictionary(selector, d)
3378
- face_selectors.append(selector)
3379
-
3380
- #topology = Cluster.ByTopologies(object_faces)
3381
- return object_faces
3382
- # if Topology.IsInstance(topology, "Topology"):
3383
- # if selfMerge:
3384
- # topology = Topology.SelfMerge(topology)
3385
- # if Topology.IsInstance(topology, "Topology"):
3386
- # if removeCoplanarFaces:
3387
- # topology = Topology.RemoveCoplanarFaces(topology, tolerance=tolerance)
3388
- # if Topology.IsInstance(topology, "Topology"):
3389
- # d = Dictionary.ByKeysValues(['name', 'color', 'opacity'], [object_name, object_color, object_opacity])
3390
- # topology = Topology.SetDictionary(topology, d)
3391
- # if len(face_selectors) > 0:
3392
- # topology = Topology.TransferDictionariesBySelectors(topology, selectors=face_selectors, tranFaces=True, tolerance=tolerance)
3393
- # return_topologies.append(topology)
3394
- # return return_topologies
3357
+
3358
+ elif tag in ("g", "o"):
3359
+ name = " ".join(parts[1:]).strip() if len(parts) > 1 else "default"
3360
+ current_group = ensure_group(name)
3361
+
3362
+ elif tag == "f" and len(parts) >= 4:
3363
+ current_group = ensure_group(current_group)
3364
+
3365
+ triplets = []
3366
+ for p in parts[1:]:
3367
+ toks = p.split("/")
3368
+ vi = int(toks[0]) if toks[0] else None
3369
+ vti = int(toks[1]) if len(toks) > 1 and toks[1] else None
3370
+ vni = int(toks[2]) if len(toks) > 2 and toks[2] else None
3371
+
3372
+ vi = resolve_index(vi, len(verts_xyz))
3373
+ triplets.append((vi, vti, vni))
3374
+
3375
+ if any(t[0] is None for t in triplets):
3376
+ continue
3377
+
3378
+ groups[current_group]["faces"].append((triplets, current_material))
3379
+
3380
+ elif tag == "l" and len(parts) >= 3:
3381
+ # OBJ "l": polyline defined by vertex indices, optionally with texture indices.
3382
+ # We'll read only vertex indices (first number before any '/')
3383
+ current_group = ensure_group(current_group)
3384
+
3385
+ idxs = []
3386
+ ok = True
3387
+ for token in parts[1:]:
3388
+ # token can be "v" or "v/vt"
3389
+ subtoks = token.split("/")
3390
+ if not subtoks or not subtoks[0]:
3391
+ ok = False
3392
+ break
3393
+ vi = resolve_index(int(subtoks[0]), len(verts_xyz))
3394
+ if vi is None:
3395
+ ok = False
3396
+ break
3397
+ idxs.append(vi)
3398
+
3399
+ if not ok or len(idxs) < 2:
3400
+ continue
3401
+
3402
+ groups[current_group]["lines"].append((idxs, current_material))
3403
+
3404
+ elif tag == "p" and len(parts) >= 2:
3405
+ # OBJ "p": point list (rare). Keep as vertices.
3406
+ current_group = ensure_group(current_group)
3407
+ for token in parts[1:]:
3408
+ try:
3409
+ vi = resolve_index(int(token), len(verts_xyz))
3410
+ except Exception:
3411
+ vi = None
3412
+ if vi is not None:
3413
+ groups[current_group]["points"].append((vi, current_material))
3414
+
3415
+ else:
3416
+ # ignore: vt, vn, s, mtllib, etc. (vt/vn are not needed for Topologic hierarchy here)
3417
+ pass
3418
+
3419
+ # If OBJ never declared a group but had geometry, ensure "default" exists
3420
+ if not groups:
3421
+ ensure_group("default")
3422
+
3423
+ # -----------------------------
3424
+ # Build Topologic hierarchy
3425
+ # -----------------------------
3426
+ def set_dict(topo, group_name: str, mat_name: str):
3427
+ c, a = material_to_color_opacity(mat_name)
3428
+ d = Dictionary.ByKeysValues(
3429
+ ["name", "group", "material", "color", "opacity"],
3430
+ [group_name, group_name, mat_name if mat_name else "", c, a]
3431
+ )
3432
+ return Topology.SetDictionary(topo, d)
3433
+
3434
+ imported = []
3435
+
3436
+ for group_name, rec in groups.items():
3437
+ faces_rec = rec.get("faces", [])
3438
+ lines_rec = rec.get("lines", [])
3439
+ points_rec = rec.get("points", [])
3440
+
3441
+ topologies = []
3442
+
3443
+ # --- Faces
3444
+ face_topos = []
3445
+ for triplets, mat in faces_rec:
3446
+ face_indices = [t[0] for t in triplets]
3447
+ # Create a face from raw coordinates (Topology.ByGeometry expects vertices as coordinate lists)
3448
+ topo_face = Topology.ByGeometry(vertices=verts_xyz, faces=[face_indices])
3449
+ if topo_face is None:
3450
+ continue
3451
+ topo_face = set_dict(topo_face, group_name, mat)
3452
+ face_topos.append(topo_face)
3453
+
3454
+ if face_topos:
3455
+ # If requested, attempt to merge coplanar faces / build higher-dimensional objects
3456
+ face_cluster = Cluster.ByTopologies(face_topos)
3457
+ face_cluster = set_dict(face_cluster, group_name, faces_rec[0][1] if faces_rec else None)
3458
+
3459
+ if selfMerge or removeCoplanarFaces:
3460
+ merged = Topology.SelfMerge(face_cluster, tolerance=tolerance)
3461
+ if merged is not None:
3462
+ face_cluster = merged
3463
+ # Keep group-level dict (SelfMerge may drop dictionaries)
3464
+ face_cluster = set_dict(face_cluster, group_name, faces_rec[0][1] if faces_rec else None)
3465
+
3466
+ topologies.append(face_cluster)
3467
+
3468
+ # --- Lines (Wires/Edges)
3469
+ line_topos = []
3470
+ for idxs, mat in lines_rec:
3471
+ v_objs = [Vertex.ByCoordinates(*verts_xyz[i]) for i in idxs]
3472
+ if len(v_objs) == 2:
3473
+ topo = Edge.ByVertices(v_objs[0], v_objs[1], tolerance=tolerance)
3474
+ else:
3475
+ topo = Wire.ByVertices(v_objs, close=False, tolerance=tolerance)
3476
+ if topo is None:
3477
+ continue
3478
+ topo = set_dict(topo, group_name, mat)
3479
+ line_topos.append(topo)
3480
+
3481
+ if line_topos:
3482
+ line_cluster = Cluster.ByTopologies(line_topos)
3483
+ line_cluster = set_dict(line_cluster, group_name, lines_rec[0][1] if lines_rec else None)
3484
+ topologies.append(line_cluster)
3485
+
3486
+ # --- Points (Vertices)
3487
+ point_topos = []
3488
+ # If there are explicit "p" points, use them.
3489
+ # If there is no geometry at all except vertices in the file and no p/l/f,
3490
+ # we’ll fall back to all vertices later.
3491
+ for vi, mat in points_rec:
3492
+ v = Vertex.ByCoordinates(*verts_xyz[vi])
3493
+ v = set_dict(v, group_name, mat)
3494
+ point_topos.append(v)
3495
+
3496
+ if point_topos:
3497
+ point_cluster = Cluster.ByTopologies(point_topos)
3498
+ point_cluster = set_dict(point_cluster, group_name, points_rec[0][1] if points_rec else None)
3499
+ topologies.append(point_cluster)
3500
+
3501
+ # --- If group had *no* faces/lines/points but there are vertices in the file, return all vertices
3502
+ if not topologies and verts_xyz:
3503
+ all_vs = [Vertex.ByCoordinates(*xyz) for xyz in verts_xyz]
3504
+ v_cluster = Cluster.ByTopologies(all_vs)
3505
+ v_cluster = set_dict(v_cluster, group_name, None)
3506
+ topologies.append(v_cluster)
3507
+
3508
+ # --- Decide "correct hierarchy" output for this group
3509
+ if not topologies:
3510
+ continue
3511
+ elif len(topologies) == 1:
3512
+ group_topo = topologies[0]
3513
+ else:
3514
+ # Mixed geometry in same group: return a cluster containing the mixed subclusters
3515
+ group_topo = Cluster.ByTopologies(topologies)
3516
+ # Use first available material as group hint
3517
+ hint_mat = None
3518
+ if faces_rec:
3519
+ hint_mat = faces_rec[0][1]
3520
+ elif lines_rec:
3521
+ hint_mat = lines_rec[0][1]
3522
+ elif points_rec:
3523
+ hint_mat = points_rec[0][1]
3524
+ group_topo = set_dict(group_topo, group_name, hint_mat)
3525
+
3526
+ imported.append(group_topo)
3527
+
3528
+ return imported
3529
+
3395
3530
 
3396
3531
  @staticmethod
3397
3532
  def ByOCCTShape(occtShape):
@@ -4537,7 +4672,7 @@ class Topology():
4537
4672
  convex_hull = Topology.Unflatten(convex_hull_2, origin=centroid, direction=normal)
4538
4673
  return convex_hull
4539
4674
 
4540
- vertices = Topology.Vertices(topology)
4675
+ vertices = Topology.Vertices(topology, silent=True)
4541
4676
  if len(vertices) < 3:
4542
4677
  if not silent:
4543
4678
  print("Topology.ConvexHull - Error: Need at least 3 points to compute a convex hull.")
@@ -5723,7 +5858,7 @@ class Topology():
5723
5858
  topology = Topology.SelfMerge(topology, tolerance=tolerance)
5724
5859
  if Topology.TypeAsString(topology).lower() == "edge":
5725
5860
  return topology
5726
- vertices = Topology.Vertices(topology)
5861
+ vertices = Topology.Vertices(topology, silent=True)
5727
5862
  if len(vertices) < 2:
5728
5863
  print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
5729
5864
  return topology
@@ -5736,7 +5871,7 @@ class Topology():
5736
5871
  topology = Topology.SelfMerge(topology, tolerance=tolerance)
5737
5872
  if Topology.TypeAsString(topology).lower() == "vertex":
5738
5873
  return topology
5739
- vertices = Topology.Vertices(topology)
5874
+ vertices = Topology.Vertices(topology,silent=True)
5740
5875
  if len(vertices) < 1:
5741
5876
  print("Topology.Fix - Error: Desired topologyType cannot be achieved. Returning original topology.")
5742
5877
  return topology
@@ -6673,7 +6808,7 @@ class Topology():
6673
6808
  if not Topology.IsInstance(topology, "Topology"):
6674
6809
  print("Topology.IsPlanar - Error: the input topology parameter is not a valid topology. Returning None.")
6675
6810
  return None
6676
- vertices = Topology.Vertices(topology)
6811
+ vertices = Topology.Vertices(topology, silent=True)
6677
6812
 
6678
6813
  result = True
6679
6814
  if len(vertices) <= 3:
@@ -8493,7 +8628,7 @@ class Topology():
8493
8628
  vertices = [v for v in vertices if Topology.IsInstance(v, "Vertex")]
8494
8629
  if len(vertices) < 1:
8495
8630
  return topology
8496
- t_vertices = Topology.Vertices(topology)
8631
+ t_vertices = Topology.Vertices(topology, silent=True)
8497
8632
  t_edges = Topology.Edges(topology)
8498
8633
  if len(t_vertices) < 1:
8499
8634
  return topology
@@ -9011,7 +9146,7 @@ class Topology():
9011
9146
  topFaces = Topology.Faces(topology)
9012
9147
  topWires = Topology.Wires(topology)
9013
9148
  topEdges = Topology.Edges(topology)
9014
- topVertices = Topology.Vertices(topology)
9149
+ topVertices = Topology.Vertices(topology, silent=True)
9015
9150
  if len(topCC) == 1:
9016
9151
  cc = topCC[0]
9017
9152
  ccVertices = Topology.Vertices(cc)
@@ -9937,6 +10072,17 @@ class Topology():
9937
10072
  faceLegendLabel="Faces",
9938
10073
  intensityKey=None,
9939
10074
  intensities=[],
10075
+
10076
+ material = "plastic",
10077
+ materialKey=None,
10078
+ ambient = None,
10079
+ ambientKey=None,
10080
+ diffuse = None,
10081
+ diffuseKey=None,
10082
+ specular = None,
10083
+ specularKey=None,
10084
+ roughness = None,
10085
+ roughnessKey=None,
9940
10086
 
9941
10087
  width=950,
9942
10088
  height=500,
@@ -10139,6 +10285,46 @@ class Topology():
10139
10285
  If not None, the dictionary of each vertex is searched for the value associated with the intensity key. This value is then used to color-code the vertex based on the colorScale. Default is None.
10140
10286
  intensities : list , optional
10141
10287
  The list of intensities against which to index the intensity of the vertex. Default is [].
10288
+ material : str , optional
10289
+ The type of object material. Case in-sensitive. Supported pre-built materials are:
10290
+ Preset Ambient Diffuse Specular Roughness Description
10291
+ --------------------------------------------------------------
10292
+ chalk 1.0 0.4 0.0 1.0 Very soft shading, low contrast
10293
+ concrete 0.85 0.75 0.05 0.9 Highly matte, micro-rough surface, minimal specular reflection
10294
+ eggshell 0.65 0.85 0.25 0.45 Slight sheen, soft highlights without gloss
10295
+ glossy 0.5 0.9 0.6 0.1 Highly polished appearance
10296
+ matte 0.9 0.7 0.0 1.0 Flat, non-reflective surfaces
10297
+ metallic 0.3 0.8 0.9 0.2 Strong, sharp reflections
10298
+ plastic 0.6 0.9 0.2 0.4 Soft highlights, good shape readability
10299
+ Default is "plastic".
10300
+ materialKey : str , optional
10301
+ The dictionary key under which the material string is stored. Default is None.
10302
+ ambient : float , optional
10303
+ Controls the strength of ambient light applied uniformly to the surface.
10304
+ Higher values reduce shading contrast by increasing overall brightness.
10305
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is 0.6.
10306
+ ambientKey : str , optional
10307
+ The dictionary key under which the ambient value (float) is stored. Default is None.
10308
+ diffuse : float , optional
10309
+ Controls the strength of diffuse (Lambertian) lighting based on the angle
10310
+ between the light direction and the surface normal.
10311
+ Higher values enhance shape perception through shading.
10312
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
10313
+ diffuseKey : str , optional
10314
+ The dictionary key under which the diffuse value (float) is stored. Default is None.
10315
+ specular : float , optional
10316
+ Controls the intensity of specular (mirror-like) highlights on the surface.
10317
+ Higher values produce sharper and brighter highlights, giving a glossy appearance.
10318
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
10319
+ specularKey : str , optional
10320
+ The dictionary key under which the specular value (float) is stored. Default is None.
10321
+ roughness : float , optional
10322
+ Controls the spread of specular highlights on the surface.
10323
+ Lower values result in sharp, concentrated highlights (smooth surfaces),
10324
+ while higher values produce broader, softer highlights (rough surfaces).
10325
+ Typical range is [0, 1]. This over-rides the material pre-sets. Default is None.
10326
+ roughnessKey : str , optional
10327
+ The dictionary key under which the roughness value (float) is stored. Default is None.
10142
10328
  showScale : bool , optional
10143
10329
  If set to True, the colorbar is shown. Default is False.
10144
10330
  cbValues : list , optional
@@ -10333,6 +10519,16 @@ class Topology():
10333
10519
  faceLegendGroup=topology_counter+3,
10334
10520
  intensityKey=intensityKey,
10335
10521
  intensities=intensities,
10522
+ material=material,
10523
+ materialKey=materialKey,
10524
+ ambient=ambient,
10525
+ ambientKey=ambientKey,
10526
+ diffuse=diffuse,
10527
+ diffuseKey=diffuseKey,
10528
+ specular=specular,
10529
+ specularKey=specularKey,
10530
+ roughness=roughness,
10531
+ roughnessKey=roughnessKey,
10336
10532
  colorScale=colorScale,
10337
10533
  mantissa=mantissa,
10338
10534
  tolerance=tolerance,
@@ -11276,7 +11472,7 @@ class Topology():
11276
11472
  topology = Topology.Triangulate(topology)
11277
11473
  if not Topology.IsInstance(origin, "Vertex"):
11278
11474
  origin = Topology.Centroid(topology)
11279
- vertices = Topology.Vertices(topology)
11475
+ vertices = Topology.Vertices(topology, silent=True)
11280
11476
  zList = [Vertex.Z(v, mantissa=mantissa) for v in vertices]
11281
11477
  z_min = min(zList)
11282
11478
  maxZ = max(zList)
@@ -11330,7 +11526,7 @@ class Topology():
11330
11526
  if not Topology.IsInstance(origin, "Vertex"):
11331
11527
  origin = Topology.Centroid(topology)
11332
11528
 
11333
- vertices = Topology.Vertices(topology)
11529
+ vertices = Topology.Vertices(topology, silent=True)
11334
11530
  zList = [Vertex.Z(v, mantissa=mantissa) for v in vertices]
11335
11531
  z_min = min(zList)
11336
11532
  maxZ = max(zList)
@@ -11392,7 +11588,7 @@ class Topology():
11392
11588
  return unflat_topology
11393
11589
 
11394
11590
  @staticmethod
11395
- def Vertices(topology, silent: bool = False):
11591
+ def Vertices(topology, silent: bool = True):
11396
11592
  """
11397
11593
  Returns the vertices of the input topology.
11398
11594
 
@@ -11694,6 +11890,8 @@ class Topology():
11694
11890
  The input topology with the dictionaries transferred to its subtopologies.
11695
11891
 
11696
11892
  """
11893
+ import time
11894
+ # timePrep = time.time()
11697
11895
  from topologicpy.Vertex import Vertex
11698
11896
  from topologicpy.Cluster import Cluster
11699
11897
  from topologicpy.Dictionary import Dictionary
@@ -11736,7 +11934,6 @@ class Topology():
11736
11934
  return [topo]
11737
11935
  else:
11738
11936
  return Topology.Vertices(topo, silent=True)
11739
-
11740
11937
  vertices = []
11741
11938
  edges = []
11742
11939
  faces = []
@@ -11754,14 +11951,31 @@ class Topology():
11754
11951
  primitives.extend(faces)
11755
11952
  primitives.extend(edges)
11756
11953
  primitives.extend(vertices)
11757
- cluster = Cluster.ByTopologies(primitives)
11954
+ # print("bvh preparation", f"{time.time() - timePrep:.4f}s", len(primitives))
11955
+
11956
+ # timeStart = time.time()
11957
+
11958
+ bvh = BVH.ByTopologies(primitives, tolerance=tolerance, silent=True)
11959
+ # print("bvh created", f"{time.time() - timeStart:.4f}s", len(bvh.items))
11960
+
11758
11961
  for s in selectors:
11759
- status, element = Vertex.IsInternal(s, cluster, identify=True, tolerance=tolerance)
11760
- if status:
11761
- d1 = Topology.Dictionary(s)
11762
- d2 = Topology.Dictionary(element)
11763
- d3 = Dictionary.ByMergedDictionaries(d1, d2)
11764
- element = Topology.SetDictionary(element, d3)
11962
+ try:
11963
+ candidates = BVH.Clashes(bvh, s, tolerance=tolerance) or []
11964
+ except Exception as e:
11965
+ # print(f"BVH clash query failed for a selector. Trying fallback. {e}")
11966
+ # Fallback if your BVH needs a non-degenerate query
11967
+ candidates = primitives
11968
+
11969
+ if not candidates:
11970
+ continue
11971
+ for element in candidates:
11972
+ status = Vertex.IsInternal(s, element, tolerance=tolerance)
11973
+ if status:
11974
+ d1 = Topology.Dictionary(s)
11975
+ d2 = Topology.Dictionary(element)
11976
+ d3 = Dictionary.ByMergedDictionaries(d1, d2)
11977
+ element = Topology.SetDictionary(element, d3)
11978
+ # print("bvh query done", f"{time.time() - timeStart:.4f}s")
11765
11979
  return topology
11766
11980
 
11767
11981
  @staticmethod
@@ -11885,7 +12099,7 @@ class Topology():
11885
12099
  silent=silent)
11886
12100
 
11887
12101
  if transferDictionaries == True:
11888
- vertices = Topology.Vertices(topology)
12102
+ vertices = Topology.Vertices(topology, silent=True)
11889
12103
  edges = Topology.Edges(topology, silent=True)
11890
12104
  wires = Topology.Wires(topology, silent=True)
11891
12105
  faces = Topology.Faces(topology, silent=True)
topologicpy/Vertex.py CHANGED
@@ -1293,7 +1293,13 @@ class Vertex():
1293
1293
  # --------------------------
1294
1294
  points = [Vertex.Coordinates(v) for v in Topology.Vertices(topology)]
1295
1295
  aabb = AABB.from_points(points, pad=tolerance)
1296
+ if(Vertex.Coordinates(vertex) is None):
1297
+ if identify:
1298
+ return (False, None)
1299
+ return False
1296
1300
  if not aabb.contains_point(Vertex.Coordinates(vertex)):
1301
+ if identify:
1302
+ return (False, None)
1297
1303
  return False
1298
1304
 
1299
1305
  # --------------------------
@@ -1333,6 +1339,8 @@ class Vertex():
1333
1339
  edges = [] if faces or cells else collect_edges(topology)
1334
1340
  vertices = [] if edges or faces or cells else collect_vertices(topology)
1335
1341
  if not cells and not faces and not edges and not vertices:
1342
+ if identify:
1343
+ return (False, None)
1336
1344
  return False
1337
1345
 
1338
1346
  # --------------------------
@@ -1345,12 +1353,14 @@ class Vertex():
1345
1353
  primitives.extend(cells)
1346
1354
  bvh = BVH.ByTopologies(primitives, maxLeafSize=maxLeafSize, tolerance=tolerance, silent=True)
1347
1355
  try:
1348
- candidates = BVH.Clashes(bvh, vertex, tolernace=tolerance) or []
1356
+ candidates = BVH.Clashes(bvh, vertex, tolerance=tolerance) or []
1349
1357
  except Exception:
1350
1358
  # Fallback if your BVH needs a non-degenerate query
1351
1359
  candidates = primitives
1352
1360
 
1353
1361
  if not candidates:
1362
+ if identify:
1363
+ return (False, None)
1354
1364
  return False
1355
1365
 
1356
1366
  # sort by types so that priority is given to lower dimensional types (e.g. vertices, then edges, then faces, then cells)
@@ -2313,6 +2323,29 @@ class Vertex():
2313
2323
  coords = [round(v, mantissa) for v in coords]
2314
2324
  return Vertex.ByCoordinates(coords)
2315
2325
 
2326
+ @staticmethod
2327
+ def Weld(vertices: list, mantissa: int = 6, tolerance: float = 0.0001):
2328
+ """
2329
+ Returns a list of vertices where vertices within a specified tolerance distance are fused while retaining duplicates, ensuring that vertices with nearly identical coordinates are replaced by a single shared coordinate.
2330
+
2331
+ Parameters
2332
+ ----------
2333
+ vertices : list
2334
+ The input list of topologic vertices.
2335
+ mantissa : int , optional
2336
+ The desired length of the mantissa for retrieving vertex coordinates. Default is 6.
2337
+ tolerance : float , optional
2338
+ The desired tolerance for computing if vertices need to be fused. Any vertices that are closer to each other than this tolerance will be fused. Default is 0.0001.
2339
+
2340
+ Returns
2341
+ -------
2342
+ list
2343
+ The list of fused vertices. This list contains the same number of vertices and in the same order as the input list of vertices. However, the coordinates
2344
+ of these vertices have now been modified so that they are exactly the same with other vertices that are within the tolerance distance.
2345
+
2346
+ """
2347
+ return Vertex.Fuse(vertices = vertices, mantissa = mantissa, tolerance = tolerance)
2348
+
2316
2349
  @staticmethod
2317
2350
  def X(vertex, mantissa: int = 6) -> float:
2318
2351
  """
topologicpy/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '0.8.97'
1
+ __version__ = '0.8.98'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: topologicpy
3
- Version: 0.8.97
3
+ Version: 0.8.98
4
4
  Summary: An AI-Powered Spatial Modelling and Analysis Software Library for Architecture, Engineering, and Construction.
5
5
  Author-email: Wassim Jabi <wassim.jabi@gmail.com>
6
6
  License: AGPL v3 License
@@ -1,6 +1,6 @@
1
1
  topologicpy/ANN.py,sha256=gpflv4lFypOW789vO7mSkMLaMF_ZftVOCqCvtGr6-JA,47873
2
2
  topologicpy/Aperture.py,sha256=wNn5miB_IrGCBYuQ18HXQYRva20dUC3id4AJCulL7to,2723
3
- topologicpy/BVH.py,sha256=ts0Ru24ILjjfHa54SYNhMc8Jkyxwej1DV0Jv7P_6BoU,22513
3
+ topologicpy/BVH.py,sha256=cdRld28xxZgKbY-k45WN4PKzLRGCf9XSrFOGYIvPG2o,22615
4
4
  topologicpy/CSG.py,sha256=09la1-xzS9vr-WnV7tpJ0I-mkZ-XY0MRSd5iB50Nfgw,15556
5
5
  topologicpy/Cell.py,sha256=CuJogHS7N7svEKbEazYrYyOhLBb38vlF2Xkwqg3mS3w,201692
6
6
  topologicpy/CellComplex.py,sha256=B8bAW6M5fClfXb9nSLDhrgtNRlU888Z4EcUzBZtBqss,68558
@@ -11,7 +11,7 @@ topologicpy/DGL.py,sha256=O7r22ss0tgak4kWaACkyExhR_rZ46O_bqIBpxAcwJkw,138918
11
11
  topologicpy/Dictionary.py,sha256=3QRxvobXxaCb7TVeiNgG3Ka3R_1Hmco-njZCVjKNQBM,78073
12
12
  topologicpy/Edge.py,sha256=m_ThzU748H-vySQHx15VBIQCekWBbbISns29cnbEGfs,77593
13
13
  topologicpy/EnergyModel.py,sha256=MEai1GF1hINeH5bhclJj_lpMU3asFTvW2RlPm40GNj4,57794
14
- topologicpy/Face.py,sha256=B1w9D0vqbEllyI_N6XlS0cjS7Cysoa-wC_df4DsmvNE,203233
14
+ topologicpy/Face.py,sha256=XEs6cSDBLzhRQLtNydIOJVLCvetkR6E4HE7KVeqhngE,203677
15
15
  topologicpy/Graph.py,sha256=7C9Cw3-0L9IqKsCTscHIo9luKoC-80s_-xXDxsBn95w,868150
16
16
  topologicpy/Grid.py,sha256=3OsBMyHh4w8gpFOTMKHMNTpo62V0CwRNu5cwm87yDUA,18421
17
17
  topologicpy/Helper.py,sha256=NsmMlbbKFPRX6jfoko-ZQVQ7MBsfVp9FD0ZvC2U7q-8,32002
@@ -19,21 +19,21 @@ topologicpy/Honeybee.py,sha256=dBk01jIvxjQMGHqSarM1Cukv16ot4Op7Dwlitn2OMoc,48990
19
19
  topologicpy/Kuzu.py,sha256=fxaDPWM5Xam7Tot0olPZsazS_7xgE_Vo-jf0Sbv3VZE,36298
20
20
  topologicpy/Matrix.py,sha256=bOofT34G3YHu9aMIWx60YHAJga4R0GbDjsZBUD4Hu_k,22706
21
21
  topologicpy/Neo4j.py,sha256=J8jU_mr5-mWC0Lg_D2dMjMlx1rY_eh8ks_aubUuTdWw,22319
22
- topologicpy/Plotly.py,sha256=Uy-UKFfJ408Vc0rcWD_lWiz5QAYjTNMtvDjWXMWIDQk,123088
22
+ topologicpy/Plotly.py,sha256=NqTdQYOnc2WLx13-vIZnumPzPD5IReQmj43pmBhufx4,129402
23
23
  topologicpy/Polyskel.py,sha256=oVfM4lqSMPTjnkHfsRU9VI8Blt6Vf0LVPkD9ebz7Wmw,27082
24
24
  topologicpy/PyG.py,sha256=wOsoBFxMgwZYWjj86OMkz_PJuQ02locV_djhSDD6dVc,109644
25
25
  topologicpy/ShapeGrammar.py,sha256=q_BvMKOBDW3GVSRjPLIGAZkHW2egw3mTOPzIyEpYOLg,23349
26
26
  topologicpy/Shell.py,sha256=2EPzDT_t0IAjBRYPDuKNAz_Ax_HaEkvNpXBxDkPdcTg,101084
27
27
  topologicpy/Speckle.py,sha256=-eiTqJugd7pHiHpD3pDUcDO6CGhVyPV14HFRzaqEoaw,18187
28
28
  topologicpy/Sun.py,sha256=ezisiHfc2nd7A_8w0Ykq2VgbS0A9WNSg-tBwvfTQAVM,36735
29
- topologicpy/Topology.py,sha256=Kj3jzEQZLtlF-OWclcEGWxn61bRaNheaHKvRwJfOjb0,565597
29
+ topologicpy/Topology.py,sha256=DZ1C7lhaEq-gORVkFuttd117SsFGWbjGyAINH3Ityi4,574696
30
30
  topologicpy/Vector.py,sha256=9COmLAEo3GSDntVFWligGFKJbEqUTSGmDWQDxpvfR-M,47485
31
- topologicpy/Vertex.py,sha256=U27XJ4Cst9KbzVzIi9p_EStfORHVp9cPaDJbJW7NnxQ,98021
31
+ topologicpy/Vertex.py,sha256=tO8A6du8AuqObSaKcdg6rf4096sfBQ3ZZ9yRNuOt_NU,99607
32
32
  topologicpy/Wire.py,sha256=3TkcBmkfGIL8Heffwnc1DQtkKyFG3qvV-kJ-lvXXB0g,273765
33
33
  topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
34
- topologicpy/version.py,sha256=xkAWU_wH3G7m4Ee1I6N049xSQRkjPtyviSda_8D2UHA,23
35
- topologicpy-0.8.97.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
36
- topologicpy-0.8.97.dist-info/METADATA,sha256=Wt9YA9TiW8yHxJoQDEjcv2uS2SXHTCMXnPUzs6zHMhY,10535
37
- topologicpy-0.8.97.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
38
- topologicpy-0.8.97.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
39
- topologicpy-0.8.97.dist-info/RECORD,,
34
+ topologicpy/version.py,sha256=7BBu0vOw-A-OJaqnGWj1psRzl_NVgYN_GFKu2htJik4,23
35
+ topologicpy-0.8.98.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
36
+ topologicpy-0.8.98.dist-info/METADATA,sha256=eObpiO-eV-0F-eooxd1qylFZ5cW2S7l9vVyb6HX-Too,10535
37
+ topologicpy-0.8.98.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
38
+ topologicpy-0.8.98.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
39
+ topologicpy-0.8.98.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.10.1)
2
+ Generator: setuptools (80.10.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5