using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using SAP2000v1;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;
namespace FeMM.Grasshopper.Components.SAPExtra
{
#if NETCOREAPP
[SupportedOSPlatform("windows")]
#endif
public class SAPSetFrameExternalForceComponent : GH_Component
{
private bool _run = false;
public SAPSetFrameExternalForceComponent()
: base(
"SAP2000 Set Frame External Forces",
"SFF",
"Apply external forces to SAP2000 frame elements. Click the button to run.",
CategoryNameConstants.CATEGORY_CHECKS,
CategoryNameConstants.SUBCATEGORY_SAPEXTRA)
{
_run = false;
}
public override void CreateAttributes()
{
// Create button attributes with button in the component header
var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Run");
attr.ButtonPressed += () =>
{
// Flash the _run value and expire solution
_run = true;
ExpireSolution(true);
};
m_attributes = attr;
}
protected override void RegisterInputParams(GH_InputParamManager p)
{
p.AddBooleanParameter("Attach to Instance", "AI", "Attach to running SAP2000 instance", GH_ParamAccess.item, true);
p[0].Optional = true;
p.AddBooleanParameter("Specify Path", "SP", "Launch SAP2000 from specified path", GH_ParamAccess.item, false);
p[1].Optional = true;
p.AddTextParameter("Program Path", "PP", "Full path to SAP2000.exe", GH_ParamAccess.item, "");
p[2].Optional = true;
p.AddTextParameter("Model Path", "MP", "SAP2000 model file path", GH_ParamAccess.item, "");
p[3].Optional = true;
p.AddTextParameter("Cases", "LC", "Tree branches have to match the frames structure, for every frame all the load cases have to be included in the tree branch", GH_ParamAccess.tree);
p[4].Optional = true;
p.AddTextParameter("Frames", "F", "Tree with each frame in its own branch", GH_ParamAccess.tree);
p[5].Optional = true;
p.AddIntegerParameter("Step Number", "SN", "Tree branches have to match the frames structure, one step number per load case", GH_ParamAccess.tree);
p[6].Optional = true;
p.AddNumberParameter("Stations", "S", "Tree branches have to match the frames structure", GH_ParamAccess.tree);
p[7].Optional = true;
p.AddNumberParameter("Forces", "Frc", "Tree branches have to match the frames structure, one sublist for every station", GH_ParamAccess.tree);
p[8].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager p)
{
p.AddTextParameter("Log", "L", "Operation log", GH_ParamAccess.item);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
if (!_run)
{
DA.SetData(0, "Click 'Run' button to execute");
return;
}
bool attach = true;
bool specifyPath = false;
string programPath = "";
string modelPath = "";
var casesTree = new GH_Structure<GH_String>();
var framesTree = new GH_Structure<GH_String>();
var stepTree = new GH_Structure<GH_Integer>();
var stationsTree = new GH_Structure<GH_Number>();
var forcesTree = new GH_Structure<GH_Number>();
DA.GetData(0, ref attach);
DA.GetData(1, ref specifyPath);
DA.GetData(2, ref programPath);
DA.GetData(3, ref modelPath);
DA.GetDataTree(4, out casesTree);
DA.GetDataTree(5, out framesTree);
DA.GetDataTree(6, out stepTree);
DA.GetDataTree(7, out stationsTree);
DA.GetDataTree(8, out forcesTree);
List<string> log = [];
try
{
// Validate input data
if (framesTree.IsEmpty)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No frames provided");
DA.SetData(0, "No frames provided");
_run = false;
return;
}
// ---------------- SAP START ----------------
cHelper helper = new Helper();
cOAPI sapObj = null;
try
{
if (attach)
{
sapObj = helper.GetObject("CSI.SAP2000.API.SapObject");
}
else if (specifyPath && !string.IsNullOrEmpty(programPath))
{
sapObj = helper.CreateObject(programPath);
sapObj.ApplicationStart();
}
else
{
sapObj = helper.CreateObjectProgID("CSI.SAP2000.API.SapObject");
sapObj.ApplicationStart();
}
if (sapObj == null)
throw new Exception("Failed to initialize SAP2000 object");
cSapModel sap = sapObj.SapModel ?? throw new Exception("Failed to get SAP model");
if (!attach && !string.IsNullOrEmpty(modelPath))
{
if (sap.File.OpenFile(modelPath) != 0)
throw new Exception($"Failed to open SAP2000 model at: {modelPath}");
}
// ---------------- PROCESS ----------------
int totalFramesProcessed = 0;
int totalCasesApplied = 0;
foreach (GH_Path path in framesTree.Paths)
{
try
{
// Get frame name
if (framesTree[path].Count == 0)
continue;
string frame = framesTree[path][0].Value;
if (string.IsNullOrWhiteSpace(frame))
{
log.Add("Frame name is empty");
continue;
}
totalFramesProcessed++;
// Validate all required data exists for this frame
if (!casesTree.PathExists(path))
{
log.Add($"Missing load cases for frame: {frame}");
continue;
}
if (!stationsTree.PathExists(path))
{
log.Add($"Missing stations for frame: {frame}");
continue;
}
if (!stepTree.PathExists(path) || stepTree[path].Count == 0)
{
log.Add($"Missing step number for frame: {frame}");
continue;
}
// Extract data
string[] cases = [.. casesTree[path].ConvertAll(x => x.Value)];
double[] stations = [.. stationsTree[path].ConvertAll(x => x.Value)];
int step = stepTree[path][0].Value;
if (cases.Length == 0 || stations.Length == 0)
{
log.Add($"Empty cases or stations for frame: {frame}");
continue;
}
// Setup SAP for this frame
sap.Results.Setup.DeselectAllCasesAndCombosForOutput();
foreach (var c in cases)
{
if (!string.IsNullOrWhiteSpace(c))
sap.LoadCases.ExternalResults.SetCase(c);
}
sap.ExternalAnalysisResults.PresetFrameCases(ref frame, cases.Length, ref cases);
sap.ExternalAnalysisResults.SetFrameStations(ref frame, ref stations);
// Process forces for each case
int casesApplied = 0;
for (int c = 0; c < cases.Length; c++)
{
var forcePath = path.AppendElement(c);
if (!forcesTree.PathExists(forcePath) || forcesTree[forcePath].Count == 0)
{
log.Add($"Missing forces for frame: {frame}, case: {cases[c]}");
continue;
}
var f = forcesTree[forcePath];
int n = stations.Length;
// Validate force data
if (f.Count < n * 6)
{
log.Add($"Insufficient force data for frame: {frame}, case: {cases[c]} (expected {n * 6} values, got {f.Count})");
continue;
}
double[] P = new double[n];
double[] V2 = new double[n];
double[] V3 = new double[n];
double[] T = new double[n];
double[] M2 = new double[n];
double[] M3 = new double[n];
for (int i = 0; i < n; i++)
{
int idx = i * 6;
P[i] = f[idx + 0].Value;
V2[i] = f[idx + 1].Value;
V3[i] = f[idx + 2].Value;
T[i] = f[idx + 3].Value;
M2[i] = f[idx + 4].Value;
M3[i] = f[idx + 5].Value;
}
int ret = sap.ExternalAnalysisResults.SetFrameForce(
frame, cases[c], step,
ref P, ref V2, ref V3, ref T, ref M2, ref M3
);
if (ret == 0)
{
casesApplied++;
totalCasesApplied++;
}
else
log.Add($"Failed to apply forces for frame: {frame}, case: {cases[c]}");
}
if (casesApplied > 0)
log.Add($"✓ Frame '{frame}': {casesApplied} load case(s) applied");
}
catch (Exception ex)
{
log.Add($"✗ Frame error: {ex.Message}");
}
}
// Summary
log.Insert(0, $"=== OPERATION COMPLETE ===");
log.Insert(1, $"Frames processed: {totalFramesProcessed}");
log.Insert(2, $"Load cases applied: {totalCasesApplied}");
log.Insert(3, "");
}
finally
{
// Note: Do not close SAP2000 here unless explicitly required
// as it may be a running instance
}
}
catch (Exception ex)
{
AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Component Error: {ex.Message}");
log.Insert(0, $"FATAL ERROR: {ex.Message}");
}
DA.SetData(0, string.Join("\n", log));
_run = false; // Reset after execution
}
protected override System.Drawing.Bitmap Icon => Properties.Resources.SAPBeamLoaderIcon;
public override Guid ComponentGuid =>
new("f4c1d3a5-1f8b-4c9b-9db6-0e3c0e8bbf41");
}
}