# Origin of shapes in Applescript

I am writing a script that sets chairs evenly around a circular table.
I used the “And Gate” shape for this but the origin appears to be at top left of shape, but not for all objects. I am setting the chairs in a circle, so would prefer to have the origin on the shape centreline so the maths is not diabolical.

PS this image I have manually located the chairs. The resultant applescript I am finding it hard to generalise the algorithm

Sketching it in JavaScript for Automation (which, unlike AppleScript, has Math.sin and Math.cos functions in the standard library), you could position the absolute centre simply by deducting half the shape width from the x position and half the shape height from the y position.

This would allow you to generate something like this:

from code of this kind:

``````(function () {
'use strict';

// positionAbsByCentre :: Shape -> (Real, Real) -> IO ()
//        -> (Real, Real)
function positionAbsCentre(shp, lstCentreXY) {
var lstSize = shp.size(),
lstOrigin = [
lstCentreXY[0] - lstSize[0] / 2,
lstCentreXY[1] - lstSize[1] / 2
];

return (
shp.origin = lstOrigin,
lstOrigin
);
}

// positionRelRadialCentre :: Shape -> (Real, Real) -> Real -> Real
//        -> IO() -> (Real, Real)
function positionRelRadialCentre(shp, lstOriginXY, rDegrees, rDistance) {
var radians = (Math.PI / 180) * rDegrees;

// Absolute position by centre
return positionAbsCentre(
shp, [
]
);
}

// radiallyPlacedAndRotated :: Shape -> Int -> Int -> Real ->
//        (Real, Real) -> Real -> Real -> IO () -> (Real, Real)
shp, total, index, rStartRotn, lstOrigin, rDistance
) {
var rDegrees = index * (360 / total);

return (
shp.rotation = rStartRotn + rDegrees,
);
}

// N copies of a named shape at given size
function circleOfShapes(oCanvas, intShapes, strShapeName, lstOrigin,
rDistance, lstSize, rStartRotn) {

var graphics = oCanvas.graphics;

return range(0, intShapes - 1)
.map(function () {
var oShape = og.Shape({
name: strShapeName,
size: lstSize
});

return (
graphics.push(oShape),
oShape
);
})
.map(function (shp, i) {
shp, intShapes, i,
rStartRotn, lstOrigin, rDistance
);
});
}

// [m..n]
// Int -> Int -> [Int]
function range(m, n) {
return Array.apply(null, Array(n - m + 1))
.map(function (x, i) {
return m + i;
});
}

var og = Application('OmniGraffle'),

ws = og.windows,
w = ws.length ? ws[0] : undefined;

return circleOfShapes(
w.canvas(), // canvas of front window
8, // number of shapes
"AndGate", // name of shape
[250, 250], // centre point [X, Y]
120, // distance from centre point to shape centres
[50, 50], // Width, Height of shapes
-90 // rotation of top shape
);

})();

``````

Hi

thanks for this feedback; I’m restricted to Applescript as I call it from Filemaker Pro.

However I’m trying to see if I can use your approach to use the centroid/centreline, so thanks for that tip.
I’ll post a solution if I get it to work

Being unused to javascript I have some difficulty understanding you code but I will extract what I can.

if FM applescript actions allow you to use ‘do shell script’ you could run a js script as an osascript command line

(nested string quotation might prove burdensome)

I have made some progress and now can draw this

The deep blue chair is where it should be located, but I cannot get the property origin of the shape or its ID to move it to its proper location.

Can anyone give me an example code:
both of these statements fail

tell application id “com.omnigroup.OmniGraffle6”
tell canvas of front window
set shapeRef to (make new shape with properties {name:“AndGate”, textSize:{1.0, 0.8}, draws shadow:false, size:{42.0, 42.0}, text:{alignment:center, font:perFont, text:perINIT}, textRotation:90, rotation:tAngle, origin:{xPos, yPos}, thickness:0, textPosition:{0.0, 0.1}, vertical padding:0, fill color:{0.0, 1.0, 1.0, 0.304984}})
#set shapeID to (origin of properties of shapeRef)
#set itemOneProps to properties of shape 1
end tell
end tell

It would be helpful to see a little more of your code.

You need to assign a list of two numbers to the shape.origin property

something like:

``````  set someShape.origin to {50, 100}
``````

Where are you deriving the values of that {rX, rY} pair ?

Hi

