Posts by retug (37)

Diaphragm Slicer New and Improved

A long time coming... a professional level plugin for ETABs, or at least a whole lot better than the python diaphragm slicer.

User Input

Results

 

This plugin will allow you to slice up your diaphragm in any direction you want, allowing you to see shear and moment along the length of the areas.

User Input

Start Point

This is the point at which you want to start the slicing. Only select an individual point, or the program will show you an error message

Areas

Select any number of objects, ensure all selected area objects share the same Z coordinate.

Input Vector

For slicing in the X direction, input X = 1, Y =0, ensure you input a positive integer. Input field will be limited to numbers and at this moment does allow for negative numbers.

Select Load Case

Pressing the Gather Cases button will retrieve all of the load cases in your current project, be sure to select one from the drop down box, this will be the outputted results from the section cut analysis

Number of Section

Input an integer between 2 and 1000, this will be the number of points that populate shear and moment diagrams

After all of these items are input, click on the run analysis button to view the results.

Reviewing the Results

3 Plots will generate after the program has run, shear (kips), moment (kip*ft), and a plot that shows how the program generated the cuts of your diaphragm for QA of your input.

The plots are all hoverable, allowing you to zoom and review results. In addition, a tabular data table is populated with location (ft), shear and moment being available to be copied and pasted in your favorite spreadsheet.

I have posted the code up on github. I hope to get this addin up on CSI's website in the future. If you have done this before, drop a comment below on how to do this. 

If you want to take this for test run, let me know below. I have yet to try this out in full production, I am sure there are bugs that need to be worked out.

The Coding

Now that the rough documentation is complete, let's dig into the fun stuff.

First thing that strikes me is that python is still freaking awesome! I was able to do this work in python in 233 lines of code and 1 file. This C# addin weighs in at around 1200 lines and 12 files. That's crazy in my opinion! Python is great for whipping up quick ideas.

Where is the variable explorer in Visual Studio Code?!?

Why in the world is there not a built in variable explorer in visual studio code similar to spyder for python. I never realized how great the variable explorer was until you were reviewing many many lines of nested list/array data. I found myself watching a lot of variables in visual studio code, super slow and tedious.

It looks like there might be a way to view large lists of data in visual studio code with a separate addin, but I did not dig into this in depth.

ETABs API

I ran into some trouble with the API when trying to make section cuts with the database tables methods. I had code that worked in ETABs v19, the OG diaphragm slicer, but the code python code would not work in v20 and the C# code I was trying to write would not work in v20 either. Turns out, the database tables headers between v20 and v19 were updated and no longer matched the visual column headers in the section cut tables. After some painful debugging we got this fixed. You can read more about my struggles here.

Matrices in C#

I use matrices in this program to convert back and forth from global coordinates to local coordinates and needed a matrix program if it was available. After some googling, it looked like mathnet.numerics was the most popular matrix package in C#, even above a c# clone of numpy it appears (package used in python to do matrix math). The mathnet package works, but is a bit clunky and I found the documentation hard to read and understand. Eventually, I was able to create a matrix and invert a matrix. Another odd one, it did not support matrix multiplication, I had to do that by hand!

