topologicpy 0.8.97__py3-none-any.whl → 0.8.99__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/ANN.py +1 -1
- topologicpy/Aperture.py +1 -1
- topologicpy/BVH.py +5 -4
- topologicpy/CSG.py +1 -1
- topologicpy/Cell.py +1 -1
- topologicpy/CellComplex.py +1 -1
- topologicpy/Cluster.py +1 -1
- topologicpy/Color.py +1 -1
- topologicpy/Context.py +1 -1
- topologicpy/DGL.py +1 -1
- topologicpy/Dictionary.py +92 -1
- topologicpy/Edge.py +1 -1
- topologicpy/EnergyModel.py +1 -1
- topologicpy/Face.py +13 -5
- topologicpy/Graph.py +887 -4
- topologicpy/Grid.py +1 -1
- topologicpy/Helper.py +1 -1
- topologicpy/Honeybee.py +1 -1
- topologicpy/Matrix.py +1 -1
- topologicpy/Neo4j.py +1 -1
- topologicpy/Plotly.py +120 -11
- topologicpy/Polyskel.py +1 -1
- topologicpy/PyG.py +1287 -2308
- topologicpy/ShapeGrammar.py +1 -1
- topologicpy/Shell.py +1 -1
- topologicpy/Speckle.py +1 -1
- topologicpy/Sun.py +1 -1
- topologicpy/Topology.py +387 -173
- topologicpy/Vector.py +1 -1
- topologicpy/Vertex.py +35 -2
- topologicpy/Wire.py +1 -1
- topologicpy/__init__.py +1 -1
- topologicpy/version.py +1 -1
- {topologicpy-0.8.97.dist-info → topologicpy-0.8.99.dist-info}/METADATA +1 -1
- topologicpy-0.8.99.dist-info/RECORD +39 -0
- {topologicpy-0.8.97.dist-info → topologicpy-0.8.99.dist-info}/WHEEL +1 -1
- topologicpy-0.8.97.dist-info/RECORD +0 -39
- {topologicpy-0.8.97.dist-info → topologicpy-0.8.99.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.97.dist-info → topologicpy-0.8.99.dist-info}/top_level.txt +0 -0
topologicpy/Topology.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright (C)
|
|
1
|
+
# Copyright (C) 2026
|
|
2
2
|
# Wassim Jabi <wassim.jabi@gmail.com>
|
|
3
3
|
#
|
|
4
4
|
# This program is free software: you can redistribute it and/or modify it under
|
|
@@ -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
|
|
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
|
|
3215
|
+
Imports a TopologicPy hierarchy from OBJ and optional MTL strings.
|
|
3216
3216
|
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
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
|
-
|
|
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.
|
|
3247
|
+
from topologicpy.Topology import Topology
|
|
3248
|
+
|
|
3249
|
+
if defaultColor is None:
|
|
3250
|
+
defaultColor = [255, 255, 255]
|
|
3254
3251
|
|
|
3255
|
-
|
|
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
|
-
|
|
3260
|
-
|
|
3261
|
-
for
|
|
3262
|
-
line =
|
|
3263
|
-
if line.startswith(
|
|
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
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
materials[
|
|
3276
|
-
elif
|
|
3277
|
-
materials[
|
|
3278
|
-
elif parts
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
materials[
|
|
3284
|
-
|
|
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
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
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
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
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
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
elif
|
|
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
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
#
|
|
3393
|
-
|
|
3394
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
11760
|
-
|
|
11761
|
-
|
|
11762
|
-
|
|
11763
|
-
|
|
11764
|
-
|
|
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)
|