The Omni Group
These forums are now read-only. Please visit our new forums to participate in discussion. A new account will be required to post in the new forums. For more info on the switch, see this post. Thank you!

Go Back   The Omni Group Forums > OmniGraffle > OmniGraffle Extras
FAQ Members List Calendar Search Today's Posts Mark Forums Read

 
Annotate objects (applescript) Thread Tools Search this Thread Display Modes
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
 
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
 
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

Last edited by Dan Thomsen; 2006-12-04 at 01:44 PM.. Reason: attachment didn't show up
 
Hi Dan,
As an Example I put your two Scripts on an Interactive Stencil.
Have look. You can Download it on
http://web.mac.com/christian.sodl/iW.../Stencils.html

Best Regards
 
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.
Attached Thumbnails
Click image for larger version

Name:	annotate.jpg
Views:	753
Size:	33.4 KB
ID:	178  

Last edited by Dan Thomsen; 2007-04-19 at 04:38 PM..
 
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.
Attached Thumbnails
Click image for larger version

Name:	stencilSnap.jpg
Views:	721
Size:	15.4 KB
ID:	179  
 
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
 
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
 
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
 
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.
 
 


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


Similar Threads
Thread Thread Starter Forum Replies Last Post
AppleScript - How to Operate on Group of Selected Graphic Objects? trouble clef OmniGraffle General 1 2011-08-17 06:24 AM
Outlining objects Arild OmniGraffle General 0 2011-03-28 01:12 PM
Selecting Multiple Objects with Dragging, but Skip Underlying Objects ScottG OmniGraffle General 1 2011-01-07 07:02 AM
how do I annotate? clamshell OmniWeb General 2 2008-11-14 06:30 AM
Need Help - Feathering Objects priceless OmniGraffle General 1 2008-04-28 07:04 AM


All times are GMT -8. The time now is 08:45 AM.


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