gridUp! Another Photoshop Tool

I’ve just finished work on a new js photoshop tool! This creates grid lines from a number of frames that the user specifies, using photoshop guides. I haven’t posted about this one because it was pretty small and quick.

gridUplogo

Grid

This mostly built on the knowledge I gained when making packUp, but there was one very interesting part – non-modal windows. This seems to be a total no-go on Windows OS post CS6. The info that’s out there about it suggests its a bug, but considering I’m working with CC 2019 and still running into issues, it seems like its not being fixed any time soon.

This makes the tool a little annoying to use – ideally I’d have a floating panel the user can have open all the time. It also raises questions for the future of these tools – I was planning a toolset with a parent window that contains them all, but this needs to function like a photoshop panel in order to be of any use.

To make a panel, it seems I either need to use actionscript or write an actual plugin. In terms of ease, the AS is definitely the winner, but I have zero desire to lean AS and the way it then calls your jsx files doesn’t look great to deal with.

A plugin would be better for the user and more elegant to write. The only issue is…it would need to be done in C++. Time to move over to the dark side? We’ll see.

Anyway, here’s a gif.

35b916cc75212673ddeed8c8de540ea7

…Or not.

The title is referring to the fact that I didn’t actually finish my channel swizzler. I asked about this on twitter and realised that this tool is useful for others, so would be pretty cool to release properly. Unfortunately that means it needs to be flexible, robust and not just work for my one use case.

Had a big battle with directory syntax and json, but setup a number of different scripts for different use cases and a json config where the user can specify their own paths. Bit better than adding 4 environment vars!

To hide my supporting scripts I had to put them in a folder with Only at the end of the name – this meaning it was hidden. Not sure this is the most sensible way of hiding scripts, but sure.

Will wrap this up and distribute it soon – link incoming! Until then, some images of the tool in use below.

create channel textureswizzle

41

Finished Channel Swizzle

I decided to finish off my channel swizzler by creating a working files and Unity asset folder connection, so that the file in the asset folder is never actually edited, and there’s no confusion with which is which. It also means Unity will never import workfiles which will happen if they are stored in the project directory.

Because my hypthothetical users might store their project folder on different drives, locations etc, I decided to control the path through an environment variable. Editing things on the user’s pc like this isn’t always the best course of action, but works well for situations when you can’t bank on getting the user’s documents or another folder that windows knows.

8

To get photoshop to use this, I used $.getenv (which I thought was broken for a while, until realizing I’d made the variable with photoshop open – oops!), and then replaced all the backslashes with forward ones. This is because backslash is an escape character, used for declaring newlines.  Before I did this, the string was broken, and photoshop kept defaulting to its own path instead.

I then wanted to match the folder within workfiles to the folder within assets/textures. This meant I could manage the Unity folder structure via my saved workfile and didn’t need to resave/move/copy anything to get a nice organised folder. To get the last element in the path.split, I had to use string.pop(), which returns the last element of the list while removing it from said list. (or should I say array? We’re not in Python anymore Toto!) This worked nicley for this purpose, but I would like to find the equivalent of Python’s list[-1] as its really handy for this sort of thing.

var myfolderPath = $.getenv('PROJECT_PATH').replace("\\", "/") + '/' + 
     'Assets/Textures' + '/' + String(currentDoc.path).split("/").pop();
var filePath = new File(myfolderPath + '/' + 
     currentDoc.name.replace(/\.[^\.]+$/, '.psd'));

9

Channel Swizzle Script

Following on from my last blog post, I’ve finished most of my channel swizzle scripts. These generate a file with layers for each channel, then export as a new file with the layers combined into one layer as channels. I channel pack all my VFX textures so it will make my workflow a lot faster!

12

If you want to use this, feel free to save it as a .jsx file in C:\Program Files\Adobe\Adobe Photoshop CC 2019\Presets\Scripts, but I’d appreciate if you drop me a comment to let me know. Code below if you want to skip my rambling!

I had a couple of issues related to understanding the class hierarchy – for example, I thought that I could save select all as an object, and then perform copy on that object, but actually I needed to have a selection object, of which selectAll and copy are both methods – but I got there in the end!

I had a very odd “has no constructor” error when trying to make a new SolidColor instance, but the only way I solved it was by copying what looked like the same code from the scripting reference documents. I have no idea why mine didn’t work – an extra space or bad character perhaps? I haven’t found as many resources though google/stack overflow as I normally find for python and maya related topics.

EDIT: The no constructor error comes from forgetting to set Photoshop as your connected target application – just a silly mistake!

This one does currently save as a psd in the same location, with the same name, so will write over the source file. I’m not sure exactly what I want to do in terms of work file and export file right now, so I’ll change that once I’ve decided on a nice workflow.

Also, found a nice way to preserve the whitespace when copy pasting code onto this blog – the <pre></pre> HTML tag allows the text to be pasted exactly as is, which is super helpful and makes it much more readable.

function main()
{
	var currentDoc = app.activeDocument 

	//make duplicate
	fname = currentDoc.name.split(".")[0]
	newDoc = currentDoc.duplicate(fname)

	swizzle(newDoc)
	savePSD(currentDoc, newDoc)
}

function swizzle(doc)
{
	//make new layer to recive contents
	newLayer = doc.artLayers.add()
	newLayer.kind = LayerKind.NORMAL
	newLayer.name = "Channel Swizzle"
	
	//Fill layer with white
	doc.selection.selectAll()
	var myWhite = new SolidColor()
	myWhite.rgb.red = 255
	myWhite.rgb.green = 255
	myWhite.rgb.blue = 255
	doc.selection. fill(myWhite)
	
	var channels = ["Red", "Green", "Blue"]
	var allLayers = doc.artLayers
	var i 
	
	for (i = 0; i < channels.length; i++)
	{
		var myChannel = doc.channels.getByName(channels[i])
		var myLayer = allLayers.getByName(channels[i])
		doc.activeLayer = myLayer
		//select all
		doc.selection.selectAll()
		//copy without merging
		doc.selection.copy()
		//set active layer
		doc.activeLayer = newLayer
		//select channel
		doc.activeChannels = [myChannel]
		//paste
		doc.paste() //needs a try catch for if there's nothing on the layer
	}

	//put active channel back to all channels
	doc.activeChannels = doc.componentChannels
}

function savePSD(currentDoc, newDoc)
{
	//set png settings and file path
	var psdSettings = new PhotoshopSaveOptions()
	psdSettings.alphaChannels = false
	psdSettings.annotations = false
	psdSettings.embedColorProfile = true
	psdSettings.layers = false
	psdSettings.spotColors = false

	//set file path - careful not to echo anything in built here!
	filePath = new File(currentDoc.path + '/' + currentDoc.name.replace(/\.[^\.]+$/, '.psd'))

	//save new doc
	newDoc.saveAs(filePath, psdSettings, true, Extension.LOWERCASE)

	//close new doc and return focus to original
	newDoc.close(SaveOptions.DONOTSAVECHANGES)
	app.activeDocument = currentDoc

}

main()

Photoshop Scripting with JavaScript

I started having a look at java script for photoshop, as there’s a couple of repetitive tasks in there that frustrate me, such as saving non-psds and channel swizzling.

I chose js rather than apple script or visual basic as it can be applied to other platforms, such as web and is a more useful general skill.

To get started, I did the first “hello world” exercise from Adobe’s scripting guide. This was pretty fun to do, and js reminds me of a python/c# hybrid, so its not too unfamiliar. This gave me a new layer with “hello world” written on it.

//set units to inches
var originalUnit = preferences.rulerUnits //remebering this so we can set it back
preferences.rulerUnits = Units.INCHES

//create new doc
var docRef = app.documents.add(2,4) //2,4 = size

//new layer with text
var artLayerRef = docRef.artLayers.add()
artLayerRef.kind = LayerKind.TEXT

//set text value
var textItemRef = artLayerRef.textItem
textItemRef.contents = “Hello World”

//release references – why do I need to do this?
docRef = null
artLayerRef = null
textItemRef = null

//restore units
app.preferences.rulerUnits = originalUnit

Next, I used what I’d learned in that script, along with the methods listed in Adobe’s javascript reference document to write a script that creates three layers, named R, G, and B. This is the start of a channel swizzle script – one that will take the contents of each of these layers, then move them onto one layer as composite channels.  This will allow for far easier creation, editing and saving of channel packed textures – something I use ALL THE TIME for shaders and VFX.

var artLayerRefR = docRef.artLayers.add()
artLayerRefR.kind = LayerKind.NORMAL
//new layer with text
var artLayerRefG = docRef.artLayers.add()
artLayerRefG.kind = LayerKind.NORMAL
//new layer with text
var artLayerRefB = docRef.artLayers.add()
artLayerRefB.kind = LayerKind.NORMAL

artLayerRefR.name = “R”
artLayerRefG.name = “G”
artLayerRefB.name = “B”

Once I’d done this, I tidied it up a bit, the syntax for for loops in js (not as nice as python! ) and making the script a bit more readable.

var layerNames = [“R”, “G”, “B”]
var i
for (i = 0; i < layerNames.length; i++)
{
var artLayerRef = docRef.artLayers.add()
artLayerRef.kind = LayerKind.NORMAL
artLayerRef.name = layerNames[i]
}

I then used the reference doc again to try making a save script. This was broken for ages despite looking correct, until I realised I’d named my file path variable “path”. I really should have known from python that this is a bad idea! (Going to blame the fact that normally PyCharm picks this up for me – I need to get a nice javascript IDE.) It echoes the inbuilt path var and therefor was either returning an error, or saving where photoshop is installed, depending on admin permissions.

function savePNG()
{
//get current doc
currentDoc = app.activeDocument

//set png settings and file path
var pngSettings = new PNGSaveOptions()
pngSettings.compression = 0
pngSettings.interlaced = false

//set file path – careful not to echo anything in built here!
filePath = new File(currentDoc.path + ‘/’ + currentDoc.name.replace(/\.[^\.]+$/, ‘.png’))

//make duplicate to fuck about with
fname = currentDoc.name.split(“.”)[0]
newDoc = currentDoc.duplicate(fname)

//save new doc
newDoc.saveAs(filePath, pngSettings, true, Extension.LOWERCASE)

//close new doc and return focus to original
newDoc.close(SaveOptions.DONOTSAVECHANGES)
app.activeDocument = currentDoc

}

savePNG()

I combined the last two scripts to make the output part of the channel swizzle script, learning how javascript does try catch in the process. Photoshop’s alert function is very nice – allowing me to provide the user an error without having to write up any ui!

function scanLayers()
{
//get all layers
var allLayers = copyDoc.artLayers

//loop through layers
var r
var g
var b

//g = allLayers.getByName(“G”)

try
{
r = allLayers.getByName(“R”)
g = allLayers.getByName(“G”)
b = allLayers.getByName(“B”)
}

catch(e)
{
var errorMsg = “Please make sure your document has layers named R, G and B”
alert(errorMsg)
CloseAndReturn(copyDoc, docRef)
}

saveAsPSD()
CloseAndReturn(copyDoc, docRef)

}

Next step isto get this copying the layer contents into channels.