This post is a quick tutorial for preparing geometry in SolidWorks for meshing with OpenFOAM's snappyHexMesh. Note that these tips are mainly for external flows, but should generally carry over to internal geometries.

1. Clean it up

This step is common for all types of simulations. Unnecessary details should be removed from the CAD model such that the behavior can be simulated with minimal geometric complexity—use your judgement. For example, if simulating aerodynamics around a car body, bolt heads may be unnecessary. If working with an assembly, I prefer to save as single part first. Small features can then be removed, and the part can be simplified such that it is a single watertight object.

2. Get the coordinate system right

Make sure the SolidWorks model is located where you want it to be in the simulation, i.e., it is oriented properly with respect to the coordinate system of the OpenFOAM mesh. SolidWorks defines the positive z-direction normal to the front plane, whereas I prefer it normal to the top. Instead of using move/copy features, you can alternatively create a new coordinate system to select when exporting from SolidWorks.

3. Export to STL

SolidWorks STL export options.
SolidWorks STL export options.

Once the model is ready, select "File->Save As..." and pick "*.STL" under "Save as type:". Next, click the options button. Make sure the option to not move the model into positive space is checked, that the units are correct (OpenFOAM works in meters), and that you are saving as an ASCII, not binary, STL. Note the capital letters SolidWorks uses in the file extension by default, and that Unix file systems are case-sensitive.

You should now have an STL compatible with snappyHexMesh, and are ready to embark on the sometimes treacherous path towards its optimal meshing settings.

OpenFOAM cases are setup through a hierarchy of text files. Git is made to track changes in code, which is text. This makes Git a perfect candidate for tracking changes in simulation settings and parameters. In this post I will not really give a full picture of Git's operation and capabilities, but detail how I use Git with OpenFOAM cases.

Why track changes?

Setting up an OpenFOAM case can be a real headache, since there are so many settings and parameters to change. In my experience, it's nice to be able to keep track of settings that worked, in case I change something and the simulation starts crashing. With Git it's easy to "check out" the last configuration that worked, and also keep track of what changes had favorable or negative effects, using commit messages.

Branches

Git has the ability to create branches, that is, versions of files that one may switch between and edit independently. This is useful for OpenFOAM cases, because sometimes it's desirable to run multiple cases with some parameter variation. For example, one might want to run one simulation with a low Reynolds number mesh and one with a high Reynolds number mesh. One may also want a case that can be run with different versions of OpenFOAM. By putting these files on different branches, one can switch between these two sets of parameters with a single checkout command.

Sharing, collaboration, and storage

Git has become the most popular way to share and collaborate on code, through the website GitHub. Using GitHub, another user may "fork" a set of case files, make some changes, and submit a "pull request" back to the main repository. This can be much more convenient than simply sharing zipped up folders of entire cases, as changes are more visible and reversible. Putting case files in a remote repository (private repositories can be hosted for free on BitBucket) is also a nice way to keep things backed up.

How to set up a Git repository inside a case

The first important step is to create a .gitignore file in the top level case directory. Inside this file, you tell Git the file names or name patterns to not track. For OpenFOAM simulations, it is probably undesirable to track the actual simulation results, since this will grow the size of the repository significantly every time the simulation results are committed, i.e., all committed results would be saved for all time. My sample .gitignore file is shown below:

*.gz
log.*
oldLogs/
postProcessing/
images/
video/
*~
processor*/
0*/
1*/
2*/
3*/
4*/
5*/
6*/
7*/
8*/
9*/
!0.org
constant/extendedFeatureEdgeMesh
*.eMesh
constant/polyMesh/*
!constant/polyMesh/blockMeshDict
constant/cellToRegion
constant/dynamicMeshDict
*.OpenFOAM

Note that I also ignore application logs, though these may be valuable troubleshooting tools. For example, one could checkout a previous commit, look at a log and compare with a newer version. However, these logs can get big, so I leave general performance information to the commit messages.

After a proper .gitignore file has been created, a repository can be initialized by running git init in a terminal (inside the top level case directory). Running git status will then list all the files that are not yet tracked. To add all of these files and create the first commit, run

git add -A
git commit -m "Initial commit message goes here"

Next, you may want to create a remote repository and set the remote location for the local repository. If using GitHub, create a new empty repository and follow these instructions for pointing your repository there.

Next, "push" your local changes to the remote by running

git push origin master

This pushes the changes to the master branch of the remote repo.

The case is now set to be tracked with Git. You can now continue on, and save your path towards, the perfect OpenFOAM case!

Traversing carriage assembly installed as part of the UNH-CORE turbine test bed.
Traversing carriage assembly installed as part of the UNH CORE turbine test bed.

In order to perform turbine wake measurements in UNH's tow tank, I designed and built a 2-axis positioning system for our Nortek Vectrino acoustic Doppler velocimeter. Some specifications are shown in the table below:

Cross-stream (y-axis) travel 3.0 m
Vertical (z-axis) travel 1.2 m
Max drag force (estimated) 25 N at 2 m/s

Components

SolidWorks rendering of the YZ traverse assembly.
SolidWorks rendering of the YZ traverse assembly.

The frame is constructed mostly from 80/20 15 series t-slot framing and hardware, with some custom brackets to mount to 1.25" pillow block linear bearings.

Attached to the frame are two Velmex BiSlide linear stages, with the y-axis driven by a stepper/belt and the z-axis driven by a stepper/ball screw. An additional igus DryLin polymer linear bearing, along with a second carriage on the z-stage BiSlide provide additional moment loading capacity.

The Vectrino probe is clamped in a cantilevered bar attached to a NACA 0020 strut. The foil is attached to an 80/20 extrusion, which mounts to the z-axis linear stage via two custom adapter blocks. Note that the adapter blocks' odd odd trapezoidal shape is due to the fact they're made from recycled material.

Two igus Energy Chain cable carriers span the horizontal and vertical axes to keep the Vectrino and motor cables from tangling during operation.

Actuation and control

The stepper motors are driven by an ACS UDMlc drive controlled by an ACS NTM EtherCAT master controller, which provides synchronized operation with the tow and turbine axes through ACS' SPiiPlus interface. In addition to the ability to write motion control programs with their ACSPL+ language, COM, and C library, the ACSpy Python wrapper for the C library allows incorporating motion commands into Python programs, e.g., TurbineDAQ.

CAD file download

A full assembly of this design, minus some minor hardware, is available for download in (SolidWorks 2012 format) here. Contact me if you would like something similar designed and/or built.

Having used SolidWorks for a while, and likely entering the job market again in O(1) years, I was curious as to which CAD software would be wisest to learn next, with respect to job opportunities and salaries. Looking to compile data from the current job market, I wrote Indeedy, a Python module for automatically searching Indeed.com. With this new tool I searched for "mechanical engineer" plus the names of various CAD systems, then plotted the number of results and average salary for each on the bar graph shown below.

Search results for "mechanical engineer" plus various CAD systems on Indeed.com.
Search results for "mechanical engineer" plus various CAD systems on Indeed.com.

From these results we can see demand for SolidWorks is dominant by a large margin. However, Catia and NX skills are compensated better on average, which makes sense considering their popularity in the aerospace and automotive industries. At first glance it seems there are more Catia than NX jobs, but when NX's results are combined with Unigraphics' (NX's ancestor), demand is comparable. However, this combination also comes with with a decrease in average salary, giving Catia a slight edge, and the title of Best CAD System to Learn (for me anyway).

A photo of the completed stand with bike.
Bike rack stand assembly.

Living in a studio apartment, storage space can be hard to come by. To have a place to store both my bike and hitch receiver bike rack, I designed and built a rolling stand that holds the rack upright, also serving as a nice work stand.

The frame is made from 80/20 t-slot extrusions and hardware, which are available at Amazon.com. On top is a 2 inch Reese hitch receiver, and below is a set of swivel casters, two of which are locking. One nice feature of this design is that it only requires cutting and tapping—no welding, drilling, or more complicated machining necessary before it's ready for assembly.

The SolidWorks 2012 CAD models for this assembly can be downloaded here.

Open the IPython Notebook server inside a directory from the Windows context menu.
Open the IPython Notebook server inside a directory from the Windows context menu.

By default, the awesomely useful and fun IPython Notebook does not integrate with Windows so seamlessly. A console and then the notebook server must be opened in the proper directory in order to open a new or existing notebook. These extra steps make the IPython Notebook slightly less ideal for quickly jotting down ideas—one of its greatest uses!

Not to fear, however, as the Windows Registry can easily be modified to

  1. Open notebooks directly from Windows Explorer.
  2. Create a shortcut to launch the IPython Notebook server within the current directory.

Simply download and run ipython.reg to add the entries shown below to your registry automatically.

These entries were put together from instructions located here. A similar solution can also be found here.

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Directory\Background\shell\ipynb]
@="Open IPython Notebook server here"

[HKEY_CLASSES_ROOT\Directory\Background\shell\ipynb\command]
@="\"C:\\Python27\\Scripts\\ipython.exe\" \"notebook\" \"%V\""

[HKEY_CLASSES_ROOT\Directory\shell\ipynb]
@="Open IPython Notebook server here"

[HKEY_CLASSES_ROOT\Directory\shell\ipynb\command]
@="\"C:\\Python27\\Scripts\\ipython.exe\" \"notebook\" \"%V\""

[HKEY_CLASSES_ROOT\ipynb_auto_file\shell\open\command]
@="\"C:\\Python27\\Scripts\\ipython.exe\" \"notebook\" \"%1\""

The code below shows how to use PyQt to create a status bar that shows the progress of an OpenFOAM simulation. Note that the arguments to re.findall may need to be tweaked depending on the specific case setup, and that the code assumes the simulation will be run in parallel, but it should be easy enough to tailor this sample to any case.

Qt Progress Bar

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This program creates a progress bar for an OpenFOAM simulation
Based on http://acaciaecho.wordpress.com/2011/01/11/pyqtprogressbar/

Put this file in your case folder and run from a terminal
"""
import re
import os
import time
import numpy as np
from PyQt4 import QtCore, QtGui


def getParams():
    """Get run parameters"""
    f = open("system/controlDict", "r")
    for line in f.readlines():
        if "endTime" in line:
            endTime = re.findall("\d.\d+", line)
            if endTime == []:
                endTime = re.findall("\d+", line)
        if "writeInterval" in line:
            writeInterval = re.findall("\d.\d+", line)
    f.close()
    endTime = endTime[0]
    writeInterval = writeInterval[0]
    return endTime, writeInterval



class Progress(QtCore.QThread):
    procDone = QtCore.pyqtSignal(bool)
    partDone = QtCore.pyqtSignal(int)
    
    def run(self):
        endTime, writeInterval = getParams()
        done = False
        
        while not done:
            # Find highest valued folder in "processor0" folder
            if os.path.isdir(endTime):
                done = True
                self.partDone.emit(100)
            else:
                dirs = os.listdir("processor0")
                numdirs = np.array([])
                for d in dirs:
                    try:
                        numdirs = np.append(numdirs, float(d))
                    except ValueError:
                        pass
                self.partDone.emit(int(np.max(numdirs)/float(endTime)*100))
            time.sleep(1)
        self.procDone.emit(True)   


class AddProgresWin(QtGui.QWidget):
    def __init__(self, parent=None):
        super(AddProgresWin, self).__init__(parent)

        self.thread = Progress()
        self.nameLine = QtGui.QLineEdit()
    
        self.progressbar = QtGui.QProgressBar()
        self.progressbar.setMinimum(1)
        self.progressbar.setMaximum(100)
        self.progressbar.setFixedWidth(400)
    
        mainLayout = QtGui.QGridLayout()
        mainLayout.addWidget(self.progressbar, 0, 0)
    
        self.setLayout(mainLayout)
        self.setWindowTitle("Solving")

        self.thread.partDone.connect(self.updatePBar)
        self.thread.procDone.connect(self.fin)
        self.thread.start()

    def updatePBar(self, val):
        self.progressbar.setValue(val)
    
    def fin(self):
        sys.exit()
        

if __name__ == '__main__':
    import sys
    app = QtGui.QApplication(sys.path)

    pbarwin = AddProgresWin()
    pbarwin.show()

    app.exec_()

OpenFOAM runs can take a long time. Wouldn't it be nice to know when a simulation is done without having to keep checking the terminal? As it turns out, this is very easy to set up with Python (I got most of the code I used from here, which details how to send an SMS). Simply create a script called send_email.py in the OpenFOAM case directory that looks like this:

#!/usr/bin/python

import smtplib
from email.mime.text import MIMEText

server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login("<username>", "<password>") 

msg = MIMEText("The simulation is complete.")
msg["Subject"] = "Simulation finished"
msg["From"] = "Me"
msg["To"] = "Me"

server.sendmail("Me", "my_email_address@gmail.com", msg.as_string())

Replace all the relevant info with your own. Note that this assumes you're using Gmail, but it can be adapted to any SMTP server. The script could also be expanded to email team members, extract information about the run, etc.

Change the permissions such that the file can be executed as a program, then at the bottom of your Allrun script add

./send_email.py

Voila. Now you'll get an email when your simulation finishes, and can go off and be productive elsewhere in the meantime.

The BeagleBone Black (BBB) is a $45 credit-card-sized computer that runs embedded Linux. I recently purchased a BBB along with an $8 4-phase stepper motor and driver to start playing around with slightly "closer-to-the-metal" motion control.

A shot of the BeagleBone Black, stepper motor, and driver.
BeagleBone Black wired up with the stepper motor and driver.

My first objective with the BBB was to simply get the stepper to move, and for this I started using the included Cloud9 IDE and BoneScript library. After a while messing around and learning a little JavaScript, along with some help from my programmer brother Tom, the stepper was moving in one direction using wave drive logic. However, the code felt awkward. I decided I'd rather use Python, since I'm a lot more comfortable with it, so I went out in search of Python packages or modules for working with the BeagleBone's general purpose I/O (GPIO) pins. I found two: Alexander Hiam's PyBBIO and Adafruit's Adafruit_BBIO. Adafruit_BBIO seemed to be more frequently maintained and better documented, so I installed it on the BeagleBone per their instructions, which was pretty quick and painless.

Next, I wrote a small, simple python module, BBpystepper, with a Stepper class for creating and working with a stepper control object. Currently the control uses full-step drive logic, with wave drive available by changing Stepper.drivemode. I may add half-stepping in the future, but for now full-stepping gets the job done.

Usage

After installing Adafruit_BBIO and BBpystepper on the BeagleBone, the module can be imported into a Python script or run from a Python interpreter. For example:

>>> from bbpystepper import Stepper
>>> mystepper = Stepper()
>>> mystepper.rotate(180, 10) # Rotates motor 180 degrees at 10 RPM
>>> mystepper.rotate(-180, 5) # Rotates motor back 180 degrees at 5 RPM
>>> mystepper.angle
0.0

Notes

  • By default the GPIO pins used are P8_13, P8_14, P8_15, and P8_16. These can be changed by modifying the Stepper.pins list.
  • By default the Stepper.steps_per_rev parameter is set to 2048 to match my motor (it has a built-in gearbox).
  • The code doesn't keep track of where it ends in the sequence of pins. It simply sets all pins low after a move. This means there could be some additional error in the Stepper.angle variable if the amount of steps moved is not divisible by 4.

Final Thoughts

As mentioned previously, half-stepping would be a nice future add-on, along with a more accurate way of keeping track of the motor shaft angle. Another logical next step would be to use one of the BBB's Programmable Real-Time Units (PRUs) to control the timing more precisely, therefore improving speed accuracy and allowing synchronization with other processes, e.g. data acquisition. However, for now this simple method gets the job done.