Skip to content

Commit

Permalink
Merge pull request #1056 from hypar-io/fitting-representation-instances
Browse files Browse the repository at this point in the history
make fittings using representation instances (#1056)
  • Loading branch information
wynged authored Nov 17, 2023
2 parents 1d0020d + 4710866 commit 7db39fc
Show file tree
Hide file tree
Showing 10 changed files with 155 additions and 35 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
- `GeometricElement.RepresentationInstances`
- `ContentRepresentation`
- `Elements.Door`
- `ComponentBase.UseRepresentationInstances` - an option flag to make generating fitting models faster/smaller.

### Fixed

Expand All @@ -64,6 +65,7 @@
- `BoundedCurve.ToPolyline` now works correctly for `EllipticalArc` class.

### Changed

- `GltfExtensions.UseReferencedContentExtension` is now true by default.
- `GeometricElement.Intersects` method now supports multiple representations.
- `GltfExtensions.ToGlTF` creates parent node for element and child nodes for representation instances.
Expand Down
49 changes: 38 additions & 11 deletions Elements.MEP/src/Fittings/Elbow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public Elbow(Vector3 position, Vector3 startDirection, Vector3 endDirection, dou

public override void UpdateRepresentations()
{
var profile = new Circle(Vector3.Origin, this.Start.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var profile = new Circle(Vector3.Origin, Start.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);

var oneSweep = new Sweep(profile,
GetSweepLine(),
Expand All @@ -38,8 +38,19 @@ public override void UpdateRepresentations()
0,
false);

var arrows = this.Start.GetArrow(this.Transform.Origin).Concat(this.End.GetArrow(this.Transform.Origin));
this.Representation = new Representation(new List<SolidOperation> { oneSweep }.Concat(arrows).Concat(GetExtensions()).ToList());
var arrows = new List<SolidOperation>();
arrows.AddRange(Start.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform()));
arrows.AddRange(End.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform()));
var solidOperations = new List<SolidOperation> { oneSweep }.Concat(arrows).Concat(GetExtensions()).ToList();

if (UseRepresentationInstances)
{
FittingRepresentationStorage.SetFittingRepresentation(this, () => solidOperations);
}
else
{
Representation = new Representation(solidOperations);
}
}

public override Port[] GetPorts()
Expand All @@ -64,23 +75,25 @@ private Vector3 BendRadiusOffset(double? bendRadius, Vector3 direction)

private Polyline GetSweepLine()
{
var sweepLine = new List<Vector3>();
sweepLine.Add(this.Start.Position - this.Transform.Origin);
var sweepLine = new List<Vector3>
{
Start.Position - Transform.Origin
};

if (this.BendRadius != 0)
if (BendRadius != 0)
{
var startDirection = Vector3.XAxis;
var startPoint = startDirection * this.BendRadius;
var startPoint = startDirection * BendRadius;
var startNormal = startDirection.Cross(Vector3.ZAxis).Unitized();

var originalPlane = new Polygon(Vector3.Origin, (this.Start.Position - this.Transform.Origin).Unitized(), (this.End.Position - this.Transform.Origin).Unitized());
var originalPlane = new Polygon(Vector3.Origin, (Start.Position - Transform.Origin).Unitized(), (End.Position - Transform.Origin).Unitized());
var transform = originalPlane.ToTransform();
var inverted = transform.Inverted();
originalPlane.Transform(inverted);

var angleBetweenOriginalVectors = originalPlane.Vertices[1].PlaneAngleTo(originalPlane.Vertices[2]) * Math.PI / 180;
var endDirection = new Vector3(Math.Cos(angleBetweenOriginalVectors), Math.Sin(angleBetweenOriginalVectors));
var endPoint = endDirection * this.BendRadius;
var endPoint = endDirection * BendRadius;
var endNormal = endDirection.Cross(Vector3.ZAxis).Unitized();

new Ray(startPoint, startNormal).Intersects(new Ray(endPoint, endNormal), out var intersectionPoint, true);
Expand All @@ -103,9 +116,17 @@ private Polyline GetSweepLine()
sweepLine.Add(Vector3.Origin);
}

sweepLine.Add(this.End.Position - this.Transform.Origin);
sweepLine.Add(End.Position - Transform.Origin);

return new Polyline(sweepLine);
if (UseRepresentationInstances)
{
var t = GetRotatedTransform().Inverted();
return new Polyline(sweepLine.Select(v => t.OfPoint(v)).ToList());
}
else
{
return new Polyline(sweepLine);
}
}

public override Transform GetRotatedTransform()
Expand All @@ -114,5 +135,11 @@ public override Transform GetRotatedTransform()
var t = new Transform(Vector3.Origin, End.Direction, zAxis);
return t;
}

