topologicpy 0.8.89__py3-none-any.whl → 0.8.90__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/Graph.py +73 -2
- topologicpy/Wire.py +375 -7
- topologicpy/version.py +1 -1
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.90.dist-info}/METADATA +1 -1
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.90.dist-info}/RECORD +8 -8
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.90.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.90.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.90.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
|
@@ -5251,7 +5251,8 @@ class Graph:
|
|
|
5251
5251
|
useInternalVertex: bool = False,
|
|
5252
5252
|
storeBREP: bool =False,
|
|
5253
5253
|
mantissa: int = 6,
|
|
5254
|
-
tolerance: float = 0.0001
|
|
5254
|
+
tolerance: float = 0.0001,
|
|
5255
|
+
silent: float = False):
|
|
5255
5256
|
"""
|
|
5256
5257
|
Creates a graph.See https://en.wikipedia.org/wiki/Graph_(discrete_mathematics).
|
|
5257
5258
|
|
|
@@ -5305,6 +5306,8 @@ class Graph:
|
|
|
5305
5306
|
If set to True, store the BRep of the subtopology in its representative vertex. Default is False.
|
|
5306
5307
|
tolerance : float , optional
|
|
5307
5308
|
The desired tolerance. Default is 0.0001.
|
|
5309
|
+
silent : bool , optional
|
|
5310
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
5308
5311
|
|
|
5309
5312
|
Returns
|
|
5310
5313
|
-------
|
|
@@ -5319,6 +5322,10 @@ class Graph:
|
|
|
5319
5322
|
from topologicpy.Topology import Topology
|
|
5320
5323
|
from topologicpy.Aperture import Aperture
|
|
5321
5324
|
|
|
5325
|
+
if not Topology.IsInstance(topology, "topology"):
|
|
5326
|
+
if not silent:
|
|
5327
|
+
print("Graph.ByTopology - Error: The input topology parameter is not a valid topology. Returning None.")
|
|
5328
|
+
return None
|
|
5322
5329
|
def _viaSharedTopologies(vt, sharedTops):
|
|
5323
5330
|
verts = []
|
|
5324
5331
|
eds = []
|
|
@@ -15577,7 +15584,7 @@ class Graph:
|
|
|
15577
15584
|
try:
|
|
15578
15585
|
gsv = Graph.NearestVertex(graph, vertexA)
|
|
15579
15586
|
gev = Graph.NearestVertex(graph, vertexB)
|
|
15580
|
-
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey)
|
|
15587
|
+
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey) # Hook to Core
|
|
15581
15588
|
if not shortest_path == None:
|
|
15582
15589
|
if Topology.IsInstance(shortest_path, "Edge"):
|
|
15583
15590
|
shortest_path = Wire.ByEdges([shortest_path])
|
|
@@ -15590,6 +15597,70 @@ class Graph:
|
|
|
15590
15597
|
except:
|
|
15591
15598
|
return None
|
|
15592
15599
|
|
|
15600
|
+
@staticmethod
|
|
15601
|
+
def shortestPathInFace(graph, face, vertexA, vertexB, mode: int = 0, meshSize: float = None, tolerance: float = 0.0001, silent: bool = False):
|
|
15602
|
+
"""
|
|
15603
|
+
Returns the shortest path that connects the input vertices.
|
|
15604
|
+
|
|
15605
|
+
Parameters
|
|
15606
|
+
----------
|
|
15607
|
+
face : topologic_core.Face
|
|
15608
|
+
The input face. This is assumed to be planar and resting on the XY plane (z = 0)
|
|
15609
|
+
vertexA : topologic_core.Vertex
|
|
15610
|
+
The first input vertex.
|
|
15611
|
+
vertexB : topologic_core.Vertex
|
|
15612
|
+
The second input vertex.
|
|
15613
|
+
mode : int , optional
|
|
15614
|
+
The desired mode of meshing algorithm. Several options are available:
|
|
15615
|
+
0: Classic
|
|
15616
|
+
1: MeshAdapt
|
|
15617
|
+
3: Initial Mesh Only
|
|
15618
|
+
5: Delaunay
|
|
15619
|
+
6: Frontal-Delaunay
|
|
15620
|
+
7: BAMG
|
|
15621
|
+
8: Fontal-Delaunay for Quads
|
|
15622
|
+
9: Packing of Parallelograms
|
|
15623
|
+
All options other than 0 (Classic) use the gmsh library. See https://gmsh.info/doc/texinfo/gmsh.html#Mesh-options
|
|
15624
|
+
WARNING: The options that use gmsh can be very time consuming and can create very heavy geometry.
|
|
15625
|
+
meshSize : float , optional
|
|
15626
|
+
The desired size of the mesh when using the "mesh" option. If set to None, it will be
|
|
15627
|
+
calculated automatically and set to 10% of the overall size of the face.
|
|
15628
|
+
tolerance : float , optional
|
|
15629
|
+
The desired tolerance. Default is 0.0001.
|
|
15630
|
+
silent : bool , optional
|
|
15631
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
15632
|
+
|
|
15633
|
+
Returns
|
|
15634
|
+
-------
|
|
15635
|
+
topologic_core.Wire
|
|
15636
|
+
The shortest path between the input vertices.
|
|
15637
|
+
|
|
15638
|
+
"""
|
|
15639
|
+
from topologicpy.Wire import Wire
|
|
15640
|
+
from topologicpy.Topology import Topology
|
|
15641
|
+
|
|
15642
|
+
if not Topology.IsInstance(face, "face"):
|
|
15643
|
+
if not silent:
|
|
15644
|
+
print("Face.ShortestPath - Error: The input face parameter is not a topologic face. Returning None.")
|
|
15645
|
+
return None
|
|
15646
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
|
15647
|
+
if not silent:
|
|
15648
|
+
print("Face.ShortestPath - Error: The input vertexA parameter is not a topologic vertex. Returning None.")
|
|
15649
|
+
return None
|
|
15650
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
|
15651
|
+
if not silent:
|
|
15652
|
+
print("Face.ShortestPath - Error: The input vertexB parameter is not a topologic vertex. Returning None.")
|
|
15653
|
+
return None
|
|
15654
|
+
|
|
15655
|
+
sp = Graph.ShortestPath(graph, vertexA, vertexB)
|
|
15656
|
+
if sp == None:
|
|
15657
|
+
if not silent:
|
|
15658
|
+
print("Face.ShortestPath - Error: Could not find the shortest path. Returning None.")
|
|
15659
|
+
return None
|
|
15660
|
+
|
|
15661
|
+
new_path = Wire.StraightenInFace(sp, face, tolerance = tolerance)
|
|
15662
|
+
return new_path
|
|
15663
|
+
|
|
15593
15664
|
@staticmethod
|
|
15594
15665
|
def ShortestPaths(graph, vertexA, vertexB, vertexKey="", edgeKey="length", timeLimit=10,
|
|
15595
15666
|
pathLimit=10, tolerance=0.0001):
|
topologicpy/Wire.py
CHANGED
|
@@ -331,16 +331,19 @@ class Wire():
|
|
|
331
331
|
|
|
332
332
|
else:
|
|
333
333
|
best_br = boundingRectangle
|
|
334
|
-
x_min, y_min,
|
|
334
|
+
x_min, y_min, x_max, y_max = best_br
|
|
335
335
|
vb1 = Vertex.ByCoordinates(x_min, y_min, 0)
|
|
336
|
-
vb2 = Vertex.ByCoordinates(
|
|
337
|
-
vb3 = Vertex.ByCoordinates(
|
|
338
|
-
vb4 = Vertex.ByCoordinates(x_min,
|
|
336
|
+
vb2 = Vertex.ByCoordinates(x_max, y_min, 0)
|
|
337
|
+
vb3 = Vertex.ByCoordinates(x_max, y_max, 0)
|
|
338
|
+
vb4 = Vertex.ByCoordinates(x_min, y_max, 0)
|
|
339
339
|
|
|
340
340
|
boundingRectangle = Wire.ByVertices([vb1, vb2, vb3, vb4], close=True, tolerance=tolerance, silent=silent)
|
|
341
341
|
boundingRectangle = Topology.Rotate(boundingRectangle, origin=origin, axis=[0, 0, 1], angle=-best_z)
|
|
342
342
|
boundingRectangle = Topology.Unflatten(boundingRectangle, origin=f_origin, direction=normal)
|
|
343
|
-
dictionary = Dictionary.ByKeysValues(["zrot"
|
|
343
|
+
dictionary = Dictionary.ByKeysValues(["zrot", "xmin", "ymin", "xmax", "ymax", "width", "length"],
|
|
344
|
+
[best_z, x_min, y_min, x_max, y_max, (x_max - x_min), (y_max - y_min)])
|
|
345
|
+
|
|
346
|
+
#dictionary = Dictionary.ByKeysValues(["zrot"], [best_z])
|
|
344
347
|
boundingRectangle = Topology.SetDictionary(boundingRectangle, dictionary)
|
|
345
348
|
return boundingRectangle
|
|
346
349
|
|
|
@@ -2426,7 +2429,254 @@ class Wire():
|
|
|
2426
2429
|
# Unflatten the wire
|
|
2427
2430
|
return_wire = Topology.Unflatten(flat_wire, origin=Vertex.Origin(), direction=normal)
|
|
2428
2431
|
return return_wire
|
|
2429
|
-
|
|
2432
|
+
|
|
2433
|
+
@staticmethod
|
|
2434
|
+
def Funnel(face,
|
|
2435
|
+
vertexA,
|
|
2436
|
+
vertexB,
|
|
2437
|
+
portals,
|
|
2438
|
+
tolerance: float = 0.0001,
|
|
2439
|
+
silent: float = False):
|
|
2440
|
+
"""
|
|
2441
|
+
Returns a Wire representing a smoothed path inside the given face using
|
|
2442
|
+
the funnel (string-pulling) algorithm.
|
|
2443
|
+
|
|
2444
|
+
The algorithm assumes that a corridor has already been computed, and is
|
|
2445
|
+
provided as an ordered list of "portals" (pairs of vertices) that lie
|
|
2446
|
+
on the face between the start and end locations.
|
|
2447
|
+
|
|
2448
|
+
Parameters
|
|
2449
|
+
----------
|
|
2450
|
+
face : topologic_core.Face
|
|
2451
|
+
The planar face on which navigation occurs. All vertices must lie
|
|
2452
|
+
on this face.
|
|
2453
|
+
vertexA : topologic_core.Vertex
|
|
2454
|
+
The start point of the path.
|
|
2455
|
+
vertexB : topologic_core.Vertex
|
|
2456
|
+
The end point of the path.
|
|
2457
|
+
portals : list of tuple(Vertex, Vertex)
|
|
2458
|
+
Ordered list of corridor edges. Each item is (leftVertex, rightVertex)
|
|
2459
|
+
describing the visible "portal" between two consecutive regions along
|
|
2460
|
+
the navmesh path.
|
|
2461
|
+
tolerance : float , optional
|
|
2462
|
+
Numerical tolerance used when comparing orientations and distances.
|
|
2463
|
+
Default is 0.0001.
|
|
2464
|
+
silent : bool , optional
|
|
2465
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
2466
|
+
|
|
2467
|
+
Returns
|
|
2468
|
+
-------
|
|
2469
|
+
wire : topologic_core.Wire
|
|
2470
|
+
A Wire representing the smoothed path from startVertex to endVertex
|
|
2471
|
+
that stays inside the navigation corridor on the face.
|
|
2472
|
+
"""
|
|
2473
|
+
from topologicpy.Dictionary import Dictionary
|
|
2474
|
+
from topologicpy.Vertex import Vertex
|
|
2475
|
+
from topologicpy.Face import Face
|
|
2476
|
+
from topologicpy.Topology import Topology
|
|
2477
|
+
|
|
2478
|
+
if not Topology.IsInstance(face, "face"):
|
|
2479
|
+
if not silent:
|
|
2480
|
+
print("Wire.Funnel - Error: The input face parameter is not a topologic face. Returning None.")
|
|
2481
|
+
return None
|
|
2482
|
+
if not Topology.IsInstance(vertexA, "vertex"):
|
|
2483
|
+
if not silent:
|
|
2484
|
+
print("Wire.Funnel - Error: The input vertexA parameter is not a topologic vertex. Returning None.")
|
|
2485
|
+
return None
|
|
2486
|
+
if not Topology.IsInstance(vertexB, "vertex"):
|
|
2487
|
+
if not silent:
|
|
2488
|
+
print("Wire.Funnel - Error: The input vertexB parameter is not a topologic vertex. Returning None.")
|
|
2489
|
+
return None
|
|
2490
|
+
|
|
2491
|
+
# ------------------------------------------------------------
|
|
2492
|
+
# 1. Basic helpers
|
|
2493
|
+
# ------------------------------------------------------------
|
|
2494
|
+
def _norm(v):
|
|
2495
|
+
return math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2])
|
|
2496
|
+
|
|
2497
|
+
def _normalize(v):
|
|
2498
|
+
n = _norm(v)
|
|
2499
|
+
if n < tolerance:
|
|
2500
|
+
return (0.0, 0.0, 0.0)
|
|
2501
|
+
return (v[0] / n, v[1] / n, v[2] / n)
|
|
2502
|
+
|
|
2503
|
+
def _dot(a, b):
|
|
2504
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
|
2505
|
+
|
|
2506
|
+
def _cross(a, b):
|
|
2507
|
+
return (
|
|
2508
|
+
a[1] * b[2] - a[2] * b[1],
|
|
2509
|
+
a[2] * b[0] - a[0] * b[2],
|
|
2510
|
+
a[0] * b[1] - a[1] * b[0],
|
|
2511
|
+
)
|
|
2512
|
+
|
|
2513
|
+
def _sub(a, b):
|
|
2514
|
+
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
|
|
2515
|
+
|
|
2516
|
+
def _tri_area2(a2, b2, c2):
|
|
2517
|
+
"""
|
|
2518
|
+
Twice the signed area of triangle (a, b, c) in 2D.
|
|
2519
|
+
Positive => c is to the left of ab
|
|
2520
|
+
Negative => c is to the right of ab
|
|
2521
|
+
"""
|
|
2522
|
+
return (b2[0] - a2[0]) * (c2[1] - a2[1]) - (b2[1] - a2[1]) * (c2[0] - a2[0])
|
|
2523
|
+
|
|
2524
|
+
def _coords3d(v):
|
|
2525
|
+
x, y, z = Vertex.Coordinates(v)
|
|
2526
|
+
return (x, y, z)
|
|
2527
|
+
|
|
2528
|
+
# ------------------------------------------------------------
|
|
2529
|
+
# 2. Build a local 2D coordinate system on the face
|
|
2530
|
+
# ------------------------------------------------------------
|
|
2531
|
+
# Face normal
|
|
2532
|
+
n_vec = Face.Normal(face) # [nx, ny, nz]
|
|
2533
|
+
n = _normalize((n_vec[0], n_vec[1], n_vec[2]))
|
|
2534
|
+
|
|
2535
|
+
# Choose an arbitrary vector not parallel to n
|
|
2536
|
+
if abs(n[0]) < 0.9:
|
|
2537
|
+
arbitrary = (1.0, 0.0, 0.0)
|
|
2538
|
+
else:
|
|
2539
|
+
arbitrary = (0.0, 1.0, 0.0)
|
|
2540
|
+
|
|
2541
|
+
u = _normalize(_cross(n, arbitrary)) # tangent
|
|
2542
|
+
v = _cross(n, u) # bitangent, already orthogonal and normalized
|
|
2543
|
+
|
|
2544
|
+
def _project_to_2d(vertex):
|
|
2545
|
+
p = _coords3d(vertex)
|
|
2546
|
+
# project onto basis (u, v)
|
|
2547
|
+
return (_dot(p, u), _dot(p, v))
|
|
2548
|
+
|
|
2549
|
+
# Precompute 2D coords for start, end and all portal vertices
|
|
2550
|
+
start2d = _project_to_2d(vertexA)
|
|
2551
|
+
end2d = _project_to_2d(vertexB)
|
|
2552
|
+
|
|
2553
|
+
portal2d = []
|
|
2554
|
+
for l_v, r_v in portals:
|
|
2555
|
+
portal2d.append((_project_to_2d(l_v), _project_to_2d(r_v)))
|
|
2556
|
+
|
|
2557
|
+
# ------------------------------------------------------------
|
|
2558
|
+
# 3. Funnel algorithm in 2D
|
|
2559
|
+
# (based on classic Recast / string-pulling implementation)
|
|
2560
|
+
# ------------------------------------------------------------
|
|
2561
|
+
path_vertices = [vertexA]
|
|
2562
|
+
|
|
2563
|
+
apex2d = start2d
|
|
2564
|
+
apexVertex = vertexA
|
|
2565
|
+
apexIndex = -1
|
|
2566
|
+
|
|
2567
|
+
left2d = start2d
|
|
2568
|
+
right2d = start2d
|
|
2569
|
+
leftVertex = vertexA
|
|
2570
|
+
rightVertex = vertexB
|
|
2571
|
+
leftIndex = -1
|
|
2572
|
+
rightIndex = -1
|
|
2573
|
+
|
|
2574
|
+
n_portals = len(portals)
|
|
2575
|
+
i = 0
|
|
2576
|
+
|
|
2577
|
+
# We will process all portals, and then a final "portal" at the goal (end, end)
|
|
2578
|
+
while i <= n_portals:
|
|
2579
|
+
if i < n_portals:
|
|
2580
|
+
newLeft2d, newRight2d = portal2d[i]
|
|
2581
|
+
newLeftVertex, newRightVertex = portals[i]
|
|
2582
|
+
else:
|
|
2583
|
+
# last "portal" is the goal point itself
|
|
2584
|
+
newLeft2d = end2d
|
|
2585
|
+
newRight2d = end2d
|
|
2586
|
+
newLeftVertex = vertexB
|
|
2587
|
+
newRightVertex = vertexB
|
|
2588
|
+
|
|
2589
|
+
# --------------------------------------------------------
|
|
2590
|
+
# Update right side of funnel
|
|
2591
|
+
# --------------------------------------------------------
|
|
2592
|
+
area_apex_right_newRight = _tri_area2(apex2d, right2d, newRight2d)
|
|
2593
|
+
if area_apex_right_newRight <= tolerance:
|
|
2594
|
+
# New right vertex is "inside" or tightening the funnel
|
|
2595
|
+
area_apex_left_newRight = _tri_area2(apex2d, left2d, newRight2d)
|
|
2596
|
+
if (apexVertex == rightVertex) or (area_apex_left_newRight > tolerance):
|
|
2597
|
+
# Tighten the funnel on the right side
|
|
2598
|
+
right2d = newRight2d
|
|
2599
|
+
rightVertex = newRightVertex
|
|
2600
|
+
rightIndex = i
|
|
2601
|
+
else:
|
|
2602
|
+
# Right over left, so left becomes the new apex
|
|
2603
|
+
path_vertices.append(leftVertex)
|
|
2604
|
+
apex2d = _project_to_2d(leftVertex)
|
|
2605
|
+
apexVertex = leftVertex
|
|
2606
|
+
apexIndex = leftIndex
|
|
2607
|
+
|
|
2608
|
+
# Reset funnel
|
|
2609
|
+
left2d = apex2d
|
|
2610
|
+
right2d = apex2d
|
|
2611
|
+
leftVertex = apexVertex
|
|
2612
|
+
rightVertex = apexVertex
|
|
2613
|
+
leftIndex = apexIndex
|
|
2614
|
+
rightIndex = apexIndex
|
|
2615
|
+
|
|
2616
|
+
# Restart from the new apex
|
|
2617
|
+
i = apexIndex + 1
|
|
2618
|
+
continue
|
|
2619
|
+
|
|
2620
|
+
# --------------------------------------------------------
|
|
2621
|
+
# Update left side of funnel
|
|
2622
|
+
# --------------------------------------------------------
|
|
2623
|
+
area_apex_left_newLeft = _tri_area2(apex2d, left2d, newLeft2d)
|
|
2624
|
+
if area_apex_left_newLeft >= -tolerance:
|
|
2625
|
+
# New left vertex is "inside" or tightening the funnel
|
|
2626
|
+
area_apex_right_newLeft = _tri_area2(apex2d, right2d, newLeft2d)
|
|
2627
|
+
if (apexVertex == leftVertex) or (area_apex_right_newLeft < -tolerance):
|
|
2628
|
+
# Tighten funnel on the left side
|
|
2629
|
+
left2d = newLeft2d
|
|
2630
|
+
leftVertex = newLeftVertex
|
|
2631
|
+
leftIndex = i
|
|
2632
|
+
else:
|
|
2633
|
+
# Left over right, so right becomes the new apex
|
|
2634
|
+
path_vertices.append(rightVertex)
|
|
2635
|
+
apex2d = _project_to_2d(rightVertex)
|
|
2636
|
+
apexVertex = rightVertex
|
|
2637
|
+
apexIndex = rightIndex
|
|
2638
|
+
|
|
2639
|
+
# Reset funnel
|
|
2640
|
+
left2d = apex2d
|
|
2641
|
+
right2d = apex2d
|
|
2642
|
+
leftVertex = apexVertex
|
|
2643
|
+
rightVertex = apexVertex
|
|
2644
|
+
leftIndex = apexIndex
|
|
2645
|
+
rightIndex = apexIndex
|
|
2646
|
+
|
|
2647
|
+
# Restart from the new apex
|
|
2648
|
+
i = apexIndex + 1
|
|
2649
|
+
continue
|
|
2650
|
+
|
|
2651
|
+
i += 1
|
|
2652
|
+
|
|
2653
|
+
# Finally, add the end point if it is not already in the path
|
|
2654
|
+
if path_vertices[-1] is not vertexB:
|
|
2655
|
+
path_vertices.append(vertexB)
|
|
2656
|
+
|
|
2657
|
+
# ------------------------------------------------------------
|
|
2658
|
+
# 4. Build and return the Topologic wire
|
|
2659
|
+
# ------------------------------------------------------------
|
|
2660
|
+
return_wire = Wire.ByVertices(path_vertices, close=False, silent=True)
|
|
2661
|
+
bb = Wire.BoundingRectangle(face)
|
|
2662
|
+
d = Topology.Dictionary(bb)
|
|
2663
|
+
width = Dictionary.ValueAtKey(d, "width")
|
|
2664
|
+
length = Dictionary.ValueAtKey(d, "length")
|
|
2665
|
+
size = max(width, length)
|
|
2666
|
+
percentage = 0.25 # Start with 25% of the total size
|
|
2667
|
+
is_ok = False
|
|
2668
|
+
while is_ok == False and percentage > 0:
|
|
2669
|
+
new_wire = Wire.Simplify(return_wire, tolerance=size*percentage, silent=True)
|
|
2670
|
+
test_wire = Topology.Scale(new_wire, Topology.Centroid(new_wire), 0.95, 0.95, 1)
|
|
2671
|
+
result = Topology.Difference(test_wire, face, tolerance=tolerance, silent=True)
|
|
2672
|
+
if result is None:
|
|
2673
|
+
is_ok = True
|
|
2674
|
+
return_wire = new_wire
|
|
2675
|
+
percentage -= 0.01
|
|
2676
|
+
print("Wire.Funnel - Result:", result)
|
|
2677
|
+
print("Wire.Funnel - Percentage:", percentage)
|
|
2678
|
+
return new_wire
|
|
2679
|
+
|
|
2430
2680
|
@staticmethod
|
|
2431
2681
|
def InteriorAngles(wire, tolerance: float = 0.0001, mantissa: int = 6) -> list:
|
|
2432
2682
|
"""
|
|
@@ -4188,7 +4438,7 @@ class Wire():
|
|
|
4188
4438
|
if not silent:
|
|
4189
4439
|
print("Wire.Simplify - Warning: Could not generate enough vertices for a simplified wire. Returning the original wire.")
|
|
4190
4440
|
wire
|
|
4191
|
-
new_wire = Wire.ByVertices(new_vertices, close=Wire.IsClosed(wire), tolerance=tolerance)
|
|
4441
|
+
new_wire = Wire.ByVertices(new_vertices, close=Wire.IsClosed(wire), tolerance=tolerance, silent=True)
|
|
4192
4442
|
if not Topology.IsInstance(new_wire, "wire"):
|
|
4193
4443
|
if not silent:
|
|
4194
4444
|
print("Wire.Simplify - Warning: Could not generate a simplified wire. Returning the original wire.")
|
|
@@ -4713,6 +4963,124 @@ class Wire():
|
|
|
4713
4963
|
sv, ev = Wire.StartEndVertices(wire, silent=silent)
|
|
4714
4964
|
return sv
|
|
4715
4965
|
|
|
4966
|
+
@staticmethod
|
|
4967
|
+
def StraightenInFace(wire, face, tolerance: float = 0.0001):
|
|
4968
|
+
"""
|
|
4969
|
+
Returns a new Wire obtained by recursively replacing segments of the
|
|
4970
|
+
input wire with the longest possible straight edge that is fully
|
|
4971
|
+
embedded in the given face.
|
|
4972
|
+
|
|
4973
|
+
For each starting vertex v_i along the wire, this method searches for
|
|
4974
|
+
the furthest vertex v_j (j > i) such that the straight Edge between
|
|
4975
|
+
v_i and v_j satisfies:
|
|
4976
|
+
|
|
4977
|
+
Topology.Difference(edge, face) == None
|
|
4978
|
+
|
|
4979
|
+
i.e. the edge lies completely within (or on the boundary of) the face.
|
|
4980
|
+
All edges of the original wire between vertex indices i and j are then
|
|
4981
|
+
replaced by this straight edge, and the process is repeated recursively
|
|
4982
|
+
from index j.
|
|
4983
|
+
|
|
4984
|
+
Parameters
|
|
4985
|
+
----------
|
|
4986
|
+
wire : topologic_core.Wire
|
|
4987
|
+
The input path wire whose vertices define the route to be
|
|
4988
|
+
straightened.
|
|
4989
|
+
face : topologic_core.Face
|
|
4990
|
+
The face within which the straightened edges must lie.
|
|
4991
|
+
tolerance : float , optional
|
|
4992
|
+
Numerical tolerance used for internal robustness checks. The
|
|
4993
|
+
Topology.Difference call itself is left with its default tolerance.
|
|
4994
|
+
Default is 1e-6.
|
|
4995
|
+
|
|
4996
|
+
Returns
|
|
4997
|
+
-------
|
|
4998
|
+
wire : topologic_core.Wire
|
|
4999
|
+
A new Wire whose vertices define the recursively straightened path.
|
|
5000
|
+
"""
|
|
5001
|
+
from topologicpy.Vertex import Vertex
|
|
5002
|
+
from topologicpy.Edge import Edge
|
|
5003
|
+
from topologicpy.Wire import Wire
|
|
5004
|
+
from topologicpy.Face import Face
|
|
5005
|
+
from topologicpy.Topology import Topology
|
|
5006
|
+
# Get ordered vertices of the wire
|
|
5007
|
+
vertices = Topology.Vertices(wire)
|
|
5008
|
+
n = len(vertices)
|
|
5009
|
+
|
|
5010
|
+
if n <= 2:
|
|
5011
|
+
# Nothing to straighten
|
|
5012
|
+
return wire
|
|
5013
|
+
|
|
5014
|
+
def _edge_inside_face(v_start, v_end):
|
|
5015
|
+
"""
|
|
5016
|
+
Returns True if the straight edge between v_start and v_end is
|
|
5017
|
+
fully embedded in the face, i.e. Topology.Difference(edge, face)
|
|
5018
|
+
returns None.
|
|
5019
|
+
"""
|
|
5020
|
+
if v_start is v_end:
|
|
5021
|
+
return True
|
|
5022
|
+
edge = Edge.ByStartVertexEndVertex(v_start, v_end)
|
|
5023
|
+
diff = Topology.Difference(edge, face)
|
|
5024
|
+
return diff is None
|
|
5025
|
+
|
|
5026
|
+
def _find_longest_valid_index(start_idx):
|
|
5027
|
+
"""
|
|
5028
|
+
For a fixed start_idx, search for the largest index j >= start_idx+1
|
|
5029
|
+
such that the direct edge (vertices[start_idx], vertices[j]) is
|
|
5030
|
+
fully inside the face.
|
|
5031
|
+
|
|
5032
|
+
If for any reason no such j exists (which should not happen if the
|
|
5033
|
+
original wire lies in the face), it falls back to start_idx + 1.
|
|
5034
|
+
"""
|
|
5035
|
+
v_start = vertices[start_idx]
|
|
5036
|
+
best_j = None
|
|
5037
|
+
|
|
5038
|
+
for j in range(start_idx + 1, n):
|
|
5039
|
+
v_end = vertices[j]
|
|
5040
|
+
if _edge_inside_face(v_start, v_end):
|
|
5041
|
+
best_j = j
|
|
5042
|
+
# Do NOT break on failure: a further vertex might still
|
|
5043
|
+
# be reachable by a straight edge that stays in the face.
|
|
5044
|
+
|
|
5045
|
+
if best_j is None:
|
|
5046
|
+
# Fallback: use the immediate next vertex to avoid stalling
|
|
5047
|
+
best_j = min(start_idx + 1, n - 1)
|
|
5048
|
+
|
|
5049
|
+
return best_j
|
|
5050
|
+
|
|
5051
|
+
def _straighten_recursive(start_idx, out_vertices):
|
|
5052
|
+
"""
|
|
5053
|
+
Recursive helper.
|
|
5054
|
+
|
|
5055
|
+
Appends the chosen vertices to out_vertices. At each step, it
|
|
5056
|
+
decides how far it can jump from start_idx with a single straight
|
|
5057
|
+
edge inside the face, then recurses from that new index.
|
|
5058
|
+
"""
|
|
5059
|
+
# Base case: we are at the last vertex
|
|
5060
|
+
if start_idx == n - 1:
|
|
5061
|
+
out_vertices.append(vertices[start_idx])
|
|
5062
|
+
return
|
|
5063
|
+
|
|
5064
|
+
# Find furthest valid index reachable from start_idx
|
|
5065
|
+
next_idx = _find_longest_valid_index(start_idx)
|
|
5066
|
+
|
|
5067
|
+
# Add the starting vertex for this segment
|
|
5068
|
+
out_vertices.append(vertices[start_idx])
|
|
5069
|
+
|
|
5070
|
+
# Recurse from the chosen furthest index
|
|
5071
|
+
_straighten_recursive(next_idx, out_vertices)
|
|
5072
|
+
|
|
5073
|
+
# Run the recursion
|
|
5074
|
+
new_vertices = []
|
|
5075
|
+
_straighten_recursive(0, new_vertices)
|
|
5076
|
+
|
|
5077
|
+
# In case of any numerical quirks, ensure the last original vertex is present
|
|
5078
|
+
if new_vertices[-1] is not vertices[-1]:
|
|
5079
|
+
new_vertices.append(vertices[-1])
|
|
5080
|
+
|
|
5081
|
+
# Build the new straightened wire
|
|
5082
|
+
return Wire.ByVertices(new_vertices, close=False)
|
|
5083
|
+
|
|
4716
5084
|
@staticmethod
|
|
4717
5085
|
def Trapezoid(origin= None, widthA: float = 1.0, widthB: float = 0.75, offsetA: float = 0.0, offsetB: float = 0.0, length: float = 1.0, direction: list = [0, 0, 1], placement: str = "center", tolerance: float = 0.0001):
|
|
4718
5086
|
"""
|
topologicpy/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.8.
|
|
1
|
+
__version__ = '0.8.90'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topologicpy
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.90
|
|
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
|
|
@@ -12,7 +12,7 @@ topologicpy/Dictionary.py,sha256=goODXIM6AoC5Qn_d8LGc5pRoxZKgIWbkn3IOEbsQ4c4,449
|
|
|
12
12
|
topologicpy/Edge.py,sha256=aiRd1xZgG2GGYHxva0bM-kDy3AVmwGA_S2pMur8EeMg,74911
|
|
13
13
|
topologicpy/EnergyModel.py,sha256=MEai1GF1hINeH5bhclJj_lpMU3asFTvW2RlPm40GNj4,57794
|
|
14
14
|
topologicpy/Face.py,sha256=qAl36LcwiyRclMM2pI9NyWHzmgNlaykXiJx1wu10RmA,201317
|
|
15
|
-
topologicpy/Graph.py,sha256=
|
|
15
|
+
topologicpy/Graph.py,sha256=c-n_9xKXizbyGGWBw62fECsqMo-UoqvQJz6nw82DWgA,811337
|
|
16
16
|
topologicpy/Grid.py,sha256=3OsBMyHh4w8gpFOTMKHMNTpo62V0CwRNu5cwm87yDUA,18421
|
|
17
17
|
topologicpy/Helper.py,sha256=NsmMlbbKFPRX6jfoko-ZQVQ7MBsfVp9FD0ZvC2U7q-8,32002
|
|
18
18
|
topologicpy/Honeybee.py,sha256=dBk01jIvxjQMGHqSarM1Cukv16ot4Op7Dwlitn2OMoc,48990
|
|
@@ -29,11 +29,11 @@ topologicpy/Sun.py,sha256=ezisiHfc2nd7A_8w0Ykq2VgbS0A9WNSg-tBwvfTQAVM,36735
|
|
|
29
29
|
topologicpy/Topology.py,sha256=E_AyPPCIx_Eq-UT74QS3LKFXIwdwekRjJJGTo1CRMRY,548577
|
|
30
30
|
topologicpy/Vector.py,sha256=pEC8YY3TeHGfGdeNgvdHjgMDwxGabp5aWjwYC1HSvMk,42236
|
|
31
31
|
topologicpy/Vertex.py,sha256=26TrlX9OCZUN-lMlZG3g4RHTWBqw69NW4AOEgRz_YMo,91269
|
|
32
|
-
topologicpy/Wire.py,sha256=
|
|
32
|
+
topologicpy/Wire.py,sha256=Rhqw0CGEWIMVL1ICQqkCp9G-VnhhHLhEiDDR00fAn_s,248919
|
|
33
33
|
topologicpy/__init__.py,sha256=RMftibjgAnHB1vdL-muo71RwMS4972JCxHuRHOlU428,928
|
|
34
|
-
topologicpy/version.py,sha256=
|
|
35
|
-
topologicpy-0.8.
|
|
36
|
-
topologicpy-0.8.
|
|
37
|
-
topologicpy-0.8.
|
|
38
|
-
topologicpy-0.8.
|
|
39
|
-
topologicpy-0.8.
|
|
34
|
+
topologicpy/version.py,sha256=Vg4EZKvQvJEbHjPwL_FRDJq1bK9YEoQB3n1ijcuv9Dk,23
|
|
35
|
+
topologicpy-0.8.90.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
|
36
|
+
topologicpy-0.8.90.dist-info/METADATA,sha256=Zk31JGzXHHQe7HU4kLuQ6SvCj6EnOW3PolmmmV1EepE,10535
|
|
37
|
+
topologicpy-0.8.90.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
topologicpy-0.8.90.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
|
39
|
+
topologicpy-0.8.90.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|