topologicpy 0.7.64__py3-none-any.whl → 0.7.66__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/Face.py CHANGED
@@ -399,7 +399,157 @@ class Face():
399
399
  print("Face.ByOffset - Warning: Could not create face from wires. Returning None.")
400
400
  return None
401
401
  return return_face
402
-
402
+
403
+ @staticmethod
404
+ def ByOffsetArea(face,
405
+ area,
406
+ offsetKey="offset",
407
+ minOffsetKey="minOffset",
408
+ maxOffsetKey="maxOffset",
409
+ defaultMinOffset=0,
410
+ defaultMaxOffset=1,
411
+ maxIterations = 1,
412
+ tolerance=0.0001,
413
+ silent = False,
414
+ numWorkers = None):
415
+ """
416
+ Creates an offset face from the input face based on the input area.
417
+
418
+ Parameters
419
+ ----------
420
+ face : topologic_core.Face
421
+ The input face.
422
+ area : float
423
+ The desired area of the created face.
424
+ offsetKey : str , optional
425
+ The edge dictionary key under which to store the offset value. The default is "offset".
426
+ minOffsetKey : str , optional
427
+ The edge dictionary key under which to find the desired minimum edge offset value. If a value cannot be found, the defaultMinOffset input parameter value is used instead. The default is "minOffset".
428
+ maxOffsetKey : str , optional
429
+ The edge dictionary key under which to find the desired maximum edge offset value. If a value cannot be found, the defaultMaxOffset input parameter value is used instead. The default is "maxOffset".
430
+ defaultMinOffset : float , optional
431
+ The desired minimum edge offset distance. The default is 0.
432
+ defaultMaxOffset : float , optional
433
+ The desired maximum edge offset distance. The default is 1.
434
+ maxIterations: int , optional
435
+ The desired maximum number of iterations to attempt to converge on a solution. The default is 1.
436
+ tolerance : float , optional
437
+ The desired tolerance. The default is 0.0001.
438
+ silent : bool , optional
439
+ If set to True, no error and warning messages are printed. Otherwise, they are. The default is False.
440
+ numWorkers : int , optional
441
+ Number of workers run in parallel to process. If you set it to 1, no parallel processing will take place.
442
+ The default is None which causes the algorithm to use twice the number of cpu cores in the host computer.
443
+
444
+ Returns
445
+ -------
446
+ topologic_core.Face
447
+ The created face.
448
+
449
+ """
450
+ from topologicpy.Wire import Wire
451
+ from topologicpy.Face import Face
452
+ from topologicpy.Topology import Topology
453
+ from topologicpy.Dictionary import Dictionary
454
+ import numpy as np
455
+ from scipy.optimize import minimize
456
+
457
+ def compute_offset_amounts(face,
458
+ area,
459
+ offsetKey="offset",
460
+ minOffsetKey="minOffset",
461
+ maxOffsetKey="maxOffset",
462
+ defaultMinOffset=0,
463
+ defaultMaxOffset=1,
464
+ maxIterations = 1,
465
+ tolerance=0.0001):
466
+
467
+ initial_offsets = []
468
+ bounds = []
469
+ for edge in edges:
470
+ d = Topology.Dictionary(edge)
471
+ minOffset = Dictionary.ValueAtKey(d, minOffsetKey) or defaultMinOffset
472
+ maxOffset = Dictionary.ValueAtKey(d, maxOffsetKey) or defaultMaxOffset
473
+ # Initial guess: small negative offsets to shrink the polygon, within the constraints
474
+ initial_offsets.append((minOffset + maxOffset) / 2)
475
+ # Bounds based on the constraints for each edge
476
+ bounds.append((minOffset, maxOffset))
477
+
478
+ # Convert initial_offsets to np.array for efficiency
479
+ initial_offsets = np.array(initial_offsets)
480
+ iteration_count = [0] # List to act as a mutable counter
481
+
482
+ def objective_function(offsets):
483
+ for i, edge in enumerate(edges):
484
+ d = Topology.Dictionary(edge)
485
+ d = Dictionary.SetValueAtKey(d, offsetKey, offsets[i])
486
+ edge = Topology.SetDictionary(edge, d)
487
+
488
+ # Offset the wire
489
+ new_face = Face.ByOffset(face, offsetKey=offsetKey, silent=silent, numWorkers=numWorkers)
490
+ # Check for an illegal wire. In that case, return a very large loss value.
491
+ if not Topology.IsInstance(new_face, "Face"):
492
+ return (float("inf"))
493
+ # Calculate the area of the new wire/face
494
+ new_area = Face.Area(new_face)
495
+
496
+ # The objective is the difference between the target hole area and the actual hole area
497
+ # We want this difference to be as close to 0 as possible
498
+ loss = (new_area - area) ** 2
499
+ # If the loss is less than the tolerance, accept the result and return a loss of 0.
500
+ if loss < tolerance:
501
+ return 0
502
+ # Otherwise, return the actual loss value.
503
+ return loss
504
+
505
+ # Callback function to track and display iteration number
506
+ def iteration_callback(xk):
507
+ iteration_count[0] += 1 # Increment the counter
508
+ if not silent:
509
+ print(f"Face.ByOffsetArea - Information: Iteration {iteration_count[0]}")
510
+
511
+ # Use scipy optimization/minimize to find the correct offsets, respecting the min/max bounds
512
+ result = minimize(objective_function,
513
+ initial_offsets,
514
+ method = "Powell",
515
+ bounds=bounds,
516
+ options={ 'maxiter': maxIterations},
517
+ callback=iteration_callback
518
+ )
519
+
520
+ # Return the offsets
521
+ return result.x
522
+
523
+ if not Topology.IsInstance(face, "Face"):
524
+ if not silent:
525
+ print("Face.OffsetByArea - Error: The input face parameter is not a valid face. Returning None.")
526
+ return None
527
+
528
+ edges = Topology.Edges(face)
529
+ # Compute the offset amounts
530
+ offsets = compute_offset_amounts(face,
531
+ area = area,
532
+ offsetKey = offsetKey,
533
+ minOffsetKey = minOffsetKey,
534
+ maxOffsetKey = maxOffsetKey,
535
+ defaultMinOffset = defaultMinOffset,
536
+ defaultMaxOffset = defaultMaxOffset,
537
+ maxIterations = maxIterations,
538
+ tolerance = tolerance)
539
+ # Set the edge dictionaries correctly according to the specified offsetKey
540
+ for i, edge in enumerate(edges):
541
+ d = Topology.Dictionary(edge)
542
+ d = Dictionary.SetValueAtKey(d, offsetKey, offsets[i])
543
+ edge = Topology.SetDictionary(edge, d)
544
+
545
+ # Offset the face
546
+ return_face = Face.ByOffset(face, offsetKey=offsetKey, silent=silent, numWorkers=numWorkers)
547
+ if not Topology.IsInstance(face, "Face"):
548
+ if not silent:
549
+ print("Face.OffsetByArea - Error: Could not create the offset face. Returning None.")
550
+ return None
551
+ return return_face
552
+
403
553
  @staticmethod
