Posts by retug (35)

One Way Load Distribution In ETABs

I have used ETABs one way load distribution button for a long time and it works great on flat floors and "slightly sloped roof" to distribute load based on tributary area.

Today though, a coworker of mine noticed on a slightly more pitched roof, angle of about 30 degrees, the one way loading was not working as intended (could not see applied loading due to tributary area).

I built a test model to explore the odd behavior.

Slab section properties uses one way distribution:

(3) sample floors are made, each with the same load and beam restraint layout, but you can see, once the floor becomes "too sloped", the load distribution switches from a line load on the frame elements to merely point loads applied at the edge of the membrane

 

The magic slope where this seems to occur is about 20 degress from horizontal.

Hopefully this post saves someone in the future the pains of troubleshooting this odd behavior

Ray Caster Algorithm

Now that the user interface for the concrete tool is up and running, I need to start working on the concrete analysis.

The first order of business is a meshing algorithm for the concrete shape. 

I do not know exactly which direction this meshing algorithm is going to head, but every direction I look, it appears as though I will need some code to check if a given point falls within an enclosed polygon. The pretty pictures above show the code in action, if the point falls within the polygon, it is painted green, if not, red.

After some research, it appeared as though the ray caster algorithm would be the ticket to solve this problem. 

The code to check if the point falls within any given abritary polygon is shown below:

function ray_casting(point, testPoly, holePolys) {
  var n = testPoly.length;
  var count = 0
  var holeCount = 0
  var x = point[0];
  var y = point[1];
  

for(var i=0; i <n; ++i) {
    if (i == n-1) {
      var side = {
        a: {
          x: testPoly[i][0],
          y: testPoly[i][1]
        },
        b: {
          x: testPoly[0][0],
          y: testPoly[0][1]
        }
      }
        var x1 = side.a.x
        var x2 = side.b.x
        var y1 = side.a.y
        var y2 = side.b.y
        if (y < y1 != y < y2 && x < (x2-x1)*(y-y1)/ (y2-y1)+x1) {
        count +=1
      }
    }
    else {
      var side = {
        a: {
          x: testPoly[i][0],
          y: testPoly[i][1]
        },
        b: {
          x: testPoly[i+1][0],
          y: testPoly[i+1][1]
        }
      }
      var x1 = side.a.x
      var x2 = side.b.x
      var y1 = side.a.y
      var y2 = side.b.y
      if (y < y1 != y < y2 && x < (x2-x1)*(y-y1)/ (y2-y1)+x1) {
        count +=1
      }
    }
  }
  for (var holePoly of holePolys) {
    var nHole = holePoly.length;
    for(var i=0; i <nHole; ++i) {
      if (i ==nHole-1){
        var side = {
          a: {
            x: holePoly[i][0],
            y: holePoly[i][1]
          },
          b: {
            x: holePoly[0][0],
            y: holePoly[0][1]
          }
        }
        var x1 = side.a.x
        var x2 = side.b.x
        var y1 = side.a.y
        var y2 = side.b.y
        if (y < y1 != y < y2 && x < (x2-x1)*(y-y1)/ (y2-y1)+x1) {
          holeCount +=1
        }
      }
      else{
        var side = {
          a: {
            x: holePoly[i][0],
            y: holePoly[i][1]
          },
          b: {
            x: holePoly[i+1][0],
            y: holePoly[i+1][1]
          }
        }
        var x1 = side.a.x
        var x2 = side.b.x
        var y1 = side.a.y
        var y2 = side.b.y
        if (y < y1 != y < y2 && x < (x2-x1)*(y-y1)/ (y2-y1)+x1) {
          holeCount +=1
        }
      }
    }
  }
  if (count % 2 == 0 || count == 0 ) {
    return [false, count, holeCount]
  }
  else if (holeCount % 2 == 1) {
    return [false, count, holeCount]
  }
  else {
    return [true, count, holeCount]
  }
}

