Associative dimensions is an advanced feature that is introduced to allow a dimension to be associated with a snapper. When a dimension is associated with a snapper, the dimension will be updated automatically to match the new position and dimension of the associated snapper, when the associated snapper is moved, stretched, etc. The video below provides a quick demonstration of the basic behaviors of a dimension that is associated with snappers.
Basic Concepts
Associative dimensions work on the basis of associative locations (ASLocation
), which are basically reference points on a snapper, which a dimension can associate itself with. The blue dots in the image below shows the associative locations of a snapper that derives the locations from its bound (ASBoundBoxLocations
).
Besides bounds, a snapper can also derive its associative locations from other information. For example, the image below shows a snapper that derives its associative locations from an APath2D
(ASPath2DStaticLocations
).
When a user associate a dimension with a snapper, the system will find the associative location on the snapper that is closest to the position of the dimension, and associate the dimension with the closest location. For example, in the following image, the left leg of the dimension is associated with the top-right point.
However, a leg of a dimension does not have to fall exactly on an associative location in order for it to be associated with the location. Instead, the leg can be associated at an offset from the location, as shown in the following image, where the left leg is associated at an offset from the bottom-middle point. When the snapper is moved, the left leg will remain associated with the snapper, but at a constant offset from the bottom-middle point.
Whenever a snapper that is associated with a dimension is moved, rotated, stretched, etc., the position of the legs of the dimension will be updated to match the new position and dimension of the associated snapper. Please refer to the ASMeasureEventViewer
class for more information regarding the update mechanism.
Supported Dimension Sub-Classes
By default, all sub-classes that extend from StaticDraw3DMeasure
, ChainDraw3DMeasure
, and DrawRadialMeasure
are associative. Override allowAssociate()
in DrawMeasure
and return false in order to disable the associative capabilities of a sub-class.
/** * Allow this measure to be associated with snappers? */ extend public bool allowAssociate() { return false; }
Associative Locations
By default, a snapper derives its associative locations from its bound. The associative locations of a snapper can be customized by overriding associativeLocations()
in Snapper
. The method can also be overridden to return null in order to make a snapper non-associative, giving the dimension snapper a small performance advantage when inserting/stretching.
Do note that we should always get the already instantiated ASBoundBoxLocations
using aslBoundLocations()
, instead of instantiating a new instance of ASBoundBoxLocations
, in order to optimize the performance of the system. The same applies to other types of ASLocations
.
/** * Associated snap location using 3D bound (Snapper default). * There can be only one. (not storing anything). */ public ASLocations aslBoundLocations() { static ASBoundBoxLocations locs(); return locs; } /** * Associative locations. * By default, the bound of this snapper is used as the default associative locations. * Override and return null in order to make a snapper non-associative. */ extend public ASLocations associativeLocations() { if (allowAssociativeDimensions) return aslBoundLocations(); return null; }
ASBoundBoxLocations
should work well for most of the snappers. However, for snappers with complex 2D shapes, more advanced ASLocations
are needed in order for associative dimensions to work properly. For example, the GIFs below provide a comparison of the behaviors of the associated dimension when the CalcAreaSnapper
derives its associative locations from its bound, to the behaviors when it derives its associative locations from its APath2D
.
The example below shows the methods that need to be overridden in order for a snapper to derive its associative locations from an APath2D
, instead of a bound.
/** * Associative locations. */ public ASLocations associativeLocations() { if (allowAssociativeDimensions) return aslPath2DStaticLocations(); return null; } /** * 2D associative path. */ public APath2D associative2DPath() { return path; }
Besides bounds and paths, there are other ASLocations
sub-classes that are implemented to cater to different 2D shapes, which are listed out as follows.
ASLocations -- cm.core.assocDims |---ASArcBoundLocations |---ASBlockLocations |---ASBoundBoxLocations |---ASGeometryLocations | |---ASDynamicGeometryLocations | | |---ASLinesLocations | | |---ASPath2DLocations | | | |---ASPath2DDynamicLocations | | | |---ASPath2DStaticLocations | | | | |---ASMultiPath2DStaticLocations | | | | | |---ASPathGroupStaticLocations | | | | | |---ASShape2DLocations | | |---ASPointsLocations | |---ASStaticGeometryLocations | | |---ASArcLocations | | |---ASCircleLocations | | |---ASEllipseLocations | | |---ASRectLocations |---ASGraphLocations | |---ASGraph2DLocations |---ASSymLocations
For example, the ASCircleLocations
, as its name implies, generates associative locations based on a circle.
/** * Associative locations. */ public ASLocations associativeLocations() { if (allowAssociativeDimensions) return aslCircleLocations(); return null; } /** * Associative circle. */ public Circle associativeCircle() { return circle(); }
In addition to ASLocations
for different geometrical shapes, there are some more complex ASLocations
, namely, ASGraphLocations
and ASSymLocations
.
ASGraphLocations
, as its name implies, generates associative locations based on the given graph. If the associative graph is a GInstance
, then associative locations will be generated for each and every sub-graph in the GInstance
. ASGraphLocations
is an advanced ASLocations
that should be used with cautions, as it might result in performance issues if the associative graph is too complex. The image below shows the associative locations of a snapper that employs ASGraphLocations
.
/** * Associative locations. */ public ASLocations associativeLocations() { if (allowAssociativeDimensions) return aslGraphLocations(); return null; } /** * Associative Graph. */ public Graph associativeGraph() { if (!graph) build2D(); return graph; }
For ASGraphLocations
with complex associative graphs, you are encouraged to cache the associative graphs in order to optimize the performance of the system, as shown in the example below.
/** * Cached associative graph. */ private Graph _cachedAssocGraph : copy=reference, stream=null, ignore modify notice; extend public Graph cachedAssocGraph=(Graph graph) { return _cachedAssocGraph = graph; } extend public Graph cachedAssocGraph() { return _cachedAssocGraph; } /** * Release 2D model. */ public void release2D() { super(..); cachedAssocGraph = null; }
/** * 2D invalidated (sent from space). */ public void invalidated(dirty2D flag2D) { super(..); updateAssocsAfterInvalidated2D(flag2D); }
/** * Associative locations. */ public ASLocations associativeLocations() { if (allowAssociativeDimensions) return aslGraphLocations(); return null; } /** * Associative Graph. */ public Graph associativeGraph() { if (!cachedAssocGraph) buildAssocGraph(); return cachedAssocGraph; } /** * Build the cached associative graph. */ extend public void buildAssocGraph() { AssocLayerBuffer layerBuffer(); systemDrawFeatures(layerBuffer, null); cachedAssocGraph = layerBuffer.assocGInstance(); } /** * Update associations after invalidated (2D). */ extend public void updateAssocsAfterInvalidated2D(dirty2D flag2D) { if (flag2D >= dirty2D.all) cachedAssocGraph = null; }
Similarly, for ASSymLocations
, associative locations are generated based on the given cmsym.
// Class: DataSymbol public ASLocations associativeLocations() { if (allowAssociativeDimensions) { if (sym()) return aslSymLocations(); else return aslBoundLocations(); } return null; }
When the scale of a scalable snapper changes, the offset of dimension legs from the closest associative locations on the snapper will need to be scaled as well, in order for the legs to remain associated with the correct points on the snapper. For this purpose, the assocDeltaTransform()
from Snapper
can be overridden to return the scale that needs to be applied to the offsets, as shown below for a DWG snapper.
// Class: DwgSnapper public Transform assocDeltaTransform() { return scaleTransform(getScale()); }
Association Manipulation
A few methods are also introduced to make it easier for developers to manipulate associations. The first method is reassociateAssociations()
, which breaks existing associations with a snapper, and re-associates back with new closest associative locations on the same snapper. This method accepts an ASReassociateEnv
as an argument, where ASReassociateEnv
contains flags that can be used to control the behaviors when re-associating.
// File: cm/core/assocDims/associativeDimensions.cm public void reassociateAssociations(Snapper snapper, ASReassociateEnv env=null)
Sometimes, a user action might cause certain associations to become invalid. For example, when a user breaks the first path member of a custom shape snapper (DrawShapeSnapper
) into 2 segments, a new path member is added as the second path member, and the indices for the subsequent path members are increased by 1. In this case, a dimension that is originally associated with the first and second path members, needs to be updated to associate with the first and third path members instead. For this purpose, reassociateAssociations()
can be used to update the associations, as shown below.
// Class: DrawPathAddPointVessel extend public void selected() { ... if (m as APath2DLineTo) { ... path.members.insert(memberIndex + 1, m2); pathChanged(candidate, path); updateAssocsAfterPathChanged(candidate); } else if (m as APath2DArcTo) { ... path.members.insert(memberIndex + 1, m2); pathChanged(candidate, path); updateAssocsAfterPathChanged(candidate); } ... }
extend public void updateAssocsAfterPathChanged(Snapper snapper) { reassociateAssociations(snapper); }
The second one is transferAssociations()
, which is normally used to transfer associations from a snapper to other snappers. This method dissociates dimensions from the original snapper, before associating the dimensions with the closest associative locations on the new candidate snappers. This method also accepts an ASTransferEnv
as an argument, where ASTransferEnv
contains flags that can be used to control the behaviors when transferring associations.
// File: cm/core/assocDims/associativeDimensions.cm public void transferAssociations(Snapper original, Snapper[] candidates, ASTransferEnv env=null)
For example, when a custom shape snapper (DrawShapeSnapper
) is sliced, the original snapper is removed from the space, and replaced with 2 new custom shape snappers. In this case, the associations need to be transferred from the original snapper to the new sliced snappers, as shown below.
// Class: DrawShapeSliceAnimation extend public void createSlicedSnappers(DrawShapeSnapper orig, APath2D pa1, APath2D pa2) { ... space.undoableInsert(dssOne); space.undoableInsert(dssTwo); updateAssocsAfterSliced(orig, [dssOne, dssTwo]); space.undoableRemove(orig); ... }
extend public void updateAssocsAfterSliced(DrawShapeSnapper oriSnapper, DrawShapeSnapper[] newSnappers) { transferAssociations(oriSnapper, newSnappers.:Snapper[]); }
There are a lot more methods that can be employed to manipulate associations, which are available in cm/core/assocDims/associativeDimensions.cm.
Comments
0 comments
Please sign in to leave a comment.