Hello!
I’m currently updating the “Medial Axis” component from @laurent_delrieu (Thank you by the way!)
I only need “Butt”/Hip Roof generation so I’ve chopped out the sand dune bits.
I’m in the process of upgrading some older components and one of them is utilizing the Brep.SweepTwoRail method and giving a warning that it has been deprecated.
I believe the new equivalent method is Brep.CreateFromSweepInParts
So I’ve went ahead and updated that but I’m struggling to understand what is expected for the “rail_params” argument as a Point2d.
My knowledge is more in grasshopper nodes and not so much code so I would think the rail would want a param as a single float/double similar to how “Evaluate Curve” functions and then a sweep profile would want a UV coordinate/Point2d such as (0,0) to get the starting corner point or (.5,.5) to get the center for instance (how the “Evaluate Surface” or “Evaluate Rectangle” component works)
Here is my “working” code snippet that is sweeping from the wrong location and I’m guessing but unsure this is because of me passing the 0,0 value in the rail_params argument?
I am requesting guidance on how to transition from the deprecated SweepTwoRail to the updated method.
Thank you for your help!
Code Snippet:
// var sweep = new Rhino.Geometry.SweepTwoRail();
// sweep.AngleToleranceRadians = RhinoDoc.ActiveDoc.ModelAngleToleranceRadians;
// sweep.ClosedSweep = false;
// sweep.SweepTolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
// Brep[] sideBreps = sweep.PerformSweep(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new LineCurve(p2 + move12, pmid));
// if (sideBreps.Length > 0) output[0] = sideBreps[0];
// sideBreps = sweep.PerformSweep(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new LineCurve(pmid, p2 + move23));
// if (sideBreps.Length > 0) output[1] = sideBreps[0];
//var sweep = new Rhino.Geometry.Brep.CreateFromSweepInParts();
//sweep.Closed = false;
//sweep.Tolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
Brep[] sideBreps = Brep.CreateFromSweepInParts(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new List<Curve> { new LineCurve(p2 + move12, pmid) }, new List<Point2d>{ new Point2d (0,0) }, false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
if (sideBreps.Length > 0) output[0] = sideBreps[0];
sideBreps = Brep.CreateFromSweepInParts(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new List<Curve> { new LineCurve(pmid, p2 + move23) }, new List<Point2d>{ new Point2d (0,0) }, false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
if (sideBreps.Length > 0) output[1] = sideBreps[0];
}
return output;
Full Code:
// Grasshopper Script Instance
using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Rhino;
using Rhino.Geometry;
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
public class Script_Instance : GH_ScriptInstance
{
//Medial Axis
//Laurent Delrieu
//31 / 12 / 2017
//l.delrieu@neuf.fr
//Modified by Michael Vollrath
//toyblock.co
//2023.09.23
private void RunScript(Rhino.Geometry.Polyline C, System.Collections.Generic.IEnumerable<double> A, bool P, out object R)
{
//Assign Tooltips
Component.Name = "Roof Creator";
Component.NickName = "Roof";
Component.Description = "A 3D roof solver with support for variable angles and optional multi-threading. \n\nDerived from the work of Laurent Delrieu entitled Medial Axis.\nThank you for sharing your work Laurent!";
Component.Params.Input[0].Name = "Boundary Curves";
Component.Params.Input[0].NickName = "C";
Component.Params.Input[0].Description = "Boundary Curves Definining Roof";
Component.Params.Input[1].Name = "Angles";
Component.Params.Input[1].NickName = "A";
Component.Params.Input[1].Description = "Set Of Angles Defining Roof Slope, One Angle Per Boundary Edge";
Component.Params.Input[2].Name = "Parallel";
Component.Params.Input[2].NickName = "P";
Component.Params.Input[2].Description = "Set True To Utilize Parallel Computing";
Component.Params.Output[0].Name = "Result";
Component.Params.Output[0].NickName = "R";
Component.Params.Output[0].Description = "Resulting Roof Geometry";
List<double> angleList = A.ToList(); // Convert IEnumerable<double> to List<double>
if (angleList.Count < (C.Count - 1))
{
double defaultAngle = Math.PI / 4;
if (angleList.Count > 0)
{
defaultAngle = angleList[angleList.Count - 1];
Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Not enough angles, by default " + RhinoMath.ToDegrees(angleList[angleList.Count - 1]) + "° taken");
}
else
{
Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Not enough angles, by default 45° is taken");
}
for (int i = angleList.Count; i < (C.Count - 1); i++)
{
angleList.Add(defaultAngle);
}
}
R = RoofGen(C, angleList, false, P);
}
//Calculation of 45° roof with differents valley
public List<Brep> RoofGen(Polyline C, List<double> A, bool isV6, bool P)
{
List<Brep> output = new List<Brep>();
if (C != null)
{
double tol = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
bool isClosed = C.IsClosedWithinTolerance(tol);
//Polyline is closed if not closed
if (!isClosed)
{
C.Add(C[0]);
}
//Just in case
C.CollapseShortSegments(tol * 2);
Curve polylineCurve = C.ToNurbsCurve();
//Definition of orientation of polyline
int orientation = 1;
if (isV6)
{
//CurveOrientation (inverted in RH5 vs RH6
// Undefined 0 Orientation is undefined.
// Clockwise -1 The curve's orientation is clockwise in the xy plane.
// CounterClockwise 1 The curve's orientation is counter clockwise in the xy plane
CurveOrientation curveOrientation = polylineCurve.ClosedCurveOrientation(Plane.WorldXY);
if (curveOrientation == CurveOrientation.Clockwise) orientation = -1;
else orientation = 1;
}
else
{
//Use of classical surface calculation of polyline
if (SurfaceOnXYplane(C) > 0) orientation = -1;
else orientation = 1;
}
//Bounding box calculation in order to have the max horizontal distances for the roofs surfaces (before cut)
BoundingBox bb = polylineCurve.GetBoundingBox(false);
double maxLength = bb.Diagonal.Length;
//List of brep representing the roof before cut
List<Brep> breps = new List<Brep>();
//Side of roof
for (int i = 0; i < (C.Count - 1); i++)
{
//Calculate direction of line
Point3d p1 = C[i];
Point3d p2 = C[(i + 1)];
Vector3d direction12 = (p2 - p1) * orientation;
direction12.Unitize();
//Calculate a perpendicular
Vector3d perp12 = Vector3d.CrossProduct(direction12, Vector3d.ZAxis);
perp12.Unitize();
//Because move is one unit horizontal (per12) + one unit in Z => 45°
//if you want other angle put a coefficient on perp12 (0 => 90°)
Vector3d move = perp12 * Math.Tan(Math.PI / 2 - A[i]) + Vector3d.ZAxis;
move *= maxLength;
//Make a sweep
var sweep = new Rhino.Geometry.SweepOneRail();
sweep.AngleToleranceRadians = RhinoDoc.ActiveDoc.ModelAngleToleranceRadians;
sweep.ClosedSweep = false;
sweep.SweepTolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
Brep[] sideBreps = sweep.PerformSweep(new LineCurve(p1, p1 + move), new LineCurve(p1, p2));
if (sideBreps.Length > 0) breps.Add(sideBreps[0]);
}
//Valley parts of the roof
for (int i = 0; i < (C.Count - 1); i++)
{
//Calculate direction of line
int index;
if (i == 0)
{
index = C.Count - 2;
}
else
{
index = i - 1;
}
Point3d p1 = C[index];
Point3d p2 = C[i];
Point3d p3 = C[i + 1];
Vector3d direction12 = (p2 - p1);
direction12.Unitize();
Vector3d direction23 = (p3 - p2);
direction23.Unitize();
Brep rp = new Brep();
//Butt type
{
Brep[] vv = ValleyButt(p2, direction12, direction23, A[index], A[i], maxLength, orientation);
if (vv.Length >= 2)
{
if (vv[0].IsValid) breps.Add(vv[0]);
if (vv[1].IsValid) breps.Add(vv[1]);
}
}
{
if (rp.IsValid) breps.Add(rp);
}
}
output = CutBreps(breps, tol, polylineCurve, P);
}
return output;
}
//Butt valley
public Brep[] ValleyButt(Point3d p2, Vector3d v12, Vector3d v23, double angle12, double angle23, double maxLength, int orientation)
{
Brep[] output = new Brep[2];
output[0] = new Brep();
output[1] = new Brep();
Vector3d vv = Vector3d.CrossProduct(v12, v23);
if (vv.Z * orientation > 0)
{
Vector3d perp12 = Vector3d.CrossProduct(v12, Vector3d.ZAxis * orientation);
perp12.Unitize();
Vector3d move12 = perp12 * Math.Tan(Math.PI / 2 - angle12) + Vector3d.ZAxis;
move12 *= maxLength;
Vector3d perp23 = Vector3d.CrossProduct(v23, Vector3d.ZAxis * orientation);
perp23.Unitize();
Vector3d move23 = perp23 * Math.Tan(Math.PI / 2 - angle23) + Vector3d.ZAxis;
move23 *= maxLength;
Line l12 = new Line(p2 + move12, v12);
Line l23 = new Line(p2 + move23, v23);
Point3d pmid = p2 + (move23 + move12) / 2;
double a, b;
if (Rhino.Geometry.Intersect.Intersection.LineLine(l12, l23, out a, out b, 0.01, false))
{
pmid = l12.PointAt(a);
}
// var sweep = new Rhino.Geometry.SweepTwoRail();
// sweep.AngleToleranceRadians = RhinoDoc.ActiveDoc.ModelAngleToleranceRadians;
// sweep.ClosedSweep = false;
// sweep.SweepTolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
// Brep[] sideBreps = sweep.PerformSweep(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new LineCurve(p2 + move12, pmid));
// if (sideBreps.Length > 0) output[0] = sideBreps[0];
// sideBreps = sweep.PerformSweep(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new LineCurve(pmid, p2 + move23));
// if (sideBreps.Length > 0) output[1] = sideBreps[0];
//var sweep = new Rhino.Geometry.Brep.CreateFromSweepInParts();
//sweep.Closed = false;
//sweep.Tolerance = RhinoDoc.ActiveDoc.ModelAbsoluteTolerance;
Brep[] sideBreps = Brep.CreateFromSweepInParts(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new List<Curve> { new LineCurve(p2 + move12, pmid) }, new List<Point2d>{ new Point2d (0,0) }, false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
if (sideBreps.Length > 0) output[0] = sideBreps[0];
sideBreps = Brep.CreateFromSweepInParts(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new List<Curve> { new LineCurve(pmid, p2 + move23) }, new List<Point2d>{ new Point2d (0,0) }, false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
if (sideBreps.Length > 0) output[1] = sideBreps[0];
}
return output;
}
//Cut the roof complete surface with curves
//Example here
// https://discourse.mcneel.com/t/splitting-a-brep-by-a-curve-rhinocommon/17435/2
public List<Brep> CutBreps(List<Brep> breps, double tol, Curve C, bool P)
{
List<Brep> output = new List<Brep>();
if (P)
{
//Set Component Message As Such
Component.Message = "Parallel";
System.Threading.Tasks.Parallel.For(0, breps.Count,
i => {
List<Curve> curves = BrepsCurvesIntersection(breps, i, tol, P);
List<Brep> bb = new List<Brep>();
BrepFace bf = breps[i].Faces[0];
Brep split = bf.Split(curves, tol);
if (split != null)
{
foreach(BrepFace splitFace in split.Faces)
{
bb.Add(splitFace.DuplicateFace(false));
}
}
output.AddRange(LowerBrep(bb, C, tol));
});
}
else
{
//Not parallel
//Set Component Message As Such
Component.Message = " ";
for (int i = 0; i < breps.Count; i++)
{
List<Curve> curves = BrepsCurvesIntersection(breps, i, tol, P);
List<Brep> bb = new List<Brep>();
BrepFace bf = breps[i].Faces[0];
Brep split = bf.Split(curves, tol);
if (split != null)
{
foreach(BrepFace splitFace in split.Faces)
{
bb.Add(splitFace.DuplicateFace(false));
}
}
output.AddRange(LowerBrep(bb, C, tol));
}
}
return output;
}
//Determine the intersection between the roofs
public List<Curve> BrepsCurvesIntersection(List<Brep> breps, int index, double tol, bool P)
{
List<Curve> output = new List<Curve> ();
if (P)
{
System.Threading.Tasks.Parallel.For(0, breps.Count,
i => {
if (i != index)
{
Curve[] intersectionCurves;
Point3d[] intersectionPoints;
if (Rhino.Geometry.Intersect.Intersection.BrepBrep(breps[i], breps[index], tol, out intersectionCurves, out intersectionPoints))
{
foreach (Curve curve in intersectionCurves)
{
output.Add(curve);
}
}
}
});
}
//Not parallel
else
{
for (int i = 0; i < (breps.Count - 0); i++)
{
if (i != index)
{
Curve[] intersectionCurves;
Point3d[] intersectionPoints;
if (Rhino.Geometry.Intersect.Intersection.BrepBrep(breps[i], breps[index], tol, out intersectionCurves, out intersectionPoints))
{
foreach (Curve curve in intersectionCurves)
{
output.Add(curve);
}
}
}
}
}
return output;
}
//Search for the lower breps, if brep touch the contour of the roof (roofCurve)
public List<Brep> LowerBrep(List<Brep> breps, Curve roofCurve, double tol)
{
List<Brep> output = new List<Brep>();
int index = -1;
if (breps.Count == 1)
{
output.Add(breps[0]);
}
else
{
for (int i = 0; i < breps.Count; i++)
{
Curve[] overlapCurves;
Point3d[] intersectionPoints;
bool test = Rhino.Geometry.Intersect.Intersection.CurveBrep(roofCurve, breps[i], tol, out overlapCurves, out intersectionPoints);
if (test)
{
//If the overlap is on the curve it is the part we want
if (overlapCurves.Length >= 1)
{
output.Add(breps[i]);
}
if (intersectionPoints.Length >= 1)
{
index = i;
}
}
}
//If there is just a point of contact, it must be a Valley
if ((output.Count == 0) && (index != -1))
{
output.Add(breps[index]);
}
}
return output;
}
//Calculate the surface of a flat closed polygon on XY plane
//The polygon could be on whatever height (allowed by substraction of Boundary Curve C[0])
// Positive if CounterClockwise
// Negative if Clockwise
public double SurfaceOnXYplane(Polyline C)
{
Vector3d sum = Vector3d.Zero;
for (int i = 0; i < (C.Count - 1); i++)
{
Vector3d cross = Vector3d.CrossProduct((Vector3d) (C[i] - C[0]), (Vector3d) (C[i + 1] - C[0]));
sum = sum + cross;
}
return sum.Z;
}
public Curve ThreePointsToArc(Point3d p0, Point3d p1, Point3d p2)
{
List<Point3d> pts = new List<Point3d>();
pts.Add(p0);
pts.Add(p1);
pts.Add(p2);
Rhino.Geometry.NurbsCurve nc = Rhino.Geometry.NurbsCurve.Create(false, 2, pts);
Point4d p41 = new Point4d(p1);
p41.W = 0.707107;
nc.Points.SetPoint(1, p41);
return nc;
}
}
Deprecated version:
Updated Attempt:
Graph Space: