using FeMM.Common.Models;
using FeMM.Grasshopper.DataTypes.FeMM;
using FeMM.Grasshopper.Forms;
using FeMM.Grasshopper.Helpers;
using GH_IO.Serialization;
using Grasshopper;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.Versioning;
using System.Windows.Forms;
namespace FeMM.Grasshopper.Components.Definitions
{
public class CombinationsTableComponent : GH_Component
{
private List<GH_Combination> _combos;
private List<CaseModel> _caseModels;
private List<FreedomCaseModel> _freedomCaseModels;
public CombinationsTableComponent()
: base("Combinations Table", "ComboTable", "Allows to create and store a list of combinations based on the load cases given as input", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_DEFINITIONS)
{
_combos = [];
_caseModels = [];
_freedomCaseModels = [];
}
protected override void RegisterInputParams(GH_InputParamManager pManager)
{
pManager.AddGenericParameter("Load Cases", "LCS", "List of load cases", GH_ParamAccess.list);
pManager.AddGenericParameter("Freedom Cases", "LCS", "List of load cases", GH_ParamAccess.list);
pManager[1].Optional = true;
}
protected override void RegisterOutputParams(GH_OutputParamManager pManager)
{
pManager.AddGenericParameter("Load Combinations", "LCombos", "The list of load combinations generated", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
List<GH_Case> loadCases = [];
List<GH_FreedomCase> freedomCases = [];
if (!DA.GetDataList(0, loadCases))
return;
DA.GetDataList(1, freedomCases);
// checks wheter load cases input list changed
bool loadCasesChanged = loadCases.Count != _caseModels.Count;
var cmIterator = _caseModels.GetEnumerator();
var lcIterator = loadCases.GetEnumerator();
while (cmIterator.MoveNext() && lcIterator.MoveNext() && !loadCasesChanged)
if (cmIterator.Current.Name != lcIterator.Current.Value.Name)
loadCasesChanged = true;
// checks wheter freedom cases input list changed
freedomCases ??= [];
bool freedomCasesChanged = freedomCases.Count != _freedomCaseModels.Count;
var fcmIterator = _freedomCaseModels.GetEnumerator();
var fcIterator = freedomCases.GetEnumerator();
while (fcmIterator.MoveNext() && fcIterator.MoveNext() && !freedomCasesChanged)
if (fcmIterator.Current.Name != fcIterator.Current.Value.Name)
freedomCasesChanged = true;
if (loadCasesChanged || freedomCasesChanged)
{
_combos.Clear();
_caseModels = [.. loadCases.Select(ghc => ghc.Value)];
_freedomCaseModels = [.. freedomCases.Select(ghfc => ghfc.Value)];
List<GH_Combination> loadCombination = [];
DA.SetDataList(0, loadCombination);
}
else
{
DA.SetDataList(0, _combos.Select(combo => new GH_Combination(combo)));
}
}
public override void CreateAttributes()
{
m_attributes = new LoadCombinationsTableAttributes(this);
}
public override bool Write(GH_IWriter writer)
{
bool success = base.Write(writer);
if (!success)
return false;
writer.SetString("Combos", JsonConvert.SerializeObject(_combos));
writer.SetString("CaseModels", JsonConvert.SerializeObject(_caseModels));
writer.SetString("FreedomCaseModels", JsonConvert.SerializeObject(_freedomCaseModels));
return true;
}
public override bool Read(GH_IReader reader)
{
bool success = base.Read(reader);
if (!success)
return false;
string combos = string.Empty;
if (!reader.TryGetString("Combos", ref combos) || string.IsNullOrWhiteSpace(combos))
return false;
string caseModels = string.Empty;
if (!reader.TryGetString("CaseModels", ref caseModels) || string.IsNullOrWhiteSpace(caseModels))
return false;
try
{
_combos = JsonConvert.DeserializeObject<List<GH_Combination>>(combos);
_caseModels = [.. JsonConvert.DeserializeObject<List<LoadCaseModel>>(caseModels).Cast<CaseModel>()];
_freedomCaseModels = [.. JsonConvert.DeserializeObject<List<FreedomCaseModel>>(caseModels).Cast<FreedomCaseModel>()];
}
catch
{
_combos = [];
_caseModels = [];
_freedomCaseModels = [];
}
return true;
}
public override GH_Exposure Exposure => GH_Exposure.septenary;
protected override System.Drawing.Bitmap Icon => Properties.Resources.LoadCombinationsParamIcon;
public override Guid ComponentGuid => new("44711be2-1a9e-4f78-a3cd-62068bb500f5");
#if NETCOREAPP
[SupportedOSPlatform("windows")]
#endif
internal class LoadCombinationsTableAttributes : GH_ResizableAttributes<CombinationsTableComponent>
{
private Rectangle _internalRect;
protected override Size MinimumSize => new(280, 100);
protected override Size MaximumSize { get { return new Size(810, 240); } }
protected override Padding SizingBorders { get { return new Padding(10); } }
public LoadCombinationsTableAttributes(CombinationsTableComponent owner)
: base(owner)
{
Bounds = new RectangleF(Pivot, MinimumSize);
}
public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
{
attributes.Add(this);
// make sure parameter attributes participate in hit-testing
foreach (var p in Owner.Params.Input)
attributes.Add(p.Attributes);
foreach (var p in Owner.Params.Output)
attributes.Add(p.Attributes);
}
protected override void Layout()
{
base.Layout();
_internalRect = GH_Convert.ToRectangle(Bounds);
_internalRect.X += 80;
_internalRect.Width -= 80;
_internalRect.Inflate(-5, -5);
// define input grips position (distributing the space evenly)
int inputCount = Owner.Params.Input.Count;
if (inputCount > 0)
{
float availableHeight = Bounds.Height - 20;
float spacing = availableHeight / inputCount;
for (int i = 0; i < inputCount; i++)
{
var param = Owner.Params.Input[i];
float yLoc = Bounds.Y + (i * spacing) + (spacing / 2) + 10;
var pivot = new PointF(Bounds.Left, yLoc);
var paramBounds = new RectangleF(Bounds.Left, yLoc - 10, 15, 20);
param.Attributes.Pivot = pivot;
param.Attributes.Bounds = paramBounds;
}
}
// define output grips position (distributing the space evenly)
int outputCount = Owner.Params.Output.Count;
if (outputCount > 0)
{
float availableHeight = Bounds.Height - 20;
float spacing = availableHeight / outputCount;
for (int i = 0; i < outputCount; i++)
{
var param = Owner.Params.Output[i];
float yLoc = Bounds.Y + (i * spacing) + (spacing / 2) + 10;
var pivot = new PointF(Bounds.Right, yLoc);
var paramBounds = new RectangleF(Bounds.Right - 15, yLoc - 10, 15, 20);
param.Attributes.Pivot = pivot;
param.Attributes.Bounds = paramBounds;
}
}
}
protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
{
if (channel == GH_CanvasChannel.Objects)
{
// set component color
GH_Palette palette = Owner.RuntimeMessageLevel switch
{
GH_RuntimeMessageLevel.Warning => GH_Palette.Warning,
GH_RuntimeMessageLevel.Error => GH_Palette.Error,
_ => GH_Palette.Normal
};
// render the capsule: component body (rectangle) and input/output grips
using (var capsule = GH_Capsule.CreateCapsule(Bounds, palette))
{
foreach (var param in Owner.Params.Input)
capsule.AddInputGrip(param.Attributes.Pivot.Y);
foreach (var param in Owner.Params.Output)
capsule.AddOutputGrip(param.Attributes.Pivot.Y);
capsule.Render(graphics, Selected, Owner.Locked, false);
}
// render input labels
StringFormat format = new()
{
Alignment = StringAlignment.Near,
LineAlignment = StringAlignment.Center,
Trimming = StringTrimming.EllipsisCharacter
};
RectangleF labelRectangle = GH_Convert.ToRectangle(Bounds);
labelRectangle.Width = 90;
labelRectangle.Height = 20;
labelRectangle.Inflate(-2, 0);
foreach (var param in Owner.Params.Input)
{
labelRectangle.Y = param.Attributes.Pivot.Y - 10;
graphics.DrawString(param.Name, GH_FontServer.Standard, Brushes.Black, labelRectangle, format);
}
// draw rectangle
using (Pen pen = new(Color.Black))
graphics.DrawRectangle(pen, _internalRect);
// define text rectangle to specify where to write
RectangleF textRectangle = _internalRect;
textRectangle.Y += 2;
textRectangle.Height = 20;
textRectangle.Inflate(-2, 0);
format.Alignment = StringAlignment.Center;
graphics.DrawString("Combinations", GH_FontServer.Standard, Brushes.Black, textRectangle, format);
// draw the combos
format.Alignment = StringAlignment.Near;
textRectangle.X += 2;
if (Owner._combos.Count != 0)
{
for (int i = 0; i < Owner._combos.Count; i++)
{
if (Owner._combos[i] == null)
continue;
textRectangle.Y += textRectangle.Height;
int textLen = GH_FontServer.StringWidth(Owner._combos[i].ToString(), GH_FontServer.Standard);
if (textLen <= textRectangle.Width)
textRectangle.Height = 20;
else
textRectangle.Height = GH_FontServer.Standard.Height * (float)Math.Ceiling(textLen / textRectangle.Width);
if (textRectangle.Y + textRectangle.Height + (i < Owner._combos.Count - 1 ? 20 : 0) > _internalRect.Y + _internalRect.Height)
{
textRectangle.Height = 20;
graphics.DrawString($"{Owner._combos.Count - i} more...", GH_FontServer.Standard, Brushes.Black, textRectangle, format);
break;
}
graphics.DrawString(Owner._combos[i].ToString(), GH_FontServer.Standard, Brushes.Black, textRectangle, format);
}
}
else
{
textRectangle.Y += textRectangle.Height;
graphics.DrawString("No combination defined...", GH_FontServer.Standard, Brushes.Black, textRectangle, format);
}
}
else if (channel == GH_CanvasChannel.Wires)
{
foreach (var p in Owner.Params.Input)
p.Attributes?.RenderToCanvas(canvas, GH_CanvasChannel.Wires);
foreach (var p in Owner.Params.Output)
p.Attributes?.RenderToCanvas(canvas, GH_CanvasChannel.Wires);
}
else
{
base.Render(canvas, graphics, channel);
}
}
public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)
{
if (e.Button == MouseButtons.Left)
{
List<GH_Combination> list = null;
if (Owner._combos.Count != 0)
list = Owner._combos;
var caseModels = Owner._caseModels.Concat(Owner._freedomCaseModels).ToArray();
LoadCombinationsForm form = new(caseModels, list);
GH_WindowsFormUtil.CenterFormOnCursor(form, true);
if (form.ShowDialog(Instances.DocumentEditor) == DialogResult.OK)
{
Owner._combos = form.Values;
Owner.ExpireSolution(true);
}
return GH_ObjectResponse.Handled;
}
return base.RespondToMouseDoubleClick(sender, e);
}
public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
{
// Check if we clicked on an input/output parameter area. If that's the case, redirect the event to the parameter's own attributes
foreach (var param in Owner.Params.Input)
if (param.Attributes.Bounds.Contains(e.CanvasLocation))
return param.Attributes.RespondToMouseDown(sender, e);
foreach (var param in Owner.Params.Output)
if (param.Attributes.Bounds.Contains(e.CanvasLocation))
return param.Attributes.RespondToMouseDown(sender, e);
return base.RespondToMouseDown(sender, e);
}
}
}
}