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 General
FAQ Members List Calendar Search Today's Posts Mark Forums Read

 
Creating .graffleshapes tutorial Thread Tools Search this Thread Display Modes
[Originally posted to the OmniGraffle users mailing list by David Kasprzyk, thought I'd post it here as well for easier access -- JP]

After much editing and such, and creating some shapes, I think I'm ready
to release my .graffleshapes tutorial for your benefit. Let me know if
you have any questions and I'd be happy to clarify any issues.

David's Shape Creation Tutorial:

The shapes file - shapes.graffleshapes:

All of the shapes and lineheads in OmniGraffle are defined in the
shapes.graffleshapes file. This file is part of the OmniGraffle.app
package. To access this file, right click (control-click if you don't
have two buttons) on the application to bring up the contextual menu.
Select 'Show Package Contents' from the menu, which will open up a new
window titled OmniGraffle. Open up the one folder inside, 'Contents',
then open up the 'Resources' folder in the new window. In the 'Resources'
window that opens, you should be able to find the shapes.graffleshapes
file. Since we don't want to damage the original file, I recommend option
dragging this file to the desktop, thus making a copy.

Creating your own personal shapes file:

Now that we have created our shapes.graffleshapes copy lets start by
renaming it, we'll call it shapestutorial.graffleshapes. Now that this is
done we need to place the file in the proper directory. All third party
graffleshapes files are stored in either,

Library/Application Support/OmniGraffle/Shapes/ if you want to give everyone
on your computer access or
~/Library/Application Support/OmniGraffle/Shapes/ if you want access only
for yourself

so lets move shapestutorial.graffleshapes into the local directory for now.

Overview of a .graffleshapes file:

Now lets open up the shapestutorial file and take a look at it. A
.graffleshapes file can be opened and edited with any text editor, such as
TextEdit and BBEdit. When you open up the file you will see the
information that creates OmniGraffle's default shapes. The basic outline
should look something like,

{
shapes = (
{
InspectorGroup = 32;
ShapeName = RoundedStack;
StrokePath = {
elements = (
...
);
};
TextBounds = "{{0.02, 0.02}, {0.855, 0.855}}";
},
{
...
}
);
}

That annoying syntax - A 'quick' discussion:

Since syntax is typically the cause of most errors in coding, it will
unfortunately be no different for creating graffleshapes. So to try and
assist you during your creation of graffleshapes I'll be going over
several of the main syntax issues.

First off all of the commands are case sensitive. Thus shapename is not
equivilant to ShapeName. All of the commands listed in this tutorial
should be copied directly for them to work properly.

Spaces and tabs are not required but they do make it easier to spot
problems if your shapes file doesn't load properly, and I highly recommend
using them.

Decimal numbers can be enter with or without the leading zero. So 0.5 is
just as valid in a graffleshapes file as .5 is. My best advice is to pick
a system and stay with it for convenience in debugging problems.

Commas and semicolons typically cause the most problems in getting your
shapes to properly load into OmniGraffle. I will therefore try and list
where semicolons and commas should go in you graffleshapes files.

Commas: Commas are used in two main sections of a graffleshapes file.
The first of these is to link elements together when defining a path. A
comma should follow every element that you define, with the exception of
the very last element. This can be seen in the following example:

elements = (
{element = MOVETO; point = "{-0.5, -0.5}"; } ,
{element = LINETO; point = "{0.5, -0.5}"; } ,
{element = LINETO; point = "{0.5, 0.5}"; } ,
{element = LINETO; point = "{-0.5, 0.5}"; } ,
{element = CLOSE; }
);

The second comma location is between each shape that is created. Like we
did with the elements, no comma should follow the last shape. The format
should be as shown below:

shapes = (
{
...
} ,
{
...
} ,
{
...
}
);

Semi Colons: While you end up using semi colons much more frequently than
commas, the placement of semi colons follows a specific rule.

The Rule of Semi Colons:
If you are setting a value, and thus using the = character, the you
must follow the expression with a semi colon. To emphasize the point here
are some examples:

shapes = (
...
);

StrokePath = {
...
};

element = MOVETO;

point = "{ ... }";

These quick guidelines should hopefully allow you to track down any
problems that you may experience in the creation of you graffleshapes.
Now that we have these guidelines set up, lets take a look at actually
creating the shapes.
__________________
"Vroom! Vroom!!"
 
[continued...]

The inner workings - the .graffleshapes commands:

So now that we've seen the basic for of a .graffleshapes document, lets
look at the formating and commands to create our own. To begin, select
everything in the shapestutorial file and delete it. Now we are ready to
create our own file.

All .graffleshapes documents open with a {. After this we can define
either shapes or arrow heads, by stating one of the following

shapes = (
...
),
or
arrowheads = (
...
);

We'll first take a look at creating shapes. To create a shape, we start
with a {, and then enter in the information about the shape. Inside of
these braces there are a number of options that can be set.

ShouldExport: This can be set to YES or NO. Since you are creating a
third party shape, this should most likely set to YES, so that the
information needed to create your shape will be saved along with any file
that contains one of your shapes.

InspectorGroup: This can be set as any real number. The
InspectorGroup is the position of your shape in relation to the other
shapes in the Shape Info panel. If you set this value to be 40+ your
shape will be added to the end of the list. NOTE: this can be a decimal
value. 8.473 comes before 9, so this allows you to add shapes in a
specific position without reordering all of the other shapes.

ShapeName: This should be the name of your shape. This isn't a big
deal, but should be a reminder to you as to what shape is defined here.

StrokePath: This is where we actually define the border of the shape
itself. The definition of the StrokePath looks something like,

StrokePath = {
elements = (
...
);
};

The data for the StrokePath consists of elements, which can take one of
four value types: MOVETO, LINETO, CURVETO, and CLOSE. All of these
values, except close also require at least one point value (CURVETO has
several requirements which will be discussed later). It is important to
note that a StrokePath doesn't necessary have to be closed. However if
you do not close a StrokePath you must define a FillPath which will be
defined later or your fill will be the area created with the MOVETO
command followed by a CLOSE command. This can have unwanted results so I
recommend using a FillPath.

points: points are the x and y coordinates that you refer too. The
positive x-axis points to the right of the origin, while the positive
y-axis points down from the origin. This is opposite of standard x, y
convention so if you draw out your shapes before hand remember to add a
negative in front of the y value. The syntax for declaring points is as
follows:

point = "{x , -y}"; NOTE: I have added a negative in front of the
y for convenience.

For conventions sake it is best to keep your x and y values between 0.5
for basic points, with a limit of 1 for control points which will be
discussed in the CURVETO section. This also makes it much easier to
position your TextBounds which will be discussed later.

MOVETO: This value takes a point to which the cursor is moved to.
This will always be the first element that is defined. The format for
MOVETO is as follows:

{element = MOVETO; point = "{x , -y}"; }

LINETO: This value takes a point to which a line will be drawn from
the current position. This can follow any of the other element types
except CLOSE. If the point that LINETO is drawing to is the original
point of the shape then this can also be the last element listed for your
StrokePath in place of CLOSE. The format for LINETO is as follows:

{element = LINETO; point = "{x , -y}"; }

CURVETO: This is the most complicated part of creating a shape.
Curves in graffleshapes are all Bezier curves. While I won't go into
great detail, I will begin the discussion of the CURVETO value with an
intro to Bezier curves.

Bezier curves are defined by four points. These are the start point, (x0,
y0) and end point, (x3, y3) of the curve and the two control points, (x1,
y1) and (x2, y2) of the curve. The bezier curve itself is defined by two
equations based off the variable t and our four points. One equation is
for x values and the other for y values. Both curves are evaluated for
all values of t between 0 and 1.

Adobe's PostScript references defines the equations as:

x(t) = axt3 + bxt2 + cxt + x0
y(t) = ayt3 + byt2 + cyt + y0

From these values of a, b, and c and the start and end points you can
calculate the control points using these equations.

x3 = x0 + ax + bx + cx
x1 = x0 + cx / 3
x2 = x1 + (cx + bx) / 3

y3 = y0 + ay + by + cy
y1 = y0 + cy / 3
y2 = y1 + (cy + by) / 3

In OmniGraffle, you will need to use the positive y value (since our axis
is inverted) in your calculations. To properly form the bezier curve, the
CURVETO element requires both of the control points be defined before it
is executed. This in done in the following manner:

control1 = "{x1 , -y1}";
control2 = "{x2 , -y2}";

Then the CURVETO command can be called like we did with MOVETO and LINETO
as shown below:

element = CURVETO; point = "{x3 , -y3}";

Curly Braces surround both the definition of the control points and the
CURVETO element. As you can see it is not an easy process to define lines,
and can be rather time consuming. However I have found several tricks
that can be used to make symmetric lines. When you sketch out your curve,
pick the control points to be on the outside of your curve and make the
first control point be symmetrically to the second point in relation to
the start and end points. When you think you have a good curve, test it
out and then make changes as necessary.

CURVETO can be used in place of the CLOSE command if the end point of the
curve is the original starting point of the shape.

CLOSE: This value simply draws a line from the current point to the
original point of the shape and closes the shape. This should be the last
element that you list if you use it. Both LINETO, and CURVETO can be use
in place of CLOSE if their end point is the starting point of the shape.
The format for CLOSE is very simple as shown below:

{element = CLOSE;}

FillPath: A FillPath is only needed for a shape if you do not close
the StrokePath and want to specify a specific fill. The format for the
FillPath command is very similar to the StrokePath as seen below.

FillPath = {
elements = (
...
);
};

The data for the FillPath consists of the same elements that are used in
the StrokePath. However unlike the StrokePath the fill path must always
end with either the CLOSE element, or a LINETO or CURVETO element with it'
s end point being the starting point of the FillPath.

TextBounds: TextBounds define the text box to be used when someone
tries to enter text into your shape. TextBounds requires an x and y
position, a height and a width. The x and y values are the position of
the upper left corner of the text box measured from the upper left corner
of the drawing box. If we use the standard convention, this means we are
measuring the distance to the right of -0.5, and down from -0.5. The x
and y values should be between (0 , 0) and ( [1-width] , [1-height] ) if
you want the box to be completely inside of your shape, but this doesn't
have to be the case. The width and the height of the box are the distance
to the right of x and down from y that you want the box to be. The format
for defining you TextBounds is:

TextBounds = "{{x , y}, {width, height}}";
__________________
"Vroom! Vroom!!"
 
[continued...]

Shapes Example:

So now that we've seen how to make shapes let run through an example. In
this example we will create a triangle with its point upward and a shape
that looks somewhat like a baseball infield but with on line at the
outfield.

Lets start off with by saying we want to make some shapes. Then we start
an new shape with a curly brace.

shapes = (
{

Next we start by defining whether it can export or not, the shapes
inspector position and the name. In this case will make it exportable,
object 40 in the inspector, and named TrianglePointUp

ShouldExport = YES;
InspectorGroup = 40;
ShapeName = TrianglePointUp;

We will then define the stroke path of our triangle by first picking a
starting point which will be the point, x = 0, y = 0.5.

StrokePath = {
elements = (
{element = MOVETO; point = "{0, -0.5}"; },

Note that since we will be adding more elements, we have a comma after the
MOVETO element. Now lets draw the three sides of the triangle and close
up the stroke path.

{element = LINETO; point = "{0.5, 0.5}"; },
{element = LINETO; point = "{-0.5, 0.5}"; },
{element = LINETO; point = "{0, -0.5}"; }
);
};

Finally we will define the boundary of the text box and end the creation
of our first shape. For this shape the text box will start at x = 0.25, y
= 0.5 and it will have a height and width of 0.5.

TextBounds = "{{0.25, 0.5}, {0.5, 0.5}}";
},

Since we will be adding another shape we add a comma after we close off
our triangle shape. If we weren't adding another shape we would omit this
comma. We'll now start off on our baseball diamond and set all the basic
values.

{
ShouldExport = YES;
InspectorGroup = 41;
ShapeName = BaseballDiamond;

Now we will create the StrokePath, remembering that we won't be closing it
off.

StrokePath = {
elements = (
{element = MOVETO; point = "{0.353553, 0.353553}"; },
{element = LINETO; point = "{-0.353553, 0.353553}"; },
{element = LINETO; point = "{-0.353553, -0.353553}"; }
);
};

After we do this we will then define our FillPath which includes the curves

FillPath = {
elements = (
{element = MOVETO; point = "{0.353553, -0.353553}"; },
{
control1 = "{0.548816, -0.158291}";
control2 = "{0.548816, 0.158291}";
element = CURVETO; point = "{0.353553, 0.353553}";
},
{element = LINETO; point = "{-0.353553, 0.353553}"; },
{element = LINETO; point = "{-0.353553, -0.353553}"; },
{
control1 = "{-0.158291, -0.548816}";
control2 = "{0.158291, -0.548816}";
element = CURVETO; point = "{0.353553, -0.353553}";
}
);
};

And we will conclude our shapes with the text bounds for the baseball
diamond.

TextBounds = "{{0.25, 0.5}, {0.5, 0.5}}";
}
);

One thing to note, this I have not had time to work out is that the
boundary point of the shape are incorrect for this baseball diamond. There
is a large chunk of empty space on the bottom and the left side that
should be removed. This is because my values that I used did not extend
all the way out to 0.5 in the left and downward directions. A
recalculation of values is necessary to correct this issue.


Now that we've take a look at shape creation, lets move on to creating
arrowheads. Like creating shapes we start a new arrowhead with a curly
brace inside of the arrowheads declaration. Like with shapes we have a
number of options that can be set for each arrowhead which will now be
discussed.

Filled: This option can be set as either YES or NO, with NO being the
default option is if this option is not set. The syntax for Filled is as
follows:

Filled = YES;

Gap and LineGap: These options are used to control the offset of the
arrowhead from its connecting object when the line size is to be scaled.
This offset is calculated by the following equation:

Offset = Gap + LineGap LineThickness

Pointier arrowheads need larger LineGap values to keep them from
overlapping with the graphics as the thickness is increased. The syntax
for Gap and Line values is as follows.

Gap = 0.5;
LineGap = 1;

To set these values, it is best to first set the LineGap value. The best
way to do this is to begin with your Gap and LineGap values set to zero.
In OmniGraffle, draw a completely horizontal line and place your new
arrowheads on both ends. Zoom in on the line to 800% and make sure the
line is selected. In the style inspector, increase the line size from 1
to 8 an observe the tips of your arrowhead.

If the tips remain at the same horizontal position for all sizes from 1 to
8, your LineGap value is properly set. If the tips move outward as you
increase the size, you need to increase the value of LineGap and
conversely if the tips move inward you need to decrease LineGap.

I recommend that you don't try for accuracy above the tenthes digit,
meaning don't try and set a value such as 1.43, since 1.4 is more than
acceptable. Once you have the LineGap set, you may still have some offset
that needs to be adjusted for. Draw two identical squares on the same
horizontal level with so gap between them. Then draw a single line that
connects the two shapes (connections will need to be turned on). Zoom in
to 800% again and look at the tips of your lines.

If the tips meet up perfectly with the edges of the squares, your Gap
value is set correctly. If there is a gap between the tips and the
squares, your Gap value is too high and needs to be decreased, while
conversley, the Gap value should be increased if the tips overlap. As
with the LineGap values, don't try for accuracy above the tenthes digit.

Name: This serves the exact same purpose as ShapeName does but for
arrowheads. It is declared as below:

Name = myArrowhead;

Path: Path is the arrowheads equivalent of StrokePath. It requires
elements just as StrokePath does, and will accept the same element values.
Path's are defined as follows:

Path = {
elements = (
...
);
};

Width: The last value for an arrowhead is the width value. This is
simply the total x distance that the arrowhead spans, and should be
calculated from the values used in the path. The form for Width is:

Width = 10;

Arrowhead Example:

So now lets take a look at how to make an arrowhead of our own. Just like
with our shapes we start by saying we want to make an arrowhead and the
creating a new one with a curly brace

arrowheads = (
{

Next we will need to define the Gap and LineGap values. For this
arrowhead, I found the Gap value to be 0.4 and the LineGap to be 2.1.

Gap = 0.4;
LineGap = 2.1;

We'll then set a name for our arrowhead and define the path outline for it.
In this case we are making a backwards Native American arrowhead, so we
need one MOVETO, three LINETO's and a CLOSE element.

Name = BackNAArrowhead;
Path = {
elements = (
{element = MOVETO; point = "{0, -4}"; },
{element = LINETO; point = "{4, 0}"; },
{element = LINETO; point = "{0, 4}"; },
{element = LINETO; point = "{10, 0}"; },
{element = CLOSE;}
);
};

And finally we will set our width value and end the shape. The width is
calculated as the maximum change in the x value for the path. In our
example, we go from x = 0 to x = 10, so the width is equal to 10 - 0 or 10.

Width = 10;
}
);

So we have now completed the tutorial for creating OmniGraffle
.graffleshapes files.
__________________
"Vroom! Vroom!!"
 
I'd like to build some custom arrow heads, and came to this tutorial. However, the file name does not seem to work with OGP 5.4.2 - I'd guess that the locations have moved.

What's much worse is that if I edit a copy of a stencil with OGP, and save it, it's a gzipped xml document, so the mapping to the terminal grammar is not really human friendly.

Is the old yaml style encoding still available, and how do I go about creating some new arrow heads?
 
The shapes.graffleshapes file in the app package is now in Contents/Frameworks/GraffleShapes.framework/Resources.*

I'm not sure about your stencil-editing problem. Are you talking about the mapping between the file's extension (.gstencil) and its actual format? The file is, in fact, an XML text file. It will be zipped if "Compress on disk" is checked in the Document:Document inspector.

*I recommend Devon Technology's EasyFind for searching in app packages. It's free in the App Store or on their site.
 
Does this still work in OG6?

I have a couple of custom shapes I created a few years ago using this tutorial and I can't get them to work under OG6. I've tried putting them in:

~/Library/Application Support/OmniGraffle 6/Shapes/

and also in

~/Library/Containers/com.omnigroup.OmniGraffle6/Data/Library/Application Support/The Omni Group/OmniGraffle/Shapes/

but in neither case could I find my custom shapes anywhere when I created a new OG doc.
 
 


Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes


Similar Threads
Thread Thread Starter Forum Replies Last Post
Creating and Editing Ad Filters - Tutorial Handycam OmniWeb General 9 2012-03-13 07:50 PM
Need help creating a script for creating a next action gcrump OmniFocus Extras 9 2009-02-21 06:10 PM
Templates tutorial? hardcoreUFO OmniGraffle General 0 2007-12-18 12:50 PM
Web Interface Tutorial sprocketjockey OmniFocus 1 for Mac 1 2007-07-05 03:09 PM
Tutorial? sjfischer OmniOutliner 3 for Mac 1 2006-10-05 01:10 PM


All times are GMT -8. The time now is 11:53 AM.


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