The algorithm is very simple, given a point, cast an inifinite line to the right. If the line crosses the polygon an even number of times, the point is outside the polygon.

Adding in some more code to test for holes and we are able to quickly tell if the point is inside or outisde the polygon with holes.

The code can be found on github.

A tease for the future:

User Interface for Concrete Design Tool

After learning the basics of threejs for the about page, I set out to develop a web based concrete design tool. A video of https://www.re-tug.com/concretedesign/  in action is below:

The Functionality

I set out to make a easy to use interface that somewhat mimics the features of my favorite structural design software packages. One of the barriers to really awesome structural engineering packages like sectionproperties is that the end user needs to know a coding language. I have some python experience and it still usually takes me a good hour of reading documentation and examples to get section properties to do what I want it to do. I am hoping this user interface will remove the programming knowledge barrier.

One feature that that I deemed mandatory was dynamic table feature. When inputting either rebar or points, I wanted to have a dynamically populated table that users could easily manipulate. The Current Selection tab of the user interface allows for modification of points and rebar dynamically. Other functions triggered by keyboard shortcuts

Keyboard Shortcuts

  • Control
    • Hold down control to add to your selections
  • Shift + R
    • This triggers the replicate function
    • Enter an X and Y coordinate
  • Delete 
    • Removes anything that is selected from the scene
    • Mac users might be out of luck at the current moment, apparently there is no delete button on mac?

 

This graphic user interface will serve as the front end for the future concrete finite element tool that I hope to develop. 

The Interesting Parts of the Code:

Code can be found on github. The javascript file at ~700 lines of code is definitely the largest file I have worked with in my coding experience. Navigating the code is super tedious! I need to go back through the code and add comments to help me better navigate and understand the code for when I come back to it.

Mouse Normalization

(If I misspeak here, please correct me in the comments below, my understanding of mouse coordinates are still quite fuzzy)

Three js sets up a scene with the 0,0 coordinates being centered in the canvas element of the webpage, while the html 0,0 coordinates are in the upper left hand portion of the webpage always and forever.

This creates some problems for my selection box, as it needs to be converted from the html coordinates to the three js coordinates. This is accomplished with the following code:

selectionBox.startPoint.set( ((event.clientX - (window.innerWidth*1/6)) /
concGui.offsetWidth)*2-1, - ( event.clientY / concGui.offsetHeight )*2+1, 0.5 );

 The user selection area on the left hand side of the webpage set to take up exactly 1/6 of the webpage and after some console logging trial and error, the following mouse coordinate normalization allowed my selection box tool to accurately select objects in the scene. 

In the lower left hand corner of 2d scene, I have added a mouse tracker. It accurately maps the 0,0 coordinate scene of the scene, but does not correctly capture the x and y coordinates of the 2d plane. I will need to update this in the future.

Making a new material for each point

When trying to implement a change of color if the point happened to fallen within the selection box, I thought that I could temporarily override the material property color. This did work, but had the adverse effect of changing all the points in the scene to the selected color.

 

This was solved by making a new material for each individual point.

Keybinds on Certain Aspects of the Webpage

My selection box event gets triggered by (3) differing events, the pointer down, pointer move and pointer up event. I needed these events to only trigger on the white space of the screen, but not on the dynamic table portion of the webpage. Initially I tried to solve this by checking if the mouse coordinates were on the left hand side of the page, but this was overly complicated.

In the gif below, you can see that I was console.logging my mouse x coordinates and the dynamic table kept resetting.

I was able to solve this problem by binding my pointer events to only the concrete gui object in the web browser. Now the pointer events only fire if they are on the white graphical user interface portion of the webpage. Sample code:

