I actually have dozens of lines in different directions and I want to keep the bits of lines that have no overlap… So a simple, “get endpoint” and make a new line will not work.
There can be some issues with that when datasets have no overlapping cures and partially repeating overlapping curves. This might help in such situations:
If you’re ok with an imperative code method (Design Script, Python, C#) you can iteratively use the Split method above, or simplify the process. It’s less complex than it looks based on list management requirements.
You can also file a feature request via the submit idea button on the public roadmap here.
If you post that python (and confirm my initial curve set looks somewhat like what you’re using and that the previous results are somewhat right) I’ll have a look shortly.
I’ve just spent the last hour on ChatGPT (I love ChatGPT)…
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import Point, Line
# Two lists of lines
lines_list1 = IN[0]
lines_list2 = IN[1]
# Function to check if a point lies on a line segment
def is_point_on_segment(point, line):
start, end = line.StartPoint, line.EndPoint
return start.DistanceTo(point) + end.DistanceTo(point) - start.DistanceTo(end) < 0.001
# Function to subtract overlapping segment and return unique segments
def subtract_overlap(line1, line2):
start1, end1 = line1.StartPoint, line1.EndPoint
start2, end2 = line2.StartPoint, line2.EndPoint
# Ensure start points are consistently ordered
if start1.DistanceTo(start2) > end1.DistanceTo(start2):
start1, end1 = end1, start1
if start2.DistanceTo(start1) > end2.DistanceTo(start1):
start2, end2 = end2, start2
# Find intersection points
intersection_points = []
for point in [start2, end2]:
if is_point_on_segment(point, line1):
intersection_points.append(point)
for point in [start1, end1]:
if is_point_on_segment(point, line2):
intersection_points.append(point)
# If no intersection, return the original line and the line from list2
if not intersection_points:
return [line1], [line2]
# Sort intersection points by their distance from start1
intersection_points.sort(key=lambda p: p.DistanceTo(start1))
result_lines1 = []
result_lines2 = []
if start1.DistanceTo(intersection_points[0]) > 0.001:
result_lines1.append(Line.ByStartPointEndPoint(start1, intersection_points[0]))
if end1.DistanceTo(intersection_points[-1]) > 0.001:
result_lines1.append(Line.ByStartPointEndPoint(intersection_points[-1], end1))
if start2.DistanceTo(intersection_points[0]) > 0.001:
result_lines2.append(Line.ByStartPointEndPoint(start2, intersection_points[0]))
if end2.DistanceTo(intersection_points[-1]) > 0.001:
result_lines2.append(Line.ByStartPointEndPoint(intersection_points[-1], end2))
return result_lines1, result_lines2
# Process each line in list1 against all lines in list2
def process_lines(lines_list1, lines_list2):
processed_lines1 = []
remaining_lines2 = lines_list2.copy()
lines_to_remove = []
for line1 in lines_list1:
current_segments = [line1]
for i, line2 in enumerate(lines_list2):
new_segments = []
for segment in current_segments:
result_segments1, result_segments2 = subtract_overlap(segment, line2)
new_segments.extend(result_segments1)
remaining_lines2[i] = result_segments2
if not result_segments2: # If result_segments2 is empty, it means line2 was fully overlapped
lines_to_remove.append(line2)
current_segments = new_segments
processed_lines1.extend(current_segments)
non_overlapped_lines2 = []
for line2 in lines_list2:
if line2 not in lines_to_remove:
non_overlapped_lines2.append(line2)
return processed_lines1 + non_overlapped_lines2
# Subtract the overlap for each line in list1 against all lines in list2
result_lines = process_lines(lines_list1, lines_list2)
OUT = result_lines
Here was my result (call ran long and I had to jump on another one right after it ended):
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
initialCrvs = IN[0]
newCrvs = []
for i in range(len(initialCrvs)):
crv = initialCrvs.pop(0) #take out a curve
others = []
[[others.append(j) for j in crv.Intersect(i) if j.__class__ == Line] for i in initialCrvs]
if len(others)!=0:
others = PolySurface.ByJoinedSurfaces([i.Extrude(Vector.ZAxis()) for i in others])
splitCrv = crv.Split(others)
[newCrvs.append(i) for i in splitCrv if not i.PointAtParameter(0.5).DoesIntersect(others)]
elif len(others) == 0:
newCrvs.append(crv)
initialCrvs.append(crv)
OUT = newCrvs
I beat your GPT by quite a few lines, but not sure which is faster.
Looking at this again, the two code bases are different.
Yours takes a list of curves to remove from the base list of curves.
Mine gets the unique segment from a flat list of curves.
Shifted to a definition incase that helps anyone further:
import sys, clr #
clr.AddReference('ProtoGeometry') #add Dynamo's geometry library to the common language runtime
from Autodesk.DesignScript.Geometry import * #import Dynamo's geometry library
def UniqueCurveSegments(initialCurves):
uniqueSegments = [] #empty list to hold the resulting curves
for i in range(len(initialCurves)): #for each index in the list of curves
crv = initialCurves.pop(0) #take out the first curve
others = [] #an empty list to hold the others
[[others.append(j) for j in crv.Intersect(i) if j.__class__ == Line] for i in initialCurves] #append the intersection between the curve and other items to the others list only if they intersect as a line
if len(others)!=0: #if there are items in the others list
others = PolySurface.ByJoinedSurfaces([i.Extrude(Vector.ZAxis()) for i in others]) #build a polysurface
splitCrv = crv.Split(others) #split the curve
[uniqueSegments.append(i) for i in splitCrv if not i.PointAtParameter(0.5).DoesIntersect(others)] #remove any segments with a midpoint not on the polysurface and append to the new curves list
elif len(others) == 0: #if the curve didn't have any other intersecting elements it needs to be included by default
uniqueSegments.append(crv) #append the curve to the list
initialCurves.append(crv) #put the curve back in the list
return uniqueSegments #return the new curves
initialCrvs = IN[0] #the list of curves from the Dynamo environment
newCrvs = UniqueCurveSegments(initialCrvs) #get the unique curve segements
OUT = newCrvs #return the unique curve segments to the Dynamo environment
List.Join might be better in these cases… think there is value in having for nodes for both Curve.Difference and Curves.UniqueSegments in Dynamo core. FYI @achintya_bhat
Not to side track the great discussion on the line intersection. But if we’re assuming the lines are always closed curves, this problem could be simplified by using surfaces rather than lines.
But also I’m following this discussion closely, because I recently came a across a similar scenario where I was trying to find the edges where floor slabs touched (for doweling and slab folds).
Topology.Edges to get the edges of the polysurface.
Edge.AdjacentSurfaces to get the adjacent surfaces to each edge.
List.Count with level set to @L2 to get the number of faces on each edge.
== to test if the count is 2.
List.FilterByBoolMask to filter the edges by the resulting boolean from the equals node.
Anything in the ‘in’ output from the FilterByBoolMask is a curve where two slabs meet. Anything in the ‘out’ is a perimeter edge (either interior loop or exterior).
If they aren’t coplanar you might have some issues with this method, as things like ramps might not be caught.
The issue that we will run into here is that an edge can be intersecting without being adjacent, don’t forget PolySurface.ByJoinedSurfaces is a join not a necessarily union. Imagine a rectangular building with n smaller private balcony slabs along the edges. We’ll end up with 1+n faces in the polysurface but no shared edges since all edges are unique.
My solution was to normalize all line directions and then do comparisons by grouped direction vectors. But it was a ton Python, and I’m assuming not well optimized.