Additional Features
Visual Studio Code extension
The PartCAD extension for Visual Studio Code is the primary graphical interface to PartCAD. It adds a PartCAD workbench with an Explorer view for browsing packages and an Inspector view for viewing objects and editing their parameters. Install it from the VS Code marketplace. See the tutorial for a step-by-step walkthrough.
Opening a package runs the code its parts are written in, so in a folder Visual Studio Code has not been told to trust (Restricted Mode) the extension starts nothing: it asks for trust, the Explorer says why it is empty, and every PartCAD command offers Manage Workspace Trust instead of running. PartCAD starts as soon as the folder is trusted, without reopening the window. The PartCAD IDE does not ask – it exists to run PartCAD, and trusts every folder.
The extension exposes the following actions from the Explorer view and the command palette:
Packages
Initialize new package — Create a
partcad.yamlpackage in the current workspace (a custom-path variant is also available).Open package — Open an existing package.
Reload the package and Restart PartCAD — Refresh the view or restart the PartCAD backend after a change.
Update PartCAD — Update the PartCAD installation used by the extension.
Objects
Add a part, assembly, scene, sketch, or interface.
Import a part, assembly, scene, or sketch from an existing file.
Display (inspect) a part, assembly, scene, sketch, or interface in the
PartCAD Viewer; parts can also be opened for display and editing together, or their source edited directly.Test a part, assembly, or scene.
Open in > — Open the object’s own source file in the application that made it: FreeCAD or Blender for a part or an assembly, KiCad for a part of type
kicad, and Gazebo or MuJoCo for a scene held in that engine’s own format (sim-gazebo:world,sim-mujoco:mjcf) when the workspace imports the package that declares the engine. This runs on your machine rather than on the daemon: the extension runspc open(see CLI command reference), which starts an application installed here, or runs one in a container when there is none andpartcad.open.useDockeris on, which it is by default. Blender reads meshes, so an object that is not one is converted on the way.
The Explorer also lists the software a package ships. Selecting one shows its path and its fileHash
in the Inspector and leaves the PartCAD Viewer as it is: software is a file, not geometry, so there is
nothing to render.
The PartCAD Viewer
Displaying an object opens the PartCAD Viewer, which is a strip of tabs over that one object rather than
a bare canvas: groups of tabs, each with tabs of its own – Design, Analysis and Supply Chain,
followed by Validation and Operations, which have nothing in them yet and are always disabled. Design
is always the first:
Design — the object itself, three ways, in tabs of its own:
3D — the shape, tessellated by PartCAD and drawn here, to turn and zoom. Always present.
2D — the object rendered to a picture, as
pc renderrenders it: PNG, JPEG or SVG, chosen from a list. For a part, an assembly, a scene or a sketch.Draft — a dimensioned technical drawing of a part or an assembly, made by a drawing package the way
pc render -euses one.//pub/feature/render/draftwrightis offered, and the formats are whatever it declares (PDF, SVG and DXF). The first drawing takes a few minutes while PartCAD installs draftwright.
2D and Draft each have a Save… button that saves the file on screen wherever you choose. A picture is shown on the tab, and can be zoomed with the mouse wheel, dragged, and put back with a double-click; a PDF or a DXF cannot be shown there, and is saved and opened in another program instead.
Analysis — what engineering analysis says about the object:
FEA and CFD — for a part: the analysis pc cae runs, and what it found.
Manufacturing — what making it takes:
Build vs Buy — for a part or an assembly: every part, piece of stock and sub-assembly it is made of, with a picture, a count, its size, weight and material, and a switch saying whether it is built or bought. A line that can be only one of the two has its switch fixed, and one that can be neither is marked Incomplete. The choices are kept on your machine, and
pc instructionsreads them too.Build — for whatever is built: the order to make it in, with the instructions for the selected step.
Buy — for whatever is bought: what it can be bought from, and a quote per supplier.
Bill of Materials — for a part or an assembly: what has to be procured to have it, as
pc bomlists it. A made part is the stock it is made from, and a sub-assembly that declares a vendor and an SKU is one item ordered whole.Assembly — for an assembly: the assembly instructions, as PDF or HTML, to read and save (
pc instructions).
Manufacturing is for a part or an assembly; for a scene, a sketch or an interface it is disabled.
Every group always shows all of its tabs and disables the ones that do not apply, and a group none of whose tabs apply is disabled itself, and says why when the mouse is over it. Design and its 3D tab are always enabled, and are what the Viewer shows before anything has been selected. A group opens on the tab you last had open in it, if that one applies to the object, and on the first one that does otherwise.
The 3D view arrives over the viewer protocol from whichever partcad asked for the shape to be shown; the
other tabs are questions put to the PartCAD daemon, fetched the first time the tab is looked at and cached
until the next object is shown. An object that belongs to no package gets the 3D view alone.
The 3D view uses the graphics card where the window can (WebGL). In a window that cannot – a virtual machine, a remote desktop, a graphics driver the editor could not use – it draws the model without it instead: flat-shaded and slower, and a big model is drawn as boxes while it is being turned. A note in the corner of the view says so, and hovering over it says how to have the graphics card back.
Down the left of the 3D view is a list of what is on screen, with a checkbox on every line: unticking one hides that line and everything under it, and ticking it again brings back exactly what was showing before. Animate and Opacity are at the bottom of the same pane.
The list is the object itself. Every object PartCAD can show is a tree, and the pane is that tree:
a part or a sketch is one line — itself;
an assembly or a scene is a line per thing it holds, named as the
.assyfile names it, as deeply as the file nests them;an interface is a line per port it is drawn with, with a line per interface it inherits holding the whole of that one (see Assembly YAML).
Under each of those lines is what that object says about connections: a line per interface instance with the
ports it is made of underneath it, and a ports line for the ports that belong to no interface. Every port is
listed once and drawn as a coordinate frame — the same frames pc render --with-ports draws on a projection
(see CLI command reference). An assembly’s own ports are the ones it declares and the ones its map: externalizes, so
they are listed on the assembly’s own line rather than on the things inside it.
A port that is drawn with a sketch — the circle of a hole, the profile of a rail — shows that too, in the blue of
the +Z line of its own frame: the shape the connection happens across, which is what makes an opening
readable as one. It is half see-through, so the opening it covers still reads as one, and the Opacity slider
leaves it alone — that slider is about the shape rather than about what is drawn on top of it. It is part of the
port, so the port’s checkbox draws or hides both.
The ports and interfaces lines start folded up — for an assembly they run to hundreds of rows, and
unfolded they bury the hierarchy they hang off. Pointing at a part or a sub-assembly makes it flicker in the
3D view, which is how to tell which of the shapes on screen a line is without moving the camera.
An object’s own ports start out drawn. The ports of everything inside it do not: an assembly of forty parts has a frame at every hole of every one of them, and all of it at once shows nothing. A line whose tick is grey rather than solid is one that is showing while something under it is hidden — which is how a folded-up line says so.
The two analysis tabs are the exception to “questions”: looking at one runs the analysis. A field over the model names which implementation runs it — pre-filled with the configured default, and editable, so a machine whose solver is elsewhere is one line away from an answer rather than stuck on an error. The model is drawn according to the format the implementation chose: a 3D result is turned and zoomed, a 2D one is panned and zoomed as a picture. Any findings are listed under it; a part with nothing to report gets the whole pane.
Export
Objects can be exported directly from the extension to any of the following formats: SVG, PNG,
JPEG, STEP, STL, 3MF, ThreeJS, OBJ, IGES, and glTF. An engine’s own scene
format — a Gazebo world (SDFormat), a MuJoCo model (MJCF) — is written by the plugin package that
implements it, so it is named through that package and exported from the command line:
pc export -S -t sim-gazebo:world.
Engineering analysis
A part can say what holds it and what it carries, and PartCAD can put that
question to a solver: pc cae fea for a finite element analysis, pc cae
cfd for computational fluid dynamics. The conditions live on the part, in a
section named after the analysis, because they are a property of the part rather
than of whoever analyses it – a bracket is bolted down at the same holes
whichever solver is asked about it:
# partcad.yaml
parts:
bracket:
type: build123d
path: bracket.py
fea:
fix:
- m3-screw # every instance of this interface is held still
load:
hook: 5 kg # every instance of this one carries this
rail:
left: 30 N # or one named instance at a time
right: 30 N
fix: and load: name interfaces the part implements,
so the same declaration that says where a part connects also says where it is
held and pulled. A load is a force: write a number and a unit, or a bare
number for a mass in kilograms, which PartCAD weighs into newtons at 9.8 N/kg.
cfd: takes the same two keys with the same meaning.
PartCAD ships no solver. The analysis is run by an implementation package,
declared in a cae: section exactly as an export or a render implementation is
declared in its own (see Analyses), and named by the
caeFeaImplementation / caeCfdImplementation user configuration options –
//pub/feature/cae/calculix:fea and //pub/feature/cae/calculix:cfd by
default, which are CalculiX (see
partcad-cae-calculix for
what it needs and what every parameter means – it names a dockerImage
carrying its solver and mesher, so a container runtime is all it asks of a host
that has neither installed). pc cae fea --implementation overrides it for one run, and so does
the field over the model in the IDE’s FEA tab.
A part may also name its own, which is what it was written against:
fea:
implementation: //pub/feature/cae/calculix:fea
fix: [m3-screw]
load: {hook: 5 kg}
That sits between the other two: -i on the command line wins, then the part,
then the user configuration. It is how a package ships an analysis that works
for whoever opens it, without every reader first pointing their own
configuration at the right place.
A relative package name here – calculix:fea rather than
//pub/feature/cae/calculix:fea – means the package this one imported under
that name, and is resolved from the package the declaration is written in. That
is not where a name a user types is resolved from: pc cae fea -i
calculix:fea means the calculix beside the user, like every other name a
command line carries. The difference matters under pc test -P //... over a tree of
packages, which runs with the tree’s root current while every part in it sits one
or more packages below.
What comes back is two things: the model, written to
<part>.<analysis>.<extension> in whichever format the implementation chose,
and the findings – a JSON array of what the analysis has to say about the
part. pc test gains an fea and a cfd check that fail a part whose
analysis produced any finding, and they apply only to a part that declares the
matching section, so a package of bolts pays nothing for them. There is one way to pass – the
analysis ran and reported nothing – and everything else fails: a malformed
section, a plugin that cannot be resolved (not a dependency, misspelt, declaring
no such file type), a plugin that resolves and cannot run (no mesher, no solver,
a sandbox that will not build, a crash), and an analysis that ran and found
something.
Not running is deliberately not a skip. A skip says the question does not apply
here; a part that declares fea: has asked, and a plugin that was asked and
delivered nothing has failed. The consequence is the point: declaring fea:
in a shared package makes pc test fail for everyone who has not installed
what that implementation needs. A package that does not want that should not
declare the section. What the failure carries is why – the implementation’s
own sentence, plus which implementation was asked and which machine it did not
work on, because “gmsh is not installed” is a puzzle and the same sentence under
//pub/feature/cae/calculix:fea on Linux-aarch64 is an answer.
There is one excuse, and it is the case where the implementation was never
given the environment it says it needs. An implementation naming a container:
or a dockerImage is stating that a container is how what pip cannot install
arrives; on a machine with no container runtime that statement has nowhere to
land, nothing was ever asked, and the check skips with a WARNING carrying the
whole report rather than failing. It is narrow in both directions. An
implementation that names no image gets no excuse at all – it said it runs in an
ordinary sandbox, and a machine with a working sandbox is a machine it was
supposed to work on. And a container runtime that is here removes the excuse
entirely: a registry that cannot be reached, an image that will not start, a
solver missing from the image are all things somebody can fix, and calling them
“unavailable” would hide exactly the failures a plugin’s own CI exists to catch.
A Docker daemon running Windows containers does not count as a container
runtime for this, or for choosing a sandbox. Every image PartCAD builds, pulls or
documents is a Linux image, and such a daemon answers a ping and then fails every
pull with no matching manifest for windows/amd64.
examples/feature_cae is the pair of cases to check an implementation against
— a cantilever and a pipe, each with a closed-form answer to compare the solver
with. Against CalculiX the cantilever reads 0.1655 mm where the model predicts
0.16-0.18 mm. The pipe declares no cfd: section: CalculiX’s CFD solver
diverges on it, a part that declares a section has asked a question, and an
example that ships a check nothing can turn green is one readers learn to scroll
past. The declaration is written out beside the part, and that example’s
README.md has the measurement.
See pc cae for the command and the units it accepts.
Route files
An object can say what is cut out of it and how, and pc cam writes the
program a machine does it with. The job lives on the object, in its
manufacturing:
section, because it is a property of the object rather than of whoever cuts it
– a panel is 18 mm thick and has to be cut through whichever router is asked:
# partcad.yaml
parts:
panel:
type: build123d
path: panel.py
manufacturing:
method: subtractive
source: sheet # the stock it is cut out of
cnc:
operation: profile # around the outside of it, and inside every hole
diameter: 6 mm # the cutter
depth_per_pass: 3 mm
feed: 2400 mm/min
speed: 18000 rpm
Saying something about being cut is the object’s opt-in, and the whole of it. pc cam with
nothing named produces a route for every sketch and part of the package that
declares one and passes over every object that does not, silently – most
objects are never cut, and a package where three parts of forty are is the
ordinary case rather than thirty-seven warnings. Naming an object that declares
nothing is an error, because naming one is asking about it.
Unlike a solver, PartCAD ships an implementation: //builtin/cam writes
G-code, and camImplementation names it by default. A route is arithmetic on
the object’s own outline rather than somebody else’s program with a release
cycle of its own, which is the test export: and render: already pass and
cae: does not – so it ships here for the same reason DXF does. A controller
that wants a dialect of its own is a package declaring a file type in its own
cam: section (see Routes), named by that option, by the object’s
own implementation:, or by -i for one run.
Every key of the object’s section is also a parameter of that file type, which
makes the two of them three layers of one namespace: the built-in package
underneath, the package’s own cam: section, then the object’s
manufacturing:. A package
cutting twenty parts from one sheet sets the tool once; the one part that needs
a smaller cutter says so for itself. Lengths, feeds and speeds may each carry a
unit and are converted at every layer, so a mm/min written by the package is
understood as surely as one written on the object.
What comes back is a file beside the package – panel.nc – and what it
counted: how many passes, how deep, how far the tool travels in the cut. The
route is the object’s outline sectioned at the bottom of the cut, offset by
the radius of the cutter, and that is both what makes it right for a prismatic
object and the limit worth knowing about every other one: where the
cross-section changes over the cut, no single outline is right, and the route
follows the bottom and says so as a warning naming how much the two ends differ
by. A route produced from an outline nobody expected is the one failure that
looks like a success all the way to the machine.
pc test runs the same thing as its cam check – it produces the route
and passes the object only if one came back – and applies it to an object that
declares the section and to nothing else, so a package of bolts pays nothing for
it. Unlike the analyses it does not keep what it produced: a route a check wrote
would be indistinguishable from the one pc cam writes, so it routes into a
temporary directory and deletes it.
The check that used to be called cam is manufacturability. It asks
whether an object can be made or bought at all – whether its geometry suits
the method it declares, whether what it is made from is reproducible, whether a
supplier could be found – which is a different question from whether a
post-processor can produce a program for it. One word answered both until
pc cam existed. -f filters by name prefix, so -f manufacturability
selects that check and its three method-specific siblings and -f cam selects
the route check alone.
examples/feature_cam is the three operations on three objects, and a fourth
that declares no section and is passed over. See pc cam for the
command and the units it accepts.
Procurement and Manufacturing
PartCAD currently supports two types of providers (entities that can provide
parts and assemblies): store and manufacturer.
store can be used to quote and order parts from existing lists:
by vendor and SKU.
manufacturer can be used to quote and order parts by using their 3D model
(for example, by 3D printing them).
# partcad.yaml
parts:
existing_part:
vendor: homedepot # for example
sku: ...
count_per_sku: 25 # if it's sold in packs of 25
...
new_part:
manufacturing:
method: additive
parameters:
color: black
material: //pub/std/manufacturing/material/plastic:pla
...
Assemblies that are sold in an assembled state are declared the same way, using
vendor and sku on the assembly itself.
See Procurement for more information about declaring purchasable objects, and Providers for more information about the providers and how PartCAD selects them.
In the future, PartCAD will support assembler, which is supposed to produce
an assembly given assembly instructions and using parts ordered from
store-s and manufacturer-s.
Currently, the provider has to be explicitly specified in the quote or order request, or explicitly specified as one of the suppliers in the package where the parts are declared:
suppliers:
myGarage: # a provider of this package
../provider_store:myGarage: # one next door
//vendor/store:myGarage: # one anywhere
A supplier is written from the point of view of the package that lists it, and is resolved against that package the way every other reference it makes is: a bare name is one of its own providers, while a qualified one lets it buy from a provider defined elsewhere instead of declaring one of its own.
A package served by a repository plugin has no partcad.yaml to write this
in, so its plugin states the same section in the package’s meta, and it is
read the same way (see Repositories):
{"desc": "Metric hex bolts", "suppliers": {"//pub/svc/commerce/homedepot:homedepot": {}}}
In the future PartCAD will be able to select providers based on the location and preferences of the requester, while leaving the possibility to enforce the use of a specific provider for corresponding parts (for example, for parts that are using a patented design).
The Python sandbox
Every CAD script PartCAD runs – a cadquery or build123d part, the
importer that reads a STEP file, a render: or cam: implementation –
runs in a sandbox rather than in the interpreter PartCAD itself is running on.
That is what lets one package render against build123d 0.11 while another wants
0.9, and what keeps a CAD stack out of the environment you work in.
pythonSandbox chooses how that sandbox is built:
|
A container, with your files mounted into it. The default wherever a container runtime answers, because it is the only one whose result does not depend on what the host happens to have: the interpreter, the compilers and the native libraries all come from an image somebody built once and published. It is also the only sandbox a package can bring non-Python dependencies to, by naming its own image – see The docker sandbox below. |
|
An environment conda provisions, interpreter included. The default where there is no container runtime and conda or mamba is installed – and in the standalone tools, the snap and the PartCAD IDE, which carry a conda of their own and use yours in preference to it when you have one. What it provisions depends on your channels, your package cache and your platform, which is why it is no longer the first choice where a container is available. |
|
A plain virtual environment of PartCAD’s own, one per interpreter version, under the internal state directory. The default when neither a container runtime nor conda is installed. Built from whichever Python the host has, so a package asking for a version the host does not have is rendered on the host’s and told so – and so not something the standalone tools can fall back to, since the machine they exist for is the one with no Python. |
|
A container that does not share a filesystem with PartCAD: the
inputs are sent to it and the outputs are sent back. This is what a
sandbox on another machine needs. Chosen rather than fallen back to,
and it needs |
|
No environment at all: scripts run on the host’s own interpreter and their dependencies are installed into it. Fast and shares whatever is already there, at the price of writing the CAD stack into the Python you work with – and unusable where that Python is not writable. |
|
A conda environment built around PyPy. |
# ~/.partcad/config.yaml pythonSandbox: venv
The equivalents everywhere else are PC_PYTHON_SANDBOX in the environment and
--python-sandbox on the command line, in the usual order of precedence.
Which one is chosen for you, when you have said nothing, is the first of
docker, conda and venv that this machine can actually provide. A
continuous integration runner with no container runtime therefore keeps
provisioning with conda exactly as before, and needs no configuration to say so.
“Can actually provide” includes the image. A container runtime answering says a
container could be started; it says nothing about whether the image to start it
from can be pulled, and on a machine that is offline, behind a firewall, or
simply not permitted to reach the registry those are different answers. So
PartCAD checks, once, and falls through to conda or venv with a warning
rather than failing every part against a registry you never asked it to talk to.
“Can actually provide” also includes the filesystem. The container gets your
directories bind-mounted into it, and that only means anything if the daemon
being asked is on the same filesystem as PartCAD. Two ordinary arrangements
where it is not: a dev container with the host’s /var/run/docker.sock bound
into it, and DOCKER_HOST pointing at another machine. There the daemon
resolves your paths against a filesystem of its own, Docker creates whatever is
missing – empty, and owned by root – and the container starts perfectly well
with directories that are not yours. Nothing announces that; the first symptom
is a permission error on a directory you can write to. So PartCAD asks the
daemon directly, once: it writes a file and has a throwaway container look for
it. A daemon inside this container – Docker in Docker – shares the filesystem
and is fine.
A dev container is usually fine too. When the file is not found and PartCAD is
running in a container of that daemon, it reads that container’s mounts –
the workspace bound in from the host, a volume – and binds each directory from
where the daemon keeps it, at the path it has inside, then asks again. This
repository’s own dev container keeps ~/.partcad and /tmp on volumes for
exactly this reason. PC_DOCKER_MOUNT_SOURCES (here=there pairs, separated
by ;) says the same by hand. Only when there is no such answer – a
DOCKER_HOST on another machine, or a directory the sandbox needs on none of
the container’s mounts – is the daemon one this sandbox cannot use, and PartCAD
says so and uses conda or a virtual environment, both of which stay on this
machine.
That fallback is only ever for a choice PartCAD made. Say pythonSandbox:
docker yourself and it is obeyed: an image that cannot be had, or a daemon
that cannot see your files, is then a failure, because being unable to do what
was asked is not a reason to quietly do something else.
The docker sandbox
The container is a place to run the interpreter, not a place to keep your work. What it can see is whatever PartCAD bind-mounts into it, and that list is kept as short as it can be – on an ordinary machine, two directories:
your home directory, which already contains most of the rest;
the temporary directory, because things land there without asking to be mounted – an ad-hoc command’s generated package, a factory’s intermediate – and one fixed mount is simpler than arranging for nothing to be temporary;
the context root – the package tree the command is working on;
the internal state directory (
~/.partcadby default), which is where the sandbox environments, the caches and the fetched dependencies live;PartCAD’s own installation, because the interpreter over there is handed PartCAD’s scripts by path and has to be able to open them.
Nested directories are mounted once, by the outermost of them, since two views
of one directory leave it undecided which a write lands in. So when the package,
~/.partcad and the installation are all under your home directory – which
is the usual arrangement – those three collapse into it, leaving the home
directory and the temporary one. On Windows even that is a single mount, since
the temporary directory lives inside the user profile; on Linux and macOS it
does not, so there are two. The rest are still named because they are not
always under either: a package on another volume, a system-wide installation, a
file an ad-hoc command was pointed at somewhere else.
That is not tidiness. A mount set that does not vary from one context to the
next is a container that never has to be replaced, which is what lets one
container serve every package you work on and keep serving it after pc
exits.
Everything is mounted writable, your home directory included. Mounting the installation read-only was tried and taken back out: it bought little – a wrapper is read and executed, and what it writes goes to the cache or back over its own protocol – and cost one more thing that could differ between two containers of one image. The isolation worth having here is the container; the mounts exist so that a path a wrapper is handed means something on the other side, and that is a stopgap rather than a security boundary.
On a POSIX host all of them are mounted at the same paths they have
outside, so a path in a log, in an error, in a cached artifact or in a
.frd a solver wrote means the same thing on both sides and nothing has to
be rewritten.
That is not available on Windows and never was: C:\Users\you\.partcad
is not a path a Linux container can have, so a drive letter is mapped the way
Docker Desktop maps it (C:\Users\you becomes /c/Users/you) and
PartCAD rewrites the command line on the way in. So identical paths are a
property of POSIX hosts rather than of the design, and translating them is
machinery PartCAD already has rather than a line it will not cross – worth
knowing before treating “the paths must match” as a constraint on some future
change to how the mounts are chosen.
The container is named after the image and nothing else, so it outlives the
process that started it: the next pc command finds it warm rather than
paying to start one, and only a new version or image tag makes it a different
container. It carries PartCAD’s labels, so pc system prune clears out the
ones a machine has stopped needing.
Because the state directory is mounted rather than copied, pip installs
persist across container restarts and the environment locking, the install
guards and the cache all work unchanged. Sandbox environments are named after
the image that built them, so a package rendered against one image never reuses
what another installed – what pip resolves and compiles depends on the
native libraries underneath it, and two images do not have the same ones.
The remote sandbox
remote makes no assumption that the container can see your disk. The
directories a command needs are packed up and sent to it, and the files it wrote
are sent back. That costs a copy per run, and it buys a sandbox that can be
anywhere – another machine, another architecture, a build farm.
It is served by partcad-service-remote-docker, which accepts those requests,
starts and reuses a container per image, multiplexes callers onto it and retires
it when nobody is using it. That service exists; run it with --host and
--port to say where.
It runs commands, so who may reach it matters. A request names the image,
the requirements and the command, and the sandbox interpreter runs whatever
Python it is handed – on a reachable address that is a shell for anybody who
can reach the port. So it binds loopback by default, and it refuses to start
on any other address without --token (or PC_REMOTE_SANDBOX_TOKEN), which
every request must then carry as Authorization: Bearer <token>. Refused at
start-up rather than warned about: somebody who passed --host 0.0.0.0 is not
going to read the log of a service that came up and appeared to work. Prefer the
environment variable to the flag, since process arguments are readable by anyone
on the machine.
The service owns the environment, and that is the arrangement worth knowing. A
sandbox is a virtual environment on a disk, and for remote that disk cannot
be the caller’s – so the service builds it in a Docker volume, installs into it,
and prepends its interpreter to whatever was asked for. The client never learns
where it is. Guards saying “numpy is installed” belong on the same disk as the
numpy they describe.
Which directories a command needs are worked out from the command: any argument naming a file on this machine contributes the directory it is in, because a script needs the siblings it imports. What a command writes cannot be inferred that way, so a caller producing a file names it, exactly as it does for a container.
Point a client at it with remoteSandbox (or PC_REMOTE_SANDBOX), as
host:port, and give it the service’s token with remoteSandboxToken (or
PC_REMOTE_SANDBOX_TOKEN) where the service was started with one. There is no
default for either: guessing at a service that runs commands is not something to
do on somebody’s behalf.
Off this machine, that has to be encrypted. Every request carries the token
and the package’s own source, so PartCAD refuses to send one to an address that
is not loopback over plain HTTP. Two ways to satisfy it: put the service behind
a TLS-terminating proxy and write https://host:port, or reach it through a
tunnel – SSH, WireGuard, whatever the network already has – and point
remoteSandbox at the near end, which is loopback and stays plain. A
container PartCAD starts on this machine is loopback too, and a certificate
between a process and its own container would secure nothing.
Today the transfer is a whole directory at a time; the intent is to replace that
with a filesystem the container mounts and pulls files through one at a time, at
which point remote becomes as cheap as docker and stops being a trade.
Containers PartCAD manages
Every image PartCAD builds is labelled as its own, and pc system prune
removes the images and containers carrying those labels – never anything else
on the machine. pc system prune --stale removes only what is out of date:
images from PartCAD releases other than this one, and containers nothing is
using.
Third-party images are asked, by convention, to carry the same labels. An image
that does not is still usable; it simply cannot be told apart from the rest of
what you have installed, so pc system prune leaves it alone.
Caching
PartCAD is capable of caching intermediate and final results of all model compilations. This can be particularly useful when working with large models or when scripting languages (like OpenSCAD, CadQuery, build123d, Chili3D or sdf) are used.
Anything PartCAD produces in a sandbox is cached under the environment that
produced it as well as under its own inputs: the interpreter version and the
versions of the CAD libraries installed alongside it. That covers parts and
sketches written as scripts, and equally parts read from CAD files, since the
importer that turns a STEP file into geometry is itself a script in a
sandbox. Moving a package to another Python or Node.js, or to another version of
Chili3D, therefore re-renders rather than serving what the previous environment
built. pc info reports that environment for the objects that have one; an
assembly does not, because it is composed from objects that each carry theirs.
At the moment code-CAD caching is experimental and can be enabled by using the following configuration:
# ~/.partcad/config.yaml cacheDependenciesIgnore: True
Cache tiers
The cache is a hierarchy of tiers, each with its own switch, because what is worth keeping in memory is not what is worth a file, and neither is what is worth a network round trip:
Tier |
Where it keeps things |
Default |
|---|---|---|
|
This process’s memory. Nearest, and lost on exit. |
on |
|
A directory under the internal state directory. Local. |
on |
|
A memcached server, shared by a team or a CI fleet. |
off |
|
An S3 bucket, which outlives all of the above. |
off |
A read walks them in that order and stops at the first hit; a write offers the entry to every tier whose size window accepts it. The two shared tiers are off by default because they need an address that only a deployment can supply:
# ~/.partcad/config.yaml cacheRemote: True cacheRemoteServer: memcached.example.com:11211 cacheS3: True cacheS3Bucket: my-partcad-cache cacheS3Region: us-east-1
Each tier also takes ...MaxEntrySize and ...MinEntrySize to set the
window of object sizes it accepts, and the memcached tier takes
cacheRemoteNamespace and cacheRemoteExpiration. cacheS3 additionally
accepts cacheS3Prefix and cacheS3EndpointUrl, the latter for an
S3-compatible store that is not AWS.
cacheRemote needs nothing installed. cacheS3 needs the aws extra
(pip install -U 'partcad[aws]'); enabling it without that reports an error
naming the package to install and leaves the remaining tiers working. See
Installation for both.
Cache keys
An object is cached under a hash of what produces it: its configuration (less
the keys that only describe it, such as desc), the environment it is built
in, and the files it is built from. A file contributes its size and its content
– all of it up to 1 MB, and the first and last 0.5 MB of a larger one – but
never its modification time, so a fresh clone on another machine produces the
same keys and a shared tier is shared. An edit confined to the middle of a file
over 1 MB that leaves its size unchanged is therefore not seen; run that command
with --cache-bypass.
An object made out of others is keyed on theirs as well: an extrude or
sweep on its sketch, a compound part on its assembly, an assy
assembly on everything it links to. Editing a part rebuilds every assembly that
uses it, and an object with cache: false makes everything built out of it
uncached too.
pc --cache-bypass <command> (or PC_CACHE_BYPASS=1, or cacheBypass: True)
bypasses every tier for one run: nothing is read from a cache and nothing built
is written to one.
The daemon keeps packages loaded between commands. Before each command it
compares the partcad.yaml of the packages the command is about, and of every
package they declare as a dependency, with what it read, by modification time
and size, and reloads a package that changed together with the packages
underneath it. Each package is compared at most once every 15 seconds.
Security
As code-CAD is gaining popularity in the community, the topic of supply chain security and the risk of running arbitrary third-party code is not sufficiently addressed. PartCAD aims to close that gap for open-source software in a way that exceeds anything commercial software has to offer at the moment.
PartCAD is capable of rendering scripted parts in sandboxed environments:
CadQuery, build123d and sdf use Python, and Chili3D uses
JavaScript.
At the moment it is only useful from a dependency management perspective (it allows third-party packages to bring their Python and npm dependencies without polluting your own environments), in the future, PartCAD aims to achieve security isolation of the sandboxed environments. That will fundamentally change the security implications of using scripted models shared online.
Telemetry
Public Repositories
By default PartCAD collects telemetry data to improve the user experience and to help understand how the tool is being used. The data collected includes the following:
What commands are being run?
How much time is consumed by each step?
What errors and exceptions are being raised?
PartCAD uses OpenTelemetry to collect telemetry data. You can disable telemetry by setting the following configuration:
# ~/.partcad/config.yaml telemetry: type: none
Alternatively, you can disable telemetry by setting the following environment variable:
PC_TELEMETRY_TYPE="none"
You can also change telemetry settings using CLI:
pc system set telemetry type none # or, if you want to collect data about PartCAD performance in your organization: pc system set telemetry type sentry pc system set telemetry env <you-org-name> pc system set telemetry sentryDsn <your-sentry-dsn>
How deep the traces go is a separate setting. By default PartCAD reports the operations it names for itself – the process a command is, and the action each object it works on is – and nothing below them:
pc system set telemetry detail actions # the default pc system set telemetry detail methods
methods adds a span for every instrumented method underneath those, which is
what a trace needs to answer “where inside this did the time go”. It is
expensive in a way that is easy to miss: creating one part passes through a
dozen instrumented methods, so a package of eight thousand of them costs a
quarter of a million spans to list. Turn it on while you are looking at
something, not for good.
pc system telemetry info reports both settings.
Private Repositories
If you are systemically using PartCAD in your organization then it makes sense to collect your own telemetry data to understand how the tool is being used in your organization, and to learn how to improve your organization performance.
The only OpenTelemetry backend provider currently supported is Sentry. Create an organization account on Sentry and obtain a DSN key.
# ~/.partcad/config.yaml telemetry: type: sentry sentryDsn: "<your-sentry-dsn>"
Other OpenTelemetry backend providers can be supported on request.
Automation Support
PartCAD allows you to set CLI options and override user configurations specified in
~/.partcad/config.yaml using environment variables. This can be particularly
useful for setting configurations dynamically or in environments where modifying
configuration files is not feasible.
Generally, all of PartCAD’s environment variables are prefixed with PC.
For CLI options, the environment variable prefix depends on the command being used. You can use the –help option to determine the corresponding environment variable for each CLI option.
Here are some examples:
# Equivalent to: pc add part --desc "testing" scad test.scad PC_ADD_PART_DESC="testing" pc add part scad test.scad
Note that, these environment variables will be overridden if the CLI option is specified.
For user configurations, the environment variables are of the format PC_ followed by the
configuration option name in upper snake case (camelCase word boundaries become underscores).
For example, to override the pythonSandbox configuration, you would set the environment
variable PC_PYTHON_SANDBOX.
Note that environment variable names are case-sensitive. Always use uppercase letters
for the PC prefix and the rest of the variable name, as shown in the examples above.
In this case, these environment variables will take precedence over the values specified in
~/.partcad/config.yaml.
Flexible Git Configuration
PartCAD imports packages from git repositories without the git command line tool:
it speaks the git protocols itself, through the libgit2 library that ships with its
dependencies. Nothing has to be installed alongside it for imports to work.
By default, PartCAD uses the system’s Git configuration when importing packages
using git, which it reads from ~/.gitconfig whether or not git itself is installed.
If you want to override these configurations, you can add your
overrides in ~/.partcad/config.yaml as shown below:
# ~/.partcad/config.yaml git: config: "user.name": "John Doe" "user.email": "johndoe@example.com" ...
Cloning over SSH is faster and more reliable because it uses an efficient protocol with lower overhead, supports compression, and maintains stable connections via key-based authentication. SSH avoids HTTPS rate limits, handles firewalls better, and eliminates credential prompts, making it ideal for large repositories or frequent interactions.
Repositories reached over SSH are authenticated with the keys held by the running SSH
agent, and then with the default key files (~/.ssh/id_ed25519, ~/.ssh/id_ecdsa,
~/.ssh/id_rsa). A key protected by a passphrase only works through the agent, since
PartCAD never asks for one: a repository it cannot authenticate to fails with an error
rather than waiting for a prompt that nothing would answer.
If you have SSH keys configured then you can add the following to the ~/.partcad/config.yaml:
# ~/.partcad/config.yaml dependencies: overrides: url: "git@github.com:": "https://github.com/"
Fetching via a Proxy
On many networks a proxy is not a preference but the only route out. PartCAD
reads the usual environment variables, and everything it downloads goes through
them: packages imported from a git repository, packages imported from a
tarball, and the individual files an object pulls with fileFrom: url.
export HTTPS_PROXY=http://proxy.example.com:3128 export NO_PROXY=git.internal.example.com,localhost pc update
HTTPS_PROXY covers https:// remotes and HTTP_PROXY covers http://
ones; the lowercase spellings work equally. NO_PROXY exempts the hosts it
names, which is what an internal git server reachable only without the proxy
needs. A proxy that wants credentials takes them in the URL
(http://user:password@proxy.example.com:3128).
Nothing has to be configured in PartCAD for any of this. A proxy that should apply to git imports alone – rather than to everything this shell runs – can be set as a git configuration option instead, and it takes precedence over the environment:
# ~/.partcad/config.yaml git: config: "http.proxy": "http://proxy.example.com:3128"
Note that a proxy re-terminating TLS presents its own certificate, which has to
be trusted or every fetch fails to verify it. That is a property of the machine
rather than of PartCAD: point SSL_CERT_FILE (and REQUESTS_CA_BUNDLE) at
the proxy’s CA bundle, or install it into the system trust store.
Personally Identifiable Information
The user section in ~/.partcad/config.yaml defines the default personal
and contact details used throughout the system. These details include
the user’s name, email, phone number, company, and address information.
# ~/.partcad/config.yaml user: firstName: <...> lastName: <...> email: <...> phone: <...> company: <...> line1: <...> line2: <(optional)> countryCode: US stateCode: <...> zipCode: <...> city: <...>
Address Configuration
Users can override any details from the user section by specifying shippingAddress and billingAddress separately.
# ~/.partcad/config.yaml user: # Default user details (includes firstName, lastName, email, phone, company, address, etc.) shippingAddress: # Optional, overrides user details for shipping firstName: <(optional)> lastName: <(optional)> phone: <(optional)> company: <(optional)> line1: <(optional)> line2: <(optional)> countryCode: <(optional)> stateCode: <(optional)> zipCode: <(optional)> city: <(optional)> billingAddress: # Optional, overrides user details for billing firstName: <(optional)> lastName: <(optional)> phone: <(optional)> company: <(optional)> line1: <(optional)> line2: <(optional)> countryCode: <(optional)> stateCode: <(optional)> zipCode: <(optional)> city: <(optional)>
Override Behavior
If shippingAddress is not specified, the system will use the user details for shipping.
If billingAddress is not specified, the system will use the user details for billing.
If shippingAddress or billingAddress is provided, it completely replaces the corresponding fields from user.
This setup allows full customization of shipping and billing details, supporting scenarios where items need to be sent to different recipients or addresses.
Parameter Configuration
The configuration file (~/.partcad/config.yaml) allows users to define
reusable parameters, which can be accessed dynamically within the provider configurations.
In ~/.partcad/config.yaml, parameters are stored under a parameters section.
Example:
# ~/.partcad/config.yaml parameters: object_id: <parameter name>: <parameter value>
Object IDs are used to reference different types of objects within a package, such as sketches, parts, assemblies, scenes, interfaces, and providers.
Accessing Parameters in Providers
In the providers section, parameters can be referenced dynamically
using a function get_from_config(), ensuring that sensitive
or reusable values (e.g., API keys, URLs) do not need to be
hardcoded multiple times.
Example:
# ~/.partcad/config.yaml providers: <provider name>: type: <store|manufacturer|enrich> url: <...> parameters: url: type: string default: {{ get_from_config() }}