document.getElementById('concGui').addEventListener( 'pointerdown', function ( event ) {

  if (event.ctrlKey) {...

Selection Box Troubles

The dynamic table updating caused a lot of problems for the selection box tool. The code was able to visually update the "scene", but the selection box was not registering the updated position.

This was solved by deleting the old point in the scene and then recreating the updated point. I still do not know why updating the location in three js does not work, I have an unanswered question out on their forum that I hope someone will answer.

Notes on Deployment

Deployment was much nicer this go around. I am so thankful that I did not have to go through deployment hell like last time on my about page. There were still troubles, but I had already learned the important aspects of webpack and node to solve these troubles.

Webpack Development Mode vs Production Mode

When I ran webpack on the main.js file, I was surprised to see I was getting a console log error. If you remember from my last post, webpack will take your readable javascript code and covert into a single massive line of code that is optimized for web performance. Trying to debug this code is damn near impossible. I was left with a console log error of:

Uncaught DOMException: Failed to execute 'add' on 'DOMTokenList': The token provided
('[object Object]') contains HTML space characters, which are not valid in tokens. 
at new <anonymous> (.../main.js:2:470798) at .../main.js:2:470705 at
.../main.js:2:485232

I was able to piece together that the error was occurring on the second line, at character 470798, the code around this area looked like the following:

(t,e.children[n])}}(bo,yo),Do=new class{constructor(t,e)
{this.element=document.createElement("div"),this.element.classList.add(e),
this.element.style.pointerEvents="none",

These areas of the code are created by webpacks optimizations and I could not pinpoint the part of my code that was causing the web problems.

Switching the webpack.config.js settings of mode: 'production" to mode: "development" leads to a less optimized javascript file, but allows for better error messages in your console.

webpack.config.js

const path = require("path");

module.exports ={
    mode: 'production', (switch to development)
    entry: "./src/concgui.js",
    output: {
        filename: 'static/concgui/[name].js',
        path: path.join(__dirname, 'blog')
      },
}

Unfortunately, I did not take a picture of the new error message, but the console log error was quite specific to a portion of the code that I had made an error in and I was able to resolve the error quite quickly. I switched the mode back to production for deployment and the file size is about 1/2 the development mode.

Webpack For Tailwind CSS

TailwindCSS provides the nice styling for the user interface. I reached out to Celt for this one as I was struggling to get tailwindCSS incorporated in the webpage and I know Celt used bootstrap (another styling CSS package) on his local server to style up his website. I could not find an easy way to download tailwind CSS directly, so I ended up using webpack again to serve up all of the styles in used in the html to a css file. I also have to thank celt for his website examples, I did lots of snooping and inspecting. I replicated the <select> html code for the rebar drop down menu.

It did take some tinkering on my config.js file to get the tailwindcss to work, but after adding an entry point to config file, all worked well and a .css file that only contains the styles utilized on the webpage popped out from the webpack run.

Sample from the Config.js File:

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./src/conc_gui.html"],
  entry: "./src/conc_gui.html",
  theme: {
    extend: {},
  },
  plugins: [],
}

Future Work

Meshing Algorithm

The next big step for this webpage is to implement a meshing algorithm. This is where I need your help! Let me know if you have any good ideas on the best way to move forward on setting up a meshing algorithm.

Options that come to my mind:

  • Triangle - Currently used in section properties I believe
  • Gmsh - This was recommended to me by an engineer at my office. I do not know much about this, but it looks promising.
  • Threejs - Threejs meshes things, but I am not sure if it is optimized for finite element type analysis? More research to be done
  • Insert your opinion here
  • Make my own?
Materials

I also want to add a way to input concrete and rebar stress strain material curves. Moving forward, I will just test with 4000psi concrete and 60ksi rebar stress strain curves, but in the future I want this be a user input. 

Dynamic Popup Forms

My current replicate function uses (2) alert popups to prompt the user for x and y values. It would be nice to have a single form pop up. I know this can be done, but may require another javascript package to accomplish. Leave a comment if you have any suggestions on dynamic popup forms in javascript.

Resolve Dead Page

The page is currently a dead page, aka, there is no top bar to get back to the main website. I will have to update this in the future. Unfortunately, this means I will also have to revisit my mouse normalization portion of the code as well. 

Updating the mouse coordinates to match the scene

The x and y coordinates currently output into the document are not correct. I need to explore mouse normalize and the raycaster function of threejs to update this I believe.

Please take the user interface for a spin, let me know any areas where it could be improved or any bugs you may encounter!

 

Minimum Joist Bearing Lengths - Revit and Dynamo

A co-worker of mine asked if I could find a way to check min. joist bearing lengths after being burned on a recent project where they had a few beams that worked for strength and deflection, but did not match SJI's minimum flange dimensions for bearing.

Tedious, easy problems like this is where dynamo really shines, lets dig into the code to make this happen.

Step 1 - Collect All Bar Joists

The dynamo graph located on my github works by pulling all the K-Series Joists and LH-Series Joists in the current 3d view.

If you have never seen a dynamo graph before, above is an image of the code used to make joist bearing length checker. I believe the term is visual programming, where you connect code bits with wires and it creates a nice visual programming look.

Please note when using the tool, it will only work based on the elements in your current view (I usually run this dynamo graph in a 3d view). 

This bit of code gathers all of the K  and LH Series Joists in the project.

import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager

doc = DocumentManager.Instance.CurrentDBDocument

category = UnwrapElement(IN[0])
joists = IN[1]
elements = FilteredElementCollector(doc, doc.ActiveView.Id)\
        .OfCategoryId(category.Id)\
        .WhereElementIsNotElementType()

filtered = []
for e in elements:
    type_id = e.GetTypeId()
    element_type = doc.GetElement(type_id)
    family = element_type.Family
    name = family.Name
    if joists[1] in name or joists[0] in name:
        filtered.append(e)

OUT = filtered
Step 2 - Check for Locations where Joist Bearing Should be checked.

Above identifies the locations where the program might want to check joist bearing, but we want to reduce the number of points the program will check if we have (2) joists bearing on the same girder.

The image below will be used in the post, note that there are  (4) total joists in the sample, the top two joists being K Series Joists, with an offset in plane, and the bottom two joists being LH Series joist, parallel to each other.

The first part of the code will identify (2) unique points for each end of each bar joist, giving a total of (8) total points. In this example though, I want to filter out the location where both LH-series joists are parallel, as this will be a location where the program should spit out only (1) unique point due the joist occurring at the same locatoin, requiring double the minimum bearing length:

This bit of code filters out points if they are close to each other:

pnts = IN[0]
tol = IN[1]
# Place your code below this line
def distance(point1,point2) :
    x1 = point1.X
    y1 = point1.Y
    z1 = point1.Z
    x2 = point2.X
    y2 = point2.Y
    z2 = point2.Z
    dist = ((x2-x1)**2+(y2-y1)**2+(z2-z1)**2)**0.5
    return dist
uni_pnts = []
temp_pnts = pnts
while len(temp_pnts) >= 1:
    if len(temp_pnts) != 1:
        pnt_init = temp_pnts[0]
        rem_pnts = temp_pnts[1:]
        j = 0
        on_off = 1
        while on_off >= 0.5:
            delta = distance(pnt_init,rem_pnts[j])
            if delta <= tol :
                uni_pnts.append(pnt_init)
                temp_pnts.pop(j+1)
                temp_pnts.pop(0)
                on_off = 0
            elif j == len(rem_pnts)-1:
                uni_pnts.append(pnt_init)
                temp_pnts.pop(0)
                on_off = 0
            else:
                j+=1
    else:
        uni_pnts.append(temp_pnts[0])
        temp_pnts.pop(0)
# Assign your output to the OUT variable.
OUT = uni_pnts

Given that the program will run for an unknown amount times, we need to utilize a while loop and remove items from our list as they go, python's .pop() function is quite useful to do this.

In our sample above, it takes the number of points from (8) to (7) as it recongnizes the (2) LH series joists being co-planar. You can see in my poor sketch below, the program effectively realizes the red points of 6 and 7 are close enough and conisders them to be a double bar joist location now.

Step 3 - Make A bounding box at each of the purple nodes

A bounding box in dynamo is quite useful for checking if geometries intersect with other geometries, in our case, we need to check if the bounding box at the purple nodes crosses any beams. Bounding boxes in dynamo show up in this orange color by default. See below

In order to do this, we use dynamo list lacing and change it to longest to check all purple bounding boxes and see if they cross any of the wide flange framing. If they do, we need to check our SJI minimum bearing lengths verse the wide flange width. In the snip below, each of the (7) purples nodes identified above are checked to see if they cross any wide flange beam. In our test model, there are only (3) beams, so 7*3 = 21 true-false results are returned. Where a true occurs, a joist is supported by the corresponding wide flange beam.

This is one area that I would like to revisit, in larger models, I am sure this task that gets computationally expensive, and quickly. If anyone has any ideas on a cool algorithm to tackle this part of the program, please let me know. I plan to explore this further in the future.

Part 4 - Testing Beam Widths vs. SJI Minimums

Now that we have idenified where double and single joist connections occur, in addition to the beam that supports the specific joist, we can now check SJI Bearing lengths, 4" minimum for a K Series joist and 6" minimum for an LH Series joist.

This is done with a long if statement shown below:

# The inputs to this node will be stored as a list in the IN variables.
joist_types = IN[0]
beam_widths = IN[1]
pnts = IN[2]

pnts_failing = []
for ind, (joist,beam_width) in enumerate(zip(joist_types,beam_widths)):
   if len(joist)==1:
       if "K" in joist[0]: #SJI Min Bearing for K Series is 4"
           min_seat = 0.3333 
           if beam_width[0] <= min_seat:
               pnts_failing.append(pnts[ind])
           else:
               pass
       else:
           min_seat = 0.5 #SJI Min Bearing for LH Series is 6"
           if beam_width[0] <= min_seat:
               pnts_failing.append(pnts[ind])
           else:
               pass
   else:
       if "LH" in joist[0] or "LH" in joist[1]: #SJI Min Bearing for (2) LH Series is 12"
           min_seat = 1
           if beam_width[0] <= min_seat:
               pnts_failing.append(pnts[ind])
           else:
               pass
       else:
           min_seat = 0.67 #SJI Min Bearing for (2) K Series is 8"
           if beam_width[0] <= min_seat:
               pnts_failing.append(pnts[ind])
           else:
               pass

Part 5 - Visualize the Results

The program places red orbs at locations where minimum joist bearing is not met. In our example the central girder is a W18x35, bf = 6". A singular red orb is placed at the LH-Series location as the LH location would need 12" of flange width to meet SJI requirements.

A nice pop up window stating how many joists did not have adequate joist bearing is also displayed.

Hopefully this example demonstrates a useful way to utilize dynamo. Let me know how you use dynamo, it is a great way to automate boring, time consuming tasks that need be completed.

About Page Eye Candy - Javascript and Web Dev Misery

With a desire to update my "about" page, I set out to kill two birds with one stone, a fresh new about page and learning 3d graphics in the browser. https://www.re-tug.com/about/ is built with three.js, a package built within Javascript to make 3d graphics. 

About threejs

I followed a tutorial on youtube to create the threejs scene in the background. My code to make the pulsating mesh can be found here. The about page turned out really cool, click and drag on the background and you should be able to rotate the pulsating mesh.

Learning both C# and javascript recently has lead to a lot of mistakes and lost time, but it has been a enjoyable process. Learning how to debug with console.log has become natural at this point.

Trying to tweak the code in the youtube link to my liking lead to some interesting errors, below was my attempt to manually recreate the wireframe pulsating:

Deployment Hell

Through building this website, I have learned that merely getting your code up and running on a development server is usually about 1/4 of the battle. After getting through the tutorial I set out to push this code up to the internet for everyone to see. (I am mostly writing this part so I have something to reference in the future when I am going through deployment pains)

Attempt #1 - Using Script Tags to Load ThreeJS

After some research, it seemed like the easiest and quickest way to get threejs, orbit controls and gsap (the packages used to make the graphics) was through script tags in htlm.

I had something that looked like the following:

<script type="importmap">
{
"imports": {
     "three" : "https://unpkg.com/three@0.126.1/build/three.module.js",
      "orbitcontrols" :      "https://unpkg.com/three@0.126.1/examples/jsm/controls/OrbitControls.js",
"datgui" : "https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.9/dat.gui.min.js"
"gsap" : "https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js"
}
}
</script>

<script type="module" src="{% static 'blog/threeBackground.js' %}"></script>

This method relies on cdns (content delivery networks) to deliver the code that is used for threejs, orbit controls etc. I was successfully able to deploy my code with this method, but the code did not work on firefox nor any of the iOS web browsers. 

With my whole family and friends using apple products, I had to find a better to make this happen. Apparently script tags are going to be outdated in the future and I think this is why it did not work on firefox and iOS web browsers.

I would get console errors like the following:

At this point I had maybe sunk 20 hours of work into getting this eye candy into the website.

Attempt #2 - Using node.js and NPM

I'll be honest, I still have no idea what node.js and NPM (node package manager) are, but they seemed like the solution to my iOS problems.

My best understanding of these, node.js is a way to write javascript outside of the web browser and NPM is similar to pythons pip command, a way to install packages. 

Using node.js and npm, I could see threejs and other modules being downloaded into a folder called node_modules in my file directory. Naively, I thought this was the ticket, I could just reference these files on my production machiene on heroku. This did not work and I spent way too long going down this route.

Attempt #3 - Using Webpack, A Bundler

Webpack is another javascript package that converts the javascript file I linked on my github into a singular javascript file that embeds all of the threejs code and other javascript libraries referenced and places it into one singular file. A sample of the file created by using webpack is shown below:

if(t.applyMatrix3(this.matrix),t.x<0||t.x>1)switch(this.wrapS){case r:t.x=t.x-Math.floor(t.x);break;case a:t.x=t.x<0?0:1;break;case
 s:1===Math.abs(Math.floor(t.x)%2)?t.x=Math.ceil(t.x)-t.x:t.x=t.x-Math.floor(t.x)}if(t.y<0||t.y>1)switch(this.wrapT){case r:t.y=t.y-Math.floor(t.y);break;case a:t.y=t.y<0?0:1;break;case 
s:1===Math.abs(Math.floor(t.y)%2)?t.y=Math.ceil(t.y)-t.y:t.y=t.y-Math.floor(t.y)}return this.flip

Lots of gibberish to my eyes...

The command to make this all encompassing file on my development server was: npm run build

This file was the ticket to making the "about" page work on all devices, it no longer requires the html code to have script tags and nor the use of CDNs for the packages like three.js

I had to upload this static file to heroku and serve it up and the rest is history. All total, I think I spent about 10 hours on the actual three.js tutorial part and 40 hours on the deployment aspect of the "about" page. I would like to thank and apologize to my wife for her having to listen me scream, laugh and cry during deployment of this page.

DEPLOYMENT IS TERRIBLE!

Notes on heroku deployment:

  • Disable collectstatic by default, have to run:
    • heroku run python manage.py collectstatic
  • Can ignore double static file reference on deployment
    • heroku defaults to ....\my_website\blog\static
  • added in gitignore file node_modules
  • Within procfile added:
    • web: gunicorn my_website.wsgi npm start

 

First Previous 3 4 5 6 7 Next Last

Sidebar

Site Info

  • Latest Comments