Some of the weird syntax:


 public class GlobalCoordinateSystem
    {
        public List<double> RefPnt { get; set; }
        public List<double> Vector { get; set; }
        public double hyp { get; set; }
        public double[,] R { get; set; }
        public string inverseMatrixText { get; set; }
        public Matrix<double> R_Matrix { get; set; }
        public double[,] R_Inv { get; set; }
        //This is the constructor, redefine the point?
        public GlobalCoordinateSystem(List<double> xyz, List<double> vector)
        {
            RefPnt = xyz;
            hyp = Math.Sqrt((vector[0] * vector[0] + vector[1] * vector[1]));
            Vector = vector;
            R = new double[,] { { vector[0] / hyp, -vector[1] / hyp, 0 }, { vector[1] / hyp, vector[0] / hyp, 0 }, { 0, 0, 1 } };
            R_Matrix = Matrix<double>.Build.DenseOfArray(R);
            R_Inv = R_Matrix.Inverse().ToArray();
        }
    }
}

        public void glo_to_loc(GlobalCoordinateSystem globalCoords)
        {
            double[] part1 = new double[] { X - globalCoords.RefPnt[0], Y - globalCoords.RefPnt[1], Z - globalCoords.RefPnt[2] };

            //the class will now have new attribute of local coordinates point.LocalCoords[0] = the X local coordinate system
            LocalCoords = new List<double>() { globalCoords.R_Inv[0, 0] * part1[0] + globalCoords.R_Inv[0, 1] * part1[1] + globalCoords.R_Inv[0, 2] * part1[2] ,
            globalCoords.R_Inv[1, 0] * part1[0] + globalCoords.R_Inv[1, 1] * part1[1] + globalCoords.R_Inv[1, 2] * part1[2],
            globalCoords.R_Inv[2, 0] * part1[0] + globalCoords.R_Inv[2, 1] * part1[1] + globalCoords.R_Inv[2, 2] * part1[2]};
        }
        public void loc_to_glo(GlobalCoordinateSystem globalCoords)
        {
            //this is the ref point
            double[] part1 = new double[] { X, Y, Z };

            //the class will now have new attribute of local coordinates point.LocalCoords[0] = the X local coordinate system
            GlobalCoords = new List<double>() { (globalCoords.R[0, 0] * X + globalCoords.R[0, 1] * Y + globalCoords.R[0, 2] * Z) + globalCoords.RefPnt[0],
            (globalCoords.R[1, 0] * X + globalCoords.R[1, 1] * Y + globalCoords.R[1, 2] * Z) + globalCoords.RefPnt[1],
            (globalCoords.R[2, 0] * X + globalCoords.R[2, 1] * Y + globalCoords.R[2, 2] * Z) + 0}; //unsure why this the way to do this. review in the future. should be the line below, without the 0
            //(globalCoords.R[2, 0] * X + globalCoords.R[2, 1] * Y + globalCoords.R[2, 2] * Z) + globalCoords.RefPnt[2]};
        }

Another point, I need to review why my Z coordinate kept messing up in this process.

C# Plotting

I settled on a plotting package called live charts in C#. This package seemed to be the go-to package back in 2018, but it seems like maybe it has stopped being supported by its developers. Again, documentation was kinda hard to find and when it was able to found, it was hard to understand. More struggling and pain to get to the pretty graphs. I do like the graphs are live, zoomable and interactive. The out-of-the-box microsoft graphs had much less of the interactiveability built into them.

UI Development

I went through a few differing versions of UIs and finally called it good enough where it was. My favorite color is orange, so I wanted to through some orange in the plots, hopefully the plots are not too painful on the eyes.

I also set up a linear gradient brush on the background because why not, it seems like most modern UI's are using color gradients.

A few snips of UI in development

A bit too generic, looks like every other windows form

A bit too much orange

Just right, enough flair to know this guy knows what he's doing, but not too visually distracting:

Eventually I want to get back into this and make rounded buttons and pretty up the tabular data, but I wanted to be done with this project and called it quits at good enough.

Coding for the UI was actually pretty enjoyable in C#, there are a lot of good videos out there on how to make your form look good.

This bit of code makes the linear gradient in the background:

private void Form1_Paint(object send, PaintEventArgs e)
        {
            Graphics mgraphics = e.Graphics;
            Pen pen = new Pen(Color.FromArgb(255, 140, 105), 1);
            Rectangle area = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

            System.Drawing.Drawing2D.LinearGradientBrush lGB2 = new System.Drawing.Drawing2D.LinearGradientBrush(area, Color.FromArgb(255, 255, 255), Color.FromArgb(159, 159, 159), LinearGradientMode.Vertical);
            mgraphics.FillRectangle(lGB2, area);
            mgraphics.DrawRectangle(pen, area);
        }

Please take this for a spin and let me know if you have any feedback!

EDIT 04/19/2023:

The section cut tool is only correct slices in the left right direction! (loading in the X global direction typically) I discovered this after using this on a real project. 

From ETABs documentation:

How is the plane of the section cut defined when drawn in a 3D view?
Expanded Question: When a section cut is drawn in a 3D view, only the X and Y coordinates are provided. How is the section-cut plane defined?

Answer: When using the Draw > Draw Section Cut command, forces are reported in the section-cut coordinate system which is defined by three axes (1,2,Z) as follows:

