Posts by retug (36)

RAM to Revit - First Update V1.0.1

The beta release of the RAM to Revit tool has gone well. I have a few people using the tool in practice and have heard some good feedback so far. I am actively trying to maintain this and make it better, so if you have any requests, please reach out. I will try to get your feature added.

Big Update #1 - Calculate X Y and Rotation Function Upgrade

The calculate X Y and rotation function has been greatly improved and does a much better job at trying to guestimate the rotational and offset parameters between your RAM coordinate system and your Revit coordinate system. My error, I had not converted the intersection point of the RAM gridlines into the Revit coordinate system which led to some really bad estimates on X and Y offsets. This has been revised now and should work in a majority of cases.

Update #2 - Mouse Tracking of Coordinate in the Mapping Plane

Should the "Calculate X Y and Rotation" button not do a good job of estimating the X Y and rotation parameters, I have added mouse coordinates in the lower left of the mapping plane. This should allow for easy hand calculations of the X and Y offset parameters should you not be aligned.

Update #3 - Additional Beam Info - RAM Beam #, Camber, LRFD Start and End Reaction

I had a user ask for some additional information the datagrid, RAM beam #, Camber, and beam end shear reactions. Eventually they want to map the start and end reactions into the revit beam for quicker documentation. Side note, I feel sorry for you east coast engineers, sometimes it's just easier to design the simple shear tab connections. 

I am working with this user to see how they like to schedule their end reactions and hope to have a way to write the start and end reaction parameter into the Revit beam soon. More to come on this. The reactions that is recorded is an LFRD factored load reaction that only looks at RAM Steel Beam gravity results. It checks both 1.4DL and 1.2DL + 1.6LL and nothing else. Use with caution.

I also have one user with a weird bug that will not allow them to access the RAM structural API.

I am hoping that this new update will give me a little more insight into why it is not working on their computer. I finally learned how to generate a meaningful error message using the "try" "catch" keywords in C Sharp. Hopefully the exception that is thrown is more insightful than my previous error message.

private void getRAMResults_Click(object sender, RoutedEventArgs e)
    if (ramFilePath == null)
        MessageBox.Show($"You must select a RAM Structural model first, .rss", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    //List<string> storyNames = RAMInfo.GET_STORY_NAMES(ramFilePath);
        List<string> storyNames = RAMInfo.GET_STORY_NAMES(ramFilePath);
        ramFloorComboBox.ItemsSource = storyNames;
    catch (Exception ex)
        MessageBox.Show($"An error occurred: {ex.Message}\n\nLikely errors include:\n\n1. RAM Structural is not installed on your computer\n\n2. Your RAM structural file is corrupt, check for .USR file \n\n3. Clear your working file directory, usually located here C:\\ProgramData\\Bentley\\Engineering\\RAM Structural System\\Working" , "Error", MessageBoxButton.OK, MessageBoxImage.Error);

Section Cut Tool Upgrades

The Section Cut Tool got a big upgrade:

  • Ability to plot multiple load steps for a load case that contains multiple directions of loading.
    • Seismic X, Seismic X +e, etc.
    • Currently only supports ASCE 7-16 and ASCE 7-22 seismic loads. If you use this tool and want your specific code included. Leave a comment here or github. Should be a quick enough update.
  • Interactive plots with the data grid below (highlight a row in the datagrid and the corresponding plots will have a gold dot filled in).
  • Tabular data now outputs the length of the section cut.
  • Youtube video for install
    • Hearing yourself on a youtube video is just as bad as one might think it is, but hopefully this will help people get this up and running on their computers.
  • Youtube video for how to use the tool

The code has been updated on github and the install files have been uploaded as well. Give it a spin.


Coding this involved much more code than what I ever could have imagined. I should now by now that I should take my time estimates to achieve a task and multiply by about 5.

ETABs API - Database Tables, Not As Good As I Once Thought

Database tables seem to be the way the ETABs API is pushing coders to move to access data. This is good and bad from my beginners perspective. The good, most anything is available programatically. The bad, the API response is this garbled, unorganized mess of data. Take for example the section cut results.

I used to be able to call this function to retrieve section cut results before upgrading the tool to pull differing load steps.

This function was nice because it explicitly tells you what it will return. If you happen to throw in a load case with multiple load steps into this function, it will not work for some reason? I was able to get around this by calling the database tables method to return the section cut database table.

            string TableKey3 = "Section Cut Forces - Analysis";
            string[] FieldKeyList = null;
            string GroupName = "All";
            int TableVersion = 1;
            string[] FieldKeysIncluded2 = null;
            int NumRecords = 0;
            string[] TableData2 = null;

            _SapModel.DatabaseTables.GetTableForDisplayArray(TableKey3, ref FieldKeyList, GroupName, ref TableVersion, ref FieldKeysIncluded2, ref NumRecords, ref TableData2);
            listSectionResults = new List<SectionResults>();

            //this is the multiple load steps
            if (FieldKeysIncluded2.Contains("StepNumber"))
                //This list will contain all of the results from one load case with multiplesteps                
                List<int> indices = new List<int>();
                // Find indices of "EqAll" in TableData2
                int loadSteps = listBoxLoadSteps.Items.Count;
                indices = SectionCutResults.FindIndices(TableData2, LoadCaseComBox.SelectedItem.ToString());

                //int step = indices.Count() / loadSteps;
                // Grab specific items by index using LINQ
                var specificItems = indices.Where((item, index2) => index2 % loadSteps == 0);
                List<string> OrderedSeismicList = new List<string>();
                OrderedSeismicList = LoadCaseList[index].GetOrderedSeismicDirections();

                //run from 0 to load steps
                for (int i  = 0; i < loadSteps; i++)
                    List<Double> F1loop = new List<double>();
                    List<Double> F2loop = new List<double>();
                    List<Double> F3loop = new List<double>();
                    List<Double> M1loop = new List<double>();
                    List<Double> M2loop = new List<double>();
                    List<Double> M3loop = new List<double>();
                    SectionResults loadCaseResults = new SectionResults();
                    //AREA OF INTEREST

                    //note index is the corresponding loadcase selected in the LoadCaseList
                    //This is Eq X, Eq X e+ etc.
                    loadCaseResults.LoadDirection = OrderedSeismicList[i];
                    //We generatate all of the results for an individual load case, load step in this loop here.
                    foreach (int sectionResult in specificItems)
                        F1loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 4]));
                        F2loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 5]));
                        F3loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 6]));
                        M1loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 7]));
                        M2loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 8]));
                        M3loop.Add(Convert.ToDouble(TableData2[sectionResult + i * 14 + 9]));
                    loadCaseResults.F1 = F1loop.ToArray();
                    loadCaseResults.F2 = F2loop.ToArray();
                    loadCaseResults.F3 = F3loop.ToArray();
                    loadCaseResults.M1 = M1loop.ToArray();
                    loadCaseResults.M2 = M2loop.ToArray();
                    loadCaseResults.M3 = M3loop.ToArray();

The problem with this? All the data that is returned is in this massive list/array. I had to write a lot of code to pick apart this massive response, a big bummer from the super clean AnalysisResults.SectionCutAnalysis method used before.

Pro tip, use the view command in visual studio for a variable explorer! So sad I did not know about this until about a week ago. A huge time saver!

The same issue arose with trying to find out what sort of seismic load cases had been generated. The ETABs API developers seem to have made a cognizant decision to move away from specific methods and move towards the database table responses for grabbing all data.

There used to be an autoseismic interface that appeared to work back in the day

but now, these functions do not return anything meaningful. I had to go to database tables to retrieve this info, more unorganized data to sift through and organize myself.

 if (CodeName == "ASCE 7-16")
     string myTableKey = "Load Pattern Definitions - Auto Seismic - ASCE 7-16";
     string[] FieldKeyList = null;
     string GroupName = "";
     int TableVersion = 0;
     string[] FieldsKeysIncluded = null;
     int NumberRecords = 0;
     string[] TableData = null;
     _SapModel.DatabaseTables.GetTableForDisplayArray(myTableKey, ref FieldKeyList, GroupName, ref TableVersion, ref FieldsKeysIncluded, ref NumberRecords, ref TableData);

Another pain point, why do the database tables for auto seismic not correspond to the section cut force analysis tables??? More coding required to dig through all this.

Lastly on the coding side, the plots are now interactive if you click on the row in the datagrid.

The interactive plots with highlighting gold selected rows was achieved through this item in live charts called "mappers". After much coding and interactions with chatgpt, I was able to get a mapper to work with the omitted selected row in the datagrid.

In addition, I attempted to add some code to check if the analysis had already run and skip the tedious rerun if required. I was able to use this function, _SapModel.Analyze.GetCaseStatus(ref NumberItems, ref CaseName, ref status);

