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
<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>
* 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')
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)
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
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
Thank you!
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..
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>
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()
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.