from math import pow, sqrt from JascApp import * # For updates to this script and other resources, visit Pixelnook at # http://pixelnook.home.comcast.net # Revision history # 0.5 More text conversion changes. Reuses stroked object layer if present. # 0.4 Fixed bug in reselection of objects after text conversion # 0.3 Modified to use tubes # 0.2 Changed to run restricted, added text support, added auto # estimate of number of points for curve tracking - Gary Barton # 0.1 Initial code - Joe Fromm, Peter Ward def ScriptProperties(): return { 'Author': 'Gary Barton, Joe Fromm, and Peter Ward', 'Copyright': 'Copyright 2003 Gary Barton and Jasc Software Inc. All rights reserved.', 'Description': 'Take a simple vector object and run a tube along its outline', 'Host': 'Paint Shop Pro 8', 'Host Version': '8.10' } def Do(Environment): # Remember the starting layer StartPath = App.Do( Environment, 'ReturnLayerProperties')['Path'] # Get all the selected vector objects. Result = App.Do( Environment, 'ReturnVectorObjectProperties', {}) ObjList = Result['ListOfObjects'] if ObjList is None: App.Do(Environment, 'MsgBox', { 'Buttons': App.Constants.MsgButtons.OK, 'Icon': App.Constants.MsgIcons.Stop, 'Text': 'No object selected. You must have one or more vector objects selected.' }) return # Temporarily convert text objects into curves so we can capture their path. ObjList += ConvertText(Environment, ObjList) # Select the original starting layer. CurrentPath = App.Do(Environment, 'ReturnLayerProperties')['Path'] App.Do( Environment, 'SelectLayer', { 'Path': (9999, StartPath[1] - CurrentPath[1], StartPath[2], App.Constants.Boolean.false) }) # Create a new raster layer for the stroked objects unless the current layer # is already a stroked object layer. CurrentLayerName = App.Do(Environment, 'ReturnLayerProperties')['General']['Name'] if CurrentLayerName != u'Stroked Objects': App.Do( Environment, 'NewRasterLayer', { 'General': { 'Name': u'Stroked Objects', }, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Match } }) # now iterate over all the objects that are selected for Obj in ObjList: BrushPath = [] # each object will start a new brush stroke. # we aren't using anything but the path data of the object if Obj['ObjectType'] =='Path': # the path is a list of tuples. The first element of the tuple is a constant that # tells us how to interpret the rest of the data. for ControlPoint in Obj['ObjectData']['Path']: if ControlPoint[0] == App.Constants.PathEntryType.MoveTo: # on a move to we put out any brush stroke we have been accumulating if len(BrushPath) > 0: App.Do( Environment, 'PictureTube', { 'Stroke': MakeBrushStroke(BrushPath), 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Default, 'AutoActionMode': App.Constants.AutoActionMode.Match } }) # movetos always start a new stroke, so clear our point list and put the start point in BrushPath = [] BrushPath.append( ControlPoint[1] ) elif ControlPoint[0] == App.Constants.PathEntryType.LineTo: # a lineto is a single line segment - just add the point to the brush stroke BrushPath.append( ControlPoint[1] ) elif ControlPoint[0] == App.Constants.PathEntryType.CurveTo: # curve tos are represented by Bezier curves. I represent the bezier as 25 # different points on the stroke, since I don't want to write the code to # intelligently split the bezier. # Beziers run from the previous point on the object (which is the last point # we added to the brush path), through 2 control handles, and to an end point. StrokePoints = GetBezierPoints( BrushPath[-1], ControlPoint[1], ControlPoint[2], ControlPoint[3]) BrushPath += StrokePoints else: # this better be a close path # on a close just put the initial point back on BrushPath.append( BrushPath[0] ) # put out a stroke before moving on if len(BrushPath) > 0: App.Do( Environment, 'PictureTube', { 'Stroke': MakeBrushStroke(BrushPath), 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Default, 'AutoActionMode': App.Constants.AutoActionMode.Match } }) BrushPath = [] #we've painted it, so clear out our brush path print 'Done' def MakeBrushStroke(Path): '''Take input in the form of x,y coordinates and return a list that is compatible with a brush stroke. This means putting on the path entry parameter, and adding a time stamp. Time stamp will be ignored, but we just bump it by one each time to make it look like real data ''' t = 0 OutPath = [] for p in Path: OutPath.append( (App.Constants.PathEntryInterpretation.Absolute, p, t) ) t += 1 return OutPath def GetNumPoints(p0, p1, p2, p3): '''Estimate the number of points needed to track the curve to the desired degree of flatness.''' def dist(x, y): return abs(x) + abs(y) flatness = 0.01 x = p0[0] - 2.0*p1[0] + p2[0] y = p0[1] - 2.0*p1[1] + p2[1] d1 = dist(x, y) x = p1[0] - 2.0*p2[0] + p3[0] y = p1[1] - 2.0*p2[1] + p3[1] d2 = dist(x, y) return int(round((1 + sqrt(0.75 * max(d1, d2) / flatness)))) def GetBezierPoints( Start, Handle1, Handle2, End): 'create a list of points along a bezier curve' # Estimate the number of points needed NumPoints = GetNumPoints(Start, Handle1, Handle2, End) # spread out our points along the curve UVals = [] for i in range( NumPoints - 1): UVals.append( float(i)/NumPoints) UVals.append( 1.0 ) # now compute the bezier for values of U Points = [] for U in UVals: px = Start[0] * pow(1.0 - U, 3) + \ Handle1[0] * 3.0 * U * pow(1.0 - U, 2) + \ Handle2[0] * 3.0 * pow(U,2) * (1.0-U) + \ End[0] * pow(U, 3) py = Start[1] * pow(1.0 - U, 3) + \ Handle1[1] * 3.0 * U * pow(1.0 - U, 2) + \ Handle2[1] * 3.0 * pow(U,2) * (1.0-U) + \ End[1] * pow(U, 3) U += (1.0 / NumPoints) Points.append( (px, py) ) return Points def ConvertText(Environment, ObjList): TextObjList = [] # Get our current layer path currentPath = App.Do(Environment, 'ReturnLayerProperties')['Path'] for Obj in ObjList: if Obj['ObjectType'] == 'Text': # Select the text layer (Out all the way, Over relative to the # current layer, and In according to the object path) App.Do( Environment, 'SelectLayer', { 'Path': (9999, Obj['Path'][1] - currentPath[1], [], App.Constants.Boolean.false) }) App.Do( Environment, 'VectorSelectNone') App.Do( Environment, 'VectorSelectionUpdate', { 'Path': (0, 0, Obj['Path'][2], App.Constants.Boolean.false), 'Type': App.Constants.ObjectSelection.Select, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Default } }) App.Do( Environment, 'ConvertTextToSingleShape') Result = App.Do( Environment, 'ReturnVectorObjectProperties', {}) App.Do( Environment, 'UndoLastCmd') TextObjList.append(Result['ListOfObjects'][0]) # If there were any text objects in the list, we'll have altered the layer # selection so reselect all the objects in the list. This isn't strictly # necessary but it's helpful if the user wants to run the script multiple # times. if len(TextObjList) > 0: # Get our current layer path currentPath = App.Do(Environment, 'ReturnLayerProperties')['Path'] App.Do( Environment, 'VectorSelectNone') for Obj in ObjList: App.Do( Environment, 'VectorSelectionUpdate', { 'Path': (9999, Obj['Path'][1] - currentPath[1], Obj['Path'][2], App.Constants.Boolean.false), 'Type': App.Constants.ObjectSelection.AddToSelection, 'GeneralSettings': { 'ExecutionMode': App.Constants.ExecutionMode.Silent, 'AutoActionMode': App.Constants.AutoActionMode.Default } }) return TextObjList