- Mirror Tools
- Using Mirror Tools for Your Own Extension
- Implementing Mirror for Your Own Snappers
- Understanding Mirror
- Interfaces to Use
- MirrorEnv
- Other Considerations for Mirroring
- Example Mirror Implementations
This page will show how to implement mirroring for snappers and using existing tool animations to mirror them.
Note that "mirroring" here can include more than just a graphical mirror.
Mirror Tools
There are two existing mirror animations in core, the MirrorFlipAnimation
and MirrorLineAnimation
. The code for these can be found in cm/abstract/draw/...
In the FIKA and extensions you may find these icons in the toolbox:
These are the MirrorFlipAnimation
(horizontal and vertical) and MirrorLineAnimation
mentioned above respectively.
To how these animations work and look like, refer to Mirror Tools.
Using Mirror Tools for Your Own Extension
You can simply add the mirror tools to your extension toolbox using an AnimationLimb like this:
// Within a container: limb AnimationLimb mFlip(pkg=#"cm.abstract.draw", "MirrorFlipAnimation", image=icon("draw3D/mirrorFlip")); limb AnimationLimb mLine(pkg=#"cm.abstract.draw", "MirrorSelectionAnimation", image=icon("draw3D/mirrorLine"));
To make it so your snappers can be mirrored, you have to overwrite allowMirror()
in Snapper
and implement the necessary interfaces as necessary for an accurate mirror. Refer to the section below.
You may also just extend from these or make your own animations because the core of mirroring is in cm/core/mirrorEnv. The animations are mainly for graphical and selection purposes to determine the snappers the user wants to be mirrored.
Implementing Mirror for Your Own Snappers
- Override
allowMirror()
in your snapper to return true:
/** * Allow Mirror. */ public bool allowMirror() { return true; }
- Either:
- Have FIKA open and use the mirror tools as mentioned in the above section to try to mirror your snapper.
- Add mirror tools to your own library as mentioned above.
- Use functions to spawn the animation in cm/abstract/draw/mirrorAnimation and cm/abstract/draw/mirrorFlipAnimation
- If your snapper is symmetrical enough it will work perfectly fine when you try to mirror it. If not, read on for how mirror works and look at examples at the bottom of the page for a better understanding.
Understanding Mirror
Mirroring a Snapper
The mirror operation is in fact a combination of rotation and position transforms applied to the snapper. The goal of the mirror operation is for the snapper to appear equivalent to a “true” mirror relative to the mirror line specified by the user.
The expected “mirrored” result of a snapper depends on its own appearance’s symmetry. The default mirror operation will work out of the box if your snapper fits these two conditions:
- The mirror point (defined by your snapper's
mirrorPoint(env)
) falls on the snapper's line of symmetry. The default mirror point is the center of the snapper's bound box, this should work for most rectangular snappers. This may not necessarily be the same as its origo point. - The snapper is symmetrical about the y-axis from its mirror point (i.e. looks same on the left and right side).
Refer to FIKA Chair example and FIKA Lateral Storage example to illustrate the symmetry for these snappers.
The FIKA 90deg and 120deg worksurfaces example show how to handle snappers that are symmetrical, but just not about the y-axis.
Non-symmetrical require some special attention. The operation can be customized with the provided interface to achieve the mirror effect. The FIKA Pedestal with Bookcase example is an example where a left-right fix would be required. Another example would be a table that is rounded on its left edge and straight on its right edge will need those to be flipped after mirror.
Mirroring a Set of Connected Snappers
When the mirror animation is applied to a set of connected snappers, the following happens (in sequence):
1. All connections are broken.
2. The snappers are picked up from space.
3. The mirror operation is performed on each snapper individually (as per the logic above).
4. The snappers are dropped back into space in their mirrored positions and rotations.
Look out for any of the snapper’s position/property changing upon its disconnected()
, pickedUp()
or dropped()
methods that may not be intended for mirroring purposes.
Also, be wary of snappers whose position/rotation is dependent on other snappers. You may refer to the FIKA panels example to see how junction snappers are mirrored relative to the panel frame snappers.
Interfaces to Use
If your snapper is asymmetrical or has components that are asymmetrical you will need to override one or more of the following interfaces to have it work correctly. Remember that you might have to do more than just make a graphical mirror, so certain properties and connectors might also need changes.
You may find the following in Snapper to override cm/core/snapper.cm:
- extend public bool allowMirror()
- extend public void beforeMirror(MirrorEnv env)
- extend public bool mirror(MirrorEnv env)
- extend public void afterMirror(MirrorEnv env)
- extend public angle mirrorAngle(MirrorEnv env)
- extend public point2D mirrorPoint(MirrorEnv env)
- extend public angle angleOfSymmetry()
To see when beforeMirror/mirror/afterMirror are called in the mirroring process, see cm/core/mirrorEnv.cm. Important differences are that beforeMirror()
is called before anything really happens, and mirror is called after snappers are disconnected and picked up.
MirrorEnv also has hooks that you can register, find them in cm/core/mirrorEnv.cm:
- public void registerPreMirrorHook(function(MirrorEnv env) hook)
public void removePreMirrorHook(function(MirrorEnv env) hook) - public void registerPostMirrorDropHook(function(MirrorEnv env) hook)
public void removePostMirrorDropHook(function(MirrorEnv env) hook) - public void registerPostMirrorHook(function(MirrorEnv env) hook)
public void removePostMirrorHook(function(MirrorEnv env) hook)
Pay extra attention to asymmetrical snappers and snappers that have lots of custom code that run on pickup/drop. The internal mirror implementation in MirrorEnv.cm will disconnect, pickup, mirror, drop, then reconnect snappers.
MirrorEnv
Aside from implementations in the snappers themselves, the 'main' mirror code can be found in cm/core/mirrorEnv.cm.
For the Mirror Tools that we have in core (see above major section for more info), the animations for these tools simply use a vessel that makes use of MirrorEnv
to mirror snappers in cm/abstract/draw/mirrorAnimation.cm.
/** * Mirror Apply Vessel. * - The vessel that actually mirrors stuff. */ public class MirrorApplyVessel extends Vessel {
The important part about the MirrorEnv
code is how to use it. You must:
- Construct the env with the space containing your snappers.
- CLEAR snappers in
env.toMirror
and pass in all the snappers you want to mirror. - Pass in the line2D you want to mirror the snapper(s) across (can be across the snapper itself).
- Call
env.mirror()
You may make your own vessel/animations/functions for mirroring using this.
Other Considerations for Mirroring
When working on implementing mirroring for snappers it is sometimes not sufficient to use the examples above. Here we list some examples of custom implementations outside of the FIKA extension.
Scaling
In some cases, we need to apply a scaling to the model on top of just mirroring. This could be when it is an asymmetrical snapper that needs to be scaled in order for it to look correct. This example is a snapper that consists of primitives which we scale individually. An example usage would be in cm/core/dwg/dwgSnapper.cm :
/** * Build 3D. */ public void build3D(FetchEnv3D env) { ... if (mirrored) mirrorPrimitives(all);
...
}
/** * Mirror all primitives in DWG */ extend public void mirrorPrimitives(Primitive3D[] prims) { for (p in prims, index=i) prims[i] = p.scale(localBound.center, (-1, 1, 1)); }
Path Snapper
For snappers which consist of a list of points or a path it is important to also mirror these points.
An example of this the mirror()
method in cm/core/abstract/draw/calcArea.cm, where we are applying the same transform as in cm/core/snapper.cm but for every part of the existing path.
Always Readable
It is not always correct to mirror snappers the standard way. For images, spreadsheets or other text-based snapper you want them to be readable after mirror. In that case we would want the angle to stay constant like the implementation in cm/core/msOffice/excel/excelSnapper.cm.
/** * Mirror angle */ public angle mirrorAngle(MirrorEnv env) { return 0deg; }
Example Mirror Implementations
You may look at the Snappers in FIKA to find examples of how mirror is implemented. This section has a few examples with explanations.
FIKA Chair
The chair is symmetrical about its y-axis (green arrow). Just return allowMirror()
to true and it's good to go!
FIKA Lateral Storage
The storage snapper's mirror point is not at its origo point. However, if you consider the axes from its mirror point (visualized by the dotted arrows), it is also symmetrical about the y-axis. No additional operations required for this snapper too.
FIKA Pedestal with Bookcase
Unlike the Lateral Storage, the Pedestal with Bookcase is not symmetrical about its y-axis, as it has a "open" bookcase shelf on one side. To achieve the desired mirrored effect, the bookcase shelf has to open on the other side. The pedestal draws its graphics and parts based on a simple bool field, "openLeft".
Since the bookcase is invalidated after mirror, we simply flip this boolean in mirror and make sure to handle it properly when drawing the graphics and getting parts.
public class FOBookcaseTower extends FOFloorCabinet { /** * Open on left or right side? */ public bool openLeft; .... /** * Get main part number. */ public str partNumber() { return super() # (openLeft ? "-L" : "-R"); } .... /** * Mirror. */ public bool mirror(MirrorEnv env) { openLeft = !openLeft; super(..); return true; } }
FIKA 90 and 120 Degree Worksurfaces
The 90deg and 120deg worksurfaces are in fact both symmetrical, but just not about the y-axis. The 90deg worksurface (shown below) has a line of symmetry along its diagonal. Hence, in this case, a correction needs to be made to the mirror angle (depends on the angle of rotation required to make it look like the mirrored version). The default mirror point falls on the line of symmetry, so no correction would be needed for mirror point.
public class FO90DegCornerWs extends FOCornerWorksurface { ... /** * Allow Mirror. */ public bool allowMirror() { return true; } /** * Return the new angle after mirrored according to env. */ public angle mirrorAngle(MirrorEnv env) { return super(..) - 90deg; } ... }
Similarly, the 120deg worksurface (shown below) also has a line of symmetry along its diagonal and also requires the mirror angle correction. However, the default mirror point (center of the bound box) does not fall on the line of symmetry, hence the mirror point was defined to a convenient point on the line of symmetry.
public class FO120DegCornerWs extends FOCornerWorksurface { ... /** * Allow Mirror. */ public bool allowMirror() { return true; } /** * Return the new angle after mirrored according to env. */ public angle mirrorAngle(MirrorEnv env) { return super(..) + 60deg; } /** * Return the point in space coordinates to mirror and rotate around. */ public point2D mirrorPoint(MirrorEnv env) { double b = dimension("b"); return toSpace((b, 0)); } ... }
FIKA Panel
The panels are made up of the panel frame snapper and junction snappers on either end of the panel. As stated above, the connections between these snappers would be broken and the snappers will be mirrored independently.
The panel frame snapper itself is quite straight-forward to mirror as it is symmetrical about its y-axis. The junction snapper on the other hand, its symmetry is based on its junction type (each junction type gives the snapper a totally different appearance and bound!). Hence its mirror operation must be customized per junction type.
Mirror Point: The default mirror point is the center3D (i.e. center of the bound box). However, this doesn’t work so great with non-rectangular snappers such as the Y-type or V-type junctions. Hence, for junctions, the mirror point is defined to be the center of its shape path.
Mirrored angle: As shown in the image above, the symmetry of each junction type is different. And the symmetry does not just depend on the appearance of the junction but also the attachPoint()
to the panels (shown with black arrows). This makes junction snappers special in the sense that they have an additional requirement for its attach points to realign with the panels after its mirrored. To correct this, an offset angle is added to the default mirrorAngle()
as determined by the MirrorEnv
and the value depends on the junction type.
public class FOPanelJunctionSnapper extends PanelJunctionSnapper { ... /** * Return the new angle after mirrored according to env. */ public angle mirrorAngle(MirrorEnv env) { angle res = super(..); if (junction as FOJunction) res += junction.mirrorAngleOffset; return res; } /** * Return the point in space coordinates to mirror and rotate around. */ public point2D mirrorPoint(MirrorEnv env) { if (APolyline2D polyline = shape.?toPolyline) { return toSpace(polyline.geometricCenter); } return super(..); } } public class FOJunction extends PanelJunction { ... /** * Return the angle offset for mirror operation */ extend public angle mirrorAngleOffset() { return 0deg; } } public class FOEJunction extends FOJunction { ... public angle mirrorAngleOffset() { return 180deg; } ... } /* mirrorAngleOffset values of other junction types: Y-type: 60deg X-type: default (0deg) T-type: default (0deg) V-type: 60deg L-type: 90deg */
Comments
0 comments
Please sign in to leave a comment.