2 Replies Latest reply on Nov 3, 2014 7:46 AM by Gaxx

    How to create path with arrowhead?

    Gaxx

      Hello. I have a document with a single path in it. How can I apply arrowhead efect to this path? I tried using Beautiful strokes suite, but was only able to get arrowhead from existing path. How can I get one if there is no path with arrowheads in the document?

        • 1. Re: How to create path with arrowhead?
          A. Patterson Level 4

          Yeah, it's really not obvious at all how to do this.

           

          The key is to use the AIBeautifulStrokesSuite. The steps are as follows:

           

          1. Get the art style of the path (AIArtStyleSuite::GetArtStyle()).
          2. Parse the style (AIArtStyleParserSuite::NewParser(), ParseStyle()).
          3. Get the focus stroke (AIArtStyleParserSuite::GetFocusStroke), which gets you an AIParserPaintField.
          4. Populate the AIParserPaintField into a CPaintField (see below for code)
          5. Use AIBeautifulStrokesSuite::SetArrowheadOptions, and pass CPaintField::GetEffectsDictionary() to the first parameter.
          6. Call CPaintField::Save().
          7. Create a new art style (AIArtStyleParserSuite::CreateNewStyle()).
          8. Apply the style to the path (AIArtStyleSuite::SetArtStyle()).
          9. Dispose of the parser (AIArtStyleParserSuite::DisposeParser()).

           

          Note that I was only interested in the 'main' stroke so I used the focus stroke (which in 99% of cases will be the only stroke). If you want to handle multiple strokes, you can iterate over all the paint fields in the style by calling AIArtStyleParserSuite::CountPaintFields() on your parser after parsing the style. You just have to test each one to see if its a stroke or a fill, since obviously you can't apply arrowheads to fills!

           

          The arrow head itself is set by passing in an AIPatternHandle, which is the handle to a symbol. You can set the start or end, and the other parameters are just the scale to use for the symbols and the alignment of the arrowheads (ArrowTipAlignment, found in AIBeautifulStrokes.h). The standard arrowheads are stored in an AI file in found in Support Files\Required\Resources\en_US (or whatever your language is). You get the path to this folder at runtime using AIFolders::FindFolder() and passing it kAIRequiredLocalizedResourcesFolderType. The filename is Arrowheads.ai.

           

          If you want to read the AI file as a library, you need to use AIPathStyleSuite::ImportStyles(). You pass it the path to the AI file and it will give you an AIDocumentHandle in return. You can use this handle with methods like AISymbolSuite::GetNthSymbolPatternFromDocument(). Just be sure to retarget anything you load for your current document; to do that, you use AISymbolSuite::RetargetForCurrentDocument(). If you don't retarget the style, you'll add a reference to art that doesn't exist in your document and when you close Illustrator, it will crash (because it will try to free up the symbol twice, once for your document & once for the library you loaded, but it only existed once!).

           

          CPaintField is a little utility class I created that basically wraps some code I was given by Adobe, so I'm happy to share it. Its useful whenever you're dealing with AIBeautifulStrokesSuite type stuff. THROW_EXCEP_IF() is a macro we use that checks error to see if its kNoErr, and if its not, we throw an exception. You can replace that with your own error checking, or just remove them, as you wish

           

          class CPaintField {
          public:
            CPaintField(AIParserPaintField& paintField);
            virtual ~CPaintField();
          
            AIDictionaryRef GetEffectsDictionary() const;
          
            void Save();
          
          private:
            ai::Ref<AIDictionaryRef> m_effectsDictionary;
            ai::Ref<AILiveEffectParameters> m_newEffectParams;
            AIParserPaintField& m_paintField;
            AILiveEffectHandle m_paintFieldEffect;
          };
          
          CPaintField::CPaintField(AIParserPaintField& paintField) : m_paintField(paintField), m_paintFieldEffect(0)
          {
            ai::Ref<AIDictionaryRef> oldBSDict;
            AILiveEffectParameters oldEffectParams = 0;
          
            AIErr error = sArtStyleParser->GetPaintLiveEffectInfo(paintField, &m_paintFieldEffect, &oldEffectParams);
            THROW_EXCEP_IF(error);
          
            bool acceptsBSOptions = true;
          
            if (oldEffectParams) {
              // If an old effect parameters dictionary already exists then we need to clone it,
              // so that the modifications will be done to a new art style, not to the old one.
              // We don’t do this until we discover whether or not the object will accept
              // Beautiful Strokes, though, because it may be that we are not modifying anything.
              acceptsBSOptions = true;
          
              AIErr error = sBeautifulStrokes->GetParamsDict(oldEffectParams, *(oldBSDict << ai::Replace));
              THROW_EXCEP_IF(error);
          
              if (acceptsBSOptions) {
                // Create an empty effect params dict
                AIErr error = sLiveEffect->CreateLiveEffectParameters( m_newEffectParams << ai::Replace );
                THROW_EXCEP_IF(error); 
          
                // Clone all the contents of the oldEffectParams into the newEffectParams
                AIErr error = sDictionary->Copy(m_newEffectParams, oldEffectParams);
                THROW_EXCEP_IF(error);
              }
            } else {
              // If there are no oldEffectParams, then the stroke can accept Beautiful Stroke options,
              // but we will have to convert it into an active style first. We do this by creating
              // a live effect param dictionary, and attaching it to the focalStroke.  
              AIErr error = sLiveEffect->CreateLiveEffectParameters(m_newEffectParams << ai::Replace);
              THROW_EXCEP_IF(error);
          
              AIStrokeStyle strokeStyle;
              AIArtStylePaintData paintData;
          
              AIErr error = sArtStyleParser->GetStroke(paintField, &strokeStyle, &paintData);
              THROW_EXCEP_IF(error);
          
              // It is also necessary to transfer the StrokeStyle into the effect params dictionary
              // ourselves, because AIArtStyleParser doesn’t do so for us.
              AIErr error = sDictionary->Set(m_newEffectParams, sDictionary->Key(kStrokeStyleKey), sEntry->FromStrokeStyle(&strokeStyle));
              THROW_EXCEP_IF(error);
            }
          
            if (m_newEffectParams && acceptsBSOptions) {
              if (oldBSDict) {
                // If we had an oldBSDict, we must have one in the cloned effect params as well.
                // So pull the new Beautiful Strokes parameter dictionary out of there.
                AIErr error = sBeautifulStrokes->GetParamsDict(m_newEffectParams, *(m_effectsDictionary << ai::Replace));
                THROW_EXCEP_IF(error);
              } else {
                // Insert a blank Beautiful Strokes dictionary into the new effects dictionary.  
                AIErr error = sBeautifulStrokes->CreateParamsDict(m_newEffectParams, *(m_effectsDictionary << ai::Replace));
                THROW_EXCEP_IF(error);
              }
            }
          }
          
          CPaintField::~CPaintField()
          {
          }
          
          void CPaintField::Save()
          {
            AIErr error = sArtStyleParser->SetPaintLiveEffectInfo(m_paintField, m_paintFieldEffect, m_newEffectParams);
            THROW_EXCEP_IF(error);
          }
          
          AIDictionaryRef CPaintField::GetEffectsDictionary() const
          {
            return m_effectsDictionary;
          }
          
          • 2. Re: How to create path with arrowhead?
            Gaxx Level 1

            Thank you. That solved my problem. As you said, it really was not obvious at all, so you really saved me a lot of time