using FeMM.Common.Helpers;
using FeMM.Common.Models;
using FeMM.Grasshopper.DataTypes.FeMM;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using System.Xml;
namespace FeMM.Grasshopper.Components.Export
{
#if NETCOREAPP
[SupportedOSPlatform("windows")]
#endif
public class Fem2BimComponent : GH_Component
{
protected bool _run;
protected ModelModel _model;
protected List<Curve> _perimeters;
protected GH_Structure<GH_Curve> _holes;
protected List<double> _thickness;
protected List<GH_Group> _groups;
protected List<double> _offsets;
BackgroundWorker _worker;
ManualResetEvent _signal;
/// <summary>
/// Initializes a new instance of the Fem2BimComponent class.
/// </summary>
public Fem2BimComponent()
: base("Fem2Bim", "F2B", "Export the model to Revit through the Fem2Bim Revit plug-in", Helpers.CategoryNameConstants.CATEGORY_FEMM, Helpers.CategoryNameConstants.SUBCATEGORY_EXPORT)
{
_run = false;
_model = null;
_perimeters = null;
_holes = null;
_thickness = null;
_groups = null;
_worker = null;
_signal = null;
}
public override void CreateAttributes()
{
var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Send");
attr.ButtonPressed += () =>
{
if (Params.Input[6].SourceCount > 0)
{
_run = true;
ExpireSolution(true);
}
else
{
if (_worker == null)
{
Go();
attr.Text = "Stop";
}
else
{
_worker.CancelAsync();
_signal.Set();
attr.Text = "Send";
}
}
};
m_attributes = attr;
}
/// <summary>
/// Registers all the input parameters for this component.
/// </summary>
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter("Model", "M", "The FEM model", GH_ParamAccess.item);
pManager.AddCurveParameter("Perimeters", "Ps", "Slabs or walls perimeters", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddCurveParameter("Holes", "Hs", "Slabs or walls holes, one branch for each list item", GH_ParamAccess.tree);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddNumberParameter("Thickness", "Ts", "Thickness of each slab or wall", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddGenericParameter("Groups", "Gs", "Group of each slab or wall", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddGenericParameter("Offsets", "Os", "Offsets of each slab or wall", GH_ParamAccess.list);
pManager[pManager.ParamCount - 1].Optional = true;
pManager.AddTextParameter("Output Path", "O", "The full path of the file model to create", GH_ParamAccess.item);
pManager[pManager.ParamCount - 1].Optional = true;
}
/// <summary>
/// Registers all the output parameters for this component.
/// </summary>
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
}
/// <summary>
/// This is the method that actually does the work.
/// </summary>
/// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
protected override void SolveInstance(IGH_DataAccess DA)
{
Message = "";
GH_Model model = null;
var perimeters = new List<Curve>();
GH_Structure<GH_Curve> holes;
var thickness = new List<double>();
var groups = new List<GH_Group>();
var offsets = new List<double>();
string outputPath = "";
double toll;
if (!DA.GetData(0, ref model))
return;
DA.GetDataList(1, perimeters);
DA.GetDataTree(2, out holes);
DA.GetDataList(3, thickness);
DA.GetDataList(4, groups);
DA.GetDataList(5, offsets);
bool has_out_file = DA.GetData("Output Path", ref outputPath);
if (has_out_file)
{
((ComponentAttributes.ComponentOneButtonAttributes)m_attributes).Text = "Save";
if (Path.GetExtension(outputPath).ToLower() != ".f2b")
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid extension in file name");
return;
}
}
if (perimeters != null && (perimeters.Count != holes.Branches.Count || perimeters.Count != thickness.Count ||
perimeters.Count != groups.Count || perimeters.Count != offsets.Count))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Incongruence in datas dimension");
return;
}
toll = 0.1;//mm
switch (model.Value.Units)
{
case ModelModel.ModelUnits.kNm:
toll /= 1000d;
break;
case ModelModel.ModelUnits.Nmm:
break;
case ModelModel.ModelUnits.SI:
toll /= 1000d;
break;
default:
throw new NotSupportedException();
}
if (perimeters != null)
{
foreach (Curve per in perimeters)
{
if (!per.IsPolyline() || !per.IsPlanar(toll))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Perimeters have to be polylines");
return;
}
}
}
if (holes != null)
{
for (int i = 0; i < holes.Branches.Count; i++)
{
if (holes[i] != null)
{
for (int j = 0; j < holes[i].Count; j++)
{
GH_Curve c = holes[i][j];
if (c != null && (!c.Value.IsPolyline() || !c.Value.IsPlanar(toll)))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Holes have to be polylines");
return;
}
}
}
}
}
//Congruence in offsets dimensions. Actually only offset 0 or +/- 0.5*thickness are handled
//if (perimeters != null)
//{
//for (int i = 0; i < perimeters.Count; i++)
//{
// if ((System.Math.Abs(offsets[i]) > toll) &&
// (System.Math.Abs(offsets[i]) - 0.5 * thickness[i] > toll))
// {
// AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Offsets have to be 0 or +/-0.5*thickness");
// return;
// }
//}
//}
_model = model.Value;
_perimeters = perimeters;
_holes = holes;
_thickness = thickness;
_groups = groups;
_offsets = offsets;
if (_run)
{
try
{
XmlDocument xmlDoc = CreateExchangeFile();
if (xmlDoc != null)
xmlDoc.Save(outputPath);
Message = "Done";
}
catch (Exception e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
Message = "Error";
}
_run = false;
}
}
private void Go()
{
Message = "";
// With Pipe
_worker = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
_worker.DoWork += Send;
_worker.RunWorkerCompleted += Sent;
_signal = new ManualResetEvent(false);
_worker.RunWorkerAsync();
}
private void Send(object sender, DoWorkEventArgs e)
{
try
{
System.Globalization.CultureInfo saved = Thread.CurrentThread.CurrentCulture;
System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
customCulture.NumberFormat.NumberDecimalSeparator = ".";
Thread.CurrentThread.CurrentCulture = customCulture;
XmlDocument xmlDoc = CreateExchangeFile();
if (xmlDoc == null)
return;
string file_content;
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter))
{
xmlDoc.Save(xmlTextWriter);
//xmlDoc.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
file_content = stringWriter.GetStringBuilder().ToString();
}
Thread.CurrentThread.CurrentCulture = saved;
using (var pipeServer = new NamedPipeServerStream("F2BInteroptPipe", PipeDirection.InOut, 1,
PipeTransmissionMode.Byte, PipeOptions.Asynchronous))
{
IAsyncResult ar = pipeServer.BeginWaitForConnection(_ => _signal.Set(), null);
AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Waiting for Revit connection ...");
_signal.WaitOne();
if (ar.IsCompleted)
{
pipeServer.EndWaitForConnection(ar);
using (var bw = new BinaryWriter(pipeServer))
{
byte[] buffer = BitConverter.GetBytes(file_content.Length);
bw.Write(buffer);
buffer = System.Text.Encoding.ASCII.GetBytes(file_content);
bw.Write(buffer);
}
}
else
{
BackgroundWorker worker = sender as BackgroundWorker;
if (worker.CancellationPending)
{
e.Cancel = true;
}
}
}
}
catch (Exception ex)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
}
}
private void Sent(object sender, RunWorkerCompletedEventArgs e)
{
ClearRuntimeMessages();
if (e.Cancelled)
Message = "Job cancelled by user";
else
Message = "Job done with success.";
_worker = null;
_signal = null;
((ComponentAttributes.ComponentOneButtonAttributes)m_attributes).Text = "Send";
}
private XmlDocument CreateExchangeFile()
{
var xmlDoc = new XmlDocument();
double st_conv = 0; // conversion factor to mm for section names
double samePosToll;
XmlNode rootNode = xmlDoc.CreateElement("model");
XmlAttribute unitsAttr = xmlDoc.CreateAttribute("units");
string coordAccuracy;
switch (_model.Units)
{
case ModelModel.ModelUnits.kNm:
case ModelModel.ModelUnits.SI:
unitsAttr.Value = "m";
st_conv = 1000;
coordAccuracy = "F5";
break;
case ModelModel.ModelUnits.Nmm:
unitsAttr.Value = "mm";
st_conv = 1;
coordAccuracy = "F2";
break;
default:
throw new NotSupportedException();
}
samePosToll = Helpers.ModelHelper.GetTolerance();
rootNode.Attributes.Append(unitsAttr);
xmlDoc.AppendChild(rootNode);
// Sections
XmlNode sectionsNode = xmlDoc.CreateElement("sections");
rootNode.AppendChild(sectionsNode);
var offsets = new Hashtable();
var angles = new Hashtable();
//var heights = new Hashtable();
//var tapered = new Hashtable();
BeamPropertyModel[] beamProps = [.. _model.Beams.Select(b => b.BeamProperty).Distinct()];
int id = 1;
for (int i = 0; i < beamProps.Length; i++)
{
BeamPropertyModel bp = beamProps[i];
bp.Id = id;
id++;
XmlNode sectionNode = xmlDoc.CreateElement("section");
XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
nAttr.Value = bp.Name;
sectionNode.Attributes.Append(nAttr);
XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
nameAttr.Value = bp.Name;
sectionNode.Attributes.Append(nameAttr);
var offset = new Point2d(0, 0);
Point2d[] perimeterPts = null;
var centroid = new Point2d(0, 0);
XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
XmlAttribute d1Attr = xmlDoc.CreateAttribute("d1");
XmlAttribute d2Attr = xmlDoc.CreateAttribute("d2");
XmlAttribute d3Attr = xmlDoc.CreateAttribute("d3");
XmlAttribute t1Attr = xmlDoc.CreateAttribute("t1");
XmlAttribute t2Attr = xmlDoc.CreateAttribute("t2");
XmlAttribute t3Attr = xmlDoc.CreateAttribute("t3");
XmlAttribute mirrorGapAAttribute = xmlDoc.CreateAttribute("mirrorGapA");
switch (bp.SectionType)
{
case SectionModel.SectionTypes.SolidCircle:
typeAttr.Value = string.Format("CSS-{0:0.#}", bp.D * st_conv);
d1Attr.Value = bp.D.ToString();
//heights.Add(bp.Name, bp.D);
offset.X = 0;
offset.Y = 0;
break;
case SectionModel.SectionTypes.HollowCircle:
typeAttr.Value = string.Format("CHS-{0:0.#}x{1:0.#}", bp.D * st_conv, bp.T * st_conv);
d1Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T.ToString();
//heights.Add(bp.Name, bp.D);
offset.X = 0;
offset.Y = 0;
break;
case SectionModel.SectionTypes.SolidRectangle:
typeAttr.Value = string.Format("RSS-{0:0.#}x{1:0.#}", bp.D * st_conv, bp.B * st_conv);
d1Attr.Value = bp.B.ToString();
d2Attr.Value = bp.D.ToString();
//heights.Add(bp.Name, bp.D);
offset.X = 0;
offset.Y = 0;
break;
case SectionModel.SectionTypes.HollowRectangle:
typeAttr.Value = string.Format("RHS-{0:0.#}x{1:0.#}x{2:0.#}-{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
d1Attr.Value = bp.B.ToString();
d2Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T1.ToString();
t2Attr.Value = bp.T2.ToString();
//heights.Add(bp.Name, bp.D);
offset.X = 0;
offset.Y = 0;
break;
case SectionModel.SectionTypes.C:
if (bp.Mirror == SectionModel.MirrorTypes.None)
{
typeAttr.Value = string.Format("C-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
}
else if (bp.Mirror == SectionModel.MirrorTypes.Left)
{
typeAttr.Value = string.Format("2C-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}x{4:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv, bp.MirrorGapA * st_conv);
mirrorGapAAttribute.Value = bp.MirrorGapA.ToString();
}
else
{
throw new NotImplementedException("Gap different from Left not implemented");
}
d1Attr.Value = bp.B.ToString();
d2Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T1.ToString();
t2Attr.Value = bp.T2.ToString();
//heights.Add(bp.Name, bp.D);
perimeterPts = new Point2d[9];
perimeterPts[0] = new Point2d(0, 0);
perimeterPts[1] = new Point2d(bp.B, 0);
perimeterPts[2] = new Point2d(bp.B, bp.T1);
perimeterPts[3] = new Point2d(bp.T2, bp.T1);
perimeterPts[4] = new Point2d(bp.T2, bp.D - bp.T1);
perimeterPts[5] = new Point2d(bp.B, bp.D - bp.T1);
perimeterPts[6] = new Point2d(bp.B, bp.D);
perimeterPts[7] = new Point2d(0, bp.D);
perimeterPts[8] = new Point2d(0, 0);
centroid = PolygonCentroid(perimeterPts);
offset.X = bp.B / 2 - centroid.X;
offset.Y = bp.D / 2 - centroid.Y;
break;
case SectionModel.SectionTypes.I:
typeAttr.Value = string.Format("H-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.B1 * st_conv, bp.D * st_conv, bp.T1 * st_conv, bp.T3 * st_conv);
d1Attr.Value = bp.B1.ToString();
d2Attr.Value = bp.B2.ToString();
d3Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T1.ToString();
t2Attr.Value = bp.T2.ToString();
t3Attr.Value = bp.T3.ToString();
//heights.Add(bp.Name, bp.D);
perimeterPts = new Point2d[13];
perimeterPts[0] = new Point2d(bp.B1 > bp.B2 ? 0 : bp.B2 / 2 - bp.B1 / 2, 0);
perimeterPts[1] = perimeterPts[0] + new Vector2d(bp.B1, 0);
perimeterPts[2] = perimeterPts[1] + new Vector2d(0, bp.T1);
perimeterPts[3] = perimeterPts[2] - new Vector2d(bp.B1 / 2 - bp.T3 / 2, 0);
perimeterPts[4] = perimeterPts[3] + new Vector2d(0, bp.D - bp.T1 - bp.T2);
perimeterPts[5] = perimeterPts[4] + new Vector2d(bp.B2 / 2 - bp.T3 / 2, 0);
perimeterPts[6] = perimeterPts[5] + new Vector2d(0, bp.T2);
perimeterPts[7] = perimeterPts[6] - new Vector2d(bp.B2, 0);
perimeterPts[8] = perimeterPts[7] - new Vector2d(0, bp.T2);
perimeterPts[9] = perimeterPts[8] + new Vector2d(bp.B2 / 2 - bp.T3 / 2, 0);
perimeterPts[10] = perimeterPts[9] - new Vector2d(0, bp.D - bp.T1 - bp.T2);
perimeterPts[11] = new Point2d(perimeterPts[0].X, bp.T1);
perimeterPts[12] = new Point2d(perimeterPts[0].X, perimeterPts[0].Y);
centroid = PolygonCentroid(perimeterPts);
offset.X = Math.Max(bp.B2, bp.B1) / 2 - centroid.X;
offset.Y = bp.D / 2 - centroid.Y;
break;
case SectionModel.SectionTypes.T:
typeAttr.Value = string.Format("T-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.B * st_conv, bp.D * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
d1Attr.Value = bp.B.ToString();
d2Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T1.ToString();
t2Attr.Value = bp.T2.ToString();
//heights.Add(bp.Name, bp.D);
perimeterPts = new Point2d[9];
perimeterPts[0] = new Point2d(bp.B / 2 - bp.T2 / 2, 0);
perimeterPts[1] = perimeterPts[0] + new Vector2d(bp.T2, 0);
perimeterPts[2] = perimeterPts[1] + new Vector2d(0, bp.D - bp.T1);
perimeterPts[3] = new Point2d(bp.B, perimeterPts[2].Y);
perimeterPts[4] = new Point2d(bp.B, bp.D);
perimeterPts[5] = new Point2d(0, bp.D);
perimeterPts[6] = perimeterPts[5] - new Vector2d(0, bp.T1);
perimeterPts[7] = perimeterPts[0] + new Vector2d(0, bp.D - bp.T1);
perimeterPts[8] = new Point2d(perimeterPts[0].X, perimeterPts[0].Y);
centroid = PolygonCentroid(perimeterPts);
offset.X = bp.B / 2 - centroid.X;
offset.Y = bp.D / 2 - centroid.Y;
break;
case SectionModel.SectionTypes.Angle:
if (bp.Mirror == SectionModel.MirrorTypes.None)
{
typeAttr.Value = string.Format("L-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
}
else if (bp.Mirror == SectionModel.MirrorTypes.Left)
{
typeAttr.Value = string.Format("2L-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}x{4:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv, bp.MirrorGapA * st_conv);
mirrorGapAAttribute.Value = bp.MirrorGapA.ToString();
}
else
{
throw new NotImplementedException("Gap different from Left not implemented");
}
d1Attr.Value = bp.B.ToString();
d2Attr.Value = bp.D.ToString();
t1Attr.Value = bp.T1.ToString();
t2Attr.Value = bp.T2.ToString();
//heights.Add(bp.Name, bp.B);
perimeterPts = new Point2d[7];
perimeterPts[0] = new Point2d(0, 0);
perimeterPts[1] = new Point2d(bp.B, 0);
perimeterPts[2] = new Point2d(bp.B, bp.T1);
perimeterPts[3] = new Point2d(bp.T2, bp.T1);
perimeterPts[4] = new Point2d(bp.T2, bp.D);
perimeterPts[5] = new Point2d(0, bp.D);
perimeterPts[6] = new Point2d(0, 0);
centroid = PolygonCentroid(perimeterPts);
offset.X = bp.B / 2 - centroid.X;
offset.Y = bp.D / 2 - centroid.Y;
break;
default:
typeAttr.Value = "Unknown";
break;
}
if (d1Attr.Value == string.Empty)
d1Attr.Value = "0";
if (d2Attr.Value == string.Empty)
d2Attr.Value = "0";
if (d3Attr.Value == string.Empty)
d3Attr.Value = "0";
if (t1Attr.Value == string.Empty)
t1Attr.Value = "0";
if (t2Attr.Value == string.Empty)
t2Attr.Value = "0";
if (t3Attr.Value == string.Empty)
t3Attr.Value = "0";
if (mirrorGapAAttribute.Value == string.Empty)
mirrorGapAAttribute.Value = "0";
sectionNode.Attributes.Append(typeAttr);
sectionNode.Attributes.Append(d1Attr);
sectionNode.Attributes.Append(d2Attr);
sectionNode.Attributes.Append(d3Attr);
sectionNode.Attributes.Append(t1Attr);
sectionNode.Attributes.Append(t2Attr);
sectionNode.Attributes.Append(t3Attr);
sectionNode.Attributes.Append(mirrorGapAAttribute);
XmlAttribute materialAttr = xmlDoc.CreateAttribute("material");
materialAttr.Value = bp.Material.Name;
sectionNode.Attributes.Append(materialAttr);
sectionsNode.AppendChild(sectionNode);
offsets.Add(bp.Id, offset);
switch (bp.SectionType)
{
case SectionModel.SectionTypes.C:
if (bp.Mirror == SectionModel.MirrorTypes.None)
{
angles.Add(bp.Id, -Math.PI / 2);
}
break;
case SectionModel.SectionTypes.Angle:
if (bp.Mirror == SectionModel.MirrorTypes.None)
{
angles.Add(bp.Id, -bp.AngleX1Rad);
}
break;
default:
angles.Add(bp.Id, 0);
break;
}
}
// Load cases
XmlNode loadCasesNode = xmlDoc.CreateElement("loadcases");
rootNode.AppendChild(loadCasesNode);
CommonModelHelper.GetLoadCases(_model, out List<LoadCaseModel> loadCases);
id = 1;
for (int i = 0; i < loadCases.Count; i++)
{
LoadCaseModel loadCase = loadCases[i];
loadCase.Name = id.ToString();
id++;
XmlNode loadcaseNode = xmlDoc.CreateElement("loadcase");
XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
idAttr.Value = loadCase.Name;
loadcaseNode.Attributes.Append(idAttr);
XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
nameAttr.Value = loadCase.Name;
loadcaseNode.Attributes.Append(nameAttr);
loadCasesNode.AppendChild(loadcaseNode);
}
// Load combinations
XmlNode loadComboNode = xmlDoc.CreateElement("loadcombinations");
rootNode.AppendChild(loadComboNode);
foreach (LoadCaseCombinationModel combo in _model.Combinations)
{
XmlNode comboNode = xmlDoc.CreateElement("loadcombination");
XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
nameAttr.Value = combo.Name;
comboNode.Attributes.Append(nameAttr);
foreach (KeyValuePair<CaseModel, double> val in combo.Values)
{
XmlNode caseNode = xmlDoc.CreateElement("loadcase");
comboNode.AppendChild(caseNode);
XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
idAttr.Value = loadCases.Single(lc => lc.Name == val.Key.Name).Name;
caseNode.Attributes.Append(idAttr);
XmlAttribute incrAttr = xmlDoc.CreateAttribute("value");
incrAttr.Value = val.Value.ToString();
caseNode.Attributes.Append(incrAttr);
}
loadComboNode.AppendChild(comboNode);
}
// Nodes
XmlNode nodesNode = xmlDoc.CreateElement("nodes");
rootNode.AppendChild(nodesNode);
foreach (NodeModel node in _model.Nodes)
{
Point3d pt = node.Position;
XmlNode nodeNode = xmlDoc.CreateElement("node");
XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
nAttr.Value = node.NodeId;
nodeNode.Attributes.Append(nAttr);
XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
idAttr.Value = node.NodeId;
nodeNode.Attributes.Append(idAttr);
XmlAttribute xyzAttr = xmlDoc.CreateAttribute("xyz");
xyzAttr.Value = pt.X.ToString(coordAccuracy) + "," + pt.Y.ToString(coordAccuracy) + "," + pt.Z.ToString(coordAccuracy);
nodeNode.Attributes.Append(xyzAttr);
nodesNode.AppendChild(nodeNode);
foreach (LoadModel load in node.Loads)
{
XmlNode loadNode = xmlDoc.CreateElement("load");
nodeNode.AppendChild(loadNode);
XmlAttribute loadCasedAttr = xmlDoc.CreateAttribute("loadcase");
loadCasedAttr.Value = loadCases.Single(lc => lc.Name == load.LoadCase.Name).Name;
loadNode.Attributes.Append(loadCasedAttr);
XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
typeAttr.Value = load.Description;
loadNode.Attributes.Append(typeAttr);
if (load is NodeForceLoadModel force_load)
{
XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
valAttr.Value = force_load.Value.ToString();
loadNode.Attributes.Append(valAttr);
}
else if (load is NodeMomentLoadModel moment_load)
{
XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
valAttr.Value = moment_load.Value.ToString();
loadNode.Attributes.Append(valAttr);
}
else if (load is NodeTemperatureLoadModel temp_load)
{
XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
valAttr.Value = temp_load.Value.ToString();
loadNode.Attributes.Append(valAttr);
}
}
}
// Beams
var added_beams = new List<string>();
XmlNode beamsNode = xmlDoc.CreateElement("beams");
rootNode.AppendChild(beamsNode);
foreach (BeamModel beam in _model.Beams)
{
if (added_beams.Contains(beam.BeamId))
continue;
string group_name = string.Join(", ", beam.Groups);
double ang = Convert.ToDouble(angles[beam.BeamProperty.Id]);
Point2d? cen = offsets[beam.BeamProperty.Id] as Point2d?;
double rads = beam.AngleDeg * Math.PI / 180;
XmlNode beamNode = xmlDoc.CreateElement("beam");
XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
nAttr.Value = beam.BeamId;
beamNode.Attributes.Append(nAttr);
XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
idAttr.Value = beam.BeamId;
beamNode.Attributes.Append(idAttr);
NodeModel startNode = _model.Nodes.SingleOrDefault(node => node.Position.DistanceTo(beam.PointFrom) < samePosToll);
NodeModel endNode = _model.Nodes.SingleOrDefault(node => node.Position.DistanceTo(beam.PointTo) < samePosToll);
if (startNode == null || endNode == null)
continue;
XmlAttribute fromAttr = xmlDoc.CreateAttribute("from");
fromAttr.Value = startNode.NodeId;
beamNode.Attributes.Append(fromAttr);
XmlAttribute toAttr = xmlDoc.CreateAttribute("to");
toAttr.Value = endNode.NodeId;
beamNode.Attributes.Append(toAttr);
XmlAttribute sectionAttr = xmlDoc.CreateAttribute("section");
sectionAttr.Value = beam.BeamProperty.Name;
beamNode.Attributes.Append(sectionAttr);
XmlAttribute groupAttr = xmlDoc.CreateAttribute("group");
groupAttr.Value = group_name.ToString();
beamNode.Attributes.Append(groupAttr);
XmlAttribute angleAttr = xmlDoc.CreateAttribute("angle");
angleAttr.Value = Convert.ToString(rads + ang);
beamNode.Attributes.Append(angleAttr);
XmlAttribute offsetAttr = xmlDoc.CreateAttribute("offset");
Vector2d beamOffset = beam.Offset;
if (beamOffset == Vector2d.Unset)
{
beamOffset = Vector2d.Zero;
}
offsetAttr.Value = string.Format("{0},{1}", cen.HasValue ? cen.Value.X + beamOffset.X : beamOffset.X,
cen.HasValue ? cen.Value.Y + beamOffset.Y : beamOffset.Y);
beamNode.Attributes.Append(offsetAttr);
//Taper
var n = new double[] { 0, 0 };
string[] propertyDatas = beam.BeamProperty.Name.Split(['_']);
string[] heigthDatas = propertyDatas[propertyDatas.Length - 1].Split(['-']);
if (heigthDatas.Length == 2)
{
if (double.TryParse(heigthDatas[0], out double startHeigth))
{
n[0] = startHeigth / beam.BeamProperty.D;
}
if (double.TryParse(heigthDatas[1], out double endHeigth))
{
n[1] = endHeigth / beam.BeamProperty.D;
}
}
XmlAttribute n1Attr = xmlDoc.CreateAttribute("n1");
n1Attr.Value = n[0] != 0 ? n[0].ToString() : "1";
beamNode.Attributes.Append(n1Attr);
XmlAttribute n2Attr = xmlDoc.CreateAttribute("n2");
n2Attr.Value = n[1] != 0 ? n[1].ToString() : "1";
beamNode.Attributes.Append(n2Attr);
beamsNode.AppendChild(beamNode);
foreach (LoadModel load in beam.Loads)
{
XmlNode loadNode = xmlDoc.CreateElement("load");
beamNode.AppendChild(loadNode);
XmlAttribute loadCasedAttr = xmlDoc.CreateAttribute("loadcase");
loadCasedAttr.Value = loadCases.Single(lc => lc.Name == load.LoadCase.Name).Name;
loadNode.Attributes.Append(loadCasedAttr);
/*if (load is BeamPreLoadModel pre_load)
{
XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
typeAttr.Value = pre_load.PreLoadType.GetDescription();
loadNode.Attributes.Append(typeAttr);
XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
valAttr.Value = pre_load.Value.ToString();
loadNode.Attributes.Append(valAttr);
}
else*/
if (load is BeamDistributedLoadModel distr_load)
{
XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
typeAttr.Value = "Distributed";
loadNode.Attributes.Append(typeAttr);
switch (distr_load.Value.LoadSchema)
{
case BeamDistributedLoadModel.LoadSchema.Uniform:
case BeamDistributedLoadModel.LoadSchema.Keystone:
XmlAttribute rd1Attr = xmlDoc.CreateAttribute("rd1");
rd1Attr.Value = distr_load.Value.A.ToString();
loadNode.Attributes.Append(rd1Attr);
XmlAttribute rd2Attr = xmlDoc.CreateAttribute("rd2");
rd2Attr.Value = distr_load.Value.B.ToString();
loadNode.Attributes.Append(rd2Attr);
// TODO : completare una volta capiti i carichi con orientamento locale e a non costanti
BeamDistributedLoadModel.LoadDirection dir = distr_load.Value.LoadDirection;
double p = 0;
switch (distr_load.Value.LoadSchema)
{
case BeamDistributedLoadModel.LoadSchema.Uniform:
p = distr_load.Value.PA;
break;
case BeamDistributedLoadModel.LoadSchema.Keystone:
p = (distr_load.Value.PA + distr_load.Value.PB) / 2;
break;
}
XmlAttribute f1Attr = xmlDoc.CreateAttribute("f1");
f1Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
(dir == BeamDistributedLoadModel.LoadDirection.X ||
dir == BeamDistributedLoadModel.LoadDirection.X_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(f1Attr);
XmlAttribute f2Attr = xmlDoc.CreateAttribute("f2");
f2Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
(dir == BeamDistributedLoadModel.LoadDirection.Y ||
dir == BeamDistributedLoadModel.LoadDirection.Y_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(f2Attr);
XmlAttribute f3Attr = xmlDoc.CreateAttribute("f3");
f3Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
(dir == BeamDistributedLoadModel.LoadDirection.Z ||
dir == BeamDistributedLoadModel.LoadDirection.Z_Projected ||
dir == BeamDistributedLoadModel.LoadDirection.Gravity ||
dir == BeamDistributedLoadModel.LoadDirection.Gravity_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(f3Attr);
XmlAttribute m1Attr = xmlDoc.CreateAttribute("m1");
m1Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
(dir == BeamDistributedLoadModel.LoadDirection.X ||
dir == BeamDistributedLoadModel.LoadDirection.X_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(m1Attr);
XmlAttribute m2Attr = xmlDoc.CreateAttribute("m2");
m2Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
(dir == BeamDistributedLoadModel.LoadDirection.Y ||
dir == BeamDistributedLoadModel.LoadDirection.Y_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(m2Attr);
XmlAttribute m3Attr = xmlDoc.CreateAttribute("m3");
m3Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
(dir == BeamDistributedLoadModel.LoadDirection.Z ||
dir == BeamDistributedLoadModel.LoadDirection.Z_Projected ||
dir == BeamDistributedLoadModel.LoadDirection.Gravity ||
dir == BeamDistributedLoadModel.LoadDirection.Gravity_Projected) ? p.ToString() : "0";
loadNode.Attributes.Append(m3Attr);
break;
//case BeamDistributedLoadModel.LoadSchema.Keystone:
// break;
}
/*
XmlAttribute schemaAttr = xmlDoc.CreateAttribute("schema");
schemaAttr.Value = distr_load.Value.LoadSchema.GetDescription();
loadNode.Attributes.Append(schemaAttr);
XmlAttribute dirAttr = xmlDoc.CreateAttribute("dir");
double sign = 1;
switch (distr_load.Value.LoadDirection)
{
case BeamDistributedLoadModel.LoadDirection.X:
case BeamDistributedLoadModel.LoadDirection.X_Projected:
dirAttr.Value = "X";
break;
case BeamDistributedLoadModel.LoadDirection.Y:
case BeamDistributedLoadModel.LoadDirection.Y_Projected:
dirAttr.Value = "Y";
break;
case BeamDistributedLoadModel.LoadDirection.Z:
case BeamDistributedLoadModel.LoadDirection.Z_Projected:
dirAttr.Value = "Z";
break;
case BeamDistributedLoadModel.LoadDirection.Gravity:
case BeamDistributedLoadModel.LoadDirection.Gravity_Projected:
dirAttr.Value = "Z";
sign = -1;
break;
}
loadNode.Attributes.Append(dirAttr);
XmlAttribute p1Attr = xmlDoc.CreateAttribute("P1");
p1Attr.Value = (distr_load.Value.P1 * sign).ToString();
loadNode.Attributes.Append(p1Attr);
XmlAttribute p2Attr = xmlDoc.CreateAttribute("P2");
p2Attr.Value = (distr_load.Value.P2 * sign).ToString();
loadNode.Attributes.Append(p2Attr);
XmlAttribute paAttr = xmlDoc.CreateAttribute("PA");
paAttr.Value = (distr_load.Value.PA * sign).ToString();
loadNode.Attributes.Append(paAttr);
XmlAttribute pbAttr = xmlDoc.CreateAttribute("PB");
pbAttr.Value = (distr_load.Value.PB * sign).ToString();
loadNode.Attributes.Append(pbAttr);
XmlAttribute aAttr = xmlDoc.CreateAttribute("a");
aAttr.Value = distr_load.Value.A.ToString();
loadNode.Attributes.Append(aAttr);
XmlAttribute bAttr = xmlDoc.CreateAttribute("b");
pbAttr.Value = distr_load.Value.B.ToString();
loadNode.Attributes.Append(pbAttr);
*/
}
else if (load is BeamPointLoadModel point_load)
{
XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
typeAttr.Value = "Point";
loadNode.Attributes.Append(typeAttr);
XmlAttribute pAttr = xmlDoc.CreateAttribute("p");
pAttr.Value = point_load.Value.P.ToString();
loadNode.Attributes.Append(pAttr);
XmlAttribute aAttr = xmlDoc.CreateAttribute("a");
aAttr.Value = point_load.Value.A.ToString();
loadNode.Attributes.Append(aAttr);
}
}
added_beams.Add(beam.BeamId);
}
// Slabs/walls
XmlNode slabsNode = xmlDoc.CreateElement("slabs");
rootNode.AppendChild(slabsNode);
XmlNode wallsNode = xmlDoc.CreateElement("walls");
rootNode.AppendChild(wallsNode);
if (_perimeters != null)
{
for (int i = 0; i < _perimeters.Count; i++)
{
if (!_perimeters[i].TryGetPolyline(out Polyline perimeter))
{
throw new NotSupportedException();
}
Maffeis.Geometry.Point3d p0 = null;
Maffeis.Geometry.Point3d p1 = null;
Maffeis.Geometry.Point3d p2 = null;
double offset = _offsets[i];
for (int j = 0; j < perimeter.Count - 2; j++)
{
Vector3d dir0, dir1;
dir0 = perimeter[j + 1] - perimeter[j];
dir0.Unitize();
dir1 = perimeter[j + 2] - perimeter[j];
dir1.Unitize();
if (Math.Abs(dir0 * dir1) < 0.999)
{
p0 = new Maffeis.Geometry.Point3d(perimeter[j].X, perimeter[j].Y, perimeter[j].Z);
p1 = new Maffeis.Geometry.Point3d(perimeter[j + 1].X, perimeter[j + 1].Y, perimeter[j + 1].Z);
p2 = new Maffeis.Geometry.Point3d(perimeter[j + 2].X, perimeter[j + 2].Y, perimeter[j + 2].Z);
break;
}
}
if (p0 == null)
{
throw new NotSupportedException();
}
Maffeis.Geometry.Vector3d orto = p1 - p0 ^ p2 - p0;
//orto = Maffeis.Geometry.Geom.Direction(orto);
orto.Unitize();
bool isSlabOtherwiseWall;
if (Math.Abs(orto * Maffeis.Geometry.Vector3d.ZAxis) > 0.999)
{
isSlabOtherwiseWall = true;
}
else if (Math.Abs(orto * Maffeis.Geometry.Vector3d.ZAxis) < 0.001)
{
isSlabOtherwiseWall = false;
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Perimeter " + i.ToString() + " inclined. Will be planarized");
if (orto * Maffeis.Geometry.Vector3d.ZAxis > 0.5)
{
//moving vertical
isSlabOtherwiseWall = true;
}
else
{
//moving planar
isSlabOtherwiseWall = false;
}
}
if (isSlabOtherwiseWall)
{
orto = Maffeis.Geometry.Vector3d.ZAxis;
}
else
{
//orto = Maffeis.Geometry.Geom.Direction(new Maffeis.Geometry.Point3d(orto.X, orto.Y, 0));
orto = new Maffeis.Geometry.Vector3d(orto.X, orto.Y, 0.0);
orto.Unitize();
}
//orienting the orto direction. Orto direction have to be positive;
Maffeis.Geometry.Vector3d positiveDir;
positiveDir = new Maffeis.Geometry.Vector3d(1, 1, 1);
if (orto * positiveDir < 0)
{
orto *= -1;
}
//Translating of offset. Perimeters/holes are always on centerline. After in revit the position line
//is moved according to offset sign
p0 += orto * offset;
p1 += orto * offset;
var trans = new Maffeis.Geometry.CoordinateSystem(p0, p1, p0/*, p0 + 1000 * orto*/);
var perimeterPoly = new Maffeis.Geometry.Polygon2d();
for (int j = 0; j < perimeter.Count; j++)
{
bool add;
if (j == perimeter.Count - 1)
{
add = (perimeter[j] - perimeter[0]).Length > samePosToll;
}
else
{
add = true;
}
if (add)
{
Point3d p;
p = perimeter[j];
var bufferP = new Maffeis.Geometry.Point3d(p.X, p.Y, p.Z);
bufferP = trans.ToLocal(bufferP);
perimeterPoly.Add(bufferP.X, bufferP.Y);
}
}
List<Maffeis.Geometry.Polygon2d> holePolysCanBeNull = null;
if (_holes.Branches[i] != null)
{
holePolysCanBeNull = [];
foreach (GH_Curve c in _holes[i])
{
if (c != null)
{
Polyline hole;
Maffeis.Geometry.Polygon2d holePoly;
if (!c.Value.TryGetPolyline(out hole))
{
throw new NotSupportedException();
}
holePoly = [];
for (int j = 0; j < hole.Count; j++)
{
bool add;
if (j == hole.Count - 1)
{
add = (hole[j] - hole[0]).Length > samePosToll;
}
else
{
add = true;
}
if (add)
{
Point3d p;
Maffeis.Geometry.Point3d bufferP;
p = hole[j];
bufferP = new Maffeis.Geometry.Point3d(p.X, p.Y, p.Z);
bufferP = trans.ToLocal(bufferP);
holePoly.Add(bufferP.X, bufferP.Y);
}
}
holePolysCanBeNull.Add(holePoly);
}
}
}
double thickness = _thickness[i];
string group = _groups[i].Value.Name;
string childName;
XmlNode platesNode;
if (isSlabOtherwiseWall)
{
childName = "slab";
platesNode = slabsNode;
}
else
{
childName = "wall";
platesNode = wallsNode;
}
//writing
XmlNode slabNode = xmlDoc.CreateElement(childName);
platesNode.AppendChild(slabNode);
//group
XmlAttribute iAttr = xmlDoc.CreateAttribute("index");
iAttr.Value = group;
slabNode.Attributes.Append(iAttr);
//thickness
XmlAttribute tAttr = xmlDoc.CreateAttribute("thickness");
tAttr.Value = thickness.ToString();
slabNode.Attributes.Append(tAttr);
//offset
XmlAttribute oAttr = xmlDoc.CreateAttribute("offset");
if (Math.Abs(offset) < samePosToll)
{
oAttr.Value = "0";
}
else if (offset > 0)
{
oAttr.Value = "1";
}
else
{
oAttr.Value = "-1";
}
slabNode.Attributes.Append(oAttr);
//fill perimeter
XmlNode fillPointsNode = xmlDoc.CreateElement("perimeter");
slabNode.AppendChild(fillPointsNode);
foreach (Maffeis.Geometry.Point2d p in perimeterPoly)
{
Maffeis.Geometry.Point3d globalP = trans.ToGlobal(p);
XmlNode point = xmlDoc.CreateElement("point");
fillPointsNode.AppendChild(point);
XmlAttribute xyz = xmlDoc.CreateAttribute("xyz");
xyz.Value = string.Format("{0:" + coordAccuracy + "},{1:" + coordAccuracy + "},{2:" + coordAccuracy + "}", globalP.X, globalP.Y, globalP.Z);
point.Attributes.Append(xyz);
}
//holes perimeter
if (holePolysCanBeNull != null)
{
int index;
index = 0;
foreach (Maffeis.Geometry.Polygon2d hole in holePolysCanBeNull)
{
XmlNode holesPointsNode = xmlDoc.CreateElement("hole");
slabNode.AppendChild(holesPointsNode);
index++;
XmlAttribute indexAttr = xmlDoc.CreateAttribute("index");
indexAttr.Value = index.ToString();
holesPointsNode.Attributes.Append(indexAttr);
foreach (Maffeis.Geometry.Point2d p in hole)
{
Maffeis.Geometry.Point3d globalP = trans.ToGlobal(p);
XmlNode point = xmlDoc.CreateElement("point");
holesPointsNode.AppendChild(point);
XmlAttribute xyz = xmlDoc.CreateAttribute("xyz");
xyz.Value = string.Format("{0:" + coordAccuracy + "},{1:" + coordAccuracy + "},{2:" + coordAccuracy + "}", globalP.X, globalP.Y, globalP.Z);
point.Attributes.Append(xyz);
}
}
}
}
}
return xmlDoc;
}
private Point2d PolygonCentroid(Point2d[] points)
{
return PolygonCentroid(points, out double area);
}
private Point2d PolygonCentroid(Point2d[] points, out double area)
{
var centroid = new Point2d(0, 0);
area = 0;
if (PointsDistance(points[0], points.Last()) > 0.001)
{
Array.Resize(ref points, points.Length + 1);
points[points.Length - 1] = new Point2d(points[0].X, points[0].Y);
}
for (int i = 0; i < points.Length - 1; i++)
{
double a = points[i].X * points[i + 1].Y - points[i + 1].X * points[i].Y;
area += a;
centroid.X += (points[i].X + points[i + 1].X) * a;
centroid.Y += (points[i].Y + points[i + 1].Y) * a;
}
area *= 0.5;
centroid.X /= 6.0 * area;
centroid.Y /= 6.0 * area;
return centroid;
}
private double PointsDistance(Point2d pt1, Point2d pt2)
{
return Math.Sqrt(Math.Pow(pt2.X - pt1.X, 2) + Math.Pow(pt2.Y - pt1.Y, 2));
}
/// <summary>
/// Provides an Icon for the component.
/// </summary>
protected override System.Drawing.Bitmap Icon => Properties.Resources.Fem2BimIcon;
/// <summary>
/// Gets the unique ID for this component. Do not change this ID after release.
/// </summary>
public override Guid ComponentGuid => new("113cb500-0ec2-48ec-9da2-0689b1263942");
}
}