Skip to content

Commit

Permalink
Feat(ETABS) : receive gridlines (#2969)
Browse files Browse the repository at this point in the history
* wip gridline receiving

* creates grids if grid system is existing

* code cleanup

* appears to be working

* small logic improvements

* move create grid to separate method

* adjust tolerances

* added documentation

* rename file

* don't override existing grid system properties

---------

Co-authored-by: Connor Ivy <[email protected]>
  • Loading branch information
connorivy and Connor Ivy authored Sep 29, 2023
1 parent 3997466 commit de5d858
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using CSiAPIv1;
using Objects.BuiltElements;
using Objects.Structural.Analysis;
using Objects.Structural.CSI.Analysis;
using Objects.Structural.CSI.Geometry;
Expand Down Expand Up @@ -112,6 +113,7 @@ public bool CanConvertToNative(Base @object)
case Load _:
//case Geometry.Line line:
case Node _:
case GridLine _:
//case Model o:
//case Property property:

Expand Down Expand Up @@ -211,6 +213,9 @@ public object ConvertToNative(Base @object)
case BuiltElements.Column o:
CurveBasedElementToNative(o, o.baseLine, ref appObj);
break;
case GridLine o:
GridLineToNative(o);
break;
#endregion
default:
appObj.Update(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
<Compile Include="$(MSBuildThisFileDirectory)Extensions\ICurveExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\LineExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Extensions\PolycurveExtensions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\DatabaseTableWrapper.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Models\ETABSGridLineDefinitionTable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Analysis\ConvertModelSettings.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Analysis\ConvertModel.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Analysis\ConvertModelInfo.cs" />
Expand Down Expand Up @@ -55,5 +57,6 @@
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Results\ConvertResults.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Results\ConvertResultSet1D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Partial Classes\Results\ConvertResultSet2D.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Services\ToNativeScalingService.cs" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using ConverterCSIShared.Services;
using CSiAPIv1;

namespace ConverterCSIShared.Models
{
internal abstract class DatabaseTableWrapper
{
public abstract string TableKey { get; }
protected readonly cSapModel cSapModel;
protected readonly ToNativeScalingService toNativeScalingService;
private int tableVersion;
protected string[] fieldKeysIncluded;
protected int numRecords;
private readonly List<string> tableData;

protected DatabaseTableWrapper(cSapModel cSapModel, ToNativeScalingService toNativeScalingService)
{
this.cSapModel = cSapModel;
this.toNativeScalingService = toNativeScalingService;
this.tableData = new List<string>(GetTableData());
}
private string[] GetTableData()
{
var tableData = Array.Empty<string>();
cSapModel.DatabaseTables.GetTableForEditingArray(
TableKey,
"this param does nothing",
ref tableVersion,
ref fieldKeysIncluded,
ref numRecords,
ref tableData
);
return tableData;
}

protected void AddRow(params string[] arguments)
{
if (arguments.Length != fieldKeysIncluded.Length)
{
throw new ArgumentException($"Method {nameof(AddRow)} was passed an array of length {arguments.Length}, but was expecting an array of length {fieldKeysIncluded.Length}");
}
tableData.AddRange(arguments);
numRecords++;
}

public void ApplyEditedTables()
{
var tableDataArray = tableData.ToArray();
cSapModel.DatabaseTables.SetTableForEditingArray(TableKey, ref tableVersion, ref fieldKeysIncluded, numRecords, ref tableDataArray);

int numFatalErrors = 0;
int numWarnMsgs = 0;
int numInfoMsgs = 0;
int numErrorMsgs = 0;
string importLog = "";
cSapModel.DatabaseTables.ApplyEditedTables(false, ref numFatalErrors, ref numErrorMsgs, ref numWarnMsgs, ref numInfoMsgs, ref importLog);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
#nullable enable
using System;
using ConverterCSIShared.Services;
using CSiAPIv1;
using Objects.BuiltElements;
using Objects.Geometry;
using Objects.Other;
using Speckle.Core.Logging;

namespace ConverterCSIShared.Models
{
/// <summary>
/// Encapsulates the logic dealing with creating and manipulating gridlines via the interactive database in ETABS
/// </summary>
internal class ETABSGridLineDefinitionTable : DatabaseTableWrapper
{
private const double gridTolerance = .001; // .05 degrees as radians
public override string TableKey => "Grid Definitions - Grid Lines";
public static string?[] DefaultRow => new string?[] {
null, // Name : Grid System name
null, // LineType
null, // Id : Grid name
null, // Ordinate : Offset in the positive direction
null, // Angle : clockwise offset in degrees for polar (cylindrical) coordinates
null, // X1
null, // Y1
null, // X2
null, // Y2
"Start", // BubbleLoc
"Yes", // Visible
};
public ETABSGridLineDefinitionTable(cSapModel cSapModel, ToNativeScalingService toNativeScalingService)
: base(cSapModel, toNativeScalingService) { }

public const string XGridLineType = "X (Cartesian)";
public const string YGridLineType = "Y (Cartesian)";
public const string RGridLineType = "R (Cylindrical)";
public const string TGridLineType = "T (Cylindrical)";

/// <summary>
/// Add a gridline that is represented by a straight line to the ETABS document
/// </summary>
/// <param name="gridSystemName"></param>
/// <param name="gridLineType"></param>
/// <param name="gridName"></param>
/// <param name="location"></param>
/// <param name="visible"></param>
/// <exception cref="ArgumentException"></exception>
public void AddCartesian(
string gridSystemName,
string gridLineType,
string gridName,
double location,
string visible = "Yes"
)
{
if (gridLineType != XGridLineType && gridLineType != YGridLineType)
{
throw new ArgumentException($"Argument gridLineType must be either {XGridLineType} or {YGridLineType}");
}
var newRow = new string?[fieldKeysIncluded.Length];
for (var index = 0; index < fieldKeysIncluded.Length; index++)
{
newRow[index] = fieldKeysIncluded[index] switch
{
"Name" => gridSystemName,
"LineType" => gridLineType,
"ID" => gridName,
"Ordinate" => location.ToString(),
"Visible" => visible,
_ => DefaultRow[index],
};
}

AddRow(newRow);
}

/// <summary>
/// Add a gridline that is represented by a straight line to the ETABS document
/// </summary>
/// <param name="gridLine"></param>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="SpeckleException"></exception>
public void AddCartesian(GridLine gridLine)
{
if (gridLine.baseLine is not Line line)
{
throw new ArgumentException("Non line based gridlines are not supported");
}

var gridRotation = GetAngleOffsetFromGlobalCoordinateSystem(line);

var gridSystem = GetExistingGridSystem(gridRotation) ?? CreateGridSystem(gridRotation);
var transform = GetTransformFromGridSystem(gridSystem);
_ = line.TransformTo(transform.Inverse(), out Line transformedLine);

var newUx = Math.Abs(transformedLine.start.x - transformedLine.end.x);
var newUy = Math.Abs(transformedLine.start.y - transformedLine.end.y);

string lineType;
double gridLineOffset;
if (newUx < gridTolerance)
{
lineType = XGridLineType;
gridLineOffset = toNativeScalingService
.ScaleLength(transformedLine.start.x, transformedLine.units ?? transformedLine.start.units);
}
else if (newUy < gridTolerance)
{
lineType = YGridLineType;
gridLineOffset = toNativeScalingService
.ScaleLength(transformedLine.start.y, transformedLine.units ?? transformedLine.start.units);
}
else
{
throw new SpeckleException($"Error in transforming line from global coordinates to grid system with rotation {gridSystem.Rotation} and x,y offsets {gridSystem.XOrigin}, {gridSystem.YOrigin}");
}

AddCartesian(gridSystem.Name, lineType, gridLine.label, gridLineOffset);
}

/// <summary>
/// Returns the rotation counter-clockwise from from the global x axis in radians
/// </summary>
/// <param name="line"></param>
/// <returns></returns>
private static double GetAngleOffsetFromGlobalCoordinateSystem(Line line)
{
var ux = line.start.x - line.end.x;
var uy = line.start.y - line.end.y;

if (Math.Abs(ux) < gridTolerance)
{
return Math.PI / 2;
}
return Math.Atan(uy / ux);
}

/// <summary>
/// Find a GridSystem in the CSi model whose local x axis is either parallel or perpendicular to the provided
/// grid angle.
/// </summary>
/// <param name="gridRotation">Rotation counter-clockwise from the global x axis in radians</param>
/// <returns></returns>
private GridSystemRepresentation? GetExistingGridSystem(double gridRotation)
{
var numGridSys = 0;
var gridSysNames = Array.Empty<string>();
this.cSapModel.GridSys.GetNameList(ref numGridSys, ref gridSysNames);

var xOrigin = 0.0;
var yOrigin = 0.0;
var rotationDeg = 0.0;
foreach (var gridSysName in gridSysNames)
{
var success = cSapModel.GridSys.GetGridSys(gridSysName, ref xOrigin, ref yOrigin, ref rotationDeg);
if (success != 0)
{
// something went wrong. This is not necessarily unexpected or bad
continue;
}

var rotationRad = rotationDeg * Math.PI / 180;
var combinedRotationsNormalized = Math.Abs((rotationRad - gridRotation) / (Math.PI / 2));
var combinedRotationsRemainder = combinedRotationsNormalized - Math.Floor(combinedRotationsNormalized);

if (combinedRotationsRemainder < gridTolerance || combinedRotationsRemainder > 1 - gridTolerance)
{
return new GridSystemRepresentation(gridSysName, xOrigin, yOrigin, rotationRad);
}
}
// could not find compatible existing grid system
return null;
}

private GridSystemRepresentation CreateGridSystem(double gridRotation)
{
var systemName = GetUniqueGridSystemName();
_ = cSapModel.GridSys.SetGridSys(systemName, 0, 0, gridRotation * 180 / Math.PI);

// when a grid system is created, it doesn't show up unless it has at least one grid in each direction
AddCartesian(systemName, XGridLineType, "Default0", 0, "No");
AddCartesian(systemName, YGridLineType, "Default1", 0, "No");
return new GridSystemRepresentation(systemName, 0, 0, gridRotation);
}

/// <summary>
/// Returns a unique name to be used for a new speckle-created grid system.
/// </summary>
/// <returns></returns>
private string GetUniqueGridSystemName()
{
var numGridSys = 0;
var gridSysNames = Array.Empty<string>();
this.cSapModel.GridSys.GetNameList(ref numGridSys, ref gridSysNames);

var numberOfGridSystems = 0;
var gridSystemNamePrefix = "SpeckleGridSystem";
foreach (var gridSysName in gridSysNames)
{
// test if this grid system is one that we already created. If it is, then we need to adjust our
// numberOfGridSystems so that if we do end up creating a new one, it doesn't override an existing one.
if (!gridSysName.StartsWith(gridSystemNamePrefix))
{
continue;
}

if (int.TryParse(gridSysName.Replace(gridSystemNamePrefix, ""), out int gridSysNum))
{
numberOfGridSystems = Math.Max(numberOfGridSystems, gridSysNum + 1);
}
}
return $"{gridSystemNamePrefix}{numberOfGridSystems}"; ;
}

private static Transform GetTransformFromGridSystem(GridSystemRepresentation sys)
{
return new Transform(
new double[]
{
Math.Cos(sys.Rotation), -Math.Sin(sys.Rotation), 0, sys.XOrigin,
Math.Sin(sys.Rotation), Math.Cos(sys.Rotation), 0, sys.YOrigin,
0, 0, 1, 0,
0, 0, 0, 1
}
);
}
}

public class GridSystemRepresentation
{
public GridSystemRepresentation(string name, double xOrigin, double yOrigin, double rotation)
{
Name = name;
XOrigin = xOrigin;
YOrigin = yOrigin;
Rotation = rotation;
}

public string Name { get; }
public double XOrigin { get; set; }
public double YOrigin { get; set; }
public double Rotation { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
using System;
using System;
using System.Collections.Generic;
using Objects.Geometry;
using Objects.Structural.Geometry;
using Objects.Structural.Analysis;
using Speckle.Core.Models;
using Objects.Structural.CSI.Geometry;
using Objects.Structural.CSI.Properties;
using Objects.BuiltElements;
using System.Linq;
using CSiAPIv1;
using ConverterCSIShared.Models;

namespace Objects.Converter.CSI
{
public partial class ConverterCSI
{
public void gridLinesToNative(CSIGridLines gridlines)
private ETABSGridLineDefinitionTable gridLineDefinitionTable;
private ETABSGridLineDefinitionTable GridLineDefinitionTable => gridLineDefinitionTable ??= new(Model, new(Model));
public void GridLineToNative(GridLine gridline)
{
throw new NotSupportedException();
GridLineDefinitionTable.AddCartesian(gridline);
GridLineDefinitionTable.ApplyEditedTables();
}

public CSIGridLines gridLinesToSpeckle(string name)
Expand Down
Loading

0 comments on commit de5d858

Please sign in to comment.