Skip to content
27 changes: 16 additions & 11 deletions Clipper_Engine/Compute/BooleanDifference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ public static partial class Compute
/**** Public methods ****/
/***************************************************/

[Description("Perform a boolean difference operation between a main region and a list of reference regions using Clipper2.")]
[Description("Perform a boolean difference operation between a main region and a list of reference regions using Clipper2. Reference regions that do not lie on the subject plane are skipped, and the subject is kept.")]
[Input("region", "The main polyline region to subtract from.")]
[Input("refRegions", "List of reference polylines to subtract from the main region.")]
[Input("curvePlane", "Optional plane for the geometry. If null, will be fitted from the main region.")]
[Input("refRegions", "List of reference polylines to subtract from the main region. Regions off the subject plane are ignored.")]
[Input("plane", "If null, processing is in 2D (the plane is fitted from the main region). If not null, 3D geometry is processed using this plane.")]
[Input("tolerance", "Tolerance for planarity checks and numerical precision. Default is Tolerance.Distance.")]
[Output("result", "List of polylines representing the difference (main region minus reference regions).")]
public static List<Polyline> BooleanDifference(this Polyline region, List<Polyline> refRegions, Plane curvePlane = null, double tolerance = Tolerance.Distance)
public static List<Polyline> BooleanDifference(this Polyline region, List<Polyline> refRegions, Plane plane = null, double tolerance = Tolerance.Distance)
{
if (region == null || refRegions == null || region.ControlPoints.Count < 3)
return null;
Expand All @@ -52,24 +52,29 @@ public static List<Polyline> BooleanDifference(this Polyline region, List<Polyli
if (clipRegions.Count == 0)
return new List<Polyline> { region };

if (curvePlane == null)
if (plane == null)
{
curvePlane = region.FitPlane();
if (region.ControlPoints.Any(x => !x.IsInPlane(curvePlane, tolerance)))
plane = region.FitPlane();
if (region.ControlPoints.Any(x => !x.IsInPlane(plane, tolerance)))
{
Base.Compute.RecordError("Clipper BooleanDifference method only works for coplanar polylines.");
return null;
}

// An opening on another plane must not discard the subject. Subtract only the clips that lie on this plane.
clipRegions = clipRegions.Where(x => x.ControlPoints.All(y => y != null && y.IsInPlane(plane, tolerance))).ToList();
if (clipRegions.Count == 0)
return new List<Polyline> { region };
}

if (clipRegions.Any(x => x.ControlPoints.Any(y => !y.IsInPlane(curvePlane, tolerance))))
if (!region.IsClosed(tolerance) || clipRegions.Any(x => !x.IsClosed(tolerance)))
{
Base.Compute.RecordError("Clipper BooleanDifference method only works for coplanar polylines.");
return null;
Base.Compute.RecordError("Boolean Difference works on closed regions.");
return new List<Polyline> { region };
}

// Find the orientation matrix to the global XY plane
TransformMatrix orientation = region.OrientationToGlobalXY(curvePlane, tolerance);
TransformMatrix orientation = region.OrientationToGlobalXY(plane, tolerance);
if (orientation == null)
return null;

Expand Down
28 changes: 17 additions & 11 deletions Clipper_Engine/Compute/BooleanIntersection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,32 +40,38 @@ public static partial class Compute
[Description("Perform a boolean intersection operation between two polylines using Clipper2.")]
[Input("poly1", "The first polyline for intersection.")]
[Input("poly2", "The second polyline for intersection.")]
[Input("curvePlane", "Optional plane for the geometry. If null, will be fitted from the first polyline.")]
[Input("plane", "If null, processing is in 2D (the plane is fitted from the first polyline). If not null, 3D geometry is processed using this plane.")]
[Input("tolerance", "Tolerance for planarity checks and numerical precision. Default is Tolerance.Distance.")]
[Output("result", "List of polylines representing the intersection of the two input polylines.")]
public static List<Polyline> BooleanIntersection(this Polyline poly1, Polyline poly2, Plane curvePlane = null, double tolerance = Tolerance.Distance)
public static List<Polyline> BooleanIntersection(this Polyline poly1, Polyline poly2, Plane plane = null, double tolerance = Tolerance.Distance)
{
if (poly1 == null || poly2 == null || poly1.ControlPoints.Count < 3 || poly2.ControlPoints.Count < 3)
return null;

if (curvePlane == null)
if (!poly1.IsClosed(tolerance) || !poly2.IsClosed(tolerance))
{
curvePlane = poly1.FitPlane();
if (poly1.ControlPoints.Any(x => !x.IsInPlane(curvePlane, tolerance)))
Base.Compute.RecordError("Boolean Intersection works on closed regions.");
return new List<Polyline>();
}

if (plane == null)
{
plane = poly1.FitPlane();
if (poly1.ControlPoints.Any(x => !x.IsInPlane(plane, tolerance)))
{
Base.Compute.RecordError("Clipper BooleanIntersection method only works for coplanar polylines.");
return null;
}
}

if (poly2.ControlPoints.Any(x => !x.IsInPlane(curvePlane, tolerance)))
{
Base.Compute.RecordError("Clipper BooleanIntersection method only works for coplanar polylines.");
return null;
if (poly2.ControlPoints.Any(x => !x.IsInPlane(plane, tolerance)))
{
Base.Compute.RecordError("Clipper BooleanIntersection method only works for coplanar polylines.");
return null;
}
}

// Find the orientation matrix to the global XY plane
TransformMatrix orientation = poly1.OrientationToGlobalXY(curvePlane, tolerance);
TransformMatrix orientation = poly1.OrientationToGlobalXY(plane, tolerance);
if (orientation == null)
return null;

Expand Down
18 changes: 12 additions & 6 deletions Clipper_Engine/Compute/BooleanUnion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ public static partial class Compute

[Description("Perform a boolean union operation between multiple polylines using Clipper2.")]
[Input("polylines", "List of polylines to union together.")]
[Input("curvePlane", "Optional plane for the geometry. If null, will be fitted from the first polyline.")]
[Input("plane", "If null, processing is in 2D (the plane is fitted from the first polyline). If not null, 3D geometry is processed using this plane.")]
[Input("tolerance", "Tolerance for planarity checks and numerical precision. Default is Tolerance.Distance.")]
[Output("result", "List of polylines representing the union of all input polylines.")]
public static List<Polyline> BooleanUnion(this List<Polyline> polylines, Plane curvePlane = null, double tolerance = Tolerance.Distance)
public static List<Polyline> BooleanUnion(this List<Polyline> polylines, Plane plane = null, double tolerance = Tolerance.Distance)
{
if (polylines == null || polylines.Count == 0)
return new List<Polyline>();
Expand All @@ -55,18 +55,24 @@ public static List<Polyline> BooleanUnion(this List<Polyline> polylines, Plane c
if (validPolylines.Count == 1)
return new List<Polyline> { validPolylines[0] };

if (curvePlane == null)
if (validPolylines.Any(x => !x.IsClosed(tolerance)))
{
curvePlane = validPolylines[0].FitPlane();
if (validPolylines.Any(x => x.ControlPoints.Any(y => !y.IsInPlane(curvePlane, tolerance))))
Base.Compute.RecordError("Boolean Union works on closed regions.");
return new List<Polyline>();
}

if (plane == null)
{
plane = validPolylines[0].FitPlane();
if (validPolylines.Any(x => x.ControlPoints.Any(y => !y.IsInPlane(plane, tolerance))))
{
Base.Compute.RecordError("Clipper BooleanUnion method only works for coplanar polylines.");
return new List<Polyline>();
}
}

// Find the orientation matrix to the global XY plane
TransformMatrix orientation = validPolylines[0].OrientationToGlobalXY(curvePlane, tolerance);
TransformMatrix orientation = validPolylines[0].OrientationToGlobalXY(plane, tolerance);
if (orientation == null)
return new List<Polyline>();

Expand Down
12 changes: 6 additions & 6 deletions Clipper_Engine/Compute/Offset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ public static partial class Compute
[Description("Offset a curve by the given distance (using Clipper http://www.angusj.com/delphi/clipper.php). Method only works for closed, planar polylines.")]
[Input("polyline", "A BHoM Polyline representing the curve to offset.")]
[Input("distance", "The distance by which to offset the curve (-Ve is inwards).")]
[Input("curvePlane", "Optional plane for the geometry. If null, will be fitted from the polyline.")]
[Input("plane", "If null, processing is in 2D (the plane is fitted from the polyline). If not null, 3D geometry is processed using this plane.")]
[Input("tolerance", "Tolerance to be used for planarity and closedness checks as well as for offset computations.")]
[Output("polylines", "Input polylines after offset.")]
public static List<Polyline> Offset(this Polyline polyline, double distance = 0, Plane curvePlane = null, double tolerance = Tolerance.Distance)
public static List<Polyline> Offset(this Polyline polyline, double distance = 0, Plane plane = null, double tolerance = Tolerance.Distance)
{
if (polyline == null)
return null;
Expand All @@ -57,18 +57,18 @@ public static List<Polyline> Offset(this Polyline polyline, double distance = 0,
return null;
}

if (curvePlane == null)
if (plane == null)
{
curvePlane = polyline.FitPlane();
if (polyline.ControlPoints.Any(x => !x.IsInPlane(curvePlane, tolerance)))
plane = polyline.FitPlane();
if (polyline.ControlPoints.Any(x => !x.IsInPlane(plane, tolerance)))
{
Base.Compute.RecordError("Clipper Offset method only works for planar polylines.");
return null;
}
}

// Find orientation matrix to the global XY plane
TransformMatrix orientation = polyline.OrientationToGlobalXY(curvePlane, tolerance);
TransformMatrix orientation = polyline.OrientationToGlobalXY(plane, tolerance);
if (orientation == null)
return null;

Expand Down
62 changes: 62 additions & 0 deletions Clipper_Engine/Query/CullDuplicates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.Engine.Geometry;
using BH.oM.Base.Attributes;
using BH.oM.Geometry;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

namespace BH.Engine.Clipper
{
public static partial class Query
{
/***************************************************/
/**** Public methods ****/
/***************************************************/

[Description("Removes duplicate polylines. Two polylines are duplicates when their control points are equal in the same order. Either list may be shifted, so a different starting point still counts as a duplicate. If plane is null, control points are compared directly. If plane is not null, they are projected onto the plane before comparison.")]
[Input("polylines", "The collection of polylines to cull duplicates from.")]
[Input("plane", "If null, control points are compared directly. If not null, control points are projected onto this plane before comparison.")]
[Output("polylines", "The collection of polylines with duplicates removed.")]
public static List<Polyline> CullDuplicates(this IEnumerable<Polyline> polylines, Plane plane = null)
{
if (polylines == null)
return new List<Polyline>();

List<Polyline> culled = new List<Polyline>();
foreach (Polyline polyline in polylines)
{
if (polyline == null)
continue;

if (!culled.Any(x => x.IsSame(polyline, plane)))
culled.Add(polyline);
}

return culled;
}

/***************************************************/
}
}
52 changes: 52 additions & 0 deletions Clipper_Engine/Query/DoesOverlap.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* This file is part of the Buildings and Habitats object Model (BHoM)
* Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
*
* Each contributor holds copyright over their respective contributions.
* The project versioning (Git) records all such contribution source information.
*
*
* The BHoM is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3.0 of the License, or
* (at your option) any later version.
*
* The BHoM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base.Attributes;
using BH.oM.Geometry;
using System.Collections.Generic;
using System.ComponentModel;
namespace BH.Engine.Clipper
{
public static partial class Query
{
/***************************************************/
/**** Public methods ****/
/***************************************************/

[Description("Checks if two polylines intersect on a plane without one fully containing the other.")]
[Input("poly1", "First polyline.")]
[Input("poly2", "Second polyline.")]
[Input("plane", "If null, processing is in 2D (the plane is fitted from the first polyline). If not null, 3D geometry is processed using this plane.")]
[Input("tolerance", "Distance tolerance.")]
[Output("intersects", "True if the polylines intersect.")]
public static bool DoesOverlap(this Polyline poly1, Polyline poly2, Plane plane = null, double tolerance = Tolerance.Distance)
{
if (poly1 == null || poly2 == null || poly1.ControlPoints.Count < 3 || poly2.ControlPoints.Count < 3)
return false;

List<Polyline> intersection = Compute.BooleanIntersection(poly1, poly2, plane, tolerance);
return intersection != null && intersection.Count > 0;
}

/***************************************************/
}
}
Loading
Loading