/// <inheritdoc/>
public override string GetRepresentationHash()
{
return $"{this.GetType().Name}-{this.Diameter}-{this.BendRadius}-{this.Angle}";
}
}
}
5 changes: 5 additions & 0 deletions Elements.MEP/src/Fittings/Fitting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ public Port[] GetConnectors()
return GetPorts();
}

public virtual string GetRepresentationHash()
{
return this.GetHashCode().ToString();
}

abstract public Port[] GetPorts();

public abstract Transform GetRotatedTransform();
Expand Down
27 changes: 27 additions & 0 deletions Elements.MEP/src/Fittings/FittingRepresentationStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

using System;
using System.Collections.Generic;
using Elements.Geometry;
using Elements.Geometry.Solids;

namespace Elements.Fittings
{
static class FittingRepresentationStorage
{
private static readonly Dictionary<string, List<RepresentationInstance>> _fittings = new Dictionary<string, List<RepresentationInstance>>();
public static Dictionary<string, List<RepresentationInstance>> Fittings => _fittings;

public static void SetFittingRepresentation(Fitting fitting, Func<IList<SolidOperation>> makeSolids)
{
var hash = fitting.GetRepresentationHash();
if (!_fittings.ContainsKey(hash))
{
var solids = makeSolids();
_fittings.Add(hash, new List<RepresentationInstance> { new RepresentationInstance(new SolidRepresentation(solids), fitting.Material) });
}
fitting.RepresentationInstances = _fittings[hash];

fitting.Transform = fitting.GetRotatedTransform().Concatenated(new Transform(fitting.Transform.Origin));
}
}
}
5 changes: 3 additions & 2 deletions Elements.MEP/src/Fittings/IComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Elements.Fittings