which is easily moved manually to its correct location, but Applescript should be able to do it.
The line outer endpoints appear to exactly (but may be a wrong interpretation) coincide with the centroid of the chair.

I’m happy to share the code:
property perFont : “GillSans”
property Seat : 42
property Gap : 3
property seatSpace : Seat + Gap

set memberCount to 15
set tPerim to memberCount * (seatSpace)
set tRadius to tPerim / (2 * pi)

set attList to {“GBa”, “JMi”, “PMi”, “DMa”, “SMi”, “PDo”, “RVi”, “JMi”, “PMi”, “DMa”, “SMi”, “PDo”, “RVi”, “JMi”, “PMi”, “DMa”, “SMi”, “PDo”, “RVi”, “JMi”, “PMi”, “DMa”, “SMi”, “PDo”, “RVi”}

tell application id “com.omnigroup.OmniGraffle6”
tell canvas of front window

``````	# draw circular table
set xOrigin to 150
set yOrigin to 250
set tDiam to 2 * tRadius
set pointList to {0, xOrigin, yOrigin}
make new layer at end with properties {name:"Table"}
--	select layer id 2
make new shape with properties {name:"Circle", textSize:{0.8, 0.7}, draws shadow:false, size:{tDiam, tDiam}, origin:{xOrigin, yOrigin}, textPosition:{0.1, 0.15}}

# now draw seats
# - set coords of centre of table
set xtCentre to xOrigin + tRadius
set ytCentre to yOrigin + tRadius

make new layer at end with properties {name:"Seats"}
--select layer 1 ??

repeat with i from 1 to memberCount
set tAngle to (i * (360 / memberCount))
set tRadians to tAngle * (2 * pi) / 360
# set chair offsets from centre
set Hoff to (tRadius + (Seat / 2) + Gap) * (cos (tRadians))
set Voff to (tRadius + (Seat / 2) + Gap) * (sin (tRadians))
#determine coords of seat
set xPos to xtCentre + Hoff
set yPos to ytCentre + Voff
# get member initials
set perINIT to text item i of attList

# draw a line from table centre to Chair origin
make new line with properties {point list:{{xtCentre, ytCentre}, {xPos, yPos}}, thickness:0.25}

set shapeRef to (make new shape with properties {name:"AndGate", textSize:{1.0, 0.8}, draws shadow:false, size:{42.0, 42.0}, text:{alignment:center, font:perFont, text:perINIT}, textRotation:90, rotation:tAngle, origin:{xPos, yPos}, thickness:0, textPosition:{0.0, 0.1}, vertical padding:0, fill color:{0.0, 1.0, 1.0, 0.304984}})
end repeat

set theOffset to {-20, -20}
--set currentLayer to layer with name:"Seating"
--		set allGraphics to shapes of currentLayer
--		slide allGraphics by theOffset
end tell
``````

end tell

The OG interface’s `origin` property is at the top left of a shape, so you just need to give it coordinates half a seat-size to the left of the calculated centroid, and half a seat-size above it.

``````#determine coords of seat
set xCentre to xtCentre + Hoff
set yCentre to ytCentre + Voff

-- Delta
set xPos to xCentre - halfSeat
set yPos to yCentre - halfSeat

``````

I think you may be pulling in sin and cos functions from an osax library (perhaps Satimage ?) so to make the following work I tacked on a slow import of Math.sin and Math.cos from JavaScript

(from `evalOSA` onwards, plus the `use Framework` line at the top of the script)

and this also meant that I had to add the keyword “my” before the sin and cos calls, but this should be testable and give you a sense of the positioning edits I made.

(For FileMaker use, you may need to revert to your existing sin and cos approach, and remove the evalOSA version)

Note: I wasn’t sure at first what your variable `Seat` was, so in this copy I’ve renamed it `seatSize` and used it in the `make new shape` expression as well.

``````use framework "Foundation"

property perFont : "GillSans"
property seatSize : 42
property Gap : 3
property seatSpace : seatSize + Gap

on run
set memberCount to 15
set tPerim to memberCount * (seatSpace)
set tRadius to tPerim / (2 * pi)

set attList to {"GBa", "JMi", "PMi", "DMa", "SMi", "PDo", "RVi", "JMi", "PMi", "DMa", "SMi", "PDo", "RVi", "JMi", "PMi", "DMa", "SMi", "PDo", "RVi", "JMi", "PMi", "DMa", "SMi", "PDo", "RVi"}

