Replacing Brep.SweepTwoRail with Brep.CreateFromSweepInParts

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:

20230923_Roof_Generator_Update_Test_01a.gh (25.1 KB)

1 Like

I think you dind’t look to the documentation. There is a simplest way !
It seems to work(in R7 and R8)

https://developer.rhino3d.com/api/rhinocommon/rhino.geometry.brep/createfromsweep

They use Point2D because there are 2 curves, so Point2d.X is the parameter on first curve, Point2d.Y is the parameter on second curve,

    Brep[] sideBreps = Brep.CreateFromSweep(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new LineCurve(p2 + move12, pmid), false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
      if (sideBreps.Length > 0) output[0] = sideBreps[0];

      sideBreps = Brep.CreateFromSweep(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new LineCurve(pmid, p2 + move23), false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
      if (sideBreps.Length > 0) output[1] = sideBreps[0];
1 Like

While I did see this method earlier, I understand now :sweat_smile:

This makes sense as we are only passing a single rail and section anyways so it made no sense for me to try to use the SweepInParts and wrap the single rails/profiles as lists.

Here’s the full code updated:

// 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
    //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. \nDerived from the work of Laurent Delrieu entitled Medial Axis.";

        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";

        //Add Component Warnings
        bool hasWarnings = false; // Flag to track if any warnings have been issued

        // Check if input C is null or empty
        if (C == null || C.Count == 0)
        {
            Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Input Boundary Curves (C) Failed To Collect Data");
            hasWarnings = true;
        }
        
        // Check if input A is null or empty
        if (A == null || !A.Any())
        {
            Component.AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Input Angles (A) Failed To Collect Data");
            hasWarnings = true;
        }

        // If any warnings were issued, set R to null and return
        if (hasWarnings)
        {
            R = null;
            return;
        }

        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);
            }
        }

        List<Brep> unjoinedBreps = RoofGen(C, angleList, false, P);

        // Join the Breps
        Brep[] joinedBrepsArray = Brep.JoinBreps(unjoinedBreps, RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
        List<Brep> joinedBreps = new List<Brep>();
        if (joinedBrepsArray != null)
        {
            joinedBreps = joinedBrepsArray.ToList(); // Convert the array to a list
        }

        // Merge coplanar faces in each joined Brep
        foreach (var brep in joinedBreps)
        {
            brep.MergeCoplanarFaces(RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
        }

        R = joinedBreps;
    }
    
    //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);
            }

            Brep[] sideBreps = Brep.CreateFromSweep(new LineCurve(p2 + move12, p2), new LineCurve(pmid, p2), new LineCurve(p2 + move12, pmid), false, Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance);
            if (sideBreps.Length > 0) output[0] = sideBreps[0];

            sideBreps = Brep.CreateFromSweep(new LineCurve(pmid, p2), new LineCurve(p2 + move23, p2), new LineCurve(pmid, p2 + move23), 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 polyline[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;
    }
}

Also, I’m realizing that (I don’t think?) you ever created a version of the script that handled both variable angles AND interior courtyard conditions.

We can solve the interior courtyards by creating inverted roofs and then boolean differencing the interior coutyards against the final roof forms.

Here’s a WIP preview with GH components:

1 Like

I stopped working on variable angles, there are many situations where it doesn’t work.

I just do these scripts to help people, I am not an architect. I am happy you continue and improve it.

1 Like

Yes, this is true, I’ve been experimenting with clamping user input values to prevent “impossible” angles and such.

And while what I show above “works” no one would likely build a roof such as what is shown.

That being said I try to solve for “bad input” cases and use cases that may be unexpected but are still “in a range of possibilities”

I’ve learned so much looking through your scripts and testing them out. Thank you for sharing and your contributions and help!