Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make fittings using representation instances #1056

Merged
merged 5 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading