Mini Kabibi Habibi
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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)