404
554
  def ByShell(shell, origin= None, angTolerance: float = 0.1, tolerance: float = 0.0001, silent=False):
405
555
  """
@@ -2127,7 +2277,7 @@ class Face():
2127
2277
  return Face.ByWires(eb, ibList)
2128
2278
 
2129
2279
  @staticmethod
2130
- def Skeleton(face, tolerance=0.001):
2280
+ def Skeleton(face, boundary: bool = True, tolerance: float = 0.001):
2131
2281
  """
2132
2282
  Creates a straight skeleton. This method is contributed by 高熙鹏 xipeng gao <gaoxipeng1998@gmail.com>
2133
2283
  This algorithm depends on the polyskel code which is included in the library. Polyskel code is found at: https://github.com/Botffy/polyskel
@@ -2136,6 +2286,8 @@ class Face():
2136
2286
  ----------
2137
2287
  face : topologic_core.Face
2138
2288
  The input face.
2289
+ boundary : bool , optional
2290
+ If set to True the original boundary is returned as part of the roof. Otherwise it is not. The default is True.
2139
2291
  tolerance : float , optional
2140
2292
  The desired tolerance. The default is 0.001. (This is set to a larger number than the usual 0.0001 as it was found to work better)
2141
2293
 
@@ -2151,7 +2303,7 @@ class Face():
2151
2303
  if not Topology.IsInstance(face, "Face"):
2152
2304
  print("Face.Skeleton - Error: The input face is not a valid topologic face. Returning None.")
2153
2305
  return None
2154
- return Wire.Skeleton(face, tolerance=tolerance)
2306
+ return Wire.Skeleton(face, boundary=boundary, tolerance=tolerance)
2155
2307
 
2156
2308
  @staticmethod
2157
2309
  def Square(origin= None, size: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):