public abstract partial class ComponentBase : IComponent
{
public static bool UseRepresentationInstances = false;
/// <summary>
/// The component that is towards the trunk of the tree.
/// </summary>
Expand Down Expand Up @@ -348,10 +349,10 @@ public static double GetLength(this ComponentBase component)
return ps.Length();
case Terminal t:
var heightDelta = Math.Abs(t.Transform.Origin.Z - t.Port.Position.Z);

var terminalTransformOrigin = t.Transform.Origin.Project(Plane.XY);
var terminalPortPosition = t.Port.Position.Project(Plane.XY);

return terminalTransformOrigin.IsAlmostEqualTo(terminalPortPosition)
? heightDelta
: heightDelta + terminalTransformOrigin.DistanceTo(terminalPortPosition);
Expand Down
2 changes: 1 addition & 1 deletion Elements.MEP/src/Fittings/Manifold.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public override List<Port> BranchSidePorts()

public override Port[] GetPorts()
{
return new[] {Trunk}.Concat(Branches).ToArray();
return new[] { Trunk }.Concat(Branches).ToArray();
}

public override Port TrunkSidePort()
Expand Down
14 changes: 7 additions & 7 deletions Elements.MEP/src/Fittings/Port.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,9 @@ public bool IsComplimentaryConnector(Port other, double positionTolerance = Vect
{
return false;
}

var angle = Direction.AngleTo(other.Direction);

return angle.ApproximatelyEquals(180, angleTolerance);
return angle.ApproximatelyEquals(180, angleTolerance);
}

public bool IsIdenticalConnector(Port other, double positionTolerance = Vector3.EPSILON, double angleTolerance = 0.5)
Expand All @@ -68,22 +67,23 @@ public bool IsIdenticalConnector(Port other, double positionTolerance = Vector3.
{
return false;
}

var angle = Direction.AngleTo(other.Direction);

return angle.ApproximatelyEquals(0, angleTolerance);
}

public Sweep[] GetArrow(Vector3 relativeTo, double arrowLineLength = 0.1)
public Sweep[] GetArrow(Vector3 relativeTo, double arrowLineLength = 0.1, Transform fittingRotationTransform = null)
{
var fittingRotationTransformInverted = fittingRotationTransform == null || !ComponentBase.UseRepresentationInstances ? new Transform() : fittingRotationTransform.Inverted();

var arrayHeadLength = 0.01;
if (ShowArrows)
{
var transformedOrigin = Position - relativeTo;
var transformedPosition = fittingRotationTransformInverted.OfPoint(Position - relativeTo);
var arrowProfile = new Circle(Vector3.Origin, 0.01).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var arrowLine = new Line(transformedOrigin, transformedOrigin + Direction * arrowLineLength);
var arrowLine = new Line(transformedPosition, transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * arrowLineLength);
var headProfile = new Circle(Vector3.Origin, 0.02).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var headLine = new Line(transformedOrigin + Direction * arrowLineLength, transformedOrigin + Direction * (arrowLineLength + arrayHeadLength));
var headLine = new Line(transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * arrowLineLength, transformedPosition + fittingRotationTransformInverted.OfPoint(Direction) * (arrowLineLength + arrayHeadLength));
var shaft = new Sweep(arrowProfile, arrowLine, 0, 0, 0, false);
var head = new Sweep(headProfile, headLine, 0, 0, 0, false);
return new Sweep[] { shaft, head };
Expand Down
2 changes: 1 addition & 1 deletion Elements.MEP/src/Fittings/Reducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void Move(Vector3 translation)
}

/// <summary>
/// Port with smaller diameter points to the +X axis.
/// Port with smaller diameter points to the +X axis.
/// If there is eccentric transform, the smaller part will be shifted to the -Z axis.
/// We point smaller diameter in the +X direction so that there is one reducer defined in the standard orientation, to which this transformation is then applied.
/// This let's us just have one size 110/90 that is rotated into a 90/110 orientation when needed.
Expand Down
49 changes: 43 additions & 6 deletions Elements.MEP/src/Fittings/Wye.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,45 @@ public override void UpdateRepresentations()
var trunkPosition = Trunk.Position;
var mainPosition = MainBranch.Position;
var branchPosition = SideBranch.Position;
var origin = this.Transform.Origin;
var origin = Transform.Origin;

var trunkProfile = new Circle(new Vector3(), this.Trunk.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var trunkProfile = new Circle(new Vector3(), Trunk.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var trunkLine = new Line(Vector3.Origin, trunkPosition - origin);
if (UseRepresentationInstances)
{
trunkLine = trunkLine.TransformedLine(GetRotatedTransform().Inverted());
}
var trunk = new Sweep(trunkProfile, trunkLine, 0, 0, 0, false);

var mainProfile = new Circle(new Vector3(), this.MainBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var mainProfile = new Circle(new Vector3(), MainBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var mainLine = new Line(Vector3.Origin, mainPosition - origin);
if (UseRepresentationInstances)
{
mainLine = mainLine.TransformedLine(GetRotatedTransform().Inverted());
}
var main = new Sweep(mainProfile, mainLine, 0, 0, 0, false);

var branchProfile = new Circle(new Vector3(), this.SideBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var branchProfile = new Circle(new Vector3(), SideBranch.Diameter / 2).ToPolygon(FlowSystemConstants.CIRCLE_SEGMENTS);
var branchLine = new Line(Vector3.Origin, branchPosition - origin);
if (UseRepresentationInstances)
{
branchLine = branchLine.TransformedLine(GetRotatedTransform().Inverted());
}
var branch = new Sweep(branchProfile, branchLine, 0, 0, 0, false);

var arrows = this.Trunk.GetArrow(this.Transform.Origin).Concat(this.SideBranch.GetArrow(this.Transform.Origin)).Concat(this.MainBranch.GetArrow(this.Transform.Origin));
var arrows = new List<SolidOperation>();
arrows.AddRange(Trunk.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform()));
arrows.AddRange(SideBranch.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform()));
arrows.AddRange(MainBranch.GetArrow(Transform.Origin, fittingRotationTransform: GetRotatedTransform()));
var solidOps = new List<SolidOperation> { trunk, main, branch }.Concat(arrows).Concat(GetExtensions()).ToList();
this.Representation = new Geometry.Representation(solidOps);
if (UseRepresentationInstances)
{
FittingRepresentationStorage.SetFittingRepresentation(this, () => solidOps);
}
else
{
Representation = new Geometry.Representation(solidOps);
}
}

public override Port[] GetPorts()
Expand Down Expand Up @@ -189,5 +211,20 @@ public override Transform GetRotatedTransform()
var t = new Transform(Vector3.Origin, Trunk.Direction, zAxis);
return t;
}

/// <inheritdoc/>
public override string GetRepresentationHash()
{
var props = new double[] {
Trunk.Diameter,
(Trunk.Position - Transform.Origin).LengthSquared(),
MainBranch.Diameter,
(MainBranch.Position - Transform.Origin).LengthSquared(),
SideBranch.Diameter,
(SideBranch.Position - Transform.Origin).LengthSquared(),
Angle
};
return $"{this.GetType().Name}-{String.Join("-", props.Select(p => p.ToString()))}";
}
}
}
35 changes: 28 additions & 7 deletions Elements.MEP/test/FittingsTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
Expand All @@ -25,7 +25,7 @@ public partial class FittingsTests
[Fact]
public void MakeWye()
{
var model = new Model();
ComponentBase.UseRepresentationInstances = true;
var branchDirection = new Vector3(Math.Sqrt(2) / 2, 1, Math.Sqrt(2) / 2);
var mainDir = new Vector3(0, 1, 0);
var connectionPoint = new Vector3(1, 0, 1);
Expand All @@ -42,10 +42,12 @@ public void MakeWye()
var pipe2 = new StraightSegment(0,
wye.SideBranch,
new Port(wye.SideBranch.Position + branchDirection, branchDirection, wye.SideBranch.Diameter));
var pipe3 = new StraightSegment(0,
new Port(wye.Trunk.Position + wye.Trunk.Direction * 2, branchDirection, wye.Trunk.Diameter),
wye.Trunk
);

model.AddElements(new Element[] { pipe1, pipe2, wye });
model.AddElement(new Mass(Polygon.Rectangle(0.1, 0.1), 0.1));
model.ToGlTF(TestUtils.GetTestPath() + "wye.gltf", false);
SaveToGltf(nameof(MakeWye), new Element[] { pipe1, pipe2, pipe3, wye });
}

[Fact]
Expand Down Expand Up @@ -79,13 +81,32 @@ public void MakeReducer()
[Fact]
public void MakeElbow()
{
ComponentBase.UseRepresentationInstances = true;
Port.ShowArrows = true;
var position = new Vector3(1, 0, 1);
var endDirection = new Vector3(1, 0, 0);
var otherDirection = new Vector3(0, 1, 0);
var otherDirection = new Vector3(0, -1, 1);

var elbow = new Elbow(position, endDirection, otherDirection, 0.2, 0.1, FittingTreeRouting.DefaultFittingMaterial);
SaveToGltf(nameof(MakeElbow), elbow);
var startReferencePipe = new StraightSegment(0, elbow.Start, new Port(elbow.Start.Position + elbow.Start.Direction,
elbow.Start.Direction.Negate(),
elbow.Start.Diameter));
var endReferencePipe = new StraightSegment(0, elbow.End, new Port(elbow.End.Position + elbow.End.Direction,
elbow.End.Direction.Negate(),
elbow.End.Diameter));

position = (2, 2, 2);
otherDirection = (0, 1, 0);

var elbow2 = new Elbow(position, endDirection, otherDirection, 0.2, 0.1, FittingTreeRouting.DefaultFittingMaterial);
var startReferencePipe2 = new StraightSegment(0, elbow2.Start, new Port(elbow2.Start.Position + elbow2.Start.Direction,
elbow2.Start.Direction.Negate(),
elbow2.Start.Diameter));
var endReferencePipe2 = new StraightSegment(0, elbow2.End, new Port(elbow2.End.Position + elbow2.End.Direction,
elbow2.End.Direction.Negate(),
elbow2.End.Diameter));

SaveToGltf(nameof(MakeElbow), new Element[] { elbow, startReferencePipe, endReferencePipe, elbow2, startReferencePipe2, endReferencePipe2 });
}

[Fact]
Expand Down

0 comments on commit 7db39fc

Please sign in to comment.