topologicpy 0.8.89__py3-none-any.whl → 0.8.91__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 +50 -6
- topologicpy/Wire.py +375 -7
- topologicpy/version.py +1 -1
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.91.dist-info}/METADATA +1 -1
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.91.dist-info}/RECORD +8 -8
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.91.dist-info}/WHEEL +0 -0
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.91.dist-info}/licenses/LICENSE +0 -0
- {topologicpy-0.8.89.dist-info → topologicpy-0.8.91.dist-info}/top_level.txt +0 -0
topologicpy/Graph.py
CHANGED
|
@@ -22,6 +22,7 @@ import warnings
|
|
|
22
22
|
|
|
23
23
|
from collections import namedtuple
|
|
24
24
|
from multiprocessing import Process, Queue
|
|
25
|
+
from typing import Any
|
|
25
26
|
|
|
26
27
|
try:
|
|
27
28
|
import numpy as np
|
|
@@ -5251,7 +5252,8 @@ class Graph:
|
|
|
5251
5252
|
useInternalVertex: bool = False,
|
|
5252
5253
|
storeBREP: bool =False,
|
|
5253
5254
|
mantissa: int = 6,
|
|
5254
|
-
tolerance: float = 0.0001
|
|
5255
|
+
tolerance: float = 0.0001,
|
|
5256
|
+
silent: float = False):
|
|
5255
5257
|
"""
|
|
5256
5258
|
Creates a graph.See https://en.wikipedia.org/wiki/Graph_(discrete_mathematics).
|
|
5257
5259
|
|
|
@@ -5305,6 +5307,8 @@ class Graph:
|
|
|
5305
5307
|
If set to True, store the BRep of the subtopology in its representative vertex. Default is False.
|
|
5306
5308
|
tolerance : float , optional
|
|
5307
5309
|
The desired tolerance. Default is 0.0001.
|
|
5310
|
+
silent : bool , optional
|
|
5311
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
5308
5312
|
|
|
5309
5313
|
Returns
|
|
5310
5314
|
-------
|
|
@@ -5319,6 +5323,10 @@ class Graph:
|
|
|
5319
5323
|
from topologicpy.Topology import Topology
|
|
5320
5324
|
from topologicpy.Aperture import Aperture
|
|
5321
5325
|
|
|
5326
|
+
if not Topology.IsInstance(topology, "topology"):
|
|
5327
|
+
if not silent:
|
|
5328
|
+
print("Graph.ByTopology - Error: The input topology parameter is not a valid topology. Returning None.")
|
|
5329
|
+
return None
|
|
5322
5330
|
def _viaSharedTopologies(vt, sharedTops):
|
|
5323
5331
|
verts = []
|
|
5324
5332
|
eds = []
|
|
@@ -15533,7 +15541,16 @@ class Graph:
|
|
|
15533
15541
|
return graph
|
|
15534
15542
|
|
|
15535
15543
|
@staticmethod
|
|
15536
|
-
def ShortestPath(graph,
|
|
15544
|
+
def ShortestPath(graph,
|
|
15545
|
+
vertexA,
|
|
15546
|
+
vertexB,
|
|
15547
|
+
vertexKey: str = "",
|
|
15548
|
+
edgeKey: str = "Length",
|
|
15549
|
+
transferDictionaries: bool = False,
|
|
15550
|
+
straighten: bool = False,
|
|
15551
|
+
face: Any = None,
|
|
15552
|
+
tolerance: float = 0.0001,
|
|
15553
|
+
silent: bool = False):
|
|
15537
15554
|
"""
|
|
15538
15555
|
Returns the shortest path that connects the input vertices. The shortest path will take into consideration both the vertexKey and the edgeKey if both are specified and will minimize the total "cost" of the path. Otherwise, it will take into consideration only whatever key is specified.
|
|
15539
15556
|
|
|
@@ -15549,8 +15566,19 @@ class Graph:
|
|
|
15549
15566
|
The vertex key to minimise. If set the vertices dictionaries will be searched for this key and the associated value will be used to compute the shortest path that minimized the total value. The value must be numeric. Default is None.
|
|
15550
15567
|
edgeKey : string , optional
|
|
15551
15568
|
The edge key to minimise. If set the edges dictionaries will be searched for this key and the associated value will be used to compute the shortest path that minimized the total value. The value of the key must be numeric. If set to "length" (case insensitive), the shortest path by length is computed. Default is "length".
|
|
15569
|
+
transferDictionaries : bool , optional
|
|
15570
|
+
If set to True, the dictionaries from the graph vertices will be transferred to the vertices of the shortest path. Otherwise, they won't. Default is False.
|
|
15571
|
+
Note: Edge dictionaries are not transferred (In straightened paths, the path edges are no longer the same as
|
|
15572
|
+
the original graph edges. Thus, you must implement your own logic to transfer edge dictionaries if needed).
|
|
15573
|
+
straighten : bool , optional
|
|
15574
|
+
If set to True, the path will be straightened as much as possible while remaining inside the specified face.
|
|
15575
|
+
Thus, the face input must be a valid topologic Face that is planar and residing on the XY plane. Default is False.
|
|
15576
|
+
face : topologic_core.Face , optional
|
|
15577
|
+
The face on which the path resides. This is used for straightening the path. Default is None.
|
|
15552
15578
|
tolerance : float , optional
|
|
15553
15579
|
The desired tolerance. Default is 0.0001.
|
|
15580
|
+
silent : bool , optional
|
|
15581
|
+
If set to True, error and warning messages are suppressed. Default is False.
|
|
15554
15582
|
|
|
15555
15583
|
Returns
|
|
15556
15584
|
-------
|
|
@@ -15563,21 +15591,29 @@ class Graph:
|
|
|
15563
15591
|
from topologicpy.Topology import Topology
|
|
15564
15592
|
|
|
15565
15593
|
if not Topology.IsInstance(graph, "Graph"):
|
|
15566
|
-
|
|
15594
|
+
if not silent:
|
|
15595
|
+
print("Graph.ShortestPath - Error: The input graph is not a valid graph. Returning None.")
|
|
15567
15596
|
return None
|
|
15568
15597
|
if not Topology.IsInstance(vertexA, "Vertex"):
|
|
15569
|
-
|
|
15598
|
+
if not silent:
|
|
15599
|
+
print("Graph.ShortestPath - Error: The input vertexA is not a valid vertex. Returning None.")
|
|
15570
15600
|
return None
|
|
15571
15601
|
if not Topology.IsInstance(vertexB, "Vertex"):
|
|
15572
|
-
|
|
15602
|
+
if not silent:
|
|
15603
|
+
print("Graph.ShortestPath - Error: The input vertexB is not a valid vertex. Returning None.")
|
|
15573
15604
|
return None
|
|
15605
|
+
if straighten == True:
|
|
15606
|
+
if not Topology.IsInstance(face, "face"):
|
|
15607
|
+
if not silent:
|
|
15608
|
+
print("Graph.ShortestPath - Error: Straighten is set to True, but the face parameter is not a valid toopologic face. Returning None.")
|
|
15609
|
+
return None
|
|
15574
15610
|
if edgeKey:
|
|
15575
15611
|
if edgeKey.lower() == "length":
|
|
15576
15612
|
edgeKey = "Length"
|
|
15577
15613
|
try:
|
|
15578
15614
|
gsv = Graph.NearestVertex(graph, vertexA)
|
|
15579
15615
|
gev = Graph.NearestVertex(graph, vertexB)
|
|
15580
|
-
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey)
|
|
15616
|
+
shortest_path = graph.ShortestPath(gsv, gev, vertexKey, edgeKey) # Hook to Core
|
|
15581
15617
|
if not shortest_path == None:
|
|
15582
15618
|
if Topology.IsInstance(shortest_path, "Edge"):
|
|
15583
15619
|
shortest_path = Wire.ByEdges([shortest_path])
|
|
@@ -15586,6 +15622,14 @@ class Graph:
|
|
|
15586
15622
|
if Topology.IsInstance(shortest_path, "Wire"):
|
|
15587
15623
|
shortest_path = Wire.Reverse(shortest_path)
|
|
15588
15624
|
shortest_path = Wire.OrientEdges(shortest_path, Wire.StartVertex(shortest_path), tolerance=tolerance)
|
|
15625
|
+
if Topology.IsInstance(shortest_path, "wire"):
|
|
15626
|
+
if straighten == True and Topology.IsInstance(face, "face"):
|
|
15627
|
+
shortest_path = Wire.StraightenInFace(shortest_path, face)
|
|
15628
|
+
if transferDictionaries == True:
|
|
15629
|
+
path_verts = Topology.Vertices(shortest_path)
|
|
15630
|
+
for p_v in path_verts:
|
|
15631
|
+
g_v = Graph.NearestVertex(graph, p_v)
|
|
15632
|
+
p_v = Topology.SetDictionary(p_v, Topology.Dictionary(g_v))
|
|
15589
15633
|
return shortest_path
|
|
15590
15634
|
except:
|
|
15591
15635
|
return None
|
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.91'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: topologicpy
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.91
|
|
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=Tokxg55wHmx5hd9eai7EMRUdptADLt7HmJNrhq00UZk,810723
|
|
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=Pd3D7UyrHnkE76qM_AOq2A2ifXXq1OxDwEg8Q1gGwk8,23
|
|
35
|
+
topologicpy-0.8.91.dist-info/licenses/LICENSE,sha256=FK0vJ73LuE8PYJAn7LutsReWR47-Ooovw2dnRe5yV6Q,681
|
|
36
|
+
topologicpy-0.8.91.dist-info/METADATA,sha256=S_NaUxOVkvDCJJ4xVVdtRSLZnkW0tzPwic19V2FLkuw,10535
|
|
37
|
+
topologicpy-0.8.91.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
38
|
+
topologicpy-0.8.91.dist-info/top_level.txt,sha256=J30bDzW92Ob7hw3zA8V34Jlp-vvsfIkGzkr8sqvb4Uw,12
|
|
39
|
+
topologicpy-0.8.91.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|