topologicpy 0.8.93__py3-none-any.whl → 0.8.97__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/Cell.py CHANGED
@@ -331,12 +331,19 @@ class Cell():
331
331
  return None
332
332
  return cell
333
333
 
334
+
334
335
  @staticmethod
335
- def ByThickenedFace(face, thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
336
- planarize: bool = False, tolerance: float = 0.0001, silent: bool = False):
336
+ def ByThickenedFace(face, thickness: float = 1.0, bothSides: bool = True, wSides: int = 1,
337
+ reverse: bool = False, tolerance: float = 0.0001, silent: bool = False):
337
338
  """
338
339
  Creates a cell by thickening the input face.
339
340
 
341
+ Behaviour:
342
+ - Only the bottom and top faces are used as horizontal faces.
343
+ - wSides controls the number of vertical segments along the thickness.
344
+ Intermediate offset layers are used only to build side faces and are
345
+ not included as horizontal faces in Cell.ByFaces.
346
+
340
347
  Parameters
341
348
  ----------
342
349
  face : topologic_core.Face
@@ -344,11 +351,17 @@ class Cell():
344
351
  thickness : float , optional
345
352
  The desired thickness. Default is 1.0.
346
353
  bothSides : bool
347
- If True, the cell will be lofted to each side of the face. Otherwise, it will be lofted in the direction of the normal to the input face. Default is True.
354
+ If True, the thickening is symmetric about the original face
355
+ (i.e. from -thickness/2 to +thickness/2).
356
+ If False, the thickening is from 0 to +thickness along the face normal.
357
+ Default is True.
348
358
  reverse : bool
349
- If True, the cell will be lofted in the opposite direction of the normal to the face. Default is False.
350
- planarize : bool, optional
351
- If set to True, the input faces of the input shell are planarized before building the cell. Otherwise, they are not. Default is False.
359
+ If True, the extrusion direction is flipped (normal is negated).
360
+ Default is False.
361
+ wSides: int, optional
362
+ The number of segments along the thickness direction.
363
+ This is the same definition regardless of bothSides.
364
+ Default is 1.
352
365
  tolerance : float , optional
353
366
  The desired tolerance. Default is 0.0001.
354
367
  silent : bool , optional
@@ -357,60 +370,161 @@ class Cell():
357
370
  Returns
358
371
  -------
359
372
  topologic_core.Cell
360
- The created cell.
361
-
373
+ The created cell, or None on failure.
362
374
  """
375
+ import math
376
+ from topologicpy.Topology import Topology
377
+ from topologicpy.Face import Face
363
378
  from topologicpy.Edge import Edge
364
379
  from topologicpy.Wire import Wire
365
- from topologicpy.Face import Face
366
- from topologicpy.Cluster import Cluster
367
- from topologicpy.Topology import Topology
380
+ from topologicpy.Cell import Cell
368
381
 
382
+ # -----------------------------
383
+ # Validation
384
+ # -----------------------------
369
385
  if not Topology.IsInstance(face, "Face"):
370
- print("Cell.ByThickenedFace - Error: The input face parameter is not a valid topologic face. Returning None.")
386
+ if not silent:
387
+ print("Cell.ByThickenedFace - Error: Input is not a valid Face. Returning None.")
371
388
  return None
372
- if reverse == True and bothSides == False:
373
- thickness = -thickness
374
- faceNormal = Face.Normal(face)
389
+
390
+ if thickness <= tolerance:
391
+ if not silent:
392
+ print("Cell.ByThickenedFace - Error: Thickness is less than or equal to the tolerance. Returning None.")
393
+ return None
394
+
395
+ if wSides < 1:
396
+ if not silent:
397
+ print("Cell.ByThickenedFace - Error: wSides is less than 1. Returning None.")
398
+ return None
399
+
400
+ if thickness/float(wSides) <= tolerance:
401
+ if not silent:
402
+ print("Cell.ByThickenedFace - Error: The distance between layers is less than or equal to the tolerance. Returning None.")
403
+ return None
404
+
405
+ # -----------------------------
406
+ # Face normal (normalized)
407
+ # -----------------------------
408
+ normal = Face.Normal(face)
409
+ if not isinstance(normal, (list, tuple)) or len(normal) != 3:
410
+ if not silent:
411
+ print("Cell.ByThickenedFace - Error: Could not compute face normal.")
412
+ return None
413
+
414
+ nx, ny, nz = normal
415
+ L = math.sqrt(nx*nx + ny*ny + nz*nz)
416
+ if L <= tolerance:
417
+ if not silent:
418
+ print("Cell.ByThickenedFace - Error: Degenerate face normal.")
419
+ return None
420
+
421
+ nx, ny, nz = nx/L, ny/L, nz/L
422
+
423
+ if reverse:
424
+ nx, ny, nz = -nx, -ny, -nz
425
+
426
+ # -----------------------------
427
+ # Build offset layers
428
+ # NOTE: We will only keep the min/max offset faces as bottom/top.
429
+ # Intermediate layers are used only for building side faces.
430
+ # -----------------------------
431
+ step = thickness / float(wSides)
432
+ layers = []
433
+
375
434
  if bothSides:
376
- bottomFace = Topology.Translate(face,
377
- x=-faceNormal[0]*0.5*thickness,
378
- y=-faceNormal[1]*0.5*thickness,
379
- z=-faceNormal[2]*0.5*thickness,
380
- transferDictionaries=False,
381
- silent=True)
382
- topFace = Topology.Translate(face,
383
- x=faceNormal[0]*0.5*thickness,
384
- y=faceNormal[1]*0.5*thickness,
385
- z=faceNormal[2]*0.5*thickness,
386
- transferDictionaries=False,
387
- silent=True)
435
+ # Symmetric: [-thickness/2, ..., +thickness/2]
436
+ start = -0.5 * thickness
437
+ for i in range(wSides + 1):
438
+ offset = start + step * i
439
+ f = Topology.Translate(face, nx*offset, ny*offset, nz*offset)
440
+ layers.append((offset, f))
388
441
  else:
389
- bottomFace = face
390
- topFace = Topology.Translate(face,
391
- x=faceNormal[0]*thickness,
392
- y=faceNormal[1]*thickness,
393
- z=faceNormal[2]*thickness,
394
- transferDictionaries=False,
395
- silent=True)
442
+ # One-sided: [0, ..., thickness]
443
+ for i in range(wSides + 1):
444
+ offset = step * i
445
+ f = Topology.Translate(face, nx*offset, ny*offset, nz*offset)
446
+ layers.append((offset, f))
396
447
 
397
- cellFaces = [Face.Invert(bottomFace), topFace]
398
- bottomEdges = Topology.Edges(bottomFace, silent=True)
448
+ if len(layers) < 2:
449
+ if not silent:
450
+ print("Cell.ByThickenedFace - Error: Not enough layers to form a volume.")
451
+ return None
399
452
 
400
- for bottomEdge in bottomEdges:
401
- topEdge = Topology.Translate(bottomEdge,
402
- x=faceNormal[0]*thickness,
403
- y=faceNormal[1]*thickness,
404
- z=faceNormal[2]*thickness,
405
- transferDictionaries=False,
406
- silent=True)
407
- sideEdge1 = Edge.ByVertices([Edge.StartVertex(bottomEdge), Edge.StartVertex(topEdge)], tolerance=tolerance, silent=silent)
408
- sideEdge2 = Edge.ByVertices([Edge.EndVertex(bottomEdge), Edge.EndVertex(topEdge)], tolerance=tolerance, silent=silent)
409
- cellWire = Topology.SelfMerge(Cluster.ByTopologies([bottomEdge, sideEdge1, topEdge, sideEdge2]), tolerance=tolerance)
410
- if Topology.IsInstance(cellWire, "wire"):
411
- if Wire.IsClosed(cellWire):
412
- cellFaces.append(Face.ByWire(cellWire, tolerance=tolerance))
413
- return Cell.ByFaces(cellFaces, planarize=planarize, tolerance=tolerance)
453
+ layers.sort(key=lambda x: x[0])
454
+
455
+ # Bottom and top faces only
456
+ bottom_face = layers[0][1]
457
+ top_face = layers[-1][1]
458
+
459
+ faces_all = [bottom_face, top_face]
460
+
461
+ # -----------------------------
462
+ # Build side faces between each consecutive pair of layers
463
+ # These are the vertical segmentation faces controlled by wSides.
464
+ # -----------------------------
465
+ for i in range(len(layers) - 1):
466
+ _, faceA = layers[i]
467
+ _, faceB = layers[i + 1]
468
+
469
+ edgesA = Topology.Edges(faceA)
470
+ edgesB = Topology.Edges(faceB)
471
+
472
+ if not edgesA or not edgesB or len(edgesA) != len(edgesB):
473
+ if not silent:
474
+ print("Cell.ByThickenedFace - Warning: Edge mismatch between layers. "
475
+ "Side faces may be incomplete.")
476
+ # We try to continue with min length
477
+ count = min(len(edgesA), len(edgesB))
478
+
479
+ for j in range(count):
480
+ eA = edgesA[j]
481
+ eB = edgesB[j]
482
+
483
+ vA = Topology.Vertices(eA)
484
+ vB = Topology.Vertices(eB)
485
+
486
+ if not vA or not vB or len(vA) != 2 or len(vB) != 2:
487
+ continue
488
+
489
+ vA1, vA2 = vA
490
+ vB1, vB2 = vB
491
+
492
+ try:
493
+ e1 = Edge.ByStartVertexEndVertex(vA1, vA2)
494
+ e2 = Edge.ByStartVertexEndVertex(vA2, vB2)
495
+ e3 = Edge.ByStartVertexEndVertex(vB2, vB1)
496
+ e4 = Edge.ByStartVertexEndVertex(vB1, vA1)
497
+
498
+ if not (e1 and e2 and e3 and e4):
499
+ continue
500
+
501
+ side_wire = Wire.ByEdges([e1, e2, e3, e4])
502
+ if not side_wire:
503
+ continue
504
+
505
+ side_face = Face.ByWire(side_wire)
506
+ if side_face:
507
+ faces_all.append(side_face)
508
+ except Exception:
509
+ # Skip problematic quads but continue
510
+ continue
511
+
512
+ # -----------------------------
513
+ # Build final cell
514
+ # -----------------------------
515
+ try:
516
+ cell = Cell.ByFaces(faces_all, tolerance=tolerance)
517
+ except Exception:
518
+ if not silent:
519
+ print("Cell.ByThickenedFace - Error: Cell.ByFaces failed.")
520
+ return None
521
+
522
+ if not Topology.IsInstance(cell, "Cell"):
523
+ if not silent:
524
+ print("Cell.ByThickenedFace - Error: Cell.ByFaces did not return a valid Cell.")
525
+ return None
526
+
527
+ return cell
414
528
 