Section-cut 1 axis is located within the plane parallel to the global X-Y plane, and rotates counterclockwise from the global X axis according to the user-defined parameter Angle (X to 1).
Section-cut 2 axis is also located within the plane parallel to the global X-Y plane, though it is oriented 90° counterclockwise from the section-cut 1 axis.
Section-cut Z axis is parallel to the global Z axis.

I will need to update the code to pull right combination of either F1 or F2. This should not be too difficult to do.

ETABs Plugin - Part 2

Having nailed down the initial steps of creating a plugin, I set out to do some real functional work with the plugin. Being naive, I went off the deep end to begin programming the diaphragm slicer, but was quickly met by a sea of syntax and foreign looking code that was difficult to navigate. I had to go back to what all good programmers do, watching youtube videos.

The Code

The example plugin above will gather all of the load combinations in your ETABs project, populate them in a "datagrid view" and combobox and will report the joint reaction forces at a specific node under the selected load combination. I followed this youtube video, thank you Structure Detailing, the video was very informative and helpful!

The code to make this plugin has been upload on github.

New Syntax

From the video, the syntax of {get; set;} in a C# class appeared multiple times.

public class JointReaction
    {
        public string Name { get; set; }
        public string LoadCase { get; set; }
        public double F1 { get; set; }
        public double F2 { get; set; }
        public double F3 { get; set; }
        public double M1 { get; set; }
        public double M2 { get; set; }
        public double M3 { get; set; }
    }

This notation appears related to "encapsulation" in C# and allows this class to be both private (hidden from other classes and other parts of the program) and public. The JointReaction class is then instantiated in the main Form1.cs code.

           React.Name = Name;
           JReact.LoadCase = LoadCase[0];
           JReact.F1 = F1[0];
           JReact.F2 = F2[0];
           JReact.F3 = F3[0];
           JReact.M1 = M1[0];
           JReact.M2 = M2[0];
           JReact.M3 = M3[0];

Calling the .F1 attribute on the class JReact somehow triggers the {get; set;} notation. I have to dig into more C# to fully understand this {get; set;} notation.

ETABs API - Unclear Returns

API calls to ETABs are foreign to me, how can there be no explicit return?

Take for example the following code:

string Name = "";
            eItemTypeElm ItemTypeElm;
            int NumberResults = 1;
            string[] Obj = null;
            string[] Elm = null;
            string[] LoadCase = null;
            string[] StepType = null;
            double[] StepNum = null;
            double[] F1 = null;
            double[] F2 = null;
            double[] F3 = null;
            double[] M1 = null;
            double[] M2 = null;
            double[] M3 = null;
            int x = -1;
            _SapModel.Results.Setup.DeselectAllCasesAndCombosForOutput();
            //Method below acts on a string
            _SapModel.Results.Setup.SetComboSelectedForOutput(LoadCombinationComBox.SelectedItem.ToString());

            //unsure why eItemTypeElm.Element does not need to be initiated
            //anything that is ref is returned, not input
            x = _SapModel.Results.JointReact("4", eItemTypeElm.Element, ref NumberResults, ref Obj, ref Elm, ref LoadCase, ref StepType, ref StepNum, ref F1, ref F2, ref F3, ref M1, ref M2, ref M3);

The confusing part of this code is that the output of _SapModel.Results.Joint(...), x, is merely an integer, 1. I have no idea how this method writes/returns the values of the results to F1, F2, F3... Anytime I have written methods, I have direct return statement and it would typically be written directly to the output variable, x. If you know what the syntax might look like in C# to make this complicated output, please let me know, I would be interested in learning how to return multiple data types, lists, and values.

FYI, the code above will output the joint reaction forces for the unique node "4" in the ETABs model. These are the values that are populated in the initial video.

DataGridViews - A Ray of Hope for Programming

Data grid views have to be the best thing I have discovered in C#. Coming from python and the miserable experience that was PyQt5 to make a GUI, this is a miracle. The code to make tables is very clean and concise. If I never have to write PyQt5 code again, I will be happy. Side note, I have been meaning to make a post on PyQt5 GUIs with ETABs, another post for a later time. A sample of the mess that is PyQt5:

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data
        self._headers = ['Frame Size', 'Cnx Type', 'Demand (kip)', 'Capacity (kip)', 'ETABs Frame #']

   def data(self, index, role):
        if role == QtCore.Qt.DisplayRole:
            # See below for the nested-list data structure.
            # .row() indexes into the outer list,
            # .column() indexes into the sub-list
            return self._data[index.row()][index.column()]
        if self._data[index.row()][index.column()] == 'SS/DS cnx NG' and role == QtCore.Qt.BackgroundRole :
            return QBrush(Qt.red)
        if self._data[index.row()][index.column()] == 'No Values' and role == QtCore.Qt.BackgroundRole :
            return QtGui.QColor('#FFA500')
        if self._data[index.row()][index.column()] == 'DS cnx Ok' and role == QtCore.Qt.BackgroundRole :
            return QBrush(Qt.yellow)
        
    def rowCount(self, index):
        # The length of the outer list.
        return len(self._data)

