using FeMM.Common.Models;
using FeMM.Grasshopper.Components.Design;
using FeMM.Grasshopper.DataTypes;
using FeMM.Grasshopper.DataTypes.FeMM;
using FeMM.Grasshopper.Helpers;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Special;
using Rhino.Geometry;
using SAP2000v1;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;
using System.Windows.Forms;
namespace FeMM.Grasshopper.Components.Analysis
{
#if NETCOREAPP
[SupportedOSPlatform("windows")]
#endif
public class SapAnalysisComponent : GH_Component
{
private bool _run;
private cOAPI _sapObject;
private cSapModel _sapModel;
private GH_Model _model;
private bool _just_loaded;
/// <summary>
/// Initializes a new instance of the SapAnalysisComponent class.
/// </summary>
public SapAnalysisComponent()
: base("Sap Analysis", "SA", "Model Analisys with SAP2000", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_ANALYSIS)
{
_run = false;
_sapObject = null;
_sapModel = null;
_model = new GH_Model();
_just_loaded = false;
}
public override void CreateAttributes()
{
var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Run");
attr.ButtonPressed += () =>
{
_run = true;
ExpireSolution(true);
};
m_attributes = attr;
// See for create a component with a buttons array
// https://rgkr-memo.blogspot.com/2017/05/CS-grasshopper-HelloWorldComponent.html
}
/// <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);
int n = pManager.AddTextParameter("Analysis Results", "AR", "The analysis result cases or combination to returns", GH_ParamAccess.list);
pManager[n].Optional = true;
n = pManager.AddGenericParameter("Design Prefs", "DP", "Design preferences", GH_ParamAccess.list);
pManager[n].Optional = true;
n = pManager.AddTextParameter("Output Path", "O", "The full path of the file model to create", GH_ParamAccess.item, "");
pManager[n].Optional = true;
pManager.AddBooleanParameter("Attach", "A", "Attach to the active instance (if exists)", GH_ParamAccess.item, false);
n = pManager.AddTextParameter("SAP Path", "P", "The path of the main SAP2000 executable", GH_ParamAccess.item, "");
pManager[n].Optional = true;
n = pManager.AddBooleanParameter("Keep Running", "K", "Run analysis automatically when input is chaged", GH_ParamAccess.item, false);
pManager[n].Optional = true;
}
/// <summary>
/// Registers all the output parameters for this component.
/// </summary>
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Model", "M", "The FEM model", GH_ParamAccess.item);
}
/// <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;
string outputPath = "";
bool attachToInstance = false;
string programPath = "";
bool keepRunning = false;
if (!DA.GetData("Model", ref model))
return;
DA.GetData("Output Path", ref outputPath);
DA.GetData("Attach", ref attachToInstance);
DA.GetData("SAP Path", ref programPath);
DA.GetData("Keep Running", ref keepRunning);
if (!attachToInstance && string.IsNullOrWhiteSpace(outputPath))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "No file path or attach to istance option");
return;
}
if (!attachToInstance)
{
if (Path.GetExtension(outputPath).ToLower() != ".sdb")
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid extension in file name");
return;
}
}
// Retrieve all te load cases used in the model
Common.Helpers.CommonModelHelper.GetSAPLoadCases(model.Value, out CaseModel[] loadCases);
// Connect the ValueList component on the analysis results input item
IGH_Param[] keys_analysisResultLists = [.. Params.Input[1].Sources.Where(s => s.GetType() == typeof(GH_ValueList))];
for (int i = 0; i < keys_analysisResultLists.Length; i++)
{
GH_ValueList vallist = (GH_ValueList)keys_analysisResultLists[i];
if (vallist.ListMode != GH_ValueListMode.CheckList)
vallist.ListMode = GH_ValueListMode.CheckList;
List<string> names = [.. loadCases.Select(lc => lc.Name).Distinct()];
names.AddRange(model.Value.Combinations.Select(c => c.Name));
ComponentsHelper.SetValueList(vallist, names);
vallist.NickName = "Results";
}
var analysisCaseResults = new List<string>();
if (DA.GetDataList("Analysis Results", analysisCaseResults))
{
for (int i = 0; i < analysisCaseResults.Count; i++)
{
string caseName = analysisCaseResults[i];
if (!loadCases.Any(lc => lc.Name == caseName) && !model.Value.Combinations.Any(c => c.Name == caseName))
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"The result case name {caseName} does not exists.");
return;
}
}
}
else
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Input parameter Analysis Results failed to collect data");
}
//Settings design inputs
IEnumerable<IGH_Param> pars = Params.Input[2].Sources;
var comboAnalysisCase = new List<string>();
for (int i = 0; i < analysisCaseResults.Count; i++)
{
string str = analysisCaseResults[i];
if (model.Value.Combinations.Any(c => c.Name == str))
{
comboAnalysisCase.Add(str);
}
}
IGH_Param[] array = [.. pars];
for (int i = 0; i < array.Length; i++)
{
if (((GH_Attributes<IGH_Component>)array[i].Attributes.GetTopLevel).Owner is SteelPrefsComponent comp)
{
comp.SetDesignCombos(comboAnalysisCase);
}
}
var prefs = new List<GH_SteelPrefs>(); // Actually only steel. For future also rc,alum,ecc
Common.Checks.SteelPrefs spref = null;
if (DA.GetDataList("Design Prefs", prefs))
{
if (prefs.Count == 1)
{
spref = prefs[0].Value;
}
else
{
return;
}
}
if (_run || keepRunning)
{
// Save statistics
#if !DEBUG
try
{
}
catch (Exception){}
#endif
Message = "";
try
{
_sapObject = Common.Helpers.SapHelper.GetSapObject(attachToInstance, "", programPath);
}
catch (Exception ex)
{
_sapObject = null;
_run = false;
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
return;
}
try
{
if (!Common.Helpers.SapHelper.CreateModel(_sapObject, attachToInstance, model.Value, Rhino.Settings.Tolerance,
out _sapModel, out List<string> warnings, out List<string> errors, spref))
{
foreach (string error in errors)
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, error);
_run = false;
return;
}
foreach (string warning in warnings)
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, warning);
_sapModel.View.RefreshView();
if (_sapModel.File.Save(outputPath) != 0)
{
Message = "Failed";
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Failed to save the file");
_run = false;
return;
}
if (_sapModel.Analyze.RunAnalysis() != 0)
{
Message = "Failed";
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Failed to run the analysis");
_run = false;
return;
}
// Delete all previous results in beams if any
for (int i = 0; i < model.Value.Beams.Count; i++)
{
model.Value.Beams.ElementAt(i).Results.Clear(); // TODO: Check if ok
}
// Select cases for the results
_sapModel.Results.Setup.DeselectAllCasesAndCombosForOutput();
for (int i = 0; i < analysisCaseResults.Count; i++)
{
string caseName = analysisCaseResults[i];
if (loadCases.Any(c => c.Name == caseName))
_sapModel.Results.Setup.SetCaseSelectedForOutput(caseName);
else if (model.Value.Combinations.Any(c => c.Name == caseName))
_sapModel.Results.Setup.SetComboSelectedForOutput(caseName);
}
// Create a deep copy of the model where to add the results
_model = (GH_Model)model.Duplicate();
if (_model.Value.Name == "")
_model.Value.Name = Path.GetFileNameWithoutExtension(outputPath);
//Getting the sap labels
var nodeDic = new Dictionary<string, NodeModel>();
for (int j = 0; j < _model.Value.Nodes.Count; j++)
{
NodeModel n = _model.Value.Nodes[j];
nodeDic.Add(Common.Helpers.CsiHelper.GetNodeLabel(n), n);
}
var beamDic = new Dictionary<string, BeamModel>();
var reversedBeamDic = new Dictionary<BeamModel, string>();
for (int i = 0; i < _model.Value.Beams.Count; i++)
{
BeamModel b = _model.Value.Beams[i];
string label = Common.Helpers.CsiHelper.GetBeamLabel(b);
beamDic.Add(label, b);
reversedBeamDic.Add(b, label);
}
var plateDic = new Dictionary<string, PlateModel>();
for (int j = 0; j < _model.Value.Plates.Count; j++)
{
PlateModel plate = _model.Value.Plates[j];
plateDic.Add(Common.Helpers.CsiHelper.GetPlateLabel(plate), plate);
}
// Node results
int nr = 0;
string[] obj = [];
string[] elm = [];
string[] lc = [];
string[] st = [];
double[] sn = [];
double[] u1 = [];
double[] u2 = [];
double[] u3 = [];
double[] r1 = [];
double[] r2 = [];
double[] r3 = [];
_sapModel.Results.JointDispl("All", eItemTypeElm.GroupElm, ref nr, ref obj, ref elm, ref lc,
ref st, ref sn, ref u1, ref u2, ref u3, ref r1, ref r2, ref r3);
for (int i = 0; i < nr; i++)
{
if (obj[i] != null)
{
NodeModel node = nodeDic[obj[i]];
CaseModel caseModel = null;
if (loadCases.Any(l => l.Name.ToLower() == lc[i].ToLower()))
caseModel = loadCases.Single(l => l.Name.ToLower() == lc[i].ToLower());
else
caseModel = _model.Value.Combinations.Single(l => l.Name.ToLower() == lc[i].ToLower());
var displ = new NodeDisplResultModel(caseModel, sn[i], st[i])
{
Displacement = new Vector3d(u1[i], u2[i], u3[i]),
Rotation = new Vector3d(r1[i], r2[i], r3[i])
};
node.Results.Add(displ);
}
}
double[] f1 = [];
double[] f2 = [];
double[] f3 = [];
double[] m1 = [];
double[] m2 = [];
double[] m3 = [];
_sapModel.Results.JointReact("All", eItemTypeElm.GroupElm, ref nr, ref obj, ref elm, ref lc,
ref st, ref sn, ref f1, ref f2, ref f3, ref m1, ref m2, ref m3);
for (int i = 0; i < nr; i++)
{
if (obj[i] != null)
{
NodeModel node = nodeDic[obj[i]];
CaseModel caseModel = null;
if (loadCases.Any(l => l.Name.ToLower() == lc[i].ToLower()))
caseModel = loadCases.Single(l => l.Name.ToLower() == lc[i].ToLower());
else
caseModel = _model.Value.Combinations.Single(l => l.Name.ToLower() == lc[i].ToLower());
var react = new NodeReactResultModel(caseModel, sn[i], st[i])
{
Force = new Vector3d(f1[i], f2[i], f3[i]),
Moment = new Vector3d(m1[i], m2[i], m3[i])
};
node.Results.Add(react);
}
}
// Beam results
double[] objSta = [];
double[] elmSta = [];
double[] p = [];
double[] v2 = [];
double[] v3 = [];
double[] t = [];
m2 = [];
m3 = [];
_sapModel.Results.FrameForce("ALL", eItemTypeElm.GroupElm, ref nr, ref obj, ref objSta, ref elm,
ref elmSta, ref lc, ref st, ref sn, ref p, ref v2, ref v3, ref t, ref m2, ref m3);
for (int i = 0; i < nr; i++)
{
if (obj[i] != null)
{
BeamModel beam = beamDic[obj[i]];
CaseModel caseModel = null;
if (loadCases.Any(l => l.Name.ToLower() == lc[i].ToLower()))
caseModel = loadCases.Single(l => l.Name.ToLower() == lc[i].ToLower());
else
caseModel = _model.Value.Combinations.Single(l => l.Name.ToLower() == lc[i].ToLower());
Common.Helpers.CsiHelper.ConvertStressesFromCSI(beam, ref m2[i], ref m3[i], ref v2[i], ref v3[i]);
var result = new BeamForceResultModel(caseModel, elmSta[i], sn[i], st[i])
{
ShearForce = new Vector2d(/*v1st, v2st*/v2[i], v3[i]),
BendingMoment = new Vector2d(/*m1st, m2St*/m2[i], m3[i]),
AxialForce = p[i],
Torque = t[i]
};
beam.Results.Add(result);
}
}
// Plate results
string[] pointElm = [];
double[] f11 = [];
double[] f22 = [];
double[] f12 = [];
double[] fMax = [];
double[] fMin = [];
double[] fAngle = [];
double[] fvm = [];
double[] m11 = [];
double[] m22 = [];
double[] m12 = [];
double[] mMax = [];
double[] mMin = [];
double[] mAngle = [];
double[] v13 = [];
double[] v23 = [];
double[] vMax = [];
double[] vAngle = [];
_sapModel.Results.AreaForceShell("All", eItemTypeElm.GroupElm, ref nr, ref obj, ref elm,
ref pointElm, ref lc, ref st, ref sn, ref f11, ref f22, ref f12, ref fMax, ref fMin,
ref fAngle, ref fvm, ref m11, ref m22, ref m12, ref mMax, ref mMin, ref mAngle, ref v13,
ref v23, ref vMax, ref vAngle);
for (int i = 0; i < nr; i++)
{
if (obj[i] != null)
{
PlateModel plate = plateDic[obj[i]];
CaseModel caseModel = null;
if (loadCases.Any(l => l.Name.ToLower() == lc[i].ToLower()))
caseModel = loadCases.Single(l => l.Name.ToLower() == lc[i].ToLower());
else
caseModel = _model.Value.Combinations.Single(l => l.Name.ToLower() == lc[i].ToLower());
var result = new PlateForceResultModel(caseModel, pointElm[i], sn[i], st[i])
{
Force = new Vector3d(f11[i], f22[i], f12[i]),
ForceMax = fMax[i],
ForceMin = fMin[i],
ForceAngle = fAngle[i],
ForceVonMises = fvm[i],
Moment = new Vector3d(-m11[i], -m22[i], -m12[i]),
MomentMax = -mMin[i],
MomentMin = -mMax[i],
MomentAngle = 90 - mAngle[i],
TransverseShearForce = new Vector2d(v13[i], v23[i]),
TransverseShearForceMax = vMax[i],
TransverseShearForceAng = vAngle[i]
};
plate.Results.Add(result);
}
}
//Design checks
if (prefs.Count > 0)
{
IEnumerable<GH_SteelPrefs> steelPrefs = prefs.Where(pref => pref is GH_SteelPrefs);
if (steelPrefs.Any())
{
if (steelPrefs.Count() != 1)
{
throw new NotSupportedException("Only one steel prefs code admitted");
}
//Running design
int ret = _sapModel.DesignSteel.StartDesign();
if (ret != 0)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Unable to check steel beams");
}
else
{
int bufferNumber = 0;
string[] strengthCombs = null, deflectionCombs = null;
_sapModel.DesignSteel.GetComboStrength(ref bufferNumber, ref strengthCombs);
_sapModel.DesignSteel.GetComboDeflection(ref bufferNumber, ref deflectionCombs);
for (int j = 0; j < _model.Value.Beams.Count; j++)
{
BeamModel beam = _model.Value.Beams[j];
if (beam.BeamProperty.Material.Kind == MaterialModel.MaterialKind.Steel)
{
string name = reversedBeamDic[beam];
int NumberItems = 0;
string[] frameName = null;
double[] Ratio = null;
int[] RatioType = null;
double[] Location = null;
string[] ComboName = null;
string[] ErrorSummary = null;
string[] WarningSummary = null;
ret = _sapModel.DesignSteel.GetSummaryResults(name, ref NumberItems, ref frameName, ref Ratio, ref RatioType,
ref Location, ref ComboName, ref ErrorSummary, ref WarningSummary);
if (ret == 0)
{
for (int i = 0; i < NumberItems; i++)
{
CaseModel caseModel = null;
caseModel = _model.Value.Combinations.SingleOrDefault(l => l.Name.ToLower() == ComboName[i].ToLower());
if (caseModel == null)
{
caseModel = new LoadCaseModel(-1, ComboName[i]);
}
double ratio;
//caso di anomalia
if (RatioType[i] == 0)
{
ratio = 10000;
}
else
{
ratio = Ratio[i];
}
var res = new BeamSteelDesignResultModel(caseModel, Location[i], 0, "")
{
Ratio = ratio,
ResultCode = (BeamSteelDesignResultModel.ResultCodes)RatioType[i]
};
beam.Results.Add(res);
}
}
}
}
}
}
}
Message = "Success";
}
catch (Exception e)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
Message = "Error";
}
_sapModel = null; // TODO: rimuovere
_run = false;
}
DA.SetData(0, _model);
}
protected override void AppendAdditionalComponentMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalComponentMenuItems(menu);
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, "Reset", (s, e) => _model = new GH_Model());
}
public override bool Write(GH_IWriter writer)
{
bool success = base.Write(writer);
if (!success)
return false;
_model.Write(writer);
return true;
}
public override bool Read(GH_IReader reader)
{
bool success = base.Read(reader);
if (!success)
return false;
_model.Read(reader);
_just_loaded = true;
return true;
}
protected override void BeforeSolveInstance()
{
base.BeforeSolveInstance();
if (_just_loaded)
{
GH_Model input_model = (GH_Model)Params.Input[0].VolatileData.get_Branch(0)[0];
ModelHelper.RestoreModelGuids(input_model, ref _model);
_just_loaded = false;
}
}
/// <summary>
/// Provides an Icon for the component.
/// </summary>
protected override System.Drawing.Bitmap Icon => Properties.Resources.SapAnalysisIcon;
/// <summary>
/// Gets the unique ID for this component. Do not change this ID after release.
/// </summary>
public override Guid ComponentGuid => new("784474c2-0590-4fe2-bb30-70ed8ea017f5");
}
}