- Characteristics
- Snapper Uses
- Unnecessary Streaming of Snapper Fields
- States
- Creating Your Own Snapper
- Using Overly Complex Localbounds
- Overcomplicating Snappers and Connectors
- Using Snappers with Internal References to Other Snappers
- Using Material Fields Without copy=reference
Snapper is the base class for interactive objects in CET. Snappers exist in a space and is streamed in a world.
What is a snapper? The rule of thumb is that physical, real-world objects that interact with other physical objects can be implemented as snappers. However, not all physical objects need to be snappers.
Characteristics
- Associated to a space.
- Can be manipulated via core properties and animations.
- Can interact with other snappers via connectors.
- Can contain product data like options, 2D/3D graphics, parts, item tags, categories etc.
- Can be tagged using part tags.
A common misconception is that every interactive, graphical object in CET is a snapper. This is not true - those objects may be vessels, components, animations, or simply some core object.
Snapper | Vessel | Component | Animation | |
---|---|---|---|---|
2D/3D graphics | ✔ | ✔ | ✔ | ✔ |
Core Properties | ✔ | ✔ | ✔ | ✔ |
Connectors | ✔ | ✖ | ✖ | ✖ |
Exists in a Space | ✔ | ✔ | ✖ | ✔ |
Streamed | ✔ | ✖ | ✔ | ✖ |
Snapper Uses
As Physical Objects
Floor, wall, character, Fika panel frame, stairs, plants.
As Non-physical Objects
Block, view clip, part tag, data field, panel type elevation, picklist.
Non-snappers
I tem tags, Fika panel tiles, remover tool.
What Should Be a Snapper?
Snappers are light-weight objects that are space-aware and can be used to represent real-world objects in space using 2D and/or 3D graphics. Deciding if a part of a real-world object should be made a snapper in CET is not always easy. It often boils down to the requirements provided, and the approach that makes the most amount of sense.
Snappers come with a lot of great features out of the box, which is fantastic, but using them in this manner can add a layer of complexity to your implementation.
What Should I Do Instead?
You should instead consider using a regular class to handle part-specific code. The class could also contain methods that return the 2D graph and 3D model of the part, which can then be added to the main snapper's graphics. If these parts ought to be selectable, as shown in the image below, you should consider using PickSurfaces.
Or maybe try using Components.
Unnecessary Streaming of Snapper Fields
When Should a Field Be Streamed?
All fields in a snapper class, by default, are streamed.
Only parameters that aren't constant and are absolutely necessary in defining the snapper (system) should be streamed. Fields that do not fall under the aforementioned category should be marked stream=null
.
Here's what the _graph
field in the GraphSnapper
class (cm/core/graphSnapper.cm) looks like:
/** * 2D graph. */ private Graph _graph : copy=reference, stream=null, ignore modify notice, public readable;
States
When debugging, you may see snappers described as being in a certain state. Here are some basic states a snapper can be in:
State | Meaning |
---|---|
suspended | Snapper is suspended in the undo chain, i.e. removed from space by an undo remove operation. |
doomed | Snapper is in its space's dead pool, i.e. scheduled to be removed from the Space. |
active | Snapper exists in a space and is not suspended or doomed. |
alive | Snapper is either active or suspended in a space. |
animating | Snapper is part of an animation currently in the animation stack. |
Creating Your Own Snapper
There are many extensible snapper subclasses available in the core and abstract packages. Sometimes it may make more sense to extend from one snapper than another. For example, to create a Worksurface snapper that interfaces with data in Catalogs, cm\abstract\dataOffice\DataWorksurface.cm may be a good option. It is recommended to explore the available options and pick the most suitable snapper for your needs.
The following example shows how cm\core\ModelSnapper.cm may be extended to show some 3D graphics in space.
use cm: core, core3D, geometry; /** * My custom snapper. */ public class MySnapper extends ModelSnapper { /** * Build 3D. */ public void build3D(FetchEnv3D env) { Box3D myBox((0, 0, 0), (1, 1, 1)); myBox.setMaterial(pineMaterial3D); model = myBox; } } { MySnapper snapper(); // Create an instance if (Space space = mainSpace()) { // Get main space space.undoableInsert(snapper); // Insert into space } }
Running the code above will give this result in 3D view:
Using Overly Complex Localbounds
There is a chunk of bound-related code in the snapper.cm file. These methods are used by the core system for various purposes. In this section, we will look at localBound()
which is called very frequently by the core system of CET. The localBound()
method is expected to be super fast, and it is always recommended to return a box that is built based on the basic parameters of snappers; width, depth, and height. It does not have to be precise. It just has to include all areas of the snapper.
What If I Want the User to Be Shown a More Precise Bound?
It is sometimes necessary to have more control over what is shown to the user. Let's look at a worksurface from the Fika extension. This worksurface can have its elevation adjusted through its elevation manipulator.
By default, the localBound()
starts from the origin of the snapper (0, 0, 0), which is its position on the floor, all the way up to the top-right edge of the snapper.
This is what the user sees:
localBound()
, and start dumping additional calculations in it. You should instead override userLocalBound()
, and return a more precise bound, which in this case, will result in the user seeing something like this instead:
Handling Fixed Cm3D Models
We have seen instances where the localBound()
of a snapper is retrieved from a cm3D file that is the only model the snapper has. Since it's fixed, it makes little sense to find the bound of the model each time the localBound()
method is called.
localBound()
that is in line with our recommendations.
Overcomplicating Snappers and Connectors
Snappers and connectors are two highly important elements in CET. The base code for these elements has been refined and improved since their inception by members of the core team, resulting in a very robust system that is capable of handling highly complex configurations. Unfortunately, it is common for developers to ignore the existing methods in snapper.cm and connector.cm when trying to implement a new connector-related feature.
It's highly possible that someone else faced the same problem before, and decided to have it added to the core system.
We often see developers adding a lot of code to the trySnap methods to achieve the desired snapping behavior. Most of the time, similar results can be achieved just by overriding and making small changes to some of the methods in snapper.cm. If you are not sure about the methods that ought to be used, please post a question on the developer forum.
Using Snappers with Internal References to Other Snappers
Snappers are supposed to be linked to each other through connectors. Connectors act as a bridge between snappers allowing for communication between snappers.
Read more about internal references to other snappers here:
Using Material Fields Without copy=reference
When a material is registered, it is tied to an ID, which can later be used for easy retrieval of materials. To preserve the identity of materials, declarations of material fields should be followed by copy=reference
. This will ensure that the material fields in both the original and copied snapper are the same, as they both occupy the same location in memory.
copy=reference
is not used, each time the snapper is copied, a new Material instance with the same values as the original will be created. This will result in a number of different material instances with the same ID.It is crucial to have copy=reference
for all fields of type Material as it only makes sense for all changes to the main material instance to be propagated to all materials that share the same ID.
//changeMaterial3D("gold", greenTransparencyMaterial3D); //changeMaterial3D("gold", goldMaterial3D); /** * Change material3D. */ private void changeMaterial3D(str id, Material3D material3D) { if (Material m = material(id)) m.material3D = material3D; }
copy=reference
is used for a Material
object in a snapper, any change made to the object (like the code shown above) will be propagated to all copies of the snapper.
Comments
0 comments
Please sign in to leave a comment.