Now that I have learned the basics of the plugin and the ETABs API, I think I should be able to struggle through my first bit of custom coding.

The Beginnings of an ETABs Plugin

My first "Hello World" addin for ETABs is shown above. No matter how many times you see it, it is always satisfying to get Hello World to print out.

To make the addin, I followed Jeremy's examples on his website. Big thanks to him and his github for walking me through the initial setup process for the ETABs API with C#.

The examples that he presented were from the 2017 community edition of visual studio, so there were a few buttons that had been moved around in the 2019 version that I was using.

First, the events tab that is shown in his examples has moved into its drop down menu, indicated by a lightning bolt. This threw me off for a bit until I found the events tab in its new home.

Also, upon re-compling the application, while having ETABs open, I would get this error:

I was able to solve this by navigating into the big/debug directory and removing the ETABS_Plugin.pdb and ctrl+alt+deleting to kill ETABs. Some weird bug that I will need to dig into more, just be sure to close ETABs before recompiling the code.

The Future

I am hoping to make the diaphragm slicer into a full fledged application. This will make the application much more approachable for others in the office and out in the interwebs.

Things I will need to research in C#, let me know if you have suggestions on these topics:

  • A matrix math package for converting between local and global coordinates
  • A graphing package in C#
  • Learning windows forms more in depth

