The Omni Group Forums

The Omni Group Forums (http://forums.omnigroup.com/index.php)
-   OmniGraffle Extras (http://forums.omnigroup.com/forumdisplay.php?f=7)
-   -   Annotate objects (applescript) (http://forums.omnigroup.com/showthread.php?t=2329)

Dan Thomsen 2006-12-04 01:26 PM

Annotate objects (applescript)
 
I make a lot of diagrams for papers, and I found myself annotating shapes, then as the diagram changes I have to move the anotations around and it was difficult to get them correctly placed.

To solve the problem I created two scripts. The first Annotate creates an annotation for each selected object. It prompts for the position, and the margins.

The Reposition Annotation script only works on Annotations created with the annotation script. It allows the annotation to be repositioned with a different margin or on a different side of the object. Note you can select multiple objects, select no change, and then simply adjust the margins.

I have script versions and applications created with Applescript studio, that have a much better interface. Scripts first
[CODE]
-- Annotate OmniGraffle objects by Dan Thomsen
-- customizable parameters
set label_font to "Arial Narrow"
set label_font_size to 12
set annotate_color to "Steel"
set annotate_thickness to 0.5

set layer_name to "Annotation Layer"
set annotate_label to "annotate here"

-- parameters that the user specifies
global horizontal_margin
global vertical_margin
global horizontal_position
global vertical_position
global annotation_position

set horizontal_margin to 20 -- default value
set vertical_margin to 20 -- default value

-- valid values are left, center right
set horizontal_position to "left" -- default value

-- valid values are top, middle, bottom
set vertical_position to "top" -- default value

-- constants
-- these are used to create magnets on the two ends and the middle of the edge
set left_edge to {{-0.5, -0.5}, {-0.5, 0}, {-0.5, 0.5}}
set right_edge to {{0.5, -0.5}, {0.5, 0}, {0.5, 0.5}}
set bottom_edge to {{-0.5, 0.5}, {0, 0.5}, {0.5, 0.5}}
set top_edge to {{-0.5, -0.5}, {0, -0.5}, {0.5, -0.5}}

-- variables



set annotation_size to {0, 0} -- used to store the size of the annotated text
set text_alignment to left -- default value
try
tell front window of application "OmniGraffle Professional"
activate

-- all annotations are put on their own layer
-- if the annotation layer does not exist create it
if (name of layers) does not contain layer_name then
make new layer at beginning of layers with properties {name:layer_name, visible:true, prints:true}
end if

-- get the annotation layer
repeat with alayer in layers
if name of alayer is equal to layer_name then
set annotation_layer to alayer
exit repeat
end if
end repeat

set objects to selection
if (length of objects < 1) then
error "no objects selected"
end if

if not my prompt_user() then
error "user abort"
end if

-- the position is saved in the annotation so that the margins can latter be adjusted by running the Reposition Annotation script
set annotation_tag to {annotation:true, position:annotation_position}

set object_count to 0

-- we are going to draw an annotation at 0,0 just to get its actual size
make new shape at beginning of graphics of annotation_layer with properties {origin:{0, 0}, draws stroke:no, fill:no fill, text:{size:label_font_size, color:annotate_color, font:label_font, alignment:text_alignment, text:(annotate_label)}, draws shadow:false, autosizing:full}
set annotation_size to size of graphic 1

-- now that we have the size we don't need the object any more
delete graphic 1

repeat with obj in objects
set object_count to object_count + 1
set obj_size to size of obj
set obj_orig to origin of obj

if horizontal_position is "left" then
set x_pos to (item 1 of obj_orig) - horizontal_margin - (item 1 of annotation_size)
set text_alignment to right
set magnet_list to right_edge
set x_header_start to x_pos + (item 1 of annotation_size)
else if horizontal_position = "center" then
set x_pos to (item 1 of obj_orig) + ((item 1 of obj_size) - (item 1 of annotation_size)) / 2
set text_alignment to center
if vertical_position = "top" then
set magnet_list to bottom_edge
else if vertical_position = "middle" then
display alert "Invalid Placement." message "Cannot place an annotation in the center of the object." buttons {"OK"} default button 1
exit repeat -- the script
else -- vertical_position ="bottom"
set magnet_list to top_edge
end if

set x_header_start to x_pos
else
set x_pos to (item 1 of obj_orig) + (item 1 of obj_size) + horizontal_margin
set text_alignment to left
set magnet_list to left_edge
set x_header_start to x_pos
end if

if vertical_position = "top" then
set y_pos to (item 2 of obj_orig) - vertical_margin - (item 2 of annotation_size)
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
else if vertical_position = "middle" then
set y_pos to (item 2 of obj_orig) + ((item 2 of obj_size) - (item 2 of annotation_size)) / 2
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
if horizontal_position = "left" then
set magnet_list to right_edge
else if horizontal_position = "center" then
display alert "Invalid Placement." message "Cannot place an annotation in the center of the object." buttons {"OK"} default button 1
exit repeat -- the script
else -- horizontal_position = right
set magnet_list to left_edge
end if

else -- bottom
set y_pos to (item 2 of obj_orig) + (item 2 of obj_size) + vertical_margin
set y_header_start to y_pos
set y_header_end to y_pos + (item 2 of annotation_size)
end if

make new shape at beginning of graphics of annotation_layer with properties {origin:{x_pos, y_pos}, size:annotation_size, draws stroke:no, fill:no fill, magnets:magnet_list, user data:annotation_tag, text:{size:label_font_size, color:annotate_color, font:label_font, alignment:text_alignment, text:(annotate_label)}, draws shadow:false, autosizing:full}

set annotated_text to graphic 1
set origin of annotated_text to {x_pos, y_pos}

-- I have not been able to get connect the line directly between two objects
-- I have to use the point list and then define source and destination after
make new line at beginning of graphics of annotation_layer with properties {stroke color:annotate_color, thickness:annotate_thickness, point list:{x_pos, y_pos}}


set connector_line to graphic 1
set source of connector_line to obj
set destination of connector_line to annotated_text
set head magnet of connector_line to 2


make new line at beginning of graphics of annotation_layer with properties {stroke color:annotate_color, thickness:annotate_thickness, point list:{{x_header_start, y_header_start}, {x_header_start, y_header_end}}}
set header_line to graphic 1
set source of header_line to annotated_text
set tail magnet of header_line to 1
set destination of header_line to annotated_text
set head magnet of header_line to 3

end repeat
-- Need 'as list' if there's only one item. Pesky; took ages to figure out.
-- object_count is multiplied by three because the text, header line and connector line are each object

set selection to a reference to graphics 1 thru (object_count * 3) as list


end tell

on error error_message
tell front window of application "OmniGraffle Professional"
activate
--say error_message
if not error_message is equal to "user abort" then
display alert "No selection." message "This script adds an annotation to an object. Select some, then run it again." buttons {"OK"} default button 1
end if
end tell
end try


-- Ask the user to fill in the four global variables
on prompt_user()
tell front window of application "OmniGraffle Professional"
-- Prompt the user as to where to place annotation
try
set answer to choose from list {"NW", "N", "NE", "W", "E", "SW", "S", "SE"} with prompt "Placement:"

if answer is false then return false

set annotation_position to (item 1 of answer)
set horizontal_position to my get_horizontal_position(annotation_position)
set vertical_position to my get_vertical_position(annotation_position)

on error error_message
display alert error_message
return false
end try

try
set answer to display dialog "Enter horizontal margin" default answer "20"
if answer is false then return false
set horizontal_margin to text returned of answer as number

set answer to display dialog "Enter vertical margin" default answer "20"
if answer is false then return false
set vertical_margin to text returned of answer as number

on error error_message
if error_message is "OmniGraffle Professional got an error: User canceled." then return false
display alert error_message buttons {"OK"} default button 1
return false
end try
return true
end tell
end prompt_user



on get_horizontal_position(position)

if {"NW", "W", "SW"} contains position then
return ("left")
else if {"N", "S"} contains position then
return "center"
else if {"NE", "E", "SE"} contains position then
return "right"
else
return ("left")
end if
end get_horizontal_position

on get_vertical_position(position)
if {"NW", "N", "NE"} contains position then
return ("top")
else if {"W", "E"} contains position then
return "middle"
else if {"SW", "S", "SE"} contains position then
return "bottom"
else
return ("top")
end if
end get_vertical_position

[/CODE]

Dan Thomsen 2006-12-04 01:29 PM

Reposition Annotation

[CODE]
-- Reposition Annotation OmniGraffle objects by Dan Thomsen
-- parameters that the user specifies
-- parameters that the user specifies
global horizontal_margin
global vertical_margin
global horizontal_position
global vertical_position
global annotation_position
set horizontal_margin to 20
set vertical_margin to 20

-- valid values are left, center right
set horizontal_position to "left"

-- valid values are top, middle, bottom
set vertical_position to "top"

-- constants
-- these are used to create magnets on the two ends and the middle of the edge
set left_edge to {{-0.5, -0.5}, {-0.5, 0}, {-0.5, 0.5}}
set right_edge to {{0.5, -0.5}, {0.5, 0}, {0.5, 0.5}}
set bottom_edge to {{-0.5, 0.5}, {0, 0.5}, {0.5, 0.5}}
set top_edge to {{-0.5, -0.5}, {0, -0.5}, {0.5, -0.5}}

set text_alignment to left -- default value

-- Prompt the user as to where to place annotation
try
tell front window of application "OmniGraffle Professional"
activate
set objects to selection
if (length of objects < 1) then
error "No annotations selected. Select annotations created with Annotation script, non-annotation objects are ignored."
end if

if not my prompt_user() then
error "user abort"
end if

set object_count to 0

repeat with obj in objects
set obj_data to user data of obj

-- note if the object is not an annotation there is an odd error that is caught by this try statement.
try

-- this statement fails if the object is not an annotation
if (annotation of obj_data is true) then
set current_position to position of obj_data
if annotation_position is "no change" then
set horizontal_position to my get_horizontal_position(current_position)
set vertical_position to my get_vertical_position(current_position)
else
set current_position to annotation_position
end if
set annotation_tag to {annotation:true, position:current_position}

set annotation to obj
set annotation_size to size of annotation -- used to store the size of the annotated text

set user data of annotation to annotation_tag

set incoming_lines to incoming line of annotation
set connector_line to (item 1 of incoming_lines)
set obj to source of connector_line
set obj_size to size of obj
set obj_orig to origin of obj

if horizontal_position = "left" then
set x_pos to (item 1 of obj_orig) - horizontal_margin - (item 1 of annotation_size)
set text_alignment to right
set magnet_list to right_edge
else if horizontal_position = "center" then
set x_pos to (item 1 of obj_orig) + ((item 1 of obj_size) - (item 1 of annotation_size)) / 2
set text_alignment to center
if vertical_position = "top" then
set magnet_list to bottom_edge
else -- vertical_position ="bottom"
set magnet_list to top_edge
end if
else
set x_pos to (item 1 of obj_orig) + (item 1 of obj_size) + horizontal_margin
set text_alignment to left
set magnet_list to left_edge
end if

if vertical_position = "top" then
set y_pos to (item 2 of obj_orig) - vertical_margin - (item 2 of annotation_size)
else if vertical_position = "middle" then
set y_pos to (item 2 of obj_orig) + ((item 2 of obj_size) - (item 2 of annotation_size)) / 2

if horizontal_position = "left" then
set magnet_list to right_edge
else -- horizontal_position = right
set magnet_list to left_edge
end if

else -- bottom
set y_pos to (item 2 of obj_orig) + (item 2 of obj_size) + vertical_margin
end if

set origin of annotation to {x_pos, y_pos}

-- just moving the magnets moves the header line
set magnets of annotation to magnet_list
end if -- an annotation
end try

end repeat

set selection to objects as list

end tell
on error error_message
if error_message is not "user abort" then
display alert error_message buttons {"OK"}
end if
end try

-- Ask the user to fill in the four global variables
on prompt_user()
tell front window of application "OmniGraffle Professional"
-- Prompt the user as to where to place annotation
try
set answer to choose from list {"no change", "NW", "N", "NE", "W", "E", "SW", "S", "SE"} with prompt "Placement:"

if answer is false then return false

set annotation_position to (item 1 of answer)
set horizontal_position to my get_horizontal_position(annotation_position)
set vertical_position to my get_vertical_position(annotation_position)
on error error_message
display alert error_message
return false
end try

try
set answer to display dialog "Enter horizontal margin" default answer "20"
if answer is false then return false
set horizontal_margin to text returned of answer as number

set answer to display dialog "Enter vertical margin" default answer "20"
if answer is false then return false
set vertical_margin to text returned of answer as number

on error error_message
if error_message is "OmniGraffle Professional got an error: User canceled." then return false
display alert error_message buttons {"OK"} default button 1
return false
end try
return true
end tell
end prompt_user



on get_horizontal_position(position)
if {"NW", "W", "SW"} contains position then
return ("left")
else if {"N", "S"} contains position then
return "center"
else if {"NE", "E", "SE"} contains position then
return "right"
else
return ("left")
end if
end get_horizontal_position

on get_vertical_position(position)
if {"NW", "N", "NE"} contains position then
return ("top")
else if {"W", "E"} contains position then
return "middle"
else if {"SW", "S", "SE"} contains position then
return "bottom"
else
return ("top")
end if
end get_vertical_position


[/CODE]

Dan Thomsen 2006-12-04 01:39 PM

Here is the applescript studio version of Annotate (much easier to use).
Simply drag it into the OmniGraffle script directory just like a script.

Doh it is too big for the forum 128.1 KB. Email me I guess and I'll send it out if you are interested.

Dan

ChristianS 2007-04-19 01:20 PM

AnnoTate and Reposition on one Stencil
 
Hi Dan,
As an Example I put your two Scripts on an Interactive Stencil.
Have look. You can Download it on
[url]http://web.mac.com/christian.sodl/iWeb/Web-Site/Stencils.html[/url]

Best Regards

Dan Thomsen 2007-04-19 04:36 PM

1 Attachment(s)
Really slick and much easier to use interface.

I did a xcode application so I could get a better user interface. I have included a snapshot of the interface.

Some of the differences are that I can put multiple annotations up at one time, which is only slightly useful.

Much more useful is the ability to change the vertical and horizontal margins. Basically once all you get your final text in, you may need to adjust the margins to get everything consistent.

I have included a snapshot.

Dan Thomsen 2007-04-19 04:42 PM

1 Attachment(s)
I updated your stencil to use the image, I used in the xcode application.

I made the checkboxes square and the text white, since the user really doesn't care what the text says. It is obvious where the annotation will be placed based on the location of the checkbox.

I'll be glad to send you the update for your site.

Dan Thomsen 2007-04-19 04:44 PM

Editing the stencil was bit tricky. There are a number of rules like the check boxes must be locked that I had to stumble over.

Do you have a way of automatically building the scripts?

Dan

ChristianS 2007-04-20 11:41 PM

Yes the Elements (Checkboxes etc.) must be locked to trigger an attached Script in OG. I will think about a Way to realize some automatic Code Generation.
Best Regards

Dan Thomsen 2007-04-21 10:57 AM

More descriptive layer names would have helped me understand how to modifiy the stencil.

You could create an invisible layer with some instructions.

You could create a script that takes the set of selected items and moves them to the correct level and locks them.

You could create two "buttons" that set parameters. For example the script could prompt the user for the dimension and then save it in the stencil. You would need a "reset to default values" button as well

Dan

ChristianS 2007-05-01 01:46 PM

Hi Dan,
On my Website is a new Version of Annotation Stencil with "Distance Editing" and "Reset to Defaults" implemented. The LayerNames have to start with the Signs descript on Interactive Elements e.g :RDG. You can append whatever you want to this Sign. Otherwise the GuiController and ScriptLib Code won't work. See the new Annotation Stencil. If you use TabButtons the Elements and Layers locked/unlocked by Code to work properly.


All times are GMT -8. The time now is 05:30 PM.

Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2019, vBulletin Solutions, Inc.