Modeling Microfluidics
Prev: Part 6: Modeling Bulks, Voids, and Shapes
The last step introduced basic geometry. Now we connect geometry to microfluidics!
Why pixels and layers matter
Pixel/layer resolution becomes critical when your feature sizes approach your printer’s limits. PyMFCAD models everything in pixels (X/Y) and layers (Z) so you can design against the actual print grid and get the most accurate, repeatable results for a given printer:
- Channel width corresponds to a fixed number of pixels.
- Channel height corresponds to a fixed number of layers.
- Small features are repeatable and scalable across prints.
If you think in mm, you can always convert:
Bulk vs void = printed material vs empty space
Start with a simple picture: the final printed part begins as a solid block, and you remove material wherever fluid should flow. In PyMFCAD:
- Bulk is the solid resin/plastic you want to keep.
- Void is the empty space you remove so fluid can flow.
This means your design process is usually:
- Add bulk to define the outer body of the device.
- Subtract voids to carve channels, reservoirs, and access ports.
So the device you see is always bulk minus voids. This mental model maps directly to printing: a printer can only deposit solid material, so you must explicitly model the absence of material anywhere you want fluid to go. Once you internalize this, microfluidic features become straightforward—channels are just void shapes, and connections to the real world are just larger voids (like pinholes or reservoirs) that intersect those channels.
Let’s see an example.
Step 1 — Device context
Before we can start modeling our device, we need to make our canvas. The Device defines the pixel grid and layer stack for a specific printer setup.
Step 2 — Define the bulk body (the printable solid)
Start with a single bulk shape that matches the device bounds. Every channel, reservoir, or access port will be carved out as a void later.
Preview now to confirm your bulk body is correct.

Step 3 — Add a simple channel (elongated cube)
Channels are usually long and thin, so we start with a rectangular void. We avoid round channels at very small widths because a few pixels can’t represent a smooth circle. We create the channel centered in X for easy placement, then translate it into position.
At this point you should now have a channel running through your bulk block.

Step 4 — Connect the channel to the real world (pinholes)
Channels need access points for tubing or reservoirs. We model these as large cylinders at the ends of the channel.
Because \(\text{px\_size} \ne \text{layer\_size}\), we convert the pinhole height in layers so the opening stays circular in mm.
At this point you have a complete device: a bulk block with a channel and two pinholes carved out as voids.

Step 5 — Parametric design (union, loops, and conditionals)
Now we can use the shape operations to combine shapes into one unit and create variants programmatically. The goal is to stop adding individual voids and instead add copies of a single parametric unit.
5.1 Union the three shapes
Replace the individual void adds from Step 3 with a single void shape:

5.2 Use loops + if statements
We can build a small array of channels with labels A, B, C, D, E using a loop and a conditional. This is a simple example of parametric design: the same unit is reused, placed, and optionally annotated.
Preview the parametric array. ```

Full parameterized example
Your final code should look something like this:
Step 6 — Polychannel version (continuous/hulled shape)
You can build the same “two pinholes + connecting channel” as a continuous composite shape using a polychannel. A polychannel is a sequence of cross‑sections that get hulled together, creating smooth, continuous geometry without manual unions between every piece.
Positioning rule: the first PolychannelShape position is absolute. Every shape after that is relative to the previous one (unless you explicitly set absolute_position=True). If a property doesn’t change (like shape_type or size), you can omit it and the previous value is reused. Full shape options are documented in the API: Polychannels.

Key idea: each PolychannelShape defines a cross‑section. The library hulls between them, so you get a smooth, continuous channel that can change size or shape along its path.
In this example we only use a 1D layout (the path stays on the X axis). If you change direction in Y or Z, you may need a full 3D shape at that junction.
Notes
- Channels should be sized in pixels/layers to match printer resolution.
- Pinholes are modeled as voids for tubing access.
- If pixel size != layer size, shapes will appear stretched. Use resize operation to reshape them.
- Union (
+) helps you treat a group of shapes as a single parametric feature.