tell application id "com.omnigroup.OmniGraffle6"
tell canvas of front window

# draw circular table
set xOrigin to 150
set yOrigin to 250
set tDiam to 2 * tRadius
set pointList to {0, xOrigin, yOrigin}
make new layer at end with properties {name:"Table"}
--    select layer id 2
make new shape with properties {name:"Circle", textSize:{0.8, 0.7}, draws shadow:false, size:{tDiam, tDiam}, origin:{xOrigin, yOrigin}, textPosition:{0.1, 0.15}}

# now draw seats
# - set coords of centre of table
set xtCentre to xOrigin + tRadius
set ytCentre to yOrigin + tRadius

set halfSeat to seatSize / 2

make new layer at end with properties {name:"Seats"}
--select layer 1 ??

repeat with i from 1 to memberCount
set tAngle to (i * (360 / memberCount))
set tRadians to tAngle * (2 * pi) / 360
# set chair offsets from centre
#determine coords of seat
set xCentre to xtCentre + Hoff
set yCentre to ytCentre + Voff

-- Delta
set xPos to xCentre - halfSeat
set yPos to yCentre - halfSeat

# get member initials
set perINIT to text item i of attList

# draw a line from table centre to Chair origin
make new line with properties {point list:{{xtCentre, ytCentre}, {xCentre, yCentre}}, thickness:0.25}

set shapeRef to (make new shape with properties {name:"AndGate", textSize:{1.0, 0.8}, draws shadow:false, size:{seatSize, seatSize}, text:{alignment:center, font:perFont, text:perINIT}, textRotation:90, rotation:tAngle, origin:{xPos, yPos}, thickness:0, textPosition:{0.0, 0.1}, vertical padding:0, fill color:{0.0, 1.0, 1.0, 0.304984}})
end repeat

end tell
end tell
end run

-- evalOSA :: ("JavaScript" | "AppleScript") -> String -> String
on evalOSA(strLang, strCode)

set ca to current application
set oScript to ca's OSAScript's alloc's initWithSource:strCode ¬
|language|:(ca's OSALanguage's languageForName:(strLang))

set {blnCompiled, oError} to oScript's compileAndReturnError:(reference)

if blnCompiled then
set {oDesc, oError} to oScript's executeAndReturnError:(reference)
if (oError is missing value) then return oDesc's stringValue as text
end if

return oError's NSLocalizedDescription as text
end evalOSA

on cos(x)
evalOSA("JavaScript", "Math.cos(" & (x as string) & ")") as real
end cos

on sin(x)
evalOSA("JavaScript", "Math.sin(" & (x as string) & ")") as real
end sin
``````
1 Like

Hi draft8

tks for this final elucidation. When I first saw you code I didn’t think it could work as your last delta was using orthogonal coordinates, rather than rotated coords. Shapes I’ve learnt rotate about their centroids but are set with an origin at top left. So in the example below - which now geometrically is behaving beautifully - I added the delta rectangle just ot prove to myself it works (which it did!).

I am still searching for an OmniGraffle Applescript primer which I’ve requested support who are helpful but…

• how do you select an object
• how do you select a layer for editing
• how do you modify a property of an object

etc

thanks for all your assistance - most appreciated

1 Like

how do you select an object

The selection property of the front window is a settable (as well as readable) list,
so you can assign a fresh list to it

`set oWindow.selection to {oObject}`

or add new objects to the existing list

``````set lstSeln to selection of oWindow
set end of lstSeln to oExtraShape
set selection of oWindow to lstSeln
``````

how do you select a layer for editing

For editing by script or by hand ?
(by script, you can identify a particular layer by name or index,
and then read/write properties of the graphics collection of that layer)

To set the scene for manual editing of a particular layer, you can make it the only one
that is unlocked (and even the only one that is visible)

``````tell application "OmniGraffle"
tell canvas of front window

set lstLayers to (layers of it) as list

repeat with oLayer in lstLayers
set locked of oLayer to true

-- possibly invisible too
-- set visible of oLayer to false
end repeat

-- Make your chosen layer the only one that is unlocked
-- (additional shapes will now be placed on it by default)

set oLayer to item 3 of lstLayers
-- or by name:
-- set oLayer to layer "Seats"

set visible of oLayer to true
set locked of oLayer to false
end tell
end tell

``````

how do you modify a property of an object

set [propertyName] of oObject to varValue

1 Like