At other CAD companies where my colleagues and I have previously worked, every aspect of the implementation was a closely guarded secret. At Onshape, one of our core values is transparency, and that includes helping our users (well, those who are interested) understand not just how to use Onshape, but also how it works. At the end of the day, understanding how Onshape thinks can make a power-user more productive than someone who sees a magical black box. For the first “Under the Hood” post, I’d like to talk about how collaboration works in Onshape.
When you look at a product, you can tell if a piece of functionality was designed from the start or tacked on as an afterthought. You could add wings and a propeller to a car and if you’re very good, it might even get off the ground, but if you want something that flies well, you have to build a plane. So even at the very beginning, as we were designing Onshape’s overall architecture, we knew we wanted collaborative CAD. Before the system that would become Onshape could sweep or chamfer or create Assemblies, it could already support multiple users simultaneously editing a part.
To understand how we do this, we have to start with how Onshape represents users’ data. I’ll illustrate with a single Part Studio; a Document with several interrelated tabs adds a layer of complexity that I won’t discuss here. We are careful to distinguish several types of data:
- The User Interface (UI) state – e.g., selection, camera view, current tab
- The Part Studio definition – e.g., feature list, part names and colors, import data
- Regeneration results – the “b-rep” (bodies, faces, edges, etc), triangles for display, regen errors
The UI state generally doesn’t persist (except for things like named views). The regeneration results are cached, but they can always be rebuilt from the definition. The Part Studio definition is what we store in the database and that is where collaborative editing happens.
What is a Microversion?
For a given Part Studio, at each point in time, the definition is stored as an eternal, immutable object that we internally call a microversion. Whenever the user changes the Part Studio definition, (e.g., edits an extrude length, renames a part, or drags a sketch), we do not change an existing microversion, but create a new one to represent this new definition. The new microversion stores a reference to the previous (parent) microversion and the actual definition change. In this way, we store the entire evolution of the Document: this is accessible to the user as the Document history, allowing the user to reliably view and restore any prior state of an Onshape Document.
This is how we might represent a Part Studio with two workspaces in the database. The workspaces branched at Version 1. Each microversion only stores the changes relative to its parent.
These definition changes are designed to be very robust: a change stored in a microversion is intended to apply to the parent microversion, but could be applied to a different one. For instance, if the change is “change the depth of Extrude 1 to 4 in,” as long as the original feature exists (identified using an internal id, so it can be renamed), this change can be applied. As a result, changes coming simultaneously from multiple collaborators can simply be applied to the latest microversion without interfering with each other. Traditional CAD systems based on saving an ever-changing memory state into files cannot do this, even if run on a remote server or with a PDM system attached: the data itself has to be collaborative.
An interesting consequence of collaborative editing is that undo/redo has to be implemented more carefully than in a one-user-at-a-time system. Suppose we’re working on the same Part Studio and I create an extruded hole, and then you create a fillet. If I then hit the undo button, I expect my hole to disappear, and not your fillet (this is obvious in hindsight, but our first implementation of undo did not do this, leading to painful interference between collaborators). This means that undo cannot simply take the Part Studio back to an earlier microversion: a microversion with your fillet but without my hole does not exist prior to my hitting undo and has to be created by applying the inverse of my change (“delete the hole”) on top of the latest microversion. Restoring to an earlier microversion via the change history does not have this behavior and is a complementary capability to undo – reliably restoring a prior state but possibly removing collaborators’ edits to do so.
1. User 1 adds a hole
2. User 2 adds a fillet
3. User 1 can undo the hole without affecting the other user’s fillet
When simultaneously working on the same Document, users are able to undo only their individual actions, preserving the work of their peers.
Branching and Merging
The same principle of microversions and definition changes allows Onshape to do Branching (workspaces) and Merging. Every workspace keeps track of its latest microversion, and as a result, the microversions form a tree structure (the version graph is a simplified view of this). This is why when you look at the histories of two workspaces, they are the same from Document creation to the branching point and different afterwards. When the user merges from one workspace into another, we look for the changes that are in the “from” workspace and have not been merged into the “to” workspace and create a microversion for the “to” workspace with those changes.
This is the Document containing the Part Studio whose microversion tree is illustrated above. In the histories of the two workspaces (Workspace 1 on the left and Workspace 2 on the right), only the most recent change is different.
This mechanism is similar to (and was inspired by) what the popular software version control system git does, with microversions corresponding to git “commits” and workspaces corresponding to branches. A conceptual difference is how we handle conflicts: if you’ve used git (or any other version control system for text or code), you’ve seen a merge conflict that happens when two branches have modifications to the same piece of text–the user is left in an invalid state until the conflict is fixed. In Onshape, we try to never block the user: if a dimension is modified in two different ways on two workspaces, in a merge, the “from” workspace will win. Merge conflicts should never happen.
More Collaboration Tools
Onshape’s Compare tool lets you explore the differences between workspaces or versions of a document. Here, we are looking at the differences between Workspace 1 and Workspace 2.
Basing Onshape on immutable microversions also makes for a great foundation for other collaboration tools: those we already have, such as the Follow Mode or the Compare tool, as well as those we are developing for the future. It also has benefits beyond just collaboration abilities: because old microversions are never modified, data integrity is better preserved, and having a history of changes allows us to debug exactly how a Document came to be when a user has a problem or when we detect a problem through our logs.
So collaborate fearlessly – your data is saved and always restorable!