////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Here are a few parameters that you can change to modify the behavior of the patterm // Feel free to modify the values, don't change the variable names modelParameters = { // scaling of the input pattern patternScale : 1, // Add spacing in pixels between subsequent patterns. spacing : 0, // in pixels, use a value between -10 to 50 (depending on the pattern size). The default is 0. // Add extra spacing to place the last symbol at the end of the path addSpaceToFit : false, // false by default // Angle of the pattern from the direction of the path angleFromPath : 0, // If set to true the direction of the rotation specified by angleFromPath will alternate alternate : true, // true or false // Distance of the center of the pattern from the path distanceFromPath : 0, // in pixels // Scale factor in percent scaleAlong : 100, // value around 100, in fact, very close to 100. // Skip Rotation skipRotation : false, // true of false // Variation of color of the pattern. // For example, value of 0.2 means that each of the red, green, and blue color components // will be multiplied by a DIFFERENT random value from interval 0.8 and 1.2. // Set to 0 if you do not want to modify the pattern color. colorRandomness : 0.05, // use a value between 0 and 1. The default is 0.05. // Variation of pattern brightness. // For example, value of 0.6 means that each of the red, green, and blue color components // will be multiplied by THE SAME random value from interval 0.4 and 1.6. // Set to 0 if you do not want to modify the pattern brightness. brightnessRandomness : 0.1, // use a value between 0 and 1. The default is 0.1. } /////////////////////////////////////////////////////////////////////////// // Get pattern and its size var pattern = RenderAPI.getParameter(kpsPattern) var patternSize = pattern.getParameter(kpsSize) var patternSizeForDialog = Math.max (patternSize.x, patternSize.y) //pattern.setParameter(kpsMaxPatternCacheSize, patternSize.x * patternSize.y * 7 * 8 * 180) var inputPaths = RenderAPI.getParameter (kpsSelectedPaths) if (inputPaths.length == 0) { Engine.error ("$$$/DecoScripts/PlaceAlongPath/NoPath=No path selected. Please select at least one path") // no need to call localize since the string is localized inside the Engine } //alert (paths.length + " path(s)") function run (api, parameters, scale) { // get size of the output area var outputSize = api.getParameter(kpsSize) var outputOrigin = api.getParameter(kpsOrigin) var patternSize = pattern.getParameter(kpsSize) var maxPatternSize = patternSize.x // we use only x in this case patternSize *= parameters.patternScale if (parameters == previewParameters) { // set scale so that we show about 10-15 patterns if (maxPatternSize < previewSize / 15) scale = previewSize / (15 * maxPatternSize) else if (maxPatternSize > previewSize / 10) scale = previewSize / (10 * maxPatternSize) } //alert ("origin = " + outputOrigin.x + ", " + outputOrigin.y) //alert ("size = " + outputSize.x + ", " + outputSize.y) // scale up if the patternsize is 1 to avoid long loop if (patternSize.x == 1 && patternSize.y == 1) { if (scale == 1) scale = 20 patternSize *= scale parameters.skipRotation = true // 1x1 patterns that are scaled up do not rotate gracefully } var paths = inputPaths if (parameters == previewParameters) { paths = new Array(0) paths.push (new DecoGeometry) //paths[0].addLineStrip(new Vector3(0,0,0) + outputOrigin, new Vector3(255, 255, 0) + outputOrigin) paths[0].addBezier (new Vector3(10, (previewSize-10), 0) + outputOrigin, new Vector3(10, 115,0 ) + outputOrigin, new Vector3( (previewSize-10), 140, 0) + outputOrigin, new Vector3( (previewSize-10), 10, 0) + outputOrigin) } var step = scale * (patternSize.y + parameters.spacing) // pattern scale doesn't affect the step if (step < 1) step = 1 // smallest step is 1 pixel for (var p = 0; p < paths.length; p++) { var geometry = paths[p] var glength = geometry.getValue(kGetGeometryLength) var d = 0 //scale*patternSize.y/2 // no spacing involved var pt1 = geometry.getValue(kGetPointAlongGeometry, 0) var pt2 = geometry.getValue(kGetPointAlongGeometry, glength - 0.1) var isClosed = (pt1 - pt2).length() < 1.5 // 1.5 pixel //alert(isClosed) if (parameters.addSpaceToFit) { if (parameters.scaleAlong == 100) { // easier case // step is fixed so after n steps we will reach the distance of n* step // Just divide glength by step, take floor and adjust step var n = Math.floor(glength / step) step = glength / n } else { // more complicated // After n step we will reach the distance of step * (scale^(n+1) - 1) / (scale - 1) var sc = parameters.scaleAlong * 0.01; if (sc < 1 && step / (1 - sc) < glength) { // we won't reach the end, adjust the step so we do step = glength * (1 - sc) } else { // we take the expression above, make it equal to length and solve for n n = Math.floor(Math.log(glength * (sc - 1) / step + 1) / Math.log(sc) - 1) // now we have a new n and we need to adjust step step = glength / ( (Math.pow(sc, n+1) - 1) / (sc - 1) ) } } } var index = 0 var seed = Math.floor(p * 2531011) % 0x7fffffff //7327) Engine.rand(seed) if (parameters == previewParameters) { api.Color (kStrokeColor, 0.4,0.4,0.4, 1) api.pushMatrix() api.lineWidth(0.5) api.translate(-outputOrigin) // path points are in screen coordinates, not in coordinates of the bounding box of the selection geometry.render(api) api.popMatrix() } while (d <= glength +1.5) { if (isClosed && d >= glength - 0.5) // skip the last one - already drawn at the beginning of teh closed path break; var pt = geometry.getValue(kGetPointAlongGeometry, d > glength-0.1 ? glength-0.1 : d) var normal = geometry.getValue(kGetNormalAlongGeometry, d > glength-0.1 ? glength-0.1 : d) var angle = Math.atan2( normal.y, normal.x) * 180 / Math.PI api.pushMatrix() api.translate(pt - outputOrigin) // path points are in screen coordinates, not in coordinates of the bounding box of the selection api.scale (scale, scale) var dir = 1 if (parameters.alternate && (index % 2) == 0) dir = -1; // adjust to normal api.rotate(90 + angle) api.translateRel(0, -dir * parameters.distanceFromPath) if (!parameters.skipRotation) { if (dir == 1) api.rotate(parameters.angleFromPath) else api.rotate(- parameters.angleFromPath) } else // adjust back api.rotate(-(90 + angle)) // Set the seed based on the current row and column - this assures that the color will be modified // in the same way for the pattern in the neighboring selected area var seed = Math.floor(d *17 + p * 2531011) % 0x7fffffff //7327) var rc = parameters.colorRandomness // color randomness var br = 1 - parameters.brightnessRandomness + Engine.rand() * parameters.brightnessRandomness*2 // brightness api.Color (kFillColor, br *(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2), br*(1 - rc + Engine.rand()*rc*2)) if (dir == -1) api.scale (1, -1) api.scale(parameters.patternScale) pattern.render(api) api.popMatrix() d += step step *= parameters.scaleAlong * 0.01 scale *= parameters.scaleAlong * 0.01 if (step < 1) break; index++ } } if (parameters == previewParameters) Engine.render (api) } ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Menu start // If you want to create a menu in which you change some of the script parameters, include the following code: // For shipped scripts we can include localized strings, prefixed with $$$/ - call method localize(string) on the prefixed string // For your own strings, you can query app.locale and select several language versions (for example, your native language and english). Try alert ("Language is: " + app.locale) var decoMenu = { // an object that defines the menu menuTitle : localize("$$$/DecoScripts/PlaceAlongPath/PlaceAlongPath=Place Along Path"), menuBackground : [0.93, 0.93, 0.93, 1], previewBackground : [1, 1, 1, 1], panels : [ { panelName : "", leftColumnWidth : 180, unitsWidth : 65, panelMenu : [ { itemName : localize("$$$/DecoScripts/PatternScale=Pattern Scale:") , itemUnit : "", itemType : 'slider', itemValue : modelParameters.patternScale, itemMin : 0.1, itemMax : 1.25, itemStep : 0.01, varName : 'patternScale' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/Spacing=Spacing:") , itemUnit : localize("$$$/DecoScripts/Units/pixels=pixels"), itemType : 'slider', itemValue : modelParameters.spacing, itemMin : Math.min(-50, -1 * patternSizeForDialog), itemMax : Math.max(150, 3 * patternSizeForDialog), itemStep : 1, varName : 'spacing' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/AdjustSpacingToFit=Adjust spacing to fit:") , itemType : 'checkbox', itemValue : modelParameters.addSpaceToFit, varName : 'addSpaceToFit' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/angleFromPath=Angle from path:"), itemUnit : localize("$$$/DecoScripts/Units/degrees=degrees"), itemType : 'slider', itemValue : modelParameters.angleFromPath , itemMin : -90, itemMax : 90, itemStep : 1, varName : 'angleFromPath' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/distanceFromPath=Distance from path:"), itemUnit : localize("$$$/DecoScripts/Units/pixels=pixels"), itemType : 'slider', itemValue : modelParameters.distanceFromPath, itemMin : 0, itemMax : Math.max(200, 4 * patternSizeForDialog), itemStep : 1, varName : 'distanceFromPath' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/aleternatePatterns=Alternate patterns:"), itemType : 'checkbox', itemValue : modelParameters.alternate, itemMin : 0, itemMax : 0, varName : 'alternate' }, { itemName : localize("$$$/DecoScripts/PlaceAlongPath/scaleProgression=Scale progression:"), itemType : 'slider', itemUnit : localize("$$$/DecoScripts/Units/percent=%"), itemValue : modelParameters.scaleAlong, itemMin : 90, itemMax : 110, itemStep : 0.1, varName : 'scaleAlong' } , { itemName : localize("$$$/DecoScripts/PlaceAlongPath/skipSymbolRotation=Skip symbol rotation:"), itemType : 'checkbox', itemValue : modelParameters.skipRotation, itemMin : 0, itemMax : 0, varName : 'skipRotation', disableItems : [ [true, [1,3]] ] } , { itemName : localize("$$$/DecoScripts/ColorRandomness=Color randomness:"), itemUnit : "", itemType : 'slider', itemValue : modelParameters.colorRandomness, itemMin : 0, itemMax : 1, itemStep : 0.01, varName : 'colorRandomness' }, { itemName : localize("$$$/DecoScripts/BrightnessRandomness=Brightness randomness:"), itemUnit : "", itemType : 'slider', itemValue : modelParameters.brightnessRandomness, itemMin : 0, itemMax : 1, itemStep : 0.01, varName : 'brightnessRandomness' }, ] } ] // end of panels } // end of menu // If livePreview is set to 1, the preview image is updated live. Note that due to limitations of scripted menus the update is slow and the flickering may be disturbing. livePreview = 0 // recommended value is 0 // Call Photoshop Script that creates the menu Engine.evalFile ("_Deco Menu.jsx") // Menu finished ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// if (typeof skipRun == 'undefined' || !skipRun) // run unles we exited the preview window without pressing a button run(RenderAPI, modelParameters, 1)