Home - UMassIonTrappers/PyOpticL GitHub Wiki
Quickstart Guide
-
Open a new document (Ctrl + N) in FreeCAD:
If you do not open a new document (Ctrl + N) in FreeCAD, then FreeCAD will have no where to put the model it creates from your script... It will look like 'nothing is happening'.
-
Once you have a new document open, then Navigate to "Macro" > "Macros ..."
-
Select the location of your script in the "User macros location:" bar
-
Now select your script in the list and hit "Execute"!
Once you've run your script once it will then also appear in the "Macro" > "Recent macros" drop down list
Or you can re-run your last macro with Ctrl-Shift-1
How-to create a macro script to generate a simple example baseplate:
The general format for a baseplate script is as follows:
from PyOpticL import layout, optomech
# define baseplate constants, calculate parameters, etc.
# define the baseplate as a function so it can be imported into other files
def example_baseplate(x=0, y=0, angle=0):
# define baseplate and beam
# add components
# draw the baseplate if the file is run as a macro
if __name__ == "__main__":
example_baseplate()
layout.redraw()
The first thing to do is define our baseplate parameters and other constants
For most files, this will look something like this:
# baseplate sizing
base_dx = 4*layout.inch
base_dy = 4*layout.inch
base_dz = layout.inch
gap = layout.inch/8
# x-y coordinates of mount holes (in inches)
mount_holes = [(0, 0), (0, 3), (3, 0), (3, 3)]
# y coordinate of beam input
input_y = 1.5*layout.inch
The footprint of a given baseplate is defined by the dx and dy parameters with a gap distance removed from all sides. Because of this, the dx and dy parameters more so define the space allotted to the baseplate rather than its physical size. The gap defines the tolerance around the baseplate such that two can be mounted directly next to one another without issue.
Next we can start building our layout
Everything we add to our layout should be defined inside of a function so it can be imported into other files.
def example_baseplate(x=0, y=0, angle=0):
# define and place baseplate object
baseplate = layout.baseplate(base_dx, base_dy, base_dz, x=x, y=y, angle=angle,
gap=gap, mount_holes=mount_holes)
It's important to save the baseplate instance here as it will be used to place all the components.
Now we can begin placing beams and components
First we'll place a beam and an input fiberport:
# add beam
beam = baseplate.add_beam_path(x=gap, y=input_y, angle=layout.cardinal['right'])
# add input fiberport, defined at the same coordinates as beam
baseplate.place_element("Input Fiberport", optomech.fiberport_mount_hca3,
x=gap, y=input_y, angle=layout.cardinal['right'])
The first two arguments of any component placement function are:
- The name for the component
- The object class for the type of component desired
All available object classes can be found under the Optomech section of the docs
How you define the position of the part can vary.
Both the beam and input fiberport are placed using an x, y coordinate and angle.
The rest of the parts will be defined along the beam instead:
# add splitter component along beam, 40 mm from beam input
baseplate.place_element_along_beam("Beam Splitter Cube", optomech.cube_splitter, beam,
beam_index=0b1, distance=40, angle=layout.cardinal['right'],
mount_type=optomech.skate_mount)
# add waveplate along the transmitted beam, 35 mm from the splitter cube, mounted in a rotation stage
baseplate.place_element_along_beam("Rotation Stage", optomech.waveplate, beam,
beam_index=0b10, distance=25, angle=layout.cardinal['right'],
mount_type=optomech.rotation_stage_rsp05)
# add mirror along the reflected beam, 1 inch from the splitter cube, mounted in a polaris mount
baseplate.place_element_along_beam("Mirror", optomech.circular_mirror, beam,
beam_index=0b11, distance=layout.inch, angle=layout.turn['up-right'],
mount_type=optomech.mirror_mount_k05s1)
# add output fiberport along the beam, defined by settings it's x position to the edge of the baseplate
baseplate.place_element_along_beam("Output Fiberport", optomech.fiberport_mount_hca3, beam,
beam_index=0b11, x=base_dx-gap, angle=layout.cardinal['left'])
There are 4 necessary arguments for placing a component along the beam:
- The beam path object
- The beam index
- The component angle
- One placement defining argument, this can be the distance from the last component, an x coordinate, or a y coordinate.
The beam index is what allows for placement along complex beam paths involving splits and diffractions.
You should think of the beam index as a binary string, with each 1 and 0 representing what path the beam took at each junction.
Every beam starts at beam index 0b1 (while you could write this as an integer, taking advantage of python's binary literals makes for much better readability).
Whenever a beam splits, a zero is added to the index of the transmitted beam and a 1 is added to the index of the reflected/diffracted beam.
This image should help demostrate the structure:
The ability to define different placement conditions can also be very helpful for both dynamic baseplate designs and specific components.
As can be seen in the example script, something like a fiberport can be defined by an x coordinate rather than a distance to ensure it is always placed at the edge of the baseplate.
One last thing to note is the use of the layout.turn dictionary for mirror angles.
The declaration is quite verbose, layout.turn['down-right'] will place a mirror which would take a downwards beam and reflect it to the right, ie placing the mirror at 45 degrees.
Now that we have our script, we're ready to run it in FreeCAD!
To recap, we should now have something like this:
from PyOpticL import layout, optomech
# baseplate constants
base_dx = 4*layout.inch
base_dy = 4*layout.inch
base_dz = layout.inch
gap = layout.inch/8
# x-y coordinates of mount holes (in inches)
mount_holes = [(0, 0), (0, 3), (3, 0), (3, 3)]
# y coordinate of beam input
input_y = 1.5*layout.inch
# function so baseplate can be added to other layouts
def example_baseplate(x=0, y=0, angle=0):
# define and place baseplate object
baseplate = layout.baseplate(base_dx, base_dy, base_dz, x=x, y=y, angle=angle,
gap=gap, mount_holes=mount_holes)
# add beam
beam = baseplate.add_beam_path(x=gap, y=input_y, angle=layout.cardinal['right'])
# add input fiberport, defined at the same coordinates as beam
baseplate.place_element("Input Fiberport", optomech.fiberport_mount_hca3,
x=gap, y=input_y, angle=layout.cardinal['right'])
# add splitter component along beam, 40 mm from beam input
baseplate.place_element_along_beam("Beam Splitter Cube", optomech.cube_splitter, beam,
beam_index=0b1, distance=40, angle=layout.cardinal['right'],
mount_type=optomech.skate_mount)
# add waveplate along the transmitted beam, 35 mm from the splitter cube, mounted in a rotation stage
baseplate.place_element_along_beam("Rotation Stage", optomech.waveplate, beam,
beam_index=0b10, distance=25, angle=layout.cardinal['right'],
mount_type=optomech.rotation_stage_rsp05)
# add mirror along the reflected beam, 1 inch from the splitter cube, mounted in a polaris mount
baseplate.place_element_along_beam("Mirror", optomech.circular_mirror, beam,
beam_index=0b11, distance=layout.inch, angle=layout.turn['up-right'],
mount_type=optomech.mirror_mount_k05s1)
# add output fiberport along the beam, defined by settings it's x position to the edge of the baseplate
baseplate.place_element_along_beam("Output Fiberport", optomech.fiberport_mount_hca3, beam,
beam_index=0b11, x=base_dx-gap, angle=layout.cardinal['left'])
# this allows the file to be run as a macro or imported into other files
if __name__ == "__main__":
example_baseplate()
layout.redraw()
-
Launch FreeCAD
-
Open a new document (Ctrl + N):
-
Navigate to "Macro" > "Macros ..."
-
Select the location of your script in the "User macros location:" bar
-
Now select your script in the list and hit "Execute"!
Once you've run your script once it will then also appear in the "Macro" > "Recent macros" drop down list
Or you can re-run your last macro with Ctrl-Shift-1
Congratulations! You're ready to start making your own layouts in PyOpticL, so get making!
Workbench Functions:
- Re-Run Last Macro - Clears and re-draws last baseplate, great for quickly checking changes
- Recalculate Beam Path - Useful to check beam paths when applying in-editor modifications
- Toggle Component Visibility - Easily hide all beams and components
- Toggle Draw Style - Toggle wire-frame draw style to easily check for hidden issues
- Export STLs - Export all baseplates and adapter components to STL for fabrication
- Export Cart - Export all parts to both a spreadsheet and a csv compatible with Thorlabs upload-a-cart system
- Reload Modules - Reload all PyOpticL modules, great for debugging new parts
- Get Orientation - Automatic orientation and importing of new components from STEP files
- Get Position - Measure offsets and mount locations from oriented STEP file
(These functions can also be scripted into macros if desired)
Model Import Guide
For this guide we'll be importing the Polaris mirror mount model K05S1:
We'll start by getting the model for our component into FreeCAD
First, download the STEP file for the component from the Thorlabs website:
https://www.thorlabs.com/thorproduct.cfm?partnumber=POLARIS-K05S1
Ensure the name of the STEP file is clear and not augmented as this will be used for stl naming automatically.
For example, our file is named "POLARIS-K05S1-Step.step", so ensure it's not "POLARIS-K05S1-Step(2).step" or similar.
Now in FreeCAD, go to File > Open and select the STEP file.
We're now ready to start orienting the component
There are two factors used to get orientation, a selection around the optical center or mount point and the camera facing.
The first step should be defining the center point.
This can be done in several ways depending on the geometry, however the general idea is to select edges or points that frame the optical center.
Different kinds of selections provide different information about the center location.
- If you select a point it will use the coordinates of the point
- If you select a straight edge it will use the center of the edge
- If you select a circular edge it will use the center of the circle it outlines
If you select multiple edges or points, it will take all the positions acquired from the rules above and average them.
If this doesn't seem clear yet don't worry, the guide will cover examples of the different selection options.
For any optical component, the center should be defined as the optical center.
For mounts and other components, centering it on a mounting region is usually best.
For our case, we're dealing with a mirror mount, so we want to define the center as the center of the backplane the mirror will rest against.
To do this, we'll select the circular edges around this backplane:
Make sure to keep these edges selected until after the orientation is complete.
Our next task is to define the correct rotation of the part.
All you need to do is orient the FreeCAD viewer such that the "front" of the component is facing you.
In general, this means with the normal of the optic facing you and the post mounting facing down:
This facing needs to be exact, so once you've got it close you should click the appropriate face on the FreeCAD camera orientation cube to align perfectly to that face:
Once this is all done you can go to the PyOpticL toolbar and run the "Get Orientation" function:
You should now see the model re-orient itself according to your specifications.
The "front" of the model should now be on the right face as defined in FreeCAD.
By enabling the origin crossing in View > Toggle axis cross, you can check if the center is where you expect.
You should also notice an output like this in the FreeCAD console:
_import_stl("POLARIS-K05S1-Step.stl", (90, 0, -90), (-4.514, 0.254, -0.254))"
This is the command you'll use when adding this model into a PyOpticL component.
For convenience, the STL for the model is automatically exported to the library when the orientation function is run.
Note that for something like mirrors there may need to be an additional offset based on the mirror thickness which should be handled in your component function.
Now that the component is oriented, we can check the mounting positions
In this case, the mirror mount has one bolt hole and two alignment pins which need to be measured.
The selection rules here are exactly the same as those for getting the center earlier.
Let's start with the bolt hole.
As you can see, the hole is recessed a bit so we can't use it's edges directly:
Instead, we can select the two lines on the bottom which perfectly frame this hole:
This selection will give us the half-way point between the centers of the two lines, which also corresponds to the mount location.
You can then run the "Get Position" function in the PyOpticL toolbar:
You should see an output in the console like this:
(-8.017, 0, -12.7)
This represents the x, y, and z offset between the mount hole and the center we defined earlier.
So this translation can be used directly in the drilling positions for the part in PyOpticL.
This process can be repeated for the two alignment pins.
However, the left pin gives us a good example of how we can define the same position using different selections:
These two selections both output the same center point of the pin slot.
Congratulations! You can now import new models into PyOpticL. Check out the optomech library for examples on how to create full PyOpticL components.
Video Guide for Importing and Drawing
Importing Demo
Create Drawing Demo