415
529
  @staticmethod
416
530
  def ByThickenedShell(shell, direction: list = [0, 0, 1], thickness: float = 1.0, bothSides: bool = True, reverse: bool = False,
@@ -1158,7 +1272,7 @@ class Cell():
1158
1272
  tolerance=tolerance,
1159
1273
  silent=silent)
1160
1274
  return_cell = Cell.ByThickenedFace(cross_shape_face, thickness=height, bothSides=True, reverse=False,
1161
- planarize = False, tolerance=tolerance, silent=silent)
1275
+ tolerance=tolerance, silent=silent)
1162
1276
  xOffset = 0
1163
1277
  yOffset = 0
1164
1278
  zOffset = 0
@@ -1456,7 +1570,7 @@ class Cell():
1456
1570
 
1457
1571
  baseWire = Wire.Circle(origin=circle_origin, radius=radius, sides=uSides, fromAngle=0, toAngle=360, close=True, direction=[0, 0, 1], placement="center", tolerance=tolerance)
1458
1572
  baseFace = Face.ByWire(baseWire, tolerance=tolerance)
1459
- cylinder = Cell.ByThickenedFace(face=baseFace, thickness=height, bothSides=False, tolerance=tolerance)
1573
+ cylinder = Cell.ByThickenedFace(face=baseFace, thickness=height, bothSides=False, reverse=False, tolerance=tolerance)
1460
1574
  if vSides > 1:
1461
1575
  cutting_planes = []
1462
1576
  baseX = Vertex.X(origin, mantissa=mantissa) + xOffset
@@ -2972,15 +3086,16 @@ class Cell():
2972
3086
  elif placement.lower() == "lowerleft":
2973
3087
  xOffset = width*0.5
2974
3088
  yOffset = length*0.5
2975
- vb1 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
2976
- vb2 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
2977
- vb3 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
2978
- vb4 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
3089
+
3090
+ vb1 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
3091
+ vb2 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)+length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
3092
+ vb3 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)+width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
3093
+ vb4 = Vertex.ByCoordinates(Vertex.X(origin, mantissa=mantissa)-width*0.5+xOffset,Vertex.Y(origin, mantissa=mantissa)-length*0.5+yOffset,Vertex.Z(origin, mantissa=mantissa)+zOffset)
2979
3094
 
2980
3095
  baseWire = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True)
2981
3096
  baseFace = Face.ByWire(baseWire, tolerance=tolerance)
2982
3097
 
2983
- prism = Cell.ByThickenedFace(baseFace, thickness=height, bothSides = False)
3098
+ prism = Cell.ByThickenedFace(baseFace, thickness=height, bothSides = False, reverse=True)
2984
3099
 
2985
3100
  if uSides > 1 or vSides > 1 or wSides > 1:
2986
3101
  prism = sliceCell(prism, width, length, height, uSides, vSides, wSides)
@@ -3092,7 +3207,7 @@ class Cell():
3092
3207
  origin = Vertex.Origin()
3093
3208
  bottom_face = Face.RHS(origin = Vertex.Origin(), width=width, length=length, thickness=thickness, outerFillet=outerFillet, innerFillet=innerFillet, sides=sides, direction=[0,0,1], placement="center", tolerance=tolerance, silent=silent)
3094
3209
  return_cell = Cell.ByThickenedFace(bottom_face, thickness=height, bothSides=True, reverse=False,
3095
- planarize = False, tolerance=tolerance, silent=silent)
3210
+ tolerance=tolerance, silent=silent)
3096
3211
  xOffset = 0
3097
3212
  yOffset = 0
3098
3213
  zOffset = 0
@@ -3434,8 +3549,9 @@ class Cell():
3434
3549
  sphere = Topology.Translate(sphere, 0, 0, radius)
3435
3550
  elif placement.lower() == "lowerleft":
3436
3551
  sphere = Topology.Translate(sphere, radius, radius, radius)
3437
- sphere = Topology.Orient(sphere, origin=Vertex.Origin(), dirA=[0, 0, 1], dirB=direction)
3438
- sphere = Topology.Place(sphere, originA=Vertex.Origin(), originB=origin)
3552
+
3553
+ if not direction == [0,0,1]:
3554
+ sphere = Topology.Orient(sphere, origin=origin, dirA=[0, 0, 1], dirB=direction)
3439
3555
  return sphere
3440
3556
 
3441
3557
  @staticmethod