Inkscape.org
Creating New Extensions New extension! Frame, circle or resize objects
  1. #1
    RW E2 RW E2 @RWE2
    *

    Here is the my "help" page from my frame.inx file:

    This extension frames or resizes existing objects

    * The new size is calculated by adding a specified +/- "margin" to the existing size for each object

    * For each selected object, the extension can resize it or create a frame for it
      * The frame can be a rectangle or an ellipse.  
      * For a rectangular frame, corner rounding can be specified.
      * Frame stroke color and thickness can be specified; defaults use attributes from selected object
      
    * The bounding box type can be specified
      * Geometrical: Works for most objects
      * Visual: Needed for text, requires a time-consuming write to a temporary file
      * Duplicate: Derives geometrical bounding box from a copy of the selected element

    The extension is written in Python2 but uses only features supported in Python3.  I used the code in existing .91 extensions as my pattern.

    Now comes the hard part: Getting "frame.py" from level 4 to level 3, 2, and 1.  The code, including comments and blank lines, is 224 lines long.  Should I post the code here?  If so, how? -- embedded? as an attachment?

    Since this is my first extension, I'm a little excited.  I wasn't sure this could be done -- I still don't know of a simple direct way to get the bounding box for text.   I used the solution used in dimension.py -- I write the text to a temporary file then read it back


     

  2. #2
    Marco Riva Marco Riva @zerocinquanta

    Thank you!

  3. #3
    RW E2 RW E2 @RWE2
    *

    Thank you, in turn, for your interest.  Are there any extensions you would like to see?  If so, maybe I can help.

    I don't know exactly how to proceed here.  I think I will try posting frame.inx and frame.py as attachments, and see what happens..  
     

     

  4. #4
    RW E2 RW E2 @RWE2

    Well, attachment does not seem to work, so I will try posting the files directly.  First, frame.inx:

    <?xml version="1.0" encoding="UTF-8"?>
    <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
      <_name>Frame an object</_name>
      <id>cjo.frame</id>
      
      <dependency type="executable" location="extensions">frame.py</dependency>
      <dependency type="executable" location="extensions">inkex.py</dependency>
        <dependency type="executable" location="extensions">pathmodifier.py</dependency>
      <dependency type="executable" location="extensions">simpletransform.py</dependency>
      
      <param name="Page" type="notebook">
        <page name="Spec" _gui-text="Specifications">
          
          <param name="NewT" type="enum" _gui-text="New object type:">
            <_item value="none">None</_item>
            <_item value="rectangle">Rectangle</_item>
            <_item value="ellipse">Ellipse</_item>
          </param>
          
          <param name="BTyp" type="enum" _gui-text="Bounding box type:">
            <_item value="geometric">Geometric</_item>
            <_item value="visual">Visual</_item>
            <_item value="duplicate">Duplicate</_item>
          </param>
          
          <param name="Marg" type="float" min="-500" max="500" _gui-text="Margin, vertical: ">40</param>
          <param name="WHRa" type="float" min="0"    max="10"  _gui-text="H/V margin ratio: ">2</param>
          <param name="CRad" type="float" min="0"    max="100" _gui-text="Corner radius: ">0</param>
          <param name="StrC" type="string"                     _gui-text="Stroke color (#RGB): ">#000000</param>
          <param name="StrW" type="float"                      _gui-text="Stroke width: ">4</param>
        
        </page>
        
        <page name="Help" _gui-text="Help">
          <_param name="instructions" type="description" xml:space="preserve">
    This extension frames, circles or resizes existing objects

    * The new size is calculated by adding a specified +/- "margin" to the existing size 
      for each object selected

    * For each selected object, the extension can resize it or create a frame for it
      * The frame can be a rectangle or an ellipse.  
      * For a rectangular frame, corner rounding can be specified.
      * Frame stroke color and thickness can be specified; defaults use attributes from selected object
      
    * The bounding box type can be specified
      * Geometrical: Works for most objects
      * Visual: Needed for text, requires a time-consuming write to a temporary file
      * Duplicate: Derives geometrical bounding box from a copy of the selected element
          </_param>      
        </page>
      
      </param>
      
      <effect>
        <object-type>path</object-type>
        <effects-menu>
          <submenu _name="Generate from Path" />
        </effects-menu>
      </effect>
      
      <script>
        <command reldir="extensions" interpreter="python">frame.py</command>
      </script>

    </inkscape-extension>
     

  5. #5
    RW E2 RW E2 @RWE2

    Now, frame.py:

    #!/usr/bin/env python
    '''
    frame.py

    * Puts a frame around an object, using the specified H margin and W/H margins ratio

    * This code is modeled after inkscape/extensions/dimension.py, developed in 
      2007 by Peter Lewerin, peter.lewerin@tele2.se

    * It uses the selection's bounding box, so if the bounding box has empty
      space in the x- or y-direction (such as with some stars) the results
      will look strange.  Strokes might also overlap the edge of the 
      bounding box.

    * This code, like dimension.py, contains code from existing effects in the Inkscape
      extensions library, and marker data from markers.svg.
    '''


    # standard library
    import sys, copy
    sys.path.append('/usr/share/inkscape/extensions')

    try:
        from subprocess import Popen, PIPE
        gBSPr = True
    except:
        gBSPr = False

    # local library
    import inkex
    import pathmodifier
    from simpletransform import *

    inkex.localize()

    import inkex, simpletransform, simplestyle

    class oFram( pathmodifier.PathModifier):
      
      def __init__( s):
        inkex.Effect.__init__( s)

        s.OptionParser.add_option('-p', '--Page', action='store', type='string', dest='aPage', default='',          help='The selected UI-tab when OK was pressed') 
        s.OptionParser.add_option('-n', '--NewT', action='store', type='string', dest='aNewT', default='rectangle', help='New object type')
        s.OptionParser.add_option('-t', '--BTyp', action='store', type='string', dest='aBTyp', default='geometric', help='Bounding box type')
        s.OptionParser.add_option('-m', '--Marg', action='store', type='float',  dest='aMarg', default=40,          help='Vertical margin')
        s.OptionParser.add_option('-r', '--WHRa', action='store', type='float',  dest='aWHRa', default=2,           help='W/H margin ratio')
        s.OptionParser.add_option('-c', '--CRad', action='store', type='float',  dest='aCRad', default=0,           help='Corner radius')
        s.OptionParser.add_option('-k', '--StrC', action='store', type='string', dest='aStrC', default='#000000',   help='Stroke color (RGB)')
        s.OptionParser.add_option('-w', '--StrW', action='store', type='float',  dest='aStrW', default=4,           help='Stroke thickness')
      
      def effect( s):

        tFill = { 
        # 'stroke'        : '#000000',
        # 'stroke-width'  : '4',
          'fill'          : 'none'
        }

        tBBox = None

        tNewT = s.options.aNewT
        tMarg = float( s.options.aMarg)
        tWHRa = float( s.options.aWHRa)
        tCRad = float( s.options.aCRad)
        tStrC = s.options.aStrC
        tStrW = s.options.aStrW

        tMovY = tMarg 
        tMovX = tMovY * tWHRa

        tScal = s.unittouu('1px')    # convert to document units
        tMovY *= tScal
        tMovX *= tScal


        # Query inkscape about selection and bounding box type
        # * ids: List of selected SVG elements: element id= value
        # * selected: Dict of selected elements: key is element id=, value is the SVG element 
        # * options: inx options plus ids (ids for selected elements)
        
        inkex.errormsg( 'args(%s)'     % (s.args))  
        inkex.errormsg( 'ids(%s)'      % (s.options.ids))  
        inkex.errormsg( 'options(%s)'  % (s.options))  
        inkex.errormsg( 'selected(%s)' % (s.selected))  

        if len( s.options.ids) == 0:
          inkex.errormsg( _( 'Please select an object.'))
          exit()
       
        tObjP = s.current_layer
       
        # Process selected items
        # : selected: dict, using item id as key and item as value
        #
        for tNoId, tNoCu in s.selected.iteritems():

          inkex.errormsg( 'Iter: tNoId(%s) tNoCu(%s)' % ( tNoId, tNoCu))
          
          tBTyp = s.options.aBTyp  # Refresh
          
          tStyl = { 'style': simplestyle.formatStyle( tFill) }
          
          tElem = None

          if tBTyp == 'duplicate':
            while tElem == None:
              if tNewT == 'rectangle': tNoNe = inkex.etree.SubElement( tObjP, inkex.addNS( 'rect', 'svg'), tStyl ); break
              if tNewT == 'ellipse':   tNoNe = inkex.etree.SubElement( tObjP, inkex.addNS( 'ellipse', 'svg'), tStyl ); break
              if tNewT == 'none':      tNoNe = tNoCu; break
              break;

            tNoNe = copy.deepcopy( tNoCu)
            tNoId = s.uniqueId( tNoId)
            tNoNe.set( 'id', tNoId)
          # simpletransform.applyTransformToNode( tCRes, tNoNe)
            s.current_layer.append( tNoNe)
            tNoCu = tNoNe
          # tNoId = tNoCu[ 'id']
          # tElem = tNoCu
            inkex.errormsg( 'Dupl: tNoId(%s) tNoCu(%s)' % ( tNoId, tNoCu))
            tBTyp = 'geometric'


          tQuer = { 'x':0, 'y':0, 'width':0, 'height':0}
         
          while True:
            if tBTyp == 'geometric':
            # s.mBBox = tBBox = computeBBox( s.selected.values())
              s.mBBox = tBBox = computeBBox( [ tNoCu])
            # s.mBBox = tBBox = computeBBox( tNoId)
              tQuer[ 'x'] = tBBox[ 0]
              tQuer[ 'y'] = tBBox[ 2]
              tQuer[ 'width']  = tBBox[ 1] - tBBox[ 0]
              tQuer[ 'height'] = tBBox[ 3] - tBBox[ 2]
              break

            if tBTyp == 'visual':
              tFile = s.args[ -1]
            # tIds0 = s.options.ids[ 0]
              tIds0 = tNoId
              for tQKey in tQuer.keys():
                if gBSPr:
                  tProc = Popen( 'inkscape --query-%s --query-id=%s "%s"' % ( tQKey, tIds0, tFile), shell=True, stdout=PIPE, stderr=PIPE)
                  tRetC = tProc.wait()
                  tQuer[ tQKey] = tScal * float( tProc.stdout.read())
                  tErrM = tProc.stderr.read()
                else:
                  tFOp3, tErrF = os.popen3('inkscape --query-%s --query-id=%s "%s"' % ( tQKey, tIds0, tFile))[ 1:]
                  tQuer[ tQKey] = tScal * float( tFOp3.read())
                  tFOp3.close()
                  tErrF.close()
              s.mBBox = tBBox = ( tQuer[ 'x'], tQuer[ 'x'] + tQuer[ 'width'], tQuer[ 'y'], tQuer[ 'y'] + tQuer[ 'height'])
              break

            break
          
          # Avoid ugly failure on rects and texts.
          #
          if 0:
                    try:
                      tTest = s.mBBox[ 0]  # Testing the water
                    
                    except TypeError:
                      inkex.errormsg( _( 'Unable to process this object.  Try changing it into a path first.'))
                      exit()
          
          if tBBox == None or tBBox[ 0] == None:
            inkex.errormsg( _( 'Unable to process this object.  Try changing it into a path first.'))
            exit()
          
          tStyl = simplestyle.parseStyle( tNoCu.get( 'style'))
          
          tAttr = { 'fill': 'none' }

          if tStyl.has_key( 'stroke'):       tAttr[ 'stroke']       = str( tStyl[ 'stroke'])
          if tStyl.has_key( 'stroke-width'): tAttr[ 'stroke-width'] = str( tStyl[ 'stroke-width'])

          while tElem == None:
            if tNewT == 'rectangle': tElem = inkex.etree.SubElement( tObjP, inkex.addNS( 'rect', 'svg'), tAttr );    break
            if tNewT == 'ellipse':   tElem = inkex.etree.SubElement( tObjP, inkex.addNS( 'ellipse', 'svg'), tAttr ); break
            if tNewT == 'none':      tElem = tNoCu; break
            break;

          while True:
            
            if tElem.tag == inkex.addNS( 'rect', 'svg'):
              tAttr = {} 
              tAttr[ 'sy'] = tQuer[ 'height'] + 2 * tMovY
              tAttr[ 'sx'] = tQuer[ 'width']  + 2 * tMovX
              tAttr[ 'py'] = tQuer[ 'y'] - tMovY
              tAttr[ 'px'] = tQuer[ 'x'] - tMovX
              
              tElem.set( 'height', str( tAttr[ 'sy']))
              tElem.set( 'width',  str( tAttr[ 'sx']))
              tElem.set( 'y',      str( tAttr[ 'py']))
              tElem.set( 'x',      str( tAttr[ 'px']))
              tElem.set( 'ry',     str( tCRad))
              tElem.set( 'rx',     str( tCRad))
              break
            
            if tElem.tag == inkex.addNS( 'ellipse', 'svg'):
              tAttr = {}
              tAttr[ 'ry'] = tQuer[ 'height'] / 2 + tMovY
              tAttr[ 'rx'] = tQuer[ 'width']  / 2 + tMovX
              tAttr[ 'cy'] = tQuer[ 'y'] + tQuer[ 'height'] / 2
              tAttr[ 'cx'] = tQuer[ 'x'] + tQuer[ 'width']  / 2
              
              tElem.set( 'ry', str( tAttr[ 'ry']))
              tElem.set( 'rx', str( tAttr[ 'rx']))
              tElem.set( 'cy', str( tAttr[ 'cy']))
              tElem.set( 'cx', str( tAttr[ 'cx']))
              break
            
            break

          if tStrC != '': tElem.set( 'stroke',       str( tStrC))
          if tStrW > 0:   tElem.set( 'stroke-width', str( tStrW))
        

    if __name__ == '__main__':
      tFram = oFram()
      tFram.affect()

     

     

  6. #6
    Tyler Durden Tyler Durden @TylerDurden
    *

    Probably better to use GitLab to work on extension code.

    That aside, to make code snippets in the forum easier to read, selecting "formatted"  from the paragraph style dropdown menu might help.

     

    #!/usr/bin/env python
    '''
    frame.py
    
    * Puts a frame around an object, using the specified H margin and W/H margins ratio
    * This code is modeled after inkscape/extensions/dimension.py, developed in 2007 by Peter Lewerin, peter.lewerin@tele2.se
Inkscape Inkscape.org Inkscape Forum Creating New Extensions New extension! Frame, circle or resize objects