to check if the load case had run. Unfortunately though, the section cut definition table is not available if the model is locked and I have a question into CSI regarding this. It seems like you should be able to add section cuts while the model is locked. More on this here.

Uses with the tool

Now that the tool can plot diaphragm shears for code mandated mass eccentricities, I want to run some sample cases to test some of my old design philosophies, for example, due to computational time constraints, I would often design my diaphragm shear DCRs to be a max of 90% to account for the +e/-e mass eccentricities. From my quick tests, this may not have been as conservative as I thought it was.


RAM to RevitĀ® - R2R - (part 2)

Link to trial the tool.

Above is the finished beta release R2R tool. It seems to work for large projects and should be a huge time saver and QA/QC tool as compared to manually checking RAM beams to Revit beams.

If you want to be a beta tester for this tool, please either leave a comment/reach out to me. I would be happy to share with you. Please be aware, the tool is still unstable in many conditions, be sure to save a local RAM model and Revit that you do not care about.

How It Works - Screen 1

Screen 1 is where you mostly focus on selection, coordinate system transformation and selection of RAM beams and Revit beams to be mapped. 

The Inputs

Select RAM File

This one is self explanatory, select the .rss file you want to inspect. Be sure that ALL RAM products are closed before doing this, the program will crash if you have RAM open. I need to add some code to catch this error and ask the user to close RAM.

Gather RAM Levels

This button gathers the RAM levels in the project from the selected RAM model. NOTE: the grids will only be imported if you set a max and min in the RAM grid definition.

Select Level

This button will load in the RAM grids into the canvas below and will select the RAM beams at that level to be compared to Revit.

Gather Revit Grids

Button will plot all Revit grids in the Revit canvas. 

Select Revit Beams

Button prompts the user to select revit beams to map, ensure your mapping window for selection is already in your current view. The revit project becomes "locked" when starting up the addin and you are not able to interact with the revit model once you have started up the app. I will work on fixing this in the future. From here, window over any objects you want to map, the selection is filtered down to only select the beams.

With the grids in the mapping window, you can select (2) corresponding grids from the revit model and the ram model and click "Calc X Y and Rotation". This function is rough and not perfect but it attempts to calculate the X Y and Rotation parameter between the RAM and Revit model. Lots of times our RAM models do not align with our Revit models and this function attempts to automate this once manual step.

With the X Y and rotation parameters set, you can save the file path of your .rss file and mapping parameters to .json file. 

Once you are happy with your mapping properties, be sure to hit save so you do not have to do this again. Clicking import brings in these settings and you can mostly skip the import screen save a few buttons:

  • RAM level, select your ram level and beams
  • Selecting your Revit beams.

Sample of the R2R_Json file created:

How it Works - Screen 2

Screen two is pretty simple, you will be greeted with your ram and revit beams being plotted on the canvas in front of you. You can check the RAM or Revit beams on or off as well as control their transparency. Then, click on map the beams and the mapping algorithm will go to town checking all beams in the models. 

A datagrid is populated on the left and discrepancies are highlighted in yellow in the datagrid. You can select the data grid row and it will highlight the selected beam in the mapping panel. The colors are a bit gross right now, but I will work to make the UI a bit more manageable moving forward.

I plan on making a youtube channel to record a video going over all the inputs, typing this all out is tedious and a video would likely be much easier for me and the end user.

The Code

I am pleasantly surprised by how quickly the mapping algorithm works. On the screen at the beginning of the post the algorithm was able to make it through ~1000 revit and ram beams in under 1 second!!! Crazy fast. The algorithm works by taking one RAM beam and then comparing its end point to every Revit beam in the project until the beam is either mapped into the Revit space or is does not find a mate. Let's try to do some quick mental math on the number of calculations performed in this process.

  • 1000 RAM beams, call a distance function on each end point of each Revit beam, assume that a match is found at about the half distance of the Revit Beam list.

  • The first beam in this list would run the distance function 500*2 times = 1000 times, next beam would then 499*2 times = 998 times
  • I thought this was 500!*2, but this seems likely impossible for even a computer to crunch in ~1 second. Google can't even give me an awesome for something over 100! Can someone smarter at math point out where I am wrong? (Edit 2024-01-08, I found my problem, it is the summation of 500*2, not 500!, 125250*2 = 250,500 calculations in ~1 second.)

Developing in C Sharp is starting to become more and more natural, I am really liking the experience now that I am more knowledgeable. At one point, I really liked Python due to it's dynamically typed nature, but type hints in the Revit API are so valuable to know what a function expects to act on. Another fun one, while you are in the visual studio debugger, you can actually make changes to your code while you are debugging and see how your changes affect the code. I had to do this a bunch in my mapping algorithm implementation, ever so slightly tweaking the code as I go. In the code below, tweaking startPoint.Y +/- was really cool while debugging.

As always, ChatGPT is truly a game changer for coding. This project was developed sooooo much quicker becuase of ChatGPT. One small example, in order to make my rows highlight in yellow if the beams between revit and ram did not match seemed like quite a difficult task. XAML does not have a does not equal expression, !=, similiar to that of python/C#, so you have a to write a "Converter" class for XAML and C #. 

I asked chatGPT to do this for me and the code worked perfectly.


I have heard a few comments asking how this tool is different from the RAM ISM connection tool. Given that I have not used the RAM ISM connection tool, I cannot answer this. If you have used the connection tool, can you let me know if R2R is the same, does a different task, worse or better the ISM Digital twin product that Bentley seems to offer. 

The Future


I am exploring a few different ways to get this tool distributed to a wider mass of structural engineers. If you have experience in dealing with Autodesk, please reach out to me, I would love to pick your brain on this topic.

Work on all levels in the RAM model at once? Expand to check camber, studs, etc.

This would be super cool, maybe there would be a way to check all levels at once with this tool. The tool could be pretty easily expanded to check camber studs, beam end reactions etc. The possibilities are endless, if you have any requests, please let me know. Hopefully this tool allows structural engineers to focus on the more important items in design and less on back checks!.

Fix pan, zoom, other graphics issues, clean up code, focus on DRY coding

Dry coding, do not repeat yourself, was not implemented in this code. I have (3) differing pan zoom and mouse over functions for the RAM and Revit canvas on screen 1, and another pan zoom and mouse over function for screen 2. This leads to lots of repetition and chances for mistakes. I would like to come back to this and make the code more DRY. In addition, the pan function only works if you select an element in the canvas, like a line or text. If you select the big empty white space, the pan function does not work? I need to dig into why. The zoom function in the canvas's are also super whack, with zooming only partially work and zoom into weird spots, not centered on the current mouse position. 

Next steps, I look forward to supporting this product and incorporating any feedback you may have. Again, leave a comment/message me if you want to test the tool out or want additional features. 

For the next part of the site, I want to focus on getting back to structural engineering and less coding. Some examples on non-linear concrete shearwalls, dynamic analysis, rubber isolators and all of that fun stuff. I am excited to get away from coding for a bit.

I also have an itch to dig into machine learning/AI, what is it besides these fun buzz words we hear all the time? I want to explore pytorch/tensorflow and really learn what it means to train a model. How do I create the input nodes for a neural network? I still have no idea how this could relate to structural engineering, but it would be fun to dig and explore the space. 



SAP2000 API Example

This blog is finally starting to pay off, people are emailing with programming questions and problems. Making programming a little more accessible has always been the goal with the site. Programming can be intimidating to get started!

Foad Kondori emailed me awhile back trying to make a SAP2000 plugin to access frame reactions in SAP2000. I had never written a plugin for SAP2000, but I had written some python code awhile back to access ETABs frame force reactions in Python. I was able to send him my code and he was able to do the rest of the magic to translate the python code to C#. 

The code he wrote is really cool and I appreciate that he allowed me to share this code on github and the blog. Sample shown below retrieving selected frame object type and name

There was one small bug he had in his program and I was able to trouble shoot the culprit, moving the data source out of the for loop did the trick.

