| FEP-0010 | |
|---|---|
| Type | Core Change |
| Status | Accepted |
| Author(s) | Pieter Hijma @pieterhijma |
| Version | 1.1 |
| Created | 2025-09-23 |
| Updated | 2025-11-08 |
| Discussion | 💬 Discussion FEP-0010: Variant Parts |
| Implementation | n/a |
This proposal introduces variant parts to FreeCAD. Variant parts are parts with with a clear interface that communicate to users what the designer of the part intended the user to vary in terms of parameters. This allows users to introduce different variants of the same part without affecting the original design.
FreeCAD has no good solution for Variant Parts. A Variant Part is a parametric part with the following properties:
- a user can create more than one instances,
- those instances can vary for specific properties that are designated for creating variants, and
- changes in properties that are not designated for creating variants, propagate to all other instances.
In FreeCAD, it is virtually impossible to create parts with these properties. As a result, in FreeCAD, parametric design is limited. It is not possible to reuse or exchange parts independently, and as such, parametric parts are not modular (see these videos [1]). This modularity in parametric designs is important because since FreeCAD is an open source application, seamless sharing and exchange of parts should be central to FreeCAD's mission.
Another reason why this functionality is important, is because parametric design is at the heart of FreeCAD. It uses the subtitle "Your own 3D parametric modeler" prominently on the website, yet, the kind of parametric design introduced by this proposal is currently a weakness of FreeCAD.
FreeCAD has functionality that comes close to true variant parts with various techniques. However, all these options have their issues, as explained in a forum topic with videos [2]. A first option is creating copies of parts. However, with this technique, it is not clear which parameters are designated for variants and a change to all instances of the variant is not possible.
A second option is copy-on-change links. However, this technique is not user friendly and it makes use of hidden references. Propagating a change to all instances is only possible by making it regular links first, which cancels all previously introduced parameterization. Moreover, a hidden copy of the part is maintained and finally, the technique requires a complex administration.
An improvement is the technique of tracked copy-on-change links. However, this has the same issues of copy-on-change links with an even more complex administration for variants, but with the benefit of propagating changes to all instances. However, these updates to the original part cause newly created document objects for each small change with new names and labels, potentially breaking geometry that was built on the tracked copy-on-change link.
Finally, copy-on-change subshape binders use a different way of storing the copy, namely in a hidden temporary document. This has the benefit, that the copy is not stored inside the FreeCAD file and is generated each time, but the temporary document is not fully hidden and sometimes, the user is confronted with a document of which the origin is not clear. Additionally, this technique is not user friendly and relies on hidden references, it has a complex administration for parts as well, and other than tracked copy-on-change links, subshape binders can only reference shapes. This means that the document object subtree is not available to users to make use of, something that is possible with regular links.
All in all, FreeCAD currently has limited support for variant parts, a capability that should be prioritized given its open-source nature and its goal to be a parametric modeler.
Various design directions have been investigated and considered. We first list the various directions and explain below why the current direction is chosen.
Keeping the current functionality is in essence leaving the current state exist and use subshape binders if only shapes need to be referenced, and tracking copy-on-change links for link behavior. However, this is not considered because these techniques have various limitations: Designs that use these techniques are brittle because of the complex administration and two separate ways of doing dependency checks (the regular mechanism and the special mechanism for hidden references). Additionally, these techniques are not user friendly.
Improved dependency checking can remove the need for using hidden references by introducing exposed properties that define for users what properties can be used to vary parts. Two directions are considered: Fine-grained dependency checking is a re-implementation of FreeCAD's dependency checking that defines relations between objects based on the properties. Current dependency checking with special cases adds exceptions for properties that are exposed but keeps the overall existing logic.
This is a re-implementation of dependency checking where dependencies are not recorded between document objects, but between the properties of document objects. This allows us to avoid circular dependencies for exposed properties and it allows improved recomputation where a property change only causes the document objects that depend on that property and not all document objects that depend on any property.
This enhances the current dependency check mechanism and creates exceptions for exposed properties to prevent cyclic dependencies. A drawback of this direction is that the dependency code becomes even more complex and it does not have the additional benefit of improved recomputation efficiency that fine-grained dependency checks offer.
Variants that are shape-based do not expose the subtree of document objects to the user, whereas subtree-based variants do.
Creating a shape-based variant of a part results in a flat part with only a shape without any subtree. A benefit of this approach is that the implementation is relatively simple. However, this implementation cannot handle more complex parts such as assemblies and it is necessary to introduce a new concept of a variant part. For example, creating a variant of an assembly, part, or body cannot show its origin or local coordinate systems because the subtree of the assembly is not incorporated.
Creating a subtree-based variant of a part provides the complete subtree of the part to the user. This allows us to make use of the logic of links, it provides users with the familiar interface of links, and this solution can handle complex parts. A drawback is that the implementation needs to be integrated with FreeCAD's link system.
To create a variant, it is always necessary to duplicate some properties. Copy-based variants maintain a full copy of the variant part whereas intercept-based variants keep the duplication to a minimum and intercept property access.
Copy-based variants maintain a full copy of the variant part and maintain an administration to give the user the illusion that there is no copy. To ensure that the correct dependencies are captured, an extensive administration is required that may go out of sync and causes problems as a result. Another risk is that document objects can be user-defined (for example, in an addon) and it may be the case that the administration is not capable of fully capturing what is required to copy.
This direction aims to minimize what is copied. Instead, it intercepts property accesses and redirects these to temporary data structures. A benefit of this system is that it gives FreeCAD support for variants at a very low level and only one mechanism is needed. Additionally, this system has the potential to be used for concurrency because reads and writes can be captured temporarily. A drawback of this approach is that the code of each property type with each property access needs to be touched.
Keeping the current functionality is not a viable option in my opinion because virtually no users seem to make use of the current mechanisms. The mechanisms are not user friendly and often result in designs that are hard to analyze and understand.
The chosen direction is fine-grained dependency checking with subtree-based and intercept-based variants. Fine-grained dependency checking is chosen because dependency checking will improve in general with:
- a means to refer to properties of parent document objects without triggering cyclic dependencies,
- one mechanism for dependency checking (as opposed to two in the current implementation), and
- remove unnecessary recomputes.
Subtree-based variants are chosen, because users are already familiar with the link interface, it can handle complex parts, and the already complex link logic will go through a new code-review cycle.
The direction of intercept-based variants is chosen because this mechanism allows us to bring FreeCAD variants as core functionality that acts at a low level instead of logic that is built on top of existing logic and has to circumvent limitations of the current system. An additional benefit is that only one mechanism is needed and complex administration problems for maintaining copies are avoided.
In this section we provide the specification for variant parts with fine-grained dependency checking, property intercepts, and providing the full subtree. Fine-grained dependency checking has the following goals:
- Allow users to avoid cyclic dependencies without requiring the use of hidden references.
- Capture the design intent for parametric design.
- Avoid unnecessary recomputes. If the exposed properties are realized using fine-grained dependency checking, we can avoid unnecessary recomputes in some cases.
Property intercepts allows us to act at a very low level of property access, ensuring that variant parts are supported by FreeCAD's core and avoiding full copies that require complex administration.
Subtree-based variants allow users to access the full subtree of the variants. This gives users variants of complex parts with the familiar interface of links.
We first provide the specification from the user point-of-view and from the developer perspective, then we describe the impact on existing subsystems, and finally we discuss backward compatibility issues.
The user obtains two new property types, namely input properties,
properties that have a status flag "input" and exposed properties,
properties that have a status flag "exposed". Marking properties as input in
obj
Only input properties can be exposed properties. The set of exposed properties define the Exposed Property Interface, properties that define how a part should be parameterized for variants (previously called "Application Geometry Interface" [3]). The Exposed Property Interface captures design intent in the context of parametric design.
In addition, links become aware of exposed properties. The exposed properties are incorporated in the link and marked clearly as exposed. Changing one of the exposed properties automatically creates a variant of the object that is linked to and only this variant will be changed. In contrast, changing an input property of the variant or any other internal properties will change all variants of that part.
Finally, the user will experience improved recomputes where a change of
property
Given
From the viewpoint of the developer, dependency checking is changed, FreeCAD acquires input and exposed properties, a new extension to document objects is introduced, and the link code is adapted to exposed properties.
Dependency checking obtains a new implementation where the dependencies are not only based on relations between objects, but the relations are labeled with the property that causes the dependency. This results in fine-grained dependency checking that can prevent unnecessary recomputes. The already existing logic for defining the relations can be reused, but the dependency graph itself contains more information. The recompute logic is changed to incorporate this new information and only recomputes objects that depend on the property change.
For dependency graph
- All properties and special vertex "HEAD" that represents an object in its entirety.
The set of directed edges
- If
$x \in V$ ,$x$ inherits fromApp::PropertyLinkBase,$y \in V$ and$x$ links to$y$ , then$(x, y) \in E$ . - If
$x \in V$ and$y \in V$ ,$x = \text{ HEAD}$ ,$x$ represents object$o$ , and$y$ is a property of$o$ , then$(x, y) \in E$ with the following exceptions:-
$y$ is an output property.
-
- If
$x \in V$ and$y \in V$ ,$x = \text{ HEAD}$ ,$x$ represents object$o$ , and$y$ is an output property$o$ , then$(y, x) \in E$ .
In FreeCAD
Properties obtain a new status flag "Input" which indicates that a property only serves as input to the document object and should not be modified in the recompute phase. More precisely, in the recompute phase, an input property may be written by the expression engine execute phase to make sure that the input properties have the correct value if they are based on expressions. Then in subsequent phases, input properties must not be written, that is, (1) in the recompute phase of the document object that includes the execute phase and (2) in the second expression engine execute phase that updates the values of the output properties.
An example of a dependency graph is listed below: The blue property is an input property and since HEAD has an edge to property Length of which we know for certain that Length cannot be modified by HEAD (so Length does not depend on HEAD), it is possible to link to Length from VarSet which depends on Part.
FreeCAD will obtain a new extension for objects called the Variant Extension. The extension manages the contexts for property intercepts and triggers a recompute of the object that acts as source of the variant. The contexts are pushed onto a stack to ensure that variants can contain other variants, for example in an assembly.
At the lowest level, the property intercept system will check whether there is a variant context on the stack before a read or write to a property is stored in the internal data structure of the property. If this is the case, the reads and writes will happen to that context.
This allows us to do a recompute on an object that acts as source for the variant part and use all of its logic to compute a new shape or outcome for the variant.
App::Link will inherit from the variant extension and needs to be adapted to
take exposed properties into account.
There is impact on document recomputes, property access, and the link system, all discussed below.
Document recomputes will obtain an alternative implementation. This will happen in three phases. First, there will be a runtime check based on a preference that allows users to turn on the new document recomputes. A second phase will be a compile-time check for the new document recomputes. The third and final phase removes the old mechanism.
The dependency tree will have more fine-grained information, recording links between properties. On a property change, only the objects that depend on the property are recomputed. Most likely, no API change is necessary, just implementation.
This is a change that needs to be made once for each property access, for each type of property. Before a property is written or read, it is necessary to check whether a context is available and if the case, the property access needs to be redirected to this context.
Since these intercepts work at a very low level, no API change is needed.
The App::Link system needs to be adapted to support exposed properties.
Links already adopt the properties of the object they link to. Links need to
be aware of exposed properties and trigger a recompute via the variant
extension. The link system is complex code, but with the anticipated changes
to it, the link system will get more code review and the inner workings become
more known within the FreeCAD developer community.
Since this proposal will only add functionality, backward compatibility is not affected. Fine-grained recomputation on older documents will only make the recomputation more efficient, pruning out unnecessary recomputations that could have been omitted in older FreeCAD versions as well. In other words, the result of the old recomputation and the new fine-grained recomputation is equivalent.
An early specification with implementation exists for the idea of variant
parts. The specification focused on VarSets having a central role in
defining the application programming interface in combination with App::Part.
Both these objects would become "special" to support variants. The
specification of this is listed in issue #12531 [4]. This idea is
rejected because it is not general enough. Only App::Part objects with an
exposed VarSet would become variants, while there are many other valid objects
that may need to be variants. An example is a simple cube that cannot contain
a VarSet inside.
The implementation is proposed in PR #12532 [5]. This implementation has been built on top of the existing logic of hidden references, rewriting expressions, and redirecting property access with adding special cases to object identifiers. This implementation brought to light that hidden references do not truly prevent cyclic dependencies as this proposal does, but it merely uses multiple dependency check mechanisms to effectively hide the fact that there are still cyclic dependencies. Because of these limitations, this implementation is rejected.
A proof-of-concept has been created in the context of the FPA program "Research Variant Parts", the results of which are listed on the forum [6]. This proposal is a direct result of that project. Other than what is proposed here, the proof-of-concept in PRs #18412, #19733, and #19735 [7, 8, 9] has:
- the current dependency check with special cases as opposed to fine-grained dependency checks,
- shape-based variants instead of subtree-based variants, and
- property intercept has only been partially implemented (not for all properties).
The implementation of this proposal will happen in two phases. The first phase
will focus on fine-grained recomputation and adding input properties. The
second phase will focus on adding property intercept, exposed properties, the
variant extension, and adapting App::Link.
The implementation will start with a simple test with a VarSet that has two
properties sizeCube1 and sizeCube2 that drive the sizes of two simple cubes
respectively. Then changing the property sizeCube1 should only cause the
first cube to be marked for recomputation and not the second. This test would
currently fail in FreeCAD. As of now, FreeCAD has few tests for recomputation,
but this implementation will provide a test suite for recomputation and
dependency checking.
For input properties, the flag App::Property::Status::User4 will be changed
to App::Property::Status::Input. Please note that User1, User2, and
User3 are already in use for various purposes. The input properties will be
incorporated into the recomputation logic and tests will be provided for this
as well.
The second phase will add the logic for acquiring variant links. This is
composed of adding property intercept, the variant extension, introducing a
flag App::Property::Status::Exposed and incorporating the variant extension
into App::Link.
More background on intercepting property access can be found on the forum [10]. A typical property access for a float property is listed below:
void PropertyFloat::setValue(double lValue)
{
aboutToSetValue();
_dValue = lValue;
hasSetValue();
}
double PropertyFloat::getValue() const
{
return _dValue;
}With property intercepts, this will become:
void PropertyFloat::setValue(double lValue)
{
if (!setInContext<PropertyFloat>(&PropertyFloat::setValue, lValue)) {
aboutToSetValue();
_dValue = lValue;
hasSetValue();
}
}
double PropertyFloat::getValue() const
{
try {
return getFromContext<PropertyFloat, double>(&PropertyFloat::getValue);
}
catch (const NoContextException& e) {
return _dValue;
}
}Most property access functions need this same change. A very strong benefit of this approach is that since property types can only be defined in C++, this change is local to the FreeCAD source and will work for any document object defined in any external workbench.
The variant extension will be roughly like the one introduced in PR #19733
[8] in the form of App::VariantExtension. On execution of a variant
extension, the extension will obtain a topologically sorted outlist of object
PR #19733 introduced a Part::Variant that extended App::VariantExtension
that only supported shape-based variants. However, in the proposed
implementation, App::Link will extend App::VariantExtension. Instead
of property Support in App::VariantExtension of type App::PropertyXLink,
the variant extension will make use of property LinkedObject of type
App::PropertyXLink in App::Link.
In PR #19733, the Part::Variant adopts the exposed properties. In the
proposed implementation that is not necessary any longer because App::Link
already adopts the properties of the linked object. However, App::Link must
treat those properties in a different way.
The user interface of FreeCAD requires almost no change. Linking to an object with exposed properties will automatically become a variant link, so the only change to the FreeCAD user interface is marking properties as exposed. This is already very similar to marking properties for different purposes.
This implementation will obtain a test-suite for each of these different topics as well.
- 1.1 (current) - Separation of concerns between input and exposed properties
- 1.0 - Moved the proposal to proposed
- 0.2 - Add a more precise definition of the dependency graph
- 0.1 - Initial version
- Two videos explaining modularity problems in FreeCAD designs: FreeCAD/FreeCAD#12532 (comment)
- Forum post with videos of the 4 alternatives: https://forum.freecad.org/viewtopic.php?p=786692&sid=3e7a311d0f05b2f10697de4128d9b33f#p786692
- Forum post with video on an interface for geometry: https://forum.freecad.org/viewtopic.php?p=788072#p788072
- Specification of exposing properties VarSets in App::Part FreeCAD/FreeCAD#12531
- PR #12532: Core: Enable exposing VarSets in App::Part
- Forum post with the results of "Research Variant Parts": https://forum.freecad.org/viewtopic.php?p=814501#p814501
- PR #18412, Core: Allow exposing properties: FreeCAD/FreeCAD#18412
- PR #19733, Core: Compute shapes for variants: FreeCAD/FreeCAD#19733
- PR #19735, Gui: Add Part variant logic: FreeCAD/FreeCAD#19735
- Forum post with background on property intercept: https://forum.freecad.org/viewtopic.php?p=812491#p812491
All FEPs are explicitly CC0 1.0 Universal.
