Copy link to clipboard
Copied
Dear forum,
When I try to use an AnchorPoint together with horizontal/verticalTranslation property, it’s ignored. It seems that AnchorPoint.TOP_LEFT_ANCHOR is always used whenever I use a different option: e.g. AnchorPoint.BOTTOM_RIGHT_ANCHOR.
In the example below I tried to use all available anchor points, tried to play with other coordinate spaces, but the result is always the same: as if in UI I set X to 50pt with top-left anchor selected.
main();
function main() {
var doc = app.activeDocument;
var obj = doc.rectangles[0];
var transMatrix = app.transformationMatrices.add({horizontalTranslation: 50});
obj.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.TOP_LEFT_ANCHOR, transMatrix);
}
However, with all other properties:
clockwiseShearAngle
counterclockwiseRotationAngle
horizontalScaleFactor
verticalScaleFactor
it works as expected
Am I doing something wrong? Am I missing something? Or, is that just a bug?
Regards,
Kasyan
P.S. Forgot to mention: I am on InDesign CC 2014 (2nd release) for Windows.
Hi Kasyan,
There would be a lot to say here but I lack time to explain in depth, so just keep in mind that the from parameter of the transform method is nothing but a temporary location which provides the origin of the transformation to be applied. This origin plays an important role in SCALING and ROTATION effects but it has no impact on the TRANSLATION component.
The purpose of myObj.transform(inSpace, fromOrigin, withMatrix, replacingCurrent) is to change the transformation state of myObj. As t
...Copy link to clipboard
Copied
Hi Kasyan,
I don't know exactly what you expect with a horizontal translation.
The whole object will be moved by 50 pt in x-direction. As expected.
Regardless of the anchor point you are choosing.
Uwe
Copy link to clipboard
Copied
@Uwe No, the object is moved to x=50 -- not by 50 pt in x-direction
Here's an example to illustrate what I mean:
1. The starting point -- a rectangle x=0, y=0, w=100, h=50 -- all measurements are in points.
2. Let's scale it by 50% using top-left anchor point.
main();
function main() {
var doc = app.activeDocument;
var obj = doc.rectangles[0];
var transMatrix = app.transformationMatrices.add({horizontalScaleFactor: 0.5});
obj.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.TOP_LEFT_ANCHOR, transMatrix);
}
The rectangle is scaled down relative to the top-left anchor point.
3. Now I change the anchor point to bottom-right.
obj.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.BOTTOM_RIGHT_ANCHOR, transMatrix);
And this time the rectangle is scaled down relative to the bottom-right anchor point.
The change of the anchor point is respected by script.
Now let's play with translation (aka moving).
4. Let's move it horizontally to x = 50 using top-left anchor point.
main();
function main() {
var doc = app.activeDocument;
var obj = doc.rectangles[0];
var transMatrix = app.transformationMatrices.add({horizontalTranslation: 50});
obj.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.TOP_LEFT_ANCHOR, transMatrix);
}
So far so good: I get the same result as if I did in inDesign manually.
5. Now I change the anchor point to bottom-right.
obj.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, AnchorPoint.BOTTOM_RIGHT_ANCHOR, transMatrix);
However, I get the same result as before for the top-left anchor (the screenshot for step 4 above) no matter whichever anchor point I use. This problem happens only with horizontalTranslation and verticalTranslation.
I expect the result like so:
@tpk1982 I can't use the Marc's function as it is because it moves an object in both directions: X and Y. But I also need to move it only X and only Y. Trying to figure out how to adjust it to my needs but it seems to be beyond my level.
Anyway, thank you both.
Regards,
Kasyan
Copy link to clipboard
Copied
Hi Kasyan
The translation is relative to the current position of the object and therefore it's irrelevant which point you refer to.
Here's a quick experiment,
Stand with your heals against a wall. Measure 1 meter from the wall, mark that point and line up your heals to that point. Mark the point where your toes end.
Go back to the same wall. This time measure a meter from your toes. Mark that point and line up your toes with that mark. Mark the point where your heals start.
The 2 sets of 2 marks should line up.
I can't understand why you expect them to be different or whats the different between moving feet or a blue rectangle.
Same point that Uwe made above just a bit more verbose.
Regards
Trevor
Copy link to clipboard
Copied
Thank you Trevor for chiming in.
After reading Kasyan's last reply I already wrapped my head around an alternative description what a horizontal translation is doing.
Now I came up with a sentence quoted from Wikipedia:
In function graphing, a horizontal translation is a transformation which results in a graph that is equivalent to shifting the base graph left or right in the direction of the x-axis.
That description usually can be seen as an equivalent for the move() method using the by-argument.
I suppose it is not exactly the same, because of other factors due to the transformations a spread or a page has been undergone.
The x-axis defined implicitly in the transformation matrix may not be in parallel with the horizontal ruler.
For that I have to re-read Marc Autret's findings at:
Indiscripts :: Coordinate Spaces & Transformations in InDesign — Chap.1-3
Kasyan,
in case this is unclear:
1. If you want to change the proxy points in the UI for any reason, do it with the property transformReferencePoint of the layoutWindow.
2. The values for horizontal and vertical transformation in a transformation matrix are always expressed as points.
Regradless of the measurement systems set for the rulers.
Uwe
Copy link to clipboard
Copied
Hi guys,
Thank you for your feedback!
Probably I wasn’t clear enough about what I’m trying to do.
Here’s the script I’m writing.
The main idea is to find frames of a certain object style using a number of “conditions” and apply a number of transformations to them.
A condition may be either an exact value or a range of numbers; it can also be positive or negative – is/is not.
For example, in the screenshots above, I want to find frames with “My style” applied whose X position is 0 and Y is not 10-20 and scale them down by 50% both horizontally and vertically using the top-left anchor point.
Now let’s see how I implemented this in code:
function TransformFrame(frame) {
var transMatrix = null;
try {
if (set.cb_widthSet) {
// Not implemented yet
}
if (set.cb_heightSet) {
// Not implemented yet
}
if (set.cb_xSet) { // This doesn't work properly
transMatrix = CatenateMatrix(transMatrix, "horizontalTranslation", set.xSet);
}
if (set.cb_ySet) { // This doesn't work properly
transMatrix = CatenateMatrix(transMatrix, "verticalTranslation", set.ySet);
}
// The following part works as expected
if (set.cb_xScaleSet) {
transMatrix = CatenateMatrix(transMatrix, "horizontalScaleFactor", set.xScaleSet / 100);
}
if (set.cb_yScaleSet) {
transMatrix = CatenateMatrix(transMatrix, "verticalScaleFactor", set.yScaleSet / 100);
}
if (set.cb_rotationSet) {
transMatrix = CatenateMatrix(transMatrix, "counterclockwiseRotationAngle", set.rotationSet);
}
if (set.cb_skewSet) {
transMatrix = CatenateMatrix(transMatrix, "clockwiseShearAngle", set.skewSet);
}
frame.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, anchorPoint, transMatrix);
}
catch(err) {
$.writeln(err.message + ", line: " + err.line);
}
}
function CatenateMatrix(transMatrix, property, value) {
if (transMatrix == null) {
transMatrix = eval("app.transformationMatrices.add({" + property + ":" + value + "});");
}
else {
transMatrix = transMatrix.catenateMatrix(eval("app.transformationMatrices.add({" + property + ":" + value + "});"));
}
return transMatrix;
}
If a check box is on, a new matrix is created for the parameter and is sent to the CatenateMatrix function which either creates a new matrix, if it doesn't exist yet, or catenates it with already existing one. At the end of the TransformFrame function all the transformations are applied in one go.
Here in the code the Set object gets parameters from the dialog box.
set.cb_xSet -- the 'set X' check box is on/off
set.cb_ySet -- the 'set Y' check box is on/off
set.xSet -- the X coordinate where to move the object (only if the 'set X to' check box is on)
set.ySet -- the Y coordinate where to move the object (only if the 'set Y to' check box is on)
and so on.
With horizontalTranslation and verticalTranslation the 2nd 'from' parameter doesn't work: the top-left point is always used. Note: this is a required parameter! And I didn't see that this parameter isn't applicable for hor/ver translation in the reference so I think it's a bug.
In UI if you select a frame and set X, say, to 50pt first using the top-left point and then using the bottom-right point, you'll get totally different results:
So the script should work in the same way.
Now I see that I have to write a function for moving X, Y and setting Width, Height on my own to handle all the possible 9 anchor points.
Kasyan,
in case this is unclear:
1. If you want to change the proxy points in the UI for any reason, do it with the property transformReferencePoint of the layoutWindow.
2. The values for horizontal and vertical transformation in a transformation matrix are always expressed as points.
Regradless of the measurement systems set for the rulers.
1. I tried this, but it changes the proxy point on the screen only, but transformation is made using top-left point as before (move, transform methods)
2. The script works in points only because my client doesn't use other units.
Regards,
Kasyan
Copy link to clipboard
Copied
Hi Kasyan,
I don't know, if you already mentioned this:
What is your version of InDesign you are testing this script with?
Uwe
Copy link to clipboard
Copied
Hi Uwe,
No, I didn't mention the version. I tested it on Mac in CC 2015.3 and CS3, and on PC in CC 2014: the same problem in all these versions.
Regards.
Kasyan
Copy link to clipboard
Copied
Hi Kasyan,
It ain't no bug. (I think)
The translateMatrix is relative. Move x distance from and as such doesn't make a diddlysquats difference from which point you reference it. What it looks like you want to do is move to translateMatrix does not do that.
obj.move([30, 50]) will move the object to
obj.move([], [30, 50]) will move the object by
If you really want to use the translateMatrix method then you first have to calculate your position at the desired anchor and work out how much to translate by to get to.
For example you have a horizontal line 50 points wide staring at 100 and ending at 150 and you want to move the line to point 30.
If you want the left side to be at point 30 then you apply a translation of -70 if you want the right side to move to 20 you apply a translation of -130. The anchor points will be irrelevant for the translation but necessary for the calculation.
Or you can use the move method the move method obj.move([30, 50]) with the anchor point set up.
HTH
Trevor
Copy link to clipboard
Copied
Hi Trevor,
Instead of using transform or move methods, I'm going to write a function which changes the frame's bounding box making the necessary calculations depending on the proxy point selected in the dialog box.
— Kas
Copy link to clipboard
Copied
Hi guys,
As it was previously mentioned, transformation matrices are additive. However, I point you to the documentation for the transform method:
undefined transform (in:CoordinateSpaces, from:Varies, withMatrix:Varies, replacingCurrent:Varies, [consideringRulerUnits:Boolean=Boolean])
The interesting parameter, in regards to this discussion is "replacingCurrent":
Transform components to consider; providing this optional parameter causes the target's existing transform components to be replaced with new values. Without this parameter, the given matrix is concatenated onto the target's existing transform combining the effect of the two. Can accept: MatrixContent enumerator, Array of MatrixContent enumerators or Long Integer. (Optional)
Copy link to clipboard
Copied
Hi Kasyan,
There would be a lot to say here but I lack time to explain in depth, so just keep in mind that the from parameter of the transform method is nothing but a temporary location which provides the origin of the transformation to be applied. This origin plays an important role in SCALING and ROTATION effects but it has no impact on the TRANSLATION component.
The purpose of myObj.transform(inSpace, fromOrigin, withMatrix, replacingCurrent) is to change the transformation state of myObj. As the initial state, it considers the matrix mapping of myObj relative to inSpace, M, then it computes something like M×T, where T basically represents the desired transformation (withMatrix) but the actual components are internally adjusted to take into account fromOrigin and replacingCurrent. The flags in replacingCurrent allow to specify whether some components must be treated as either new values or operands. Finally, the affine map of myObj is updated accordingly.
It would be wrong to think that the TRANSLATION components reflect the location of the object in the ruler space. When you need to reach a certain location through a translation, you always have to calculate the [tx,ty] parameters as offsets. This is what is done here Move object by "Reference Point" (as mentioned by tpk1982.)
@
Marc
Copy link to clipboard
Copied
Hello, it's me again, guys.
Thanks a lot for your help. I followed your recommendations and here’s where I’ve got so far:
function TransformFrame(frame) {
var x, y, width, height,
transMatrix = null,
bounds = GetBounds(frame);
try {
// Set width and/or height
if (set.cb_widthSet || set.cb_heightSet) {
if (set.cb_widthSet && set.cb_heightSet) {
width = set.widthSet;
height = set.heightSet;
}
else if (set.cb_widthSet && !set.cb_heightSet) {
width = set.widthSet;
height = bounds.height;
}
else if (!set.cb_widthSet && set.cb_heightSet) {
width = bounds.width;
height = set.heightSet;
}
SetWidthHeight(frame, width, height);
}
// Move X and/or Y
if (set.cb_xSet || set.cb_ySet) {
if (set.cb_xSet && set.cb_ySet) {
x = set.xSet;
y = set.ySet;
}
else if (set.cb_xSet && !set.cb_ySet) {
x = set.xSet;
if (anchorPoint == AnchorPoint.BOTTOM_LEFT_ANCHOR || anchorPoint == AnchorPoint.BOTTOM_CENTER_ANCHOR || anchorPoint == AnchorPoint.BOTTOM_RIGHT_ANCHOR) {
y = bounds.bottom;
}
else if (anchorPoint == AnchorPoint.LEFT_CENTER_ANCHOR || anchorPoint == AnchorPoint.CENTER_ANCHOR || anchorPoint == AnchorPoint.RIGHT_CENTER_ANCHOR) {
y = bounds.halfHeight;
}
else {
y = bounds.top;
}
}
else if (!set.cb_xSet && set.cb_ySet) {
y = set.ySet;
if (anchorPoint == AnchorPoint.TOP_RIGHT_ANCHOR || anchorPoint == AnchorPoint.RIGHT_CENTER_ANCHOR || anchorPoint == AnchorPoint.BOTTOM_RIGHT_ANCHOR) {
x = bounds.right;
}
else if (anchorPoint == AnchorPoint.TOP_CENTER_ANCHOR || anchorPoint == AnchorPoint.CENTER_ANCHOR || anchorPoint == AnchorPoint.BOTTOM_CENTER_ANCHOR) {
x = bounds.halfWidth;
}
else {
x = bounds.left;
}
}
MoveToConsideringAnchor(frame, [x, y]);
}
if (set.cb_xScaleSet || set.cb_yScaleSet || set.cb_rotationSet || set.cb_skewSet) {
if (set.cb_xScaleSet) {
transMatrix = CatenateMatrix(transMatrix, "horizontalScaleFactor", set.xScaleSet / 100);
}
if (set.cb_yScaleSet) {
transMatrix = CatenateMatrix(transMatrix, "verticalScaleFactor", set.yScaleSet / 100);
}
if (set.cb_rotationSet) {
transMatrix = CatenateMatrix(transMatrix, "counterclockwiseRotationAngle", set.rotationSet);
}
if (set.cb_skewSet) {
transMatrix = CatenateMatrix(transMatrix, "clockwiseShearAngle", set.skewSet);
}
frame.transform(CoordinateSpaces.PASTEBOARD_COORDINATES, anchorPoint, transMatrix);
}
}
catch(err) {
$.writeln(err.message + ", line: " + err.line);
}
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------
function SetWidthHeight(frame, width, height) {
frame.resize(CoordinateSpaces.INNER_COORDINATES, anchorPoint, ResizeMethods.REPLACING_CURRENT_DIMENSIONS_WITH, [width, height, CoordinateSpaces.INNER_COORDINATES]);
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------
function MoveToConsideringAnchor(obj, xyDest) {
var coordinates = CoordinateSpaces.PASTEBOARD_COORDINATES,
xy0 = obj.resolve(anchorPoint, coordinates)[0],
xy1 = obj.resolve([xyDest, anchorPoint], coordinates, true)[0],
dx = xy1[0] - xy0[0],
dy = xy1[1] - xy0[1];
obj.transform(coordinates, [0, 0], [1, 0, 0, 1, dx, dy]);
}
//--------------------------------------------------------------------------------------------------------------------------------------------------------
function CatenateMatrix(transMatrix, property, value) {
if (transMatrix == null) {
transMatrix = eval("app.transformationMatrices.add({" + property + ":" + value + "});");
}
else {
transMatrix = transMatrix.catenateMatrix(eval("app.transformationMatrices.add({" + property + ":" + value + "});"));
}
return transMatrix;
}
In fact I used Marc’s functions as a starting point and modified them at my own discretion. They work but I don’t understand how they work. I read, and re-read (a few times) Marc’s Coordinate Spaces Transformations in InDesign, but understood almost nothing. The problem is that in my youth I studied at art school where they taught me to draw nude models – a totally useless skill nowadays. Though officially we had math, geometry, etc. lessons, but our teachers considered them a waste of time and used us (students) for all kinds of useful work – moving furniture, hanging curtains, etc. – since the school was in the process of remodeling then.
That’s why I ask so many stupid questions here on the forum; please make allowance for me.
I think I did as you, guys, advised me. However, for some reason, with complex transformations the script brings a different result from the same steps done manually in InDesign.
For example, let’s rotate the rectangle 45° and then skew it 15° using the top-left proxy.
Manually I get this
By script I get this
Do you have any idea why this happens? Maybe I’m missing some parameter?
I tried to do transformations separately, in two steps, -- without catenation matrices -- but got exactly the same result as with catenation.
Regards,
Kasyan
Copy link to clipboard
Copied
Hi Kasyan,
Matrix product is not commutative, so concatenation order matters. In particular, SHEAR-then-ROTATE is not the same as ROTATE-then-SHEAR, as shown below:
var mxID = app.transformationMatrices.add({matrixValues:[1,0,0,1,0,0]}),
mxShearRotate = mxID.shearMatrix(15).rotateMatrix(45), // SHEAR x ROTATE
mxRotateShear = mxID.rotateMatrix(45).shearMatrix(15); // ROTATE x SHEAR
alert( mxRotateShear.matrixValues.toSource()===mxShearRotate.matrixValues.toSource() ); // => false
You can also check than calling transform() with mxShearRotate on one hand, and with mxRotateShear on the other hand, do not lead to the same result:
Now in your code you perform CatenateMatrix(…) in the following order: 1. scaling, 2. rotation, 3. shear (optional resize and translation are done first.)
Unfortunately this is not the "canonical transformation order" which InDesign's GUI relies on. When you transform something straight from the application, matrix parameters are always changed in a way that preserves the implicit SCALING×SHEAR×ROTATION×TRANSLATION order (which I abbreviate S×H×R×T in my PDF.)
As a consequence, even if the user first rotate the object 45°, then skew it 15°, the underlying transformation matrix remains SHEAR×ROTATION (in accordance with the canonical scheme.) Make the test and you will see that there is no difference in InDesign between rotate-first-then-skew and skew-first-then-rotate.
So, if you want to perform transformations with respect to the natural order that the user experiments in the GUI, then you have to compute your matrix in the S×H×R×T order.
@+
Marc
Copy link to clipboard
Copied
Hi Marc,
Thank you very much for your clarification. I'll adjust the script according to your advice.
Regards,
Kasyan
Copy link to clipboard
Copied