Introduction
Controlling the visibility of a snapper can get a little confusing at times, especially since we have two coexisting concepts to handle this problem: layers and categories. We usually deal with snapper visibility when there exists the need to control the appearance of a snapper depending on the active ViewMode
.
The ability to control the visibility of snappers paves way for users to work on different categories of snappers in the same drawing easily. For example, we might want a light snapper to be shown in both normal, and electrical view modes. When users want to work only on electrical snappers, they would be able to switch over to the electrical view mode, and all snappers (and sub-snappers) will be hidden from their view apart from the electrical snappers (and sub-snappers).
Layer
A layer is simply a symbol that we use for visibility control. For example, if my snapper should appear in the electrical view mode, I could add the #electrical layer to the existing set of layers.
Category
A category on the other hand is a set of layers. By default, snappers get their layer expressions from categories. We will look at how categories are used in snappers later.
symbol{} s(); s << #electrical;
Layer Expressions (cm.geometry.layerExpr)
The LayerExpr class is a generic layer boolean expression.
LayerExpr -- cm.geometry - AlwaysVisible - LayerAnd - LayerNot - LayerOr - LayerSet - NeverVisible - SingleLayer
Note: For the sake of simplicity, we will focus on the SingleLayer as well as the LayerSet classes in this guide.
We use layer expressions to evaluate a layer or a set of layers using the eval method.
/** * Evaluate 'layerSet' and return the boolean result. */ final public bool eval(LayerSet layerSet) : inline { const bool empty = !this or this == emptyLayerSet or !layerSet; return empty or internalEval(layerSet); }
internalEval()
method is called, which is implemented by all the LayerExpr subclasses.
SingleLayer
A SingleLayer layer expression consists of only one layer, of course.
/** * Layer. */ public symbol layer;
This layer is used in internalEval()
to determine whether the layer is part of the layerSet that the method is fed with.
/** * Evaluate 'layerSet' and return the boolean result. */ package bool internalEval(LayerSet layerSet) { return !layerSet or layer in layerSet; }
So if the single layer is #electrical, and the layerSet does not contain the #electrical layer, then internalEval()
will return false. We can create a single layer using the following function:
/** * Return a single layer expression. */ public LayerExpr layer(symbol layer) { return layerManager.putLayerExpr(SingleLayer(layer, layerManager)); } layer(#electrical);
LayerSet
A LayerSet layer expression on the other hand consists of a set of layers. This set of layers is used in internalEval()
to determine whether the set consists of layers that are part of the layerSet that the method is fed with.
/** * Evaluate 'layerSet' and return the boolean result. */ package bool internalEval(LayerSet layerSet) { return !layerSet or this.intersects(layerSet); }
The intersects method returns true if any of the layers in this.layerSet
exists in the layerSet that the method is fed with. So if the layer #electrical exists in both layerSets, then true will be returned.
Snapper Visibility Types
In this section, we will look at three common visibility-related requirements.
- Complete Snapper Visibility - Show/hide the entire snapper.
- Sub-Snapper Visibility - Show/hide a part of the snapper.
- Material Selector - Change the material of (a part of) the snapper.
Complete Snapper Visibility
In snapper, you'll find two methods that allow us to control the visibility of snappers in 2D and 3D. These methods allow us to control the visibility of the whole snapper.
/** * Visibility 2D. */ extend public LayerExpr visibility2D() { return categoryVisibility(); } /** * Visibility 3D (similar to visibility 2D) */ extend public LayerExpr visibility3D() { return categoryVisibility(); }
These methods return a layer expression that will allow the system to determine when the snapper should be shown and hidden. We will look at how that's done when we explore view modes.
Using a Single Layer to Control Visibility
Let's first try to get our favorite snapper, the Snowman to appear only in the Architectural View Mode using the two visibility methods shown above.
To do this, we need a SingleLayer layer expression, and as discussed earlier, an expression of this type can be created using the layer()
function.
/** * Visibility 3D (similar to visibility 2D) */ public LayerExpr visibility3D() { return layer(#architectural); }
There is a simpler way of achieving the above using widening converters.
/** * Visibility 3D (similar to visibility 2D) */ public LayerExpr visibility3D() { return #architectural; // widening covert to single layer }
Using a Set of Layers to Control Visibility
/** * Visibility 3D (similar to visibility 2D) */ public LayerExpr visibility3D() { return layerSet({#architectural, #normal}); }
The layerSet function takes in a set of layers (a set of symbols), and returns a LayerSet, which is exactly what we need here. Now, the Snowman snapper will appear both in the Normal, and the Architectural view modes.
Just like single layers, we can use widening converters to simplify the code to create a layer set.
{ LayerExpr expr; // str[], str{}, symbol[] and symbol{} all convert to LayerSet expr = ["normal", "architectural"]; // 8.0 expr = {"normal", "architectural"}; // 8.0 expr = [#normal, #architectural]; expr = {#normal, #architectural}; }
Sub-Snapper Visibility
We might not want to hide every part of a snapper all the time. The methods shown above would not help us if we wanted the Snowman's nose to only be visible in some modes, as they define layers for the entire snapper. If you need to have better control over what's shown and hidden in a snapper, you can set layers on primitives, and graphs, on top of the snapper visibility layers.
Primitive3D nose = cache3D(myNoseMaterial, 0) { point n0 = f0 - (0, hr + extra, 0); Primitive3D nose = Cone3D(n0, n0 + (0, -0.2, 0), hr*0.25, 0); nose.rotate(f0, (1, 0, 0), 10 deg); nose.layer = layer(#architectural); if (myNoseMaterial) nose.setMaterial(myNoseMaterial.material3D(env)); result Instance3D(nose); };
We have set the layer of the nose primitive3D to a SingleLayer consisting of the #architectural symbol, so it will be hidden in the Normal View Mode.
Material Selector
We use material selectors to set different materials to snappers depending on the active view mode. One common use of this is to make snappers transparent in the electrical view mode. The snapper, of course, shouldn't stay transparent when the user switches back to the normal view mode. We can do the following to get the Snowman's nose to be green in colour in the normal view mode, and blue in the architectural view mode.
MaterialSelector3D selector(); selector << option3D(plainBlueMaterial3D, layer(#architectural)); selector << option3D(plainGreenMaterial3D, layer(#normal)); selector.finalizeSelector(); nose.setMaterial(selector);
View Modes
We have seen how a snapper's visibility can be easily controlled by overriding the visibility2D()
/visibility3D()
methods, and how selecting a different view mode affects a snapper's visibility. How does a view mode decide, when it's active, if a snapper should be shown or hidden? You guessed right. Using layer expressions, of course!
We looked at the eval
method in the layer expressions section that compares a layer/layerSet in the LayerExpression object to a layerSet argument that is passed in.
In the ViewMode class in cm.core, you'll find a layerSet field which, for each view mode, will be populated with all the layers that it ought to have. This layerSet object is passed in as an argument to the snapper's LayerExpr's eval()
, effectively allowing the ViewMode to decide if a snapper's layers are compatible with its own layers.
I Want To Know Exactly How View Modes Show and Hide Snappers…
In space.cm, in the get()
method, we first create a filter.
/** * Get snappers. */ extend public Snapper{} get(Object search, double r=0, Snapper{} snapperSet=null, SnapperFilter snapperFilter=null, View view=null, bool filterViewModes=true, bool filterSpaceVolumes=true) { SnapperFilter filter = createViewFilter(view, snapperFilter, filterViewModes, filterSpaceVolumes); GeometricFinder finder = geometricFinder(search, r, filter); return bsp ? bsp.get(finder, snapperSet) : null; }
The view is created using the createViewFilter()
method.
/** * Create a filter on a view. */ extend public SnapperFilter createViewFilter(View view, SnapperFilter filter, bool filterViewModes, bool filterSpaceVolumes) { view = findView(view); if (view) { if (filterViewModes and filterSpaceVolumes) return CombinedFilter(filter, LayerSetSnapperFilter(view.layerSet), SpaceVolumeSnapperFilter(view.volumeLayer)); ... } return filter; }
If filterViewModes
is true, we create a LayerSetSnapperFilter, and we pass in the view's layerSet as an argument to the constructor. The view's layerSet method returns the viewMode's layerSet, so we are basically passing in the active viewMode's layerSet as an argument to the filter.
At the end of the get()
method, we return bsp.get()
. We get snappers from individual nodes in the bsp, and for each snapper, we first check if the filter accepts it.
/** * Returns true if the filter accepts 'snapper'. */ public bool accepts(Snapper snapper) { if (!snapper) return false; if (!layerSet) return true; // Visible in 2D LayerExpr visibility = snapper.visibility2D(); if (visibility.eval(layerSet)) return true; // Visible in 3D visibility = snapper.visibility3D(); return visibility.eval(layerSet); }
If the layer expression evaluation returns true, the snapper is added to the snapper set.
Testing
Simply start the "Visibility Debugger" animation, and click on the snapper you're working on. All the necessary information will be shown in the compilation buffer, as shown below. More information regarding the visibility debugger tool can be found here: Visibility Debugger
Comments
0 comments
Please sign in to leave a comment.