I uploaded the code on github (basically a copy of Jeremy's code) if you want to take a look.

RAM Structural API - A Practical Example

After unlocking the first part of the RAM API, I wanted to create a practical example of how this could be used to automate a few tedious tasks that we perform in the office.

Chord and Collector Design, A Tedious Task

I chose chord and collector design as this is a very repetitive task in our office. The task typically involves pulling the following information for a chord of collector element from RAM Structural System Model:

  • Member size, e.g. W16x31
  • Member length to determine unbraced lengths
  • Member demands (mainly flexural demands)
  • Member capacity (again mainly flexural)

We then take this information and plug it directly in a nice spreadsheet that will perform a combined axial and flexural demand calculation on our steel chord and collector members. 

Looking at the API functions in the RAM documentation it looked like I could pull most of these values pretty easily.

User Input

The user input for this dynamo program involves the following items

  • RAM Structural System file path
  • Story of Interest
    • This is an integer at the moment, with 0 being the first level.
  • RAM Beam #
    • Each beam on each story in a RAM model has a unquie Beam ID, input this # to pull information
  • Sds for your project
  • Make sure you have a green light (RAM structural home page) on gravity beam design model before running this script!

With this information, the program opens the RAM model and pulls out the pertinent information and writes it to excel, let's dig into some of the fun items for making this happen.

Member Size

To get the member size, I was able to copy an example from Marcello and pull all beam sizes on the user input level. I would like to modify this in the future to only pull the member sizes based on the user input beam number, but reviewing the API documentation, I did not see a function immediately retrieved the beam ID, given a story and beam # that corresponds to the numbers on the screen in RAM structural. The code to pull this is shown below:

public static List<string> GET_RAM_BM_SIZE(string FileName, int In_Story_Count)
        {
            RamDataAccess1 RAMDataAccess = new RAMDATAACCESSLib.RamDataAccess1();
            RAMDATAACCESSLib.IDBIO1 IDBI = (RAMDATAACCESSLib.IDBIO1)
                RAMDataAccess.GetInterfacePointerByEnum(EINTERFACES.IDBIO1_INT);
            RAMDATAACCESSLib.IModel IModel = (RAMDATAACCESSLib.IModel)
                RAMDataAccess.GetInterfacePointerByEnum(EINTERFACES.IModel_INT);
            //OPEN
            IDBI.LoadDataBase2(FileName, "1");
            IStories My_stories = IModel.GetStories();
            int My_story_count = My_stories.GetCount();
            IStory My_Story = My_stories.GetAt(In_Story_Count);
            IBeams My_Beams = My_Story.GetBeams();
            int Beam_Count = My_Beams.GetCount();
            List<string> ListLine = new List<string>();
            //create loop herenthru all count
            //start..end..step
            for (int i = 0; i < Beam_Count; i = i + 1)
            {
                string My_Beam_Size = My_Story.GetBeams().GetAt(i).strSectionLabel;
                ListLine.Add(My_Beam_Size);
            }
            //CLOSE           
            IDBI.CloseDatabase();
            return ListLine;
        }

 

Giving A Description

Previously, we would describe our chord/collector locations based on maybe grid lines and stories. Given that we typically provide a beam map to our plan reviewers with all beam #s labelled, I thought it might be easier to just provide a description based on beam # and story number. This makes it easy for internal review and plan review. 

Making text for this with a little python was pretty easy and actually the first time I used the new fStrings in Python. fStrings were added in python 3.6 and I think they infinetly times easier to read and write, a great change.

Determining the moment

One of the pains of RAM structural system beam designer is that it will spit out diagrams for 1.4 Dead Load (DL) and 1.2DL + 1.6 Live Load (LL).

Sometimes you just want to know what the DL moment and the LL moment so you can modify these for differing load combinations like that in a seismic load combination, 1.2DL + 0.2Sds + 0.5LL typically.

I was able to use Marcello's examples from his packets and tweak some C# code to retreive the unfactored DL and LL moments from a RAM structural API method. GetGravBeamForcesLeftAt(...) did the trick.

[MultiReturn(new[] { "pdDeadMoment", "pdDeadShear", "pdCDMoment", "pdCDShear", "pdCLMoment", "pdCLShear",
                "pdPosLiveMoment","pdPosLiveShear","pdNegLiveMoment", "pdNegLiveShear"})]
        public static Dictionary<string, object> GET_GRV_BEAM_FORCES(string FileName, int BeamID, double BeamLocation)
        {
            RamDataAccess1 RAMDataAccess = new RAMDATAACCESSLib.RamDataAccess1();
            RAMDATAACCESSLib.IDBIO1 IDBI = (RAMDATAACCESSLib.IDBIO1)RAMDataAccess.GetInterfacePointerByEnum(EINTERFACES.IDBIO1_INT); //casting this to an object
            RAMDATAACCESSLib.IModel IModel = (RAMDATAACCESSLib.IModel)
                RAMDataAccess.GetInterfacePointerByEnum(EINTERFACES.IModel_INT);
            RAMDATAACCESSLib.IForces1 IForces1 = (RAMDATAACCESSLib.IForces1)
                RAMDataAccess.GetInterfacePointerByEnum(EINTERFACES.IForces_INT);
            Dictionary<string, object> OutPutPorts = new Dictionary<string, object>();
            //OPEN
            IDBI.LoadDataBase2(FileName, "1");
            double pdDeadMoment = 0;
            double pdDeadShear= 0;
            double pdCDMoment = 0;
            double pdCDShear = 0;
            double pdCLMoment = 0;
            double pdCLShear = 0;
            double pdPosLiveMoment = 0;
            double pdPosLiveShear = 0;
            double pdNegLiveMoment = 0;
            double pdNegLiveShear = 0;
            IForces1.GetGravBeamForcesLeftAt(BeamID, BeamLocation, ref pdDeadMoment, ref pdDeadShear,
                ref pdCDMoment, ref pdCDShear, ref pdCLMoment, ref pdCLShear,
                ref pdPosLiveMoment, ref pdPosLiveShear, ref pdNegLiveMoment, ref pdNegLiveShear);
            //CLOSE           
            IDBI.CloseDatabase();
            OutPutPorts.Add("pdDeadMoment", pdDeadMoment); OutPutPorts.Add("pdDeadShear", pdDeadShear);
            OutPutPorts.Add("pdCDMoment", pdCDMoment); OutPutPorts.Add("pdCDShear", pdCDShear);
            OutPutPorts.Add("pdCLMoment", pdCLMoment); OutPutPorts.Add("pdCLShear", pdCLShear);
            OutPutPorts.Add("pdPosLiveMoment", pdPosLiveMoment); OutPutPorts.Add("pdPosLiveShear", pdPosLiveShear);
            OutPutPorts.Add("pdNegLiveMoment", pdNegLiveMoment); OutPutPorts.Add("pdNegLiveShear", pdNegLiveShear);
            return OutPutPorts;
        }

 

A WORD OF CAUTION

The way I have this setup, this function only pulls the beam results at mid span, should you have an unsymmetrical loaded beam, you MAY BE MISSING THE MAX MOMENTS. I have not dug deep enough into the API to know if there is a way to pull out the max moments along the beam.

Determining Phi*Mn

From my review of the documentation, there does not seem to be an explicit function that returns Phi*Mn for a steel beam. Marcello has an example the returns design results, with one of the results being a strength DCR. I wrote some code that determines Mu and backs into Phi*Mn by taking Mu and dividing by the DCR. I thought this was pretty slick until I realized that sometimes the pre-composite strength of a beam might control the design.

Take the following example below.

The way I currently have the code written, my program will correctly determine that Mu = 120.1 kip*ft as noted on the RAM output. But, you can see that the strength DCR is 0.88. The program will determine that phi*Mn = 120.1/0.88 = 136.36 kip*ft, but this is incorrect. As you can see, the pre-composite strength is actually governing this beam with a DCR of 0.88 coming from the prec-omposite check, 43kip*ft/48kip*ft.

The true moment capacity of the composite beam is 341 kip*ft, another item to be aware when using the program.

I will not touch on length, this one is pretty easy to retrieve from the RAM API and is written in the excel file at the end of the program.

Writing To Excel

At the end, we write all the info we just pulled out of our RAM model and write into excel.

Review

Hopefully this will speed up steel chord/collector design in our office. I am worried about some of the pitfalls noted above, but as long as I stay alert and pay attention non-symmetrical loadings and reviewing phi*Mn calculations, I think the benefits outweigh the cons.

Testing on Large Models

I wanted to run a speed test on a larger model to see how long this would take to run. I picked a large (6) story building and tried to retrieve information about 20 beams.

There were a total of 352 beams on the level I selected and the excel file was populated in 33 seconds for 20 beams. Not bad, way faster than having to do that by hand. The populated excel file:

If you have any requests on what I should explore next in RAM Structural API, let me know. I think we may be just scratching the surface.

RAM API - Cracked

I finally cracked into the RAM Structural System API after maybe 3 years of wanting to get into it.

The key to getting in? More powerful googling. 

Googling "RAM Structural API" yields some very uninspiring result. Usually the results pointed to the RAM Structural API documentation that looks very cryptic and appears to require C++ as coding language. A few snips from the API documentation. 

All of this looked pretty intimidating and I had about 0 desire to learn a new programming language. I put learning the RAM API on the backburner even though this is our offices daily driver for most buildings.

Marcello Sgambelluri to the Rescue

One day I happened to stumble on a post on Autodesk's forum regarding RAM API access by Marcello. I knew Macello's name and I knew this would be the ticket to getting into the API. He always does a great job of documenting his madness.

A link to the material that cracked the API.

Following this link is kinda crazy. Never in my wildest dreams would I imagine that cracking the RAM API would involve some tech with Revit, Dynamo and C#... the whole RAM API documentation is in C++!! How, why? Seriously if you have an answer to how Marcello was able to access the API with C# and not C++, let me know, there is not a single mention of C# in the documentation. Maybe all .net languages can be intertwined??

Anyways, Marcello's examples utilize C# and something called zero touch nodes in dyanmo to unlock the RAM API. The examples are quite helpful and I can't wait to dig in deeper to see what the API might unlock.

I have posted the code on github. The hardest part was not the code, but setting up all the references in the visual studio IDE and getting the code to successfully compile. After some tweaking on the settings, I was able to get a successful build. 

Upon loading the .dll (a .dll file is the compiled code as I understand it) into dynamo, you feed the file name into the "node" and the code returns the total number of stories in your RAM structural model. This is a simple example, but opens the doors to so much more.

The picture above correctly returns the number of stories in the sample RAM structural model I fed to it. HOW COOL IS THAT!

 

Moving forward, I hope to keep digging into the RAM API, exploring more of Marcello's examples and building some code of my own to automate tasks in RAM Structural. 

First Previous 2 3 4 5 6 Next Last

Sidebar

Site Info

  • Latest Comments