Copy link to clipboard
Copied
Basically, I switch between the stamp tool preset and the mixer brush preset.
Often I end up adjusting the tool size and hardness depending on image characteristics.
Is it possible to script a stamp tool preset that includes all the tool parameters except the stamp tool size and hardness? The idea here is to inherit the last used brush size and hardness.
Alternatively, is it possible to edit a toolPrest.tpl file to edit the tool size and hardness parameters to undefined?
Try this code. Script uses Photoshop events and launches itself when notifications appear, so be sure to save it to disk before running.
On first run, the script will set up event tracking for painting to canvas and switching presets. As you paint on the canvas by any tool from object 'trackTools', Photoshop will call a script to remember the tool's name and brush options. After you switch the preset, the script will receive current tool data and try to find the previously saved brush parameters,
...Copy link to clipboard
Copied
Copy link to clipboard
Copied
That sounds good. I have not opened a .tpl file before. And, not sure if I am opening the file correctly.
I try with the mac visual studio code with the hex editor extension and I get a block of numbers and letters that look like the screenshot. I was not able to locate the syntax DmtrUntF#Pxl and HrdNUntF#Prc in this view.
What is the recommended way to open a .tpl file?
Copy link to clipboard
Copied
I figure out how to edit the tpl file with a different hex editor.
There are several instances of the hex code DmtrUntF#Pxl and HrdnUntF#Prc in the tpl file.
Apparently belonging to different tools. I did not find the clone stamp tool in the file. The losest is the magicStampTool. How do I identify the clone stamp tool in this tpl file?
Copy link to clipboard
Copied
Your problem can be solved in another way. As I will be in the workplace, I will post my version.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Try this code. Script uses Photoshop events and launches itself when notifications appear, so be sure to save it to disk before running.
On first run, the script will set up event tracking for painting to canvas and switching presets. As you paint on the canvas by any tool from object 'trackTools', Photoshop will call a script to remember the tool's name and brush options. After you switch the preset, the script will receive current tool data and try to find the previously saved brush parameters, if it succeeds, it will update the tool settings and return the previous parameters.
When you restart Photoshop, the brush settings remembered by the script are reset. To disable event tracking, simply run the script again.
#target photoshop
var t2s = typeIDToStringID,
s2t = stringIDToTypeID,
evt = null,
tgt = null;
var trackTools = {
spotHealingBrushTool: true,
magicStampTool: true,
paintbrushTool: true,
pencilTool: true,
colorReplacementBrushTool: true,
wetBrushTool: true,
cloneStampTool: true,
patternStampTool: true,
historyBrushTool: true,
artBrushTool: true,
eraserTool: true,
backgroundEraserTool: true,
blurTool: true,
sharpenTool: true,
smudgeTool: true,
dodgeTool: true,
burnInTool: true,
saturationTool: true
};
try { tgt = arguments[0], evt = t2s(arguments[1]) } catch (e) { };
try {
if (evt) {
switch (evt) {
case 'toolModalStateChanged':
if (t2s(tgt.getEnumerationValue(s2t('kind'))) == 'paint') {
if (tool = getCurrentToolOptions())
if (trackTools[tool.toolName]) app.putCustomOptions(tool.toolName, tool.brush, false)
}
break;
case 'select':
if (tool = getCurrentToolOptions()) {
try { var tracked = app.getCustomOptions(tool.toolName) } catch (e) { }
if (tracked) {
tool.brush.putUnitDouble(s2t('diameter'), s2t('pixelsUnit'), tracked.getUnitDoubleValue(s2t('diameter')));
tool.brush.putUnitDouble(s2t('hardness'), s2t('percentUnit'), tracked.getUnitDoubleValue(s2t('hardness')));
tool.options.putObject(s2t('brush'), s2t('computedBrush'), tool.brush);
(r = new ActionReference()).putClass(s2t(tool.toolName));
(d = new ActionDescriptor()).putReference(s2t('target'), r);
d.putObject(s2t('to'), s2t('target'), tool.options);
executeAction(s2t('set'), d, DialogModes.NO);
}
}
break;
}
} else {
var ntf = new Notifiers,
status = ntf.checkNotifier();
if (status) ntf.delNotifier(); else ntf.addNotifier();
alert('Brush tracking ' + (status ? 'disabled' : 'enabled') + '!\nRun "' + decodeURI(File($.fileName).name) + '" again to ' + (status ? 'enable' : 'disable') + ' it')
}
} catch (e) { }
function getCurrentToolOptions() {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('tool'));
r.putEnumerated(s2t('application'), s2t('ordinal'), s2t('targetEnum'));
var d = executeActionGet(r),
o = d.getObjectValue(s2t('currentToolOptions'));
if (o.hasKey(s2t('brush'))) {
return {
toolName: t2s(d.getEnumerationType(s2t('tool'))),
options: o,
brush: o.getObjectValue(s2t('brush'))
}
} else return null
}
function Notifiers() {
this.addNotifier = function () {
app.notifiersEnabled = true
var handlerFile = File($.fileName)
app.notifiers.add('toolModalStateChanged', handlerFile)
app.notifiers.add('select', handlerFile, 'toolPreset')
}
this.delNotifier = function () {
var handlerFile = File($.fileName).name,
len = app.notifiers.length;
for (var i = 0; i < len; i++) {
if (app.notifiers[i].eventFile.name == handlerFile) {
app.notifiers[i].remove(); i--; len--
}
}
}
this.checkNotifier = function () {
var handlerFile = File($.fileName).name,
len = app.notifiers.length;
for (var i = 0; i < len; i++) {
if (app.notifiers[i].eventFile.name == handlerFile) return true
}
return false
}
}
P.S. I tried to set the brush parameters directly, but this caused unwanted effects, in particular, the scattering option always resetted. Couldn't quickly fix this problem, so the script updates whole tool settings.
Copy link to clipboard
Copied
Thanks for providing this code. I will test it during the week to see how it functions and share what I find.
I like to make sure I understood the script ideas.
To use the Script, the Script Events Manager can activate the script when the Photoshop application starts to automatically enable it.
Copy link to clipboard
Copied
if (trackTools[tool.toolName]) app.putCustomOptions(tool.toolName, tool.brush, false)
Here's a small example using paintbrush:
Copy link to clipboard
Copied
You can save persistent data after closing photoshop.
I didn't try it but I remember in the past to use this where you can save your action descriptor values with 2 options:
- persistent True (remains on memory if you restart photoshop)
- persistent Off (only during the session)
(note: why Adobe don't have this on libraries as it should, remains a mistery)
///// args (key:string, customObject:ActionDescriptor, persistent:Boolean)
///// key: unique name | customObject: object to save in the registry | persistent: persists after the active session has finished (after photoshop has closed)
//~ app.putCustomOptions()
//~ app.getCustomOptions()
// real examples:
// persistent
var AD1 = new ActionDescriptor();
AD1.putString(stringIDToTypeID("AnyString_A"), "AnyDataString")
AD1.putInteger(stringIDToTypeID("AnyString_B"), 6)
AD1.putPath(stringIDToTypeID("MainImage"), new File( "C:\\Users\\admin\\Dropbox\\images examples\\123456789_Main.psd" ))
AD1.putPath(stringIDToTypeID("Image01"), new File( "C:\\Users\\admin\\Dropbox\\images examples\\123456789_back01.psd" ))
AD1.putPath(stringIDToTypeID("Image02"), new File( "C:\\Users\\admin\\Dropbox\\images examples\\123456789_back02.psd" ))
// It may be stored in this registry:
// C:\Program Files (x86)\Common Files\Adobe\Adobe Photoshop CC 2017\32 bit Photoshop dlls\libmmd.dll
// C:\Program Files (x86)\Common Files\Adobe\Adobe Photoshop CC 2017\32 bit Photoshop dlls\libifcoremd.dll
// C:\Windows\System32\KERNELBASE.dll
// PERSISTENT ON PHOTOSHOP REGISTRY ALL SESSIONS on PC
app.putCustomOptions("Testing", AD1, true);
// some examples not persistent (exists per photoshop session)
var AD2 = new ActionDescriptor();
AD2.putPath(stringIDToTypeID("Image00"), new File( "C:\\Users\\admin\\Dropbox\\images examples\\789_back00.psd" ))
// NON PERSISTENT (only active photoshop session data available)
app.putCustomOptions("Testing_temp", AD2, false);
// read some data:
data1_persistent = app.getCustomOptions("Testing").getPath(stringIDToTypeID("Image02")).fsName
data2_session_only = app.getCustomOptions("Testing_temp").getPath(stringIDToTypeID("Image00")).fsName
Copy link to clipboard
Copied
That I know custom options are stored in 'Adobe Photoshop 2021 Prefs.psp' at end of: C:\Users\User\AppData\Roaming\Adobe\Adobe Photoshop 2021\Adobe Photoshop 2021 Settings. Could you then elaborate this part:
// It may be stored in this registry:
// C:\Program Files (x86)\Common Files\Adobe\Adobe Photoshop CC 2017\32 bit Photoshop dlls\libmmd.dll
// C:\Program Files (x86)\Common Files\Adobe\Adobe Photoshop CC 2017\32 bit Photoshop dlls\libifcoremd.dll
// C:\Windows\System32\KERNELBASE.dll
Copy link to clipboard
Copied
These old code I found it in the past, and that part was there. Can't tell the source anymore, sorry. May be you are right but I think this is one of the many things Adobe never clear out.
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Thanks for providing the video and the script. I tested the brush-tracking-options script with the clone stamp. paintbrush and mixer brush tools and it works well when activating the tool preset manually.
Usually, I work with a clone stamp tool preset on the current layer and a different preset on the current layer & below option. I use a custom script that activates the clone stamp tool present based on the layer name. A script listener function executes the tool preset.
In this case, activating a tool preset using a custom script defaults to the original preset settings bypassing the brush-tracking-options. On the other hand, activating the tool preset manually remembers the last clone stamp options.
Does the brush-tracking-options script support a tool preset that is activated using a different script?
Copy link to clipboard
Copied
No, the other script does not send preset selection notifications, so my code will not work. Perhaps in this case, r-bin's answer with modifying the preset file will be easier.
Do you have access to the source code of the script that selects the preset? Can you find in code where the preset is called? You can add the following code there (it will call my script from any other):
(d = new ActionDescriptor()).putBoolean (stringIDToTypeID("runFromOtherScirpt"), true)
executeAction(stringIDToTypeID("6c3cf554-0267-4dcd-a763-4842fc60d204"), d, DialogModes.NO)
In order for Photoshop to call my script by ID, you need to save the following version of the code in the Presets\Scripts
/*
// BEGIN__HARVEST_EXCEPTION_ZSTRING
<javascriptresource>
<name>Track brush settings</name>
<eventid>6c3cf554-0267-4dcd-a763-4842fc60d204</eventid>
</javascriptresource>
// END__HARVEST_EXCEPTION_ZSTRING
*/
#target photoshop
var t2s = typeIDToStringID,
s2t = stringIDToTypeID,
evt = null,
tgt = null;
var trackTools = {
spotHealingBrushTool: true,
magicStampTool: true,
paintbrushTool: true,
pencilTool: true,
colorReplacementBrushTool: true,
wetBrushTool: true,
cloneStampTool: true,
patternStampTool: true,
historyBrushTool: true,
artBrushTool: true,
eraserTool: true,
backgroundEraserTool: true,
blurTool: true,
sharpenTool: true,
smudgeTool: true,
dodgeTool: true,
burnInTool: true,
saturationTool: true
};
if (app.playbackParameters.count) {
tgt = true; evt = 'select'
} else {
try { tgt = arguments[0], evt = t2s(arguments[1]) } catch (e) { }
}
try {
if (evt) {
switch (evt) {
case 'toolModalStateChanged':
if (t2s(tgt.getEnumerationValue(s2t('kind'))) == 'paint') {
if (tool = getCurrentToolOptions())
if (trackTools[tool.toolName]) app.putCustomOptions(tool.toolName, tool.brush, false)
}
break;
case 'select':
if (tool = getCurrentToolOptions()) {
try { var tracked = app.getCustomOptions(tool.toolName) } catch (e) { }
if (tracked) {
tool.brush.putUnitDouble(s2t('diameter'), s2t('pixelsUnit'), tracked.getUnitDoubleValue(s2t('diameter')));
tool.brush.putUnitDouble(s2t('hardness'), s2t('percentUnit'), tracked.getUnitDoubleValue(s2t('hardness')));
tool.options.putObject(s2t('brush'), s2t('computedBrush'), tool.brush);
(r = new ActionReference()).putClass(s2t(tool.toolName));
(d = new ActionDescriptor()).putReference(s2t('target'), r);
d.putObject(s2t('to'), s2t('target'), tool.options);
executeAction(s2t('set'), d, DialogModes.NO);
}
}
break;
}
} else {
var ntf = new Notifiers,
status = ntf.checkNotifier();
if (status) ntf.delNotifier(); else ntf.addNotifier();
alert('Brush tracking ' + (status ? 'disabled' : 'enabled') + '!\nRun "' + decodeURI(File($.fileName).name) + '" again to ' + (status ? 'enable' : 'disable') + ' it')
}
} catch (e) { }
function getCurrentToolOptions() {
(r = new ActionReference()).putProperty(s2t('property'), p = s2t('tool'));
r.putEnumerated(s2t('application'), s2t('ordinal'), s2t('targetEnum'));
var d = executeActionGet(r),
o = d.getObjectValue(s2t('currentToolOptions'));
if (o.hasKey(s2t('brush'))) {
return {
toolName: t2s(d.getEnumerationType(s2t('tool'))),
options: o,
brush: o.getObjectValue(s2t('brush'))
}
} else return null
}
function Notifiers() {
this.addNotifier = function () {
app.notifiersEnabled = true
var handlerFile = File($.fileName)
app.notifiers.add('toolModalStateChanged', handlerFile)
app.notifiers.add('select', handlerFile, 'toolPreset')
}
this.delNotifier = function () {
var handlerFile = File($.fileName).name,
len = app.notifiers.length;
for (var i = 0; i < len; i++) {
if (app.notifiers[i].eventFile.name == handlerFile) {
app.notifiers[i].remove(); i--; len--
}
}
}
this.checkNotifier = function () {
var handlerFile = File($.fileName).name,
len = app.notifiers.length;
for (var i = 0; i < len; i++) {
if (app.notifiers[i].eventFile.name == handlerFile) return true
}
return false
}
}
Copy link to clipboard
Copied
Yes, I pulled from the source code the preset call. But I am not sure where to place the amendment. In the if statement or the toolPreset function.
Copy link to clipboard
Copied
function toolPreset(presetLabel) {
try {
// =======================================================
var idselect = stringIDToTypeID("select");
var desc36 = new ActionDescriptor();
var idnull = stringIDToTypeID("null");
var ref15 = new ActionReference();
var idtoolPreset = stringIDToTypeID("toolPreset");
ref15.putName(idtoolPreset, presetLabel);
desc36.putReference(idnull, ref15);
executeAction(idselect, desc36, DialogModes.NO);
(d = new ActionDescriptor()).putBoolean(stringIDToTypeID("runFromOtherScirpt"), true)
executeAction(stringIDToTypeID("6c3cf554-0267-4dcd-a763-4842fc60d204"), d, DialogModes.NO)
} catch (error) {
alert(error)
}
}
Copy link to clipboard
Copied
Great, I added the code the toolPreset() function and now the brush-tracking-options script works well when called from a different script. I tried it with the string "RunFormOtherScript" and with "BrushTrackingOptions.jsx" and it worked in both cases. The string did not seem to make a difference. What is this string used for?
Copy link to clipboard
Copied
This is an arbitrary argument, its value is not used directly in the script. It is only needed so that the descriptor passed to the script is not empty and the script can understand that it was called from another script and simulate an event trigger:
if (app.playbackParameters.count) {
tgt = true; evt = 'select'
} else {
try { tgt = arguments[0], evt = t2s(arguments[1]) } catch (e) { }
}
(when launched manually, the script receives an empty app.playbackParameters descriptor, i.e. this code will not work. When launched by the app.playbackParameters event, it will be empty, and the arguments will be passed through arguments. When launched through executeAction from another script, the script receives the contents of the descriptor that we can control)
Copy link to clipboard
Copied
Okey, now I understand! I didn't read all so I thought it works like in other threads used, so fired by actions. I'm happy it can be used by other scripts too. The difference is that in actions you can rerecord used value to other, while in script you had to think up of some tricky method when you want to change current value to other, probably triggering script with BridgeTalk and pressing for ex. ctrl key to reset used value, and to be asked for new one. But maybe there are some easier way.
Copy link to clipboard
Copied
I have never tried to track keystrokes when running a script (although for some scripts this would be ideal). Is it possible? Have a sample code?
Copy link to clipboard
Copied
Press and hold for at least few milliseconds ctrl after triggering script to see it was pressed:
if ($.engineName)
((bt = new BridgeTalk()).target = BridgeTalk.appSpecifier,
(bt.body = '$.evalFile(' + $.fileName.toSource() + ')', bt.send()))
else alert(ScriptUI.environment.keyboardState.ctrlKey)
Copy link to clipboard
Copied
What to do to this block worked:
if (app.playbackParameters.count) {
tgt = true; evt = 'select'
alert('Hello?')
}
Copy link to clipboard
Copied
send non-empty descriptor to the script by its eventid:
(d = new ActionDescriptor()).putBoolean(stringIDToTypeID("runFromOtherScirpt"), true)
executeAction(stringIDToTypeID("6c3cf554-0267-4dcd-a763-4842fc60d204"), d, DialogModes.NO)
(since we needed to call the script, why not immediately pass parameters to it when calling it !?)
Copy link to clipboard
Copied
Btw where you find 'runFromOtherScirpt' keyword? And how you knew 'playbackParameters' can be used in called script not only from action but also from other script to see used params?