His example also provided me some insight on better ways to manage if the SAP/ETABs model has run. 

 bool isModelRunning = _SapModel.GetModelIsLocked();
            if (isModelRunning)

                //get Frame Force

                DialogResult result_2 = MessageBox.Show("the model is not run, Do you want Run the model ?"
, "Run Analysis", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

                if (result_2 == DialogResult.Yes)

These are things I need to implement in the section cutter tool to prevent time consuming re-runs.

Also, I need to dig in and understand why the section cutter ETABs plugin does not work in SAP2000. The APIs between the two program look very similar, but the section cutter plugin does not work in SAP 2000. Foad's example will be a good place to start when trying to troubleshoot.

To CSI, can we improve the SAP OAPi documentation? It only appears to be written for VBA, maybe it could be updated to match that of ETABs, with samples provided for multiple languages?

Feel free to reach out anytime if you are running into programming trouble, I'll try to help if I have the time.

RAM to Revit (R2R) Part 1

The Tool - Download

For a long time I’ve wanted a quick way to spot check if structural beams in our analysis models like RAM or ETABs matched our Revit model. The gif above shows the beginning of bringing this idea to life. The tool will be a plugin for Revit that will allow users to import their RAM structural model into an in-between file that will interact with RAM and Revit.

I'm a little sad that I am not developing this tool for ETABs, as this is my daily structural analysis driver, but our office is much more of RAM office than ETABs office. Gotta do what the people want and will (hopefully) use.

How It Works

The initial screen is split between a RAM Structural side (left) and a Revit side (right). After pointing the program to the location of the RAM SS model, the user will collect the levels and select the level of interest in the combo box. From here, the program will gather the structural grids in the RAM SS model and plot them onto the RAM canvas. A similar process occurs on the Revit side to plot all of the grids in the Revit model.

From here, the user will be promoted to select two interesting gridlines that will share the same coordinates in both the RAM model and Revit model. This intersection will be use in future to generate the global coordinate system for the project as a whole, allowing for the RAM and Revit model to have differing coordinate systems and still map into each other.

Next Steps

The mapping algorithm. The way the project is currently constructed, the program will determine the x and y offset by calculating the intersection point of the selected RAM and Revit grids.

From here, I will work on a function to determine the rotation parameter. This might be a bit tricky to determine, given that a rotation parameter could either be positive or negative, I might prompt the user to select the rotation parameter.

With the x, y and rotation parameters set, I can set up the local coordinate system for the RAM beams and map them in the global coordinate system of the revit project.

Fun Parts of the Coding Experience

It’s funny how many pieces of my previous coding experience came into play making this.

  • Canvas and graphics
    • A lot of the lessons I learned in javascript and threejs from the concrete design tool were able to be applied to the plotting performed in this script.
    • Do not forget that the default canvas is coordinates have the y coordinate being negative. This threw me for a loop for a bit trying to trouble shoot why the grid plots were always upside down
  • Intricate point class
    • The r2rPoint class is on the upper end of my ability to understand classes in C#. There are some very complicated properties and methods used in the class and it is a big improvement from the point class utilized in the section cutter tool. It allows the point to be defined in either the global (GCS) or local coordinate system (LCS)
    • This came in handy when pulling grid data out RAM structural due to the grid system definition. Essentially each grid system in RAM structural has its own local coordinate system that the r2rPoint class can handle quite elegantly. The way a point is defined as global or local is through the isGlobal Boolean.
    //Initialize the point class in either the global or local coordinate system thru isGlobal Flag

        public r2rPoint(double X, double Y, double Z)
            this.X = X;
            this.Y = Y;
            this.Z = Z;
            isGlobal = true;

        public r2rPoint(double U, double V, double W, bool isGlobal = false)
            this.U = U;
            this.V = V;
            this.W = W;
            this.isGlobal = isGlobal;

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

            //the class will now have new attribute of local coordinates point.LocalCoords[0] = the X local coordinate system
            LocalCoords = new List<double>() { gcs.R_Inv[0, 0] * part1[0] + gcs.R_Inv[0, 1] * part1[1] + gcs.R_Inv[0, 2] * part1[2] ,
            gcs.R_Inv[1, 0] * part1[0] + gcs.R_Inv[1, 1] * part1[1] + gcs.R_Inv[1, 2] * part1[2],
            gcs.R_Inv[2, 0] * part1[0] + gcs.R_Inv[2, 1] * part1[1] + gcs.R_Inv[2, 2] * part1[2]};

            this.U = LocalCoords[0];
            this.V = LocalCoords[1];
            this.W = LocalCoords[2];

        public void Convert_To_Global(GlobalCoordinateSystem gcs)
            //this is the ref point
            double[] part1 = new double[] { U, V, W };

            //the class will now have new attribute of local coordinates point.LocalCoords[0] = the X local coordinate system
            GlobalCoords = new List<double>() { (gcs.R[0, 0] * U + gcs.R[0, 1] * V + gcs.R[0, 2] * W) + gcs.RefPnt[0],
            (gcs.R[1, 0] * U + gcs.R[1, 1] * V + gcs.R[1, 2] * W) + gcs.RefPnt[1],
            (gcs.R[2, 0] * U + gcs.R[2, 1] * V + gcs.R[2, 2] * W) + 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]};

            this.X = GlobalCoords[0];
            this.Y = GlobalCoords[1];
            this.Z = GlobalCoords[2];

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string propertyName)
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

Each grid system in RAM structural has it's own local corodinate system:

  • Xaml and Html Similarities
    • This is the first time I have used XAML and WPF (Note, someday I dream of experiencing less "first"s in programming. The programming world seems so large and ever expanding compared to that of structural engineering where it is the same old stuff day in and day). Xaml looks very similar to Html, Maybe Xaml stole some ideas from Html? </> formats are familiar from brief experience in web development and getting this website up and running. It did not take too long to make the Xaml work, which I attribute to having some background in Html. 
      • I learned from some previous coworkers that Xaml and WPF is a more "modern" approach to UI development compared to winforms which was used to make the sectioncutter ui. Personally, I find winforms super easy to get up and running, but for more complicated projects like this one, maybe xalm and WPF will pay off.
  • ChatGPT Skills
    • I am continually amazed by ChatGPT and it’s ability to get thing up and running quickly
    • 90% of my canvas events were written by ChatGPT and it’s really impressive how quickly ChatGPT was able to take me from an idea to a functional example, allowing me to fill in the blanks
    • I originally went down the route of using LiveCharts2 to do the interactive plotting, which I wasted a lot of time exploring. ChatGPT could not help me due to the fact that LiveCharts2 was not released until 2022. ChatGPT was really good at making up stuff that used to work in the original LiveCharts and that would not work in LiveCharts2. I eventually gave up and went to the old fanshioned canvas to handle events and plotting needs.
    • I wrote the concrete design tool before ChatGPT and I would estimate that it took me about 3 solid weeks of coding and internet forum conversations to get a workable zoom, pan and selection function to work with the canvas. ChatGPT was able to write these functions in the better part of a 20 min interaction with it. Super impressive.
    • I find myself having to thank the robot overlord all the time for the work that it does for me : ), I am not sure if others do this as well.
Me: I get an error that Ellipse does not contain a definition for child

ChatGPT: I apologize for the confusion. My previous response contained incorrect information. Ellipse does not have a direct Child property to add text inside it. Instead, you can achieve this by placing both the Ellipse and TextBlock inside a container like a Grid. Here's how you can do it:

Copy code
private void AddCircleWithText(double x, double y, string text)
    Grid container = new Grid();

    Ellipse circle = new Ellipse
        Width = 10, // Diameter = 2 * Radius
        Height = 10, // Diameter = 2 * Radius
        Fill = Brushes.Red,
        Stroke = Brushes.Black,
        StrokeThickness = 1,

    TextBlock textBlock = new TextBlock
        Text = text,
        Foreground = Brushes.White,
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center


    // Position the container at (x, y)
    Canvas.SetLeft(container, x - 5); // Adjust for the radius
    Canvas.SetTop(container, y - 5); // Adjust for the radius


Now, each circle and text combination is enclosed in a Grid, which allows you to display text inside the circle on the canvas. You can use the AddCircleWithText function with the (x, y) coordinates for the center of the circle and the desired text to place inside it.

Me: This looks great thank you! How can I make it so that the elements in the canvas are larger on my screen via scrolling in and out?

Another oddity about the code, when accessing the RAM structural API, I needed to add a reference to the Microfsoft.CSharp .dll. I had never had to do this before in my previous experience following Marcello’s examples, but the binding functions and casting functions would not work without import the CSharp reference. After some confusion and googling, I was able to resolve this error.

Please note that this code is not DRY (do not repeat yourself), I would like to come back in the future and make it more DRY, many of the canvas functions are duplicated, one with ramZoom or revitZoom. I bet there is a way to make this work without repetitive canvas events.

Future Work

  • Keep going on point mapping algorithm
  • Add a save and read functionality to store the mapping parameters
    • I have not done save and read before in my programming experience so this should be a good learning experience.
  • Address radial grids
    • Both RAM and Revit
  • Address that RAM grids only work with a max and min parameter set.
  • Add zoom to fit function
    • Right now the grids are not centered in the canvas. Maybe add a double middle mouse button click to zoom extents.

First Previous 1 2 3 4 Next Last


Site Info

  • Latest Comments