├── LICENSE ├── README.md ├── README.ru.md └── jsx ├── ArtboardsFromCSV.jsx ├── ClearLayer.jsx ├── ExportPathsToAi.jsx ├── GeneratePreview.jsx ├── MultiEditText.jsx ├── RenameArtboardAsSize.jsx ├── SaveAll.jsx ├── SelectShapesByColor.jsx ├── TIFF2Print.jsx ├── TextBlock.jsx └── ToggleLayersLocksByName.jsx /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sergey Osokin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![header](https://i.ibb.co/Ycswctn/emblem-ps.png) 2 | 3 | # Adobe Photoshop Scripts 4 | 5 | ![Downloads](https://img.shields.io/badge/Downloads-1k+-27CF7D.svg) [![Telegram](https://img.shields.io/badge/Telegram%20Channel-%40aiscripts-0088CC.svg)](https://t.me/aiscripts) [![Yotube](https://img.shields.io/badge/Youtube-%40SergOsokinArt-FF0000.svg)](https://www.youtube.com/c/SergOsokinArt/videos) 6 | 7 | *Instructions in other languages: [English](README.md), [Русский](README.ru.md)* 8 | 9 | ## 👨‍💻 Hi 10 | This is a collection of JS scripts for Adobe Photoshop. 11 | 12 | The descriptions for each file can be found in the file’s header text. Test environment: Photoshop CC 2019, 2024 (Mac OS). 13 | 14 | ## Scripts 15 | * [ArtboardsFromCSV](https://github.com/creold/photoshop-scripts#artboardsfromcsv) `v0.2 - new, 15.01.2024` 16 | * [ClearLayer](https://github.com/creold/photoshop-scripts#clearlayer) `v0.1 - 06.2019` 17 | * [ExportPathsToAi](https://github.com/creold/photoshop-scripts#exportpathstoai) `v0.2 - upd, 08.2022` 18 | * [GeneratePreview](https://github.com/creold/photoshop-scripts#generatepreview) `v0.1 - 10.2018` 19 | * [MultiEditText](https://github.com/creold/photoshop-scripts#multiedittext) `v0.2 — upd, 14.02.2025` 20 | * [RenameArtboardAsSize](https://github.com/creold/photoshop-scripts#renameartboardassize) `v0.1.1 - new, 08.01.2024` 21 | * [SaveAll](https://github.com/creold/photoshop-scripts#saveall) `v0.1 - 10.2018` 22 | * [SelectShapesByColor](https://github.com/creold/photoshop-scripts#selectshapesbycolor) `v0.2 - 04.2022` 23 | * [TextBlock](https://github.com/creold/photoshop-scripts/blob/master/README.md#textblock) `v0.2 - upd, 30.04.2025` 24 | * [TIFF2Print](https://github.com/creold/photoshop-scripts#tiff2print) `v1.1 - 08.2018` 25 | * [ToggleLayersLocksByName](https://github.com/creold/photoshop-scripts#togglelayerslocksbyname) `v0.1 - 09.2021` 26 | 27 | 28 | 29 | 30 | 31 | ## How to download one script 32 | 1. In the script description, click the "Direct Link" button. 33 | 2. The tab will open the script code. 34 | 3. Press Cmd/Ctrl + S for download. 35 | 36 | ## How to run scripts 37 | 38 | #### Variant 1 — Install 39 | 40 | 1. [Download archive](http://bit.ly/2wLaIkq) and unzip. All scripts are in the folder `jsx` 41 | 2. Place `.jsx` in the Photoshop Scripts folder: 42 | - OS X: `/Applications/Adobe Photoshop [version]/Presets/Scripts/` 43 | - Windows (32 bit): `C:\Program Files (x86)\Adobe\Adobe Photoshop [version]\Presets\Scripts\` 44 | - Windows (64 bit): `C:\Program Files\Adobe\Adobe Photoshop [version] (64 Bit)\Presets\Scripts\` 45 | 3. Restart Photoshop 46 | 4. You can also setup a custom hotkey in `Edit → Keyboard Shortctus…` 47 | 48 | #### Variant 2 — Drag & Drop 49 | Drag and drop the script file (JS or JSX) on Adobe Photoshop icon 50 | 51 | #### Variant 3 — Use extensions 52 | I recommend the [Scripshon Trees](https://exchange.adobe.com/creativecloud.details.15873.scripshon-trees.html) panel. In it you can specify which folder your script files are stored in. 53 | 54 | ## Donate 55 | Many scripts are free to download thanks to user support. Help me to develop new scripts and update existing ones by supporting my work with any amount via [Buymeacoffee], [Tinkoff], [ЮMoney], [Donatty], [DonatePay]. Thank you. 56 | 57 | [Buymeacoffee]: https://www.buymeacoffee.com/aiscripts 58 | [Tinkoff]: https://www.tinkoff.ru/rm/osokin.sergey127/SN67U9405/ 59 | [ЮMoney]: https://yoomoney.ru/to/410011149615582 60 | [Donatty]: https://donatty.com/sergosokin 61 | [DonatePay]: https://new.donatepay.ru/@osokin 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ## ArtboardsFromCSV 80 | [![Direct](https://img.shields.io/badge/Direct%20Link-ArtboardsFromCSV.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-absfromcsv) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 81 | 82 | The script creates artboards vertically in the document using information from CSV tables. The size of the artboards is calculated in units from `Preferences → Units & Rulers`. 83 | *Based on [Kristian Andersen's script](https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-create-artboards-from-script/m-p/9345495#M112921), 2017.* 84 | 85 | ![ArtboardsFromCSV](https://i.ibb.co/BjGNPGz/Artboards-From-CSV.gif) 86 | 87 | ## ClearLayer 88 | [![Direct](https://img.shields.io/badge/Direct%20Link-ClearLayer.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-clrlyr) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 89 | 90 | Simple script to clear layers content. 91 | 92 | ![ClearLayer](https://i.ibb.co/hV7NFxB/Clear-Layer.gif) 93 | 94 | ## ExportPathsToAi 95 | [![Direct](https://img.shields.io/badge/Direct%20Link-ExportPathsToAi.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-exppths) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 96 | 97 | Exports all visible vector layers from `.psd` to a `.ai` file in the same folder as the original file. Known Photoshop restrictions: 98 | 99 | * paths are exported unfilled 100 | * reverse paths order. To fix this, select the paths in Illustrator and click `Reverse Order` in the Layers panel 101 | 102 | ![ExportPathsToAi](https://i.ibb.co/SXt6r4X/Export-Paths-To-Ai.gif) 103 | 104 | ## GeneratePreview 105 | [![Direct](https://img.shields.io/badge/Direct%20Link-GeneratePreview.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-genprvw) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 106 | 107 | Generate JPG preview image from active document. Supports multiple saving with auto-numbering. If you want to change JPG size, edit number in script file `var jpegSizeMax = 1200`. 108 | 109 | ![GeneratePreview](https://i.ibb.co/HrcPNvs/Generate-Preview.gif) 110 | 111 | ## MultiEditText 112 | [![Direct](https://img.shields.io/badge/Direct%20Link-MultiEditText.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-metxt) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 113 | 114 | Multi-editing of selected text layers. The script allows you to enter the same text, replace the current text layer content or add the entered text to the current one. 115 | 116 | * Edit Separately - edit contents of frames separately, contents are separated by `@@@@` symbols. 117 | * List by XY - sort the order of texts by their position, otherwise they will be displayed in order in layers 118 | * Reverse Apply - replace contents in reverse order 119 | 120 | > [!TIP] 121 | > If you want to change the size of the text area, open the script file with a text editor and change the CFG `width: 300` and `height: 210` to another value. The key to displaying different content is `ph: ''` and the text divider `divider: '\n@@@@@\n'`, where `\n` is a line break. `softBreak: '@#'` — soft line break char. 122 | > For a line break (new paragraph), use Ctrl + Enter on a PC or Enter on Mac OS. To insert a soft line break chat (no paragraph indent), press Shift + Enter. 123 | 124 | See also [Adobe Illustrator version](https://github.com/creold/illustrator-scripts/blob/master/md/Text.md) 125 | 126 | ![MultiTextEdit](https://i.ibb.co/Wngmytk/Multi-Edit-Text.gif) 127 | 128 | ## RenameArtboardAsSize 129 | [![Direct](https://img.shields.io/badge/Direct%20Link-RenameArtboardAsSize.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-renabsassize) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 130 | 131 | Renames artboards according to their size in units from `Preferences > Units & Rulers` menu. 132 | 133 | > [!NOTE] 134 | > [Script version for Adobe Illustrator](https://github.com/creold/illustrator-scripts/blob/master/md/Artboard.md#renameartboardassize) 135 | 136 | ![RenameArtboardAsSize](https://i.ibb.co/1nzr1xh/Rename-Artboard-As-Size.gif) 137 | 138 | ## SaveAll 139 | [![Direct](https://img.shields.io/badge/Direct%20Link-SaveAll.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-svall) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 140 | 141 | Save all opened docs in one click. 142 | 143 | ## SelectShapesByColor 144 | [![Direct](https://img.shields.io/badge/Direct%20Link-SelectShapesByColor.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-selbycol) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 145 | 146 | Selects all vector layers and text objects in the document that have the same color as the active layer. Locked layers will also be selected. If you also want to select solid layers `Layer > New Fill Layer > Solid Color...`, edit in script file `var inclSolid = true`. 147 | 148 | ![SelectShapesByColor](https://i.ibb.co/12FjgfN/Select-Shapes-By-Color.gif) 149 | 150 | ## TextBlock 151 | [![Direct](https://img.shields.io/badge/Direct%20Link-TextBlock.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-txtblck) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 152 | 153 | Creates a block of selected text layers with specified width and spacing. The order of the layers in the block corresponds to the order of the original layers on the Y-axis. Layers are grouped together. Supported units: px, pt, in, mm, cm, m, ft, and yd. 154 | 155 | See also [Adobe Illustrator version](https://github.com/creold/illustrator-scripts/blob/master/md/Text.md) 156 | 157 | ![TextBlock](https://i.ibb.co/fzjtj6pB/Text-Block-PS.gif) 158 | 159 | ## TIFF2Print 160 | [![Direct](https://img.shields.io/badge/Direct%20Link-TIFF2Print.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-tif2prt) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 161 | 162 | Script to save a print ready .tif file. 163 | 164 | **Features** 165 | 166 | * Adding width and height (mm) in file name 167 | * Shorten measure units (cm/m) when possible 168 | * Save the preview image with the file for printing 169 | * Auto adding an index to the name, to save multiple files 170 | * Parameters are easily configured in the script code 171 | 172 | ![TIFF2Print](https://i.ibb.co/ypbCFtX/tiff2print.gif) 173 | 174 | ## ToggleLayersLocksByName 175 | [![Direct](https://img.shields.io/badge/Direct%20Link-ToggleLayersLocksByName.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-tglyrlock) [![Download](https://img.shields.io/badge/Download%20All-Zip%20archive-AAA9BC.svg)](https://bit.ly/2wLaIkq) 176 | 177 | Locks layers in the document based on the keyword in the name. Open the script file with a text editor if you want to specify another keyword and replace the text in quotes `key = '[lock]'`. 178 | 179 | ![ToggleLayersLocksByName](https://i.ibb.co/48zYWg4/Toggle-Layers-Locks-By-Name.gif) 180 | 181 | Don't forget sharing link with a friend 🙂 182 | 183 | ## Contribute 184 | 185 | Found a bug? Please [submit a new issues](https://github.com/creold/photoshop-scripts/issues) on GitHub. 186 | 187 | ## Contact 188 | Email 189 | Telegram [@sergosokin](https://t.me/sergosokin) 190 | 191 | ### License 192 | 193 | All scripts is licensed under the MIT licence. 194 | See the included LICENSE file for more details. -------------------------------------------------------------------------------- /README.ru.md: -------------------------------------------------------------------------------- 1 | ![header](https://i.ibb.co/Ycswctn/emblem-ps.png) 2 | 3 | # Adobe Photoshop Scripts 4 | 5 | ![Downloads](https://img.shields.io/badge/Скачивания-1k+-27CF7D.svg) [![Telegram](https://img.shields.io/badge/Telegram--канал-%40aiscripts-0088CC.svg)](https://t.me/aiscripts) [![Yotube](https://img.shields.io/badge/Youtube-%40SergOsokinArt-FF0000.svg)](https://www.youtube.com/c/SergOsokinArt/videos) 6 | 7 | *Инструкция на других языках: [English](README.md), [Русский](README.ru.md)* 8 | 9 | ## 👨‍💻 Привет 10 | Это коллекция авторских скриптов для Adobe Photoshop. 11 | 12 | Описание каждого скрипта также находится внутри его файла. Тестировалось в Photoshop CC 2019, 2024 (Mac OS). 13 | 14 | ## Скрипты 15 | * [ArtboardsFromCSV](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#artboardsfromcsv) `v0.2 - new, 15.01.2024` 16 | * [ClearLayer](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#clearlayer) `v0.1 - 06.2019` 17 | * [ExportPathsToAi](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#exportpathstoai) `v0.2 - upd, 08.2022` 18 | * [GeneratePreview](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#generatepreview) `v0.1 - 10.2018` 19 | * [MultiEditText](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#multiedittext) `v0.2 — upd, 14.02.2025` 20 | * [RenameArtboardAsSize](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#renameartboardassize) `v0.1.1 - new, 08.01.2024` 21 | * [SaveAll](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#saveall) `v0.1 - 10.2018` 22 | * [SelectShapesByColor](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#selectshapesbycolor) `v0.2 - 04.2022` 23 | * [TextBlock](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#textblock) `v0.2 - upd, 30.04.2025` 24 | * [TIFF2Print](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#tiff2print) `v1.1 - 08.2018` 25 | * [ToggleLayersLocksByName](https://github.com/creold/photoshop-scripts/blob/master/README.ru.md#togglelayerslocksbyname) `v0.1 - 09.2021` 26 | 27 | 28 | 29 | 30 | 31 | ## Как скачать один скрипт 32 | 1. В описании скрипта нажмите кнопку «Прямая ссылка». 33 | 2. Во вкладке откроется код скрипта. 34 | 3. Нажмите Cmd/Ctrl + S, чтобы сохранить файл на диск. 35 | 36 | ## Как запускать скрипты 37 | 38 | #### Вариант 1 — Установка 39 | 40 | 1. [Скачайте архив](http://bit.ly/2wLaIkq) и распакуйте. Все скрипты находятся в папке `jsx` 41 | 2. Поместите `<имя_скрипта>.jsx` в папку скриптов Adobe Photoshop: 42 | - OS X: `/Applications/Adobe Photoshop [version]/Presets/Scripts/` 43 | - Windows (32 bit): `C:\Program Files (x86)\Adobe\Adobe Photoshop [version]\Presets\Scripts\` 44 | - Windows (64 bit): `C:\Program Files\Adobe\Adobe Photoshop [version] (64 Bit)\Presets\Scripts\` 45 | 3. Перезапустите Photoshop 46 | 4. Вы можете назначить горячие клавиши скриптам через `Edit → Keyboard Shortctus…` 47 | 48 | #### Вариант 2 — Drag & Drop 49 | Перетащите файл скрипта из папки напрямую на иконку Adobe Photoshop в панели задач Windows или Finder в Mac OS. 50 | 51 | #### Вариант 3 — Расширения (Extension) 52 | Если часто приходится запускать скрипты, то чтобы не открывать постоянно меню, можно установить бесплатную панель [Scripshon Trees](https://exchange.adobe.com/creativecloud.details.15873.scripshon-trees.html). 53 | 54 | ## Поддержка 55 | Многие скрипты бесплатны для скачивания благодаря поддержке пользователей. Помогите продолжать разработку новых и обновление текущих скриптов, поддержав мою работу любой суммой через [Buymeacoffee] (иностр. карты), [Tinkoff], [ЮMoney], [Donatty], [DonatePay]. Спасибо. 56 | 57 | [Buymeacoffee]: https://www.buymeacoffee.com/aiscripts 58 | [Tinkoff]: https://www.tinkoff.ru/rm/osokin.sergey127/SN67U9405/ 59 | [ЮMoney]: https://yoomoney.ru/to/410011149615582 60 | [Donatty]: https://donatty.com/sergosokin 61 | [DonatePay]: https://new.donatepay.ru/@osokin 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | ## ArtboardsFromCSV 80 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-ArtboardsFromCSV.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-absfromcsv) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 81 | 82 | Скрипт создаёт в документе артборды в одну колонку по информации из CSV-таблиц. Размер артбордов рассчитывается в единицах из `Preferences → Units & Rulers`. 83 | *Основано на скрипте [Кристиана Андерсена](https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-create-artboards-from-script/m-p/9345495#M112921), 2017.* 84 | 85 | ![ArtboardsFromCSV](https://i.ibb.co/BjGNPGz/Artboards-From-CSV.gif) 86 | 87 | ## ClearLayer 88 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-ClearLayer.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-clrlyr) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 89 | 90 | Скрипт удаляет видимое содержимое выбранного слоя. Заменяет ручные команды: выделить всё и очистить. 91 | 92 | ![ClearLayer](https://i.ibb.co/hV7NFxB/Clear-Layer.gif) 93 | 94 | ## ExportPathsToAi 95 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-ExportPathsToAi.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-exppths) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 96 | 97 | Экспортирует все видимые векторные слои из `.psd` в файл `.ai` в ту же папку, где и оригинальный файл. Ограничения Photoshop: 98 | 99 | * контуры экспортируются без заливки 100 | * обратный порядок слоев. Для этого выделите полученные контуры в Illustrator и примените команду `Reverse Order` в меню панели Layers 101 | 102 | ![ExportPathsToAi](https://i.ibb.co/SXt6r4X/Export-Paths-To-Ai.gif) 103 | 104 | ## GeneratePreview 105 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-GeneratePreview.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-genprvw) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 106 | 107 | Сохраняет JPG активного документа. При повторном запуске на документе может сохранять под новым номером, например, чтобы сохранять в множестве JPG разное состояние документа. Если хотите изменить размер JPG, откройте файл скрипта текстовым редактором и замените число в пикселях `var jpegSizeMax = 1200`. Это размер бОльшей стороны, которую скрипт автоматически определит и сохранит пропорционально. 108 | 109 | ![GeneratePreview](https://i.ibb.co/HrcPNvs/Generate-Preview.gif) 110 | 111 | ## MultiEditText 112 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-MultiEditText.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-metxt) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 113 | 114 | Мультиредактирование выбранных текстовых слоев. Скрипт позволяет вводить одинаковый текст, заменяя текущее содержимое текстового слоя или добавляя введенный текст к текущему. 115 | 116 | * Edit Separately - редактировать содержимое фреймов раздельно, контент разделяется символами `@@@` 117 | * List by XY - сортировать порядок текстов по их позиции, иначе выводится по порядку в слоях 118 | * Reverse Apply - заменить контент в обратном порядке 119 | 120 | > [!TIP] 121 | > Если хотите изменить размер текстовой области, то откройте файл скрипта текстовым редактором и поменяйте CFG `width: 300` и `height: 210` на другое число. Ключ для отображения разного контента `ph: ''` и разделитель текстов `divider: '\n@@@\n'`, где `\n` — перенос строки.`softBreak: '@#'` — символ мягкого переноса. 122 | > Для переноса строки (новый абзац) используйте Ctrl + Enter на ПК или Enter на Mac OS. Для вставки специального символа мягкого переноса (без абзацного отступа) нажмите Shift + Enter. 123 | 124 | Смотрите также [версию для Adobe Illustrator](https://github.com/creold/illustrator-scripts/blob/master/md/Text.ru.md) 125 | 126 | ![MultiTextEdit](https://i.ibb.co/Wngmytk/Multi-Edit-Text.gif) 127 | 128 | ## RenameArtboardAsSize 129 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-RenameArtboardAsSize.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-renabsassize) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 130 | 131 | Добавляет к имени артборда его размеры в единицах измерения из `Preferences > Units & Rulers`. 132 | 133 | > [!NOTE] 134 | > [Версия скрипта для Иллюстратора](https://github.com/creold/illustrator-scripts/blob/master/md/Artboard.ru.md#renameartboardassize) 135 | 136 | ![RenameArtboardAsSize](https://i.ibb.co/1nzr1xh/Rename-Artboard-As-Size.gif) 137 | 138 | ## SaveAll 139 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-SaveAll.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-svall) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 140 | 141 | Сохраняет все открытые документы. 142 | 143 | ## SelectShapesByColor 144 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-SelectShapesByColor.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-selbycol) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 145 | 146 | Выделяет все векторные слои и текстовые объекты в документе, которые имеют тот же цвет, что и активный слой. Будут выделены и заблокированные слои. Если хотите выделять также слои-заливки `Layer > New Fill Layer > Solid Color...`, откройте файл скрипта текстовым редактором и поставьте `var inclSolid = true`. 147 | 148 | ![SelectShapesByColor](https://i.ibb.co/12FjgfN/Select-Shapes-By-Color.gif) 149 | 150 | ## TextBlock 151 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-TextBlock.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-txtblck) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 152 | 153 | Создаёт блок заданной ширины из выбранных текстовых слоёв с отступами. Порядок в блоке соответствует расположению исходных текстов по оси Y сверху вниз. Слои объединяются в группу. Поддерживаемые единицы измерения: px, pt, in, mm, cm, m, ft и yd. 154 | 155 | Смотрите также [версию для Adobe Illustrator](https://github.com/creold/illustrator-scripts/blob/master/md/Text.ru.md) 156 | 157 | ![TextBlock](https://i.ibb.co/fzjtj6pB/Text-Block-PS.gif) 158 | 159 | ## TIFF2Print 160 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-TIFF2Print.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-tif2prt) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 161 | 162 | Сохраняет .tif файл для печати из макета. 163 | 164 | **Возможности** 165 | 166 | * Добавление размеров (мм) в имя файла 167 | * Автоматическое сокращение записи размеров до см или м 168 | * Сохранение уменьшенного jpg в той же папке 169 | * Добавление индекса в имя при сохранении нескольких tif с одного файла 170 | * Изменение параметров в коде скрипта 171 | 172 | ![TIFF2Print](https://i.ibb.co/ypbCFtX/tiff2print.gif) 173 | 174 | ## ToggleLayersLocksByName 175 | [![Direct](https://img.shields.io/badge/Прямая%20ссылка-ToggleLayersLocksByName.jsx-4873FF.svg)](https://link.aiscripts.ru/ps-tglyrlock) [![Download](https://img.shields.io/badge/Скачать%20все-Zip--архив-AAA9BC.svg)](https://bit.ly/2wLaIkq) 176 | 177 | Блокирует в документе слои по ключевому слову в имени. Откройте файл скрипта текстовым редактором, если хотите задать другое ключевое слово и замените текст в кавычках `key = '[lock]'`. 178 | 179 | ![ToggleLayersLocksByName](https://i.ibb.co/48zYWg4/Toggle-Layers-Locks-By-Name.gif) 180 | 181 | Не забывайте поделиться ссылкой со знакомыми дизайнерами 🙂 182 | 183 | ## Развитие 184 | 185 | Нашли ошибку? [Создайте запрос](https://github.com/creold/photoshop-scripts/issues) на Github или напишите мне на почту. 186 | 187 | ## Контакты 188 | Email 189 | Telegram [@sergosokin](https://t.me/sergosokin) 190 | 191 | ### Лицензия 192 | 193 | Скрипты распространяются по лицензии MIT. 194 | Больше деталей во вложенном файле LICENSE. -------------------------------------------------------------------------------- /jsx/ArtboardsFromCSV.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | ArtboardsFromCSV.jsx for Adobe Photoshop 3 | Description: The script creates artboards of specified sizes in one column, one below the other 4 | 5 | Original idea by Kristian Andersen, krilleandersen@gmail.com: 6 | https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-create-artboards-from-script/td-p/9345492 7 | 8 | Modification by Sergey Osokin, email: hi@sergosokin.ru 9 | 10 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 11 | 12 | Release notes: 13 | 0.1 Initial version (Kristian Andersen) 14 | 0.2 Parsing artboards data from CSV with options 15 | 16 | Donate (optional): 17 | If you find this script helpful, you can buy me a coffee 18 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 19 | - via Donatty https://donatty.com/sergosokin 20 | - via DonatePay https://new.donatepay.ru/en/@osokin 21 | - via YooMoney https://yoomoney.ru/to/410011149615582 22 | 23 | NOTICE: 24 | Tested with Adobe Photoshop CC 2019, 2024 (Mac OS). 25 | This script is provided "as is" without warranty of any kind. 26 | Free to use, not for sale 27 | 28 | Released under the MIT license 29 | http://opensource.org/licenses/mit-license.php 30 | 31 | Check my other scripts: https://github.com/creold 32 | */ 33 | 34 | //@target photoshop 35 | app.bringToFront(); 36 | 37 | function main(){ 38 | if (!isCorrectEnv()) return; 39 | 40 | var doc = app.activeDocument; 41 | var units = getUnits(); 42 | var origUnits = app.preferences.rulerUnits; 43 | app.preferences.rulerUnits = Units.PIXELS; 44 | 45 | var win = new Window('dialog', 'ArtboardsFromCSV v0.2'); 46 | win.orientation = 'column'; 47 | win.alignChildren = ['fill', 'top']; 48 | 49 | // CSV file 50 | var csvPnl = win.add('panel', undefined, 'Artboard List (WxH in ' + units + ')'); 51 | csvPnl.alignChildren = ['fill', 'center']; 52 | csvPnl.margins = [12, 14, 10, 14]; 53 | 54 | var selCSVInp = csvPnl.add('edittext', undefined, Folder.desktop, {readonly: false}); 55 | selCSVInp.maximumSize.width = 180; 56 | var selCSVBtn = csvPnl.add('button', undefined, 'Select CSV'); 57 | 58 | // Options 59 | var optPnl = win.add('panel', undefined, 'Vertical Spacing'); 60 | optPnl.alignChildren = ['fill', 'center']; 61 | optPnl.margins = [12, 14, 10, 14]; 62 | 63 | var padGrp = optPnl.add('group'); 64 | 65 | var padInp = padGrp.add('edittext', undefined, 50); 66 | padInp.characters = 6; 67 | 68 | padGrp.add('statictext', undefined, units); 69 | 70 | // Name options 71 | var namePnl = win.add('panel', undefined, 'Artboard Name'); 72 | namePnl.alignChildren = ['fill', 'center']; 73 | namePnl.margins = [12, 14, 10, 14]; 74 | 75 | var isOnlyName = namePnl.add('radiobutton', undefined, 'Name from CSV'); 76 | isOnlyName.value = true; 77 | 78 | var isNameWithSize = namePnl.add('radiobutton', undefined, 'Name with Size'); 79 | var isOnlySize = namePnl.add('radiobutton', undefined, 'Only Size'); 80 | 81 | // Background options 82 | var bgPnl = win.add('panel', undefined, 'Background'); 83 | bgPnl.alignChildren = ['fill', 'center']; 84 | bgPnl.margins = [12, 14, 10, 14]; 85 | 86 | var isWhite = bgPnl.add('radiobutton', undefined, 'White'); 87 | isWhite.value = true; 88 | 89 | var isBlack = bgPnl.add('radiobutton', undefined, 'Black'); 90 | var isTransparent = bgPnl.add('radiobutton', undefined, 'Transparent'); 91 | 92 | if (/mm|cm|in/.test(units)) { 93 | win.add('statictext', undefined, 'Script can slowly add artboards'); 94 | } 95 | 96 | // Buttons 97 | var btns = win.add('group'); 98 | btns.alignChildren = ['fill', 'center']; 99 | 100 | var cancel, ok; 101 | if (/mac/i.test($.os)) { 102 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 103 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 104 | } else { 105 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 106 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 107 | } 108 | cancel.helpTip = 'Press Esc to Close'; 109 | ok.helpTip = 'Press Enter to Run'; 110 | 111 | var copyright = win.add('statictext', undefined, '\u00A9 Sergey Osokin. Visit Github'); 112 | copyright.justify = 'center'; 113 | 114 | loadSettings(); 115 | 116 | copyright.addEventListener('mousedown', function () { 117 | openURL('https://github.com/creold'); 118 | }); 119 | 120 | // Select CSV file 121 | selCSVBtn.onClick = function () { 122 | selectFiles('.csv', 'Select CSV file...', selCSVInp); 123 | } 124 | 125 | cancel.onClick = win.close; 126 | 127 | ok.onClick = function () { 128 | doc.suspendHistory('Add Artboards', 'okClick()'); 129 | } 130 | 131 | function okClick() { 132 | ok.text = 'Wait...'; 133 | app.refresh(); 134 | 135 | var sheet = new File(selCSVInp.text); 136 | if (!sheet.exists) { 137 | alert('CSV file not found\nMake sure the file path is correct', 'Script error'); 138 | return; 139 | } 140 | 141 | var pad = convertUnits(toAbsNum(padInp.text, 0), units, 'px'); 142 | var nameKey = isOnlyName.value ? 0 : (isNameWithSize.value ? 1 : 2); 143 | var bgKey = isWhite.value ? 1 : (isBlack.value ? 2 : 3); 144 | var abData = parseCsvData(sheet, units, nameKey); 145 | var delta = 0; 146 | 147 | for (var i = 0, len = abData.length; i < len; i++) { 148 | var ab = abData[i]; 149 | createArtboard(ab.width, ab.height, delta, ab.name, bgKey); 150 | delta = parseFloat(delta + ab.height + pad); 151 | } 152 | 153 | saveSettings(); 154 | win.close(); 155 | } 156 | 157 | // Select files with the given extension 158 | function selectFiles(ext, title, inp) { 159 | var re = new RegExp('(' + ext + ')$', 'i'); 160 | var fType = ($.os.match('Windows')) ? '*' + ext + ';' : function(f) { 161 | return f instanceof Folder || (f instanceof File && f.name.match(re)); 162 | }; 163 | var f = File.openDialog(title, fType, true); 164 | if (f.fullName !== null) inp.text = decodeURI(f); 165 | } 166 | 167 | function saveSettings() { 168 | var desc = new ActionDescriptor(); 169 | desc.putString(0, selCSVInp.text); 170 | desc.putString(1, padInp.text); 171 | desc.putInteger(2, isOnlyName.value ? 0 : (isNameWithSize.value ? 1 : 2)); 172 | desc.putInteger(3, isWhite.value ? 0 : (isBlack.value ? 1 : 2)); 173 | app.putCustomOptions('AbFromCSV_settings', desc, true); 174 | } 175 | 176 | function loadSettings() { 177 | try { 178 | var desc = app.getCustomOptions('AbFromCSV_settings'); 179 | } catch (err) {} 180 | if (typeof desc != 'undefined') { 181 | try { 182 | selCSVInp.text = desc.getString(0); 183 | padInp.text = desc.getString(1); 184 | namePnl.children[desc.getInteger(2)].value = true; 185 | bgPnl.children[desc.getInteger(3)].value = true; 186 | return; 187 | } catch (err) {} 188 | } 189 | } 190 | 191 | app.preferences.rulerUnits = origUnits; 192 | win.center(); 193 | win.show(); 194 | } 195 | 196 | // Check the script environment 197 | function isCorrectEnv() { 198 | var args = ['app', 'document']; 199 | 200 | for (var i = 0; i < args.length; i++) { 201 | switch (args[i].toString().toLowerCase()) { 202 | case 'app': 203 | if (!/photoshop/i.test(app.name)) { 204 | alert('Wrong application\nRun script from Adobe Photoshop', 'Script error'); 205 | return false; 206 | } 207 | break; 208 | case 'document': 209 | if (!documents.length) { 210 | alert('No documents\nOpen a document and try again', 'Script error'); 211 | return false; 212 | } 213 | break; 214 | } 215 | } 216 | 217 | return true; 218 | } 219 | 220 | // Get ruler units 221 | function getUnits() { 222 | var units = 'px'; 223 | var ruler = app.preferences.rulerUnits.toString(); 224 | var key = ruler.replace('Units.', ''); 225 | 226 | switch (key) { 227 | case 'PIXELS': units = 'px'; break; 228 | case 'INCHES': units = 'in'; break; 229 | case 'CM': units = 'cm'; break; 230 | case 'MM': units = 'mm'; break; 231 | case 'POINTS': units = 'pt'; break; 232 | case 'PICAS': units = 'pc'; break; 233 | case 'PERCENT': units = 'px'; break; 234 | } 235 | 236 | return units; 237 | } 238 | 239 | // Convert units of measurement 240 | function convertUnits(value, currUnits, newUnits) { 241 | UnitValue.baseUnit = UnitValue (1 / activeDocument.resolution, 'in'); 242 | var newValue = new UnitValue(value, currUnits); 243 | newValue = newValue.as(newUnits); 244 | UnitValue.baseUnit = null; 245 | return newValue; 246 | } 247 | 248 | // Convert string to absolute number 249 | function toAbsNum(str, def) { 250 | if (arguments.length == 1 || !def) def = 1; 251 | str = str.replace(/,/g, '.').replace(/[^\d.]/g, ''); 252 | str = str.split('.'); 253 | str = str[0] ? str[0] + '.' + str.slice(1).join('') : ''; 254 | if (isNaN(str) || !str.length) return parseFloat(def); 255 | else return parseFloat(str); 256 | } 257 | 258 | // Get artboard name, width, height 259 | function parseCsvData(f, units, nameKey) { 260 | var rows = readCSV(f); 261 | var arr = []; 262 | 263 | for (var i = 1, len = rows.length; i < len; i++) { 264 | var curRow = rows[i].replace(/\"/g, ''); 265 | var data = curRow.split(/,|:/); 266 | if (!data[1] || !data[1].length) continue; 267 | if (!data[2] || !data[2].length) continue; 268 | 269 | var w = toAbsNum(data[1], 0); 270 | var h = toAbsNum(data[2], 0); 271 | var size = w + 'x' + h + units; 272 | 273 | w = convertUnits(w, units, 'px'); 274 | h = convertUnits(h, units, 'px'); 275 | 276 | if (w < 1 || h < 1) continue; 277 | 278 | var name = (data[0] && data[0].replace(/\s/g, '').length) ? data[0] : ''; 279 | if (nameKey === 0 && name === '') name = size; 280 | else if (nameKey === 1) name += (name.length ? '_' : '') + size; 281 | else if (nameKey === 2) name = size; 282 | 283 | arr.push({ 284 | name: name, 285 | width: w, 286 | height: h, 287 | }); 288 | } 289 | 290 | return arr; 291 | } 292 | 293 | // Get data from a table 294 | function readCSV(f) { 295 | f.open('r'); 296 | var s = f.read(); 297 | f.close(); 298 | return s.split(/\r\n|\n/); 299 | } 300 | 301 | // Add an artboard with custom parameters 302 | function createArtboard(w, h, delta, name, bg) { 303 | var ww = parseFloat(w); 304 | var hh = parseFloat(h); 305 | var size = w + 'x' + h + 'px'; 306 | 307 | // Make artboard 308 | var idMk = cTID('Mk '); 309 | var desc = new ActionDescriptor(); 310 | var idNull = cTID('null'); 311 | var ref = new ActionReference(); 312 | var idArtboardSection = sTID('artboardSection'); 313 | ref.putClass(idArtboardSection); 314 | desc.putReference(idNull, ref); 315 | var idLayerSectionStart = sTID('layerSectionStart'); 316 | desc.putInteger(idLayerSectionStart, 5); 317 | var idLayerSectionEnd = sTID('layerSectionEnd'); 318 | desc.putInteger(idLayerSectionEnd, 6); 319 | var idNm = cTID('Nm '); 320 | desc.putString(idNm, "" + size + ""); 321 | 322 | var idArtboardRect = sTID('artboardRect'); 323 | var descRect = new ActionDescriptor(); 324 | 325 | var idTop = cTID('Top '); 326 | descRect.putDouble(idTop, 0.000000); 327 | 328 | var idLeft = cTID('Left'); 329 | descRect.putDouble(idLeft, 0.000000); 330 | 331 | var idBtom = cTID('Btom'); 332 | descRect.putDouble(idBtom, parseInt(h)); // Height 333 | 334 | var idRght = cTID('Rght'); 335 | descRect.putDouble(idRght, parseInt(w)); // Width 336 | 337 | var idClassFloatRect = sTID('classFloatRect'); 338 | desc.putObject(idArtboardRect, idClassFloatRect, descRect); 339 | executeAction(idMk, desc, DialogModes.NO); 340 | 341 | // Set artboard name 342 | var idSetD = cTID('setd'); 343 | var descName1 = new ActionDescriptor(); 344 | var refName = new ActionReference(); 345 | var idLyr = cTID('Lyr '); 346 | var idOrdn = cTID('Ordn'); 347 | var idTrgt = cTID('Trgt'); 348 | refName.putEnumerated(idLyr, idOrdn, idTrgt); 349 | descName1.putReference(idNull, refName); 350 | var idT = cTID('T '); 351 | var descName2 = new ActionDescriptor(); 352 | 353 | // Artboard name 354 | descName2.putString(idNm, "" + name + ""); 355 | 356 | descName1.putObject(idT, idLyr, descName2); 357 | executeAction(idSetD, descName1, DialogModes.NO); 358 | 359 | // Artboard position 360 | var idEditArtboardEvent = sTID('editArtboardEvent'); 361 | var descPos1 = new ActionDescriptor(); 362 | var refPos = new ActionReference(); 363 | refPos.putEnumerated(idLyr, idOrdn, idTrgt); 364 | descPos1.putReference(idNull, refPos); 365 | var idArtboard = sTID('artboard'); 366 | var descPos2 = new ActionDescriptor(); 367 | 368 | descRect.putDouble(idTop, delta); // Artboard Y 369 | descRect.putDouble(idLeft, 0.000000); // Artboard X 370 | descRect.putDouble(idBtom, hh); 371 | descRect.putDouble(idRght, ww); 372 | 373 | descPos2.putObject(idArtboardRect, idClassFloatRect, descRect); 374 | var idGuideIDs = sTID('guideIDs'); 375 | var list = new ActionList(); 376 | descPos2.putList(idGuideIDs, list); 377 | var idArtboardPresetName = sTID('artboardPresetName'); 378 | descPos2.putString(idArtboardPresetName, "" + size + ""); 379 | 380 | // Color 381 | var idClr = cTID('Clr '); 382 | var desc98 = new ActionDescriptor(); 383 | var idRd = cTID('Rd '); 384 | desc98.putDouble(idRd, 255.000000); 385 | var idGrn = cTID('Grn '); 386 | desc98.putDouble(idGrn, 255.000000); 387 | var idBl = cTID('Bl '); 388 | desc98.putDouble(idBl, 255.000000); 389 | var idRGBC = cTID('RGBC'); 390 | descPos2.putObject(idClr, idRGBC, desc98); 391 | var idArtboardBackgroundType = sTID('artboardBackgroundType'); 392 | 393 | descPos2.putInteger(idArtboardBackgroundType, parseInt(bg)); 394 | 395 | var idArtboard = sTID('artboard'); 396 | descPos1.putObject(idArtboard, idArtboard, descPos2); 397 | var idChangeSizes = sTID('changeSizes'); 398 | descPos1.putInteger(idChangeSizes, 5); 399 | executeAction(idEditArtboardEvent, descPos1, DialogModes.NO); 400 | } 401 | 402 | function cTID(s) { 403 | return app.charIDToTypeID(s); 404 | } 405 | 406 | function sTID(s) { 407 | return app.stringIDToTypeID(s); 408 | } 409 | 410 | // Open link in browser 411 | function openURL(url) { 412 | var html = new File(Folder.temp.absoluteURI + '/aisLink.html'); 413 | html.open('w'); 414 | var htmlBody = '

'; 415 | html.write(htmlBody); 416 | html.close(); 417 | html.execute(); 418 | } 419 | 420 | try { 421 | main(); 422 | } catch(err) {} -------------------------------------------------------------------------------- /jsx/ClearLayer.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | ClearLayer.jsx for Adobe Photoshop 3 | Description: Simple script to clear layers content. 4 | Date: June, 2019 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Donate (optional): 10 | If you find this script helpful, you can buy me a coffee 11 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 12 | - via Donatty https://donatty.com/sergosokin 13 | - via DonatePay https://new.donatepay.ru/en/@osokin 14 | - via YooMoney https://yoomoney.ru/to/410011149615582 15 | 16 | NOTICE: 17 | Tested with Adobe Photoshop CC 2019-2022. 18 | This script is provided "as is" without warranty of any kind. 19 | Free to use, not for sale 20 | 21 | Released under the MIT license. 22 | http://opensource.org/licenses/mit-license.php 23 | 24 | Check my other scripts: https://github.com/creold 25 | */ 26 | 27 | //@target photoshop 28 | 29 | app.bringToFront(); 30 | 31 | function main() { 32 | var doc = app.activeDocument; 33 | var selLayers = getSelectedLayersIdx(); 34 | for (var i = 0; i < selLayers.length; i++) { 35 | makeActiveByIndex(selLayers[i], false); 36 | doc.activeLayer.clear(); 37 | } 38 | } 39 | 40 | // This solution by geppettol66959005 41 | // https://community.adobe.com/t5/photoshop/how-to-find-selected-layers-and-run-events/td-p/10269273?page=1 42 | function getSelectedLayersIdx() { 43 | var selectedLayers = new Array 44 | var ref = new ActionReference() 45 | ref.putEnumerated(charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")) 46 | var desc = executeActionGet(ref) 47 | if (desc.hasKey(stringIDToTypeID('targetLayers'))){ 48 | desc = desc.getList(stringIDToTypeID('targetLayers')) 49 | var selectedLayers = new Array() 50 | for (var i = 0; i < desc.count; i++){ 51 | try { 52 | activeDocument.backgroundLayer 53 | selectedLayers.push(desc.getReference(i).getIndex()) 54 | } catch (e) { 55 | selectedLayers.push(desc.getReference(i).getIndex() + 1) 56 | } 57 | } 58 | } else { 59 | var ref = new ActionReference() 60 | ref.putProperty(charIDToTypeID("Prpr"), charIDToTypeID("ItmI")); 61 | ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt")); 62 | try{ 63 | activeDocument.backgroundLayer 64 | selectedLayers.push(executeActionGet(ref).getInteger(charIDToTypeID("ItmI")) - 1); 65 | } catch (e) { 66 | selectedLayers.push(executeActionGet(ref).getInteger(charIDToTypeID("ItmI"))) 67 | } 68 | } 69 | return selectedLayers 70 | } 71 | 72 | function makeActiveByIndex(idx, visible) { 73 | var desc = new ActionDescriptor() 74 | var ref = new ActionReference() 75 | ref.putIndex(charIDToTypeID("Lyr "), idx) 76 | desc.putReference(charIDToTypeID("null"), ref) 77 | desc.putBoolean(charIDToTypeID("MkVs"), visible) 78 | executeAction(charIDToTypeID("slct"), desc, DialogModes.NO) 79 | } 80 | 81 | // Run script 82 | try { 83 | app.activeDocument.suspendHistory('Clear Layers Script', 'main()'); 84 | } catch (e) { } -------------------------------------------------------------------------------- /jsx/ExportPathsToAi.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | ExportPathsToAi.jsx for Adobe Photoshop 3 | Description: Export all visible vector layers as unfilled paths to Illustrator 4 | Date: August, 2022 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Original idea: 8 | https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-export-paths-to-ai-in-action-batch/td-p/11049168 9 | 10 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 11 | 12 | Release notes: 13 | 0.1 Initial version by Stephen_A_Marsh 14 | 0.2 Added one-click export of all vector paths 15 | 16 | Donate (optional): 17 | If you find this script helpful, you can buy me a coffee 18 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 19 | - via Donatty https://donatty.com/sergosokin 20 | - via DonatePay https://new.donatepay.ru/en/@osokin 21 | - via YooMoney https://yoomoney.ru/to/410011149615582 22 | 23 | NOTICE: 24 | Tested with Adobe Photoshop CC 2019-2022. 25 | This script is provided "as is" without warranty of any kind. 26 | Free to use, not for sale 27 | 28 | Released under the MIT license 29 | http://opensource.org/licenses/mit-license.php 30 | 31 | Check my other scripts: https://github.com/creold 32 | */ 33 | 34 | //@target photoshop 35 | 36 | function main() { 37 | if (!isCorrectEnv()) return; 38 | 39 | var doc = activeDocument, 40 | docName = doc.name.replace(/\.[^\.]+$/, ''), 41 | shapes = getVisShapes(doc), // Array of layer ID's 42 | outFolder; 43 | 44 | try { 45 | outFolder = doc.path; 46 | } catch (e) { 47 | outFolder = Folder.desktop; 48 | } 49 | 50 | if (!shapes.length) { 51 | alert('Visible vector layers have not been found'); 52 | return; 53 | } 54 | 55 | deselect(); 56 | 57 | for (var i = 0; i < shapes.length; i++) { 58 | selectByID(shapes[i]); 59 | } 60 | 61 | var result = exportPaths(outFolder, docName, '.ai'); 62 | if (result) { 63 | alert(shapes.length + ' visible vector layers have been exported to ' + '\n' + decodeURI(result)); 64 | } 65 | } 66 | 67 | // Check the script environment 68 | function isCorrectEnv() { 69 | var args = ['app', 'document']; 70 | 71 | for (var i = 0; i < args.length; i++) { 72 | switch (args[i].toString().toLowerCase()) { 73 | case 'app': 74 | if (!/photoshop/i.test(app.name)) { 75 | alert('Error\nRun script from Adobe Photoshop'); 76 | return false; 77 | } 78 | break; 79 | case 'document': 80 | if (!documents.length) { 81 | alert('Error\nOpen a document and try again'); 82 | return false; 83 | } 84 | break; 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | // Get indexes of all visible vector layers 92 | function getVisShapes(collection) { 93 | var out = []; 94 | 95 | for (var i = 0; i < collection.layers.length; i++) { 96 | var lyr = collection.layers[i]; 97 | if (!lyr.visible) continue; 98 | if (/art/i.test(lyr.typename)) { 99 | if (isType(lyr, 4)) out.push(lyr.id); // const kVectorSheet = 4; 100 | } else if (/set/i.test(lyr.typename)) { 101 | out = [].concat(out, getVisShapes(lyr)); 102 | } else { 103 | out = [].concat(out, getVisShapes(lyr)); 104 | } 105 | } 106 | 107 | return out; 108 | } 109 | 110 | // Check LayerKind code 111 | function isType(layer, code) { 112 | try { 113 | activeDocument.activeLayer = layer; 114 | } catch (e) {} 115 | 116 | var ref = new ActionReference(); 117 | ref.putProperty(sTID('property'), sTID('layerKind')); 118 | ref.putEnumerated(sTID('layer'), sTID('ordinal'), sTID('targetEnum')); 119 | var type = executeActionGet(ref).getInteger(sTID('layerKind')); 120 | 121 | return type == code; 122 | } 123 | 124 | function cTID(s) { 125 | return app.charIDToTypeID(s); 126 | } 127 | 128 | function sTID(s) { 129 | return app.stringIDToTypeID(s); 130 | } 131 | 132 | // Deselect all layers in the document 133 | function deselect() { 134 | var ref = new ActionReference(), 135 | desc = new ActionDescriptor(); 136 | 137 | ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt') ); 138 | desc.putReference(cTID('null'), ref); 139 | executeAction(sTID('selectNoLayers'), desc, DialogModes.NO); 140 | } 141 | 142 | // Select layer by index 143 | function selectByID(id) { 144 | var ref = new ActionReference(), 145 | desc = new ActionDescriptor(); 146 | 147 | ref.putIdentifier(cTID('Lyr '), id); 148 | desc.putReference(cTID('null'), ref); 149 | desc.putEnumerated(sTID('selectionModifier'), sTID('selectionModifierType'), sTID('addToSelection')); 150 | desc.putBoolean(cTID('MkVs'), false); 151 | executeAction(cTID('slct'), desc, DialogModes.NO); 152 | } 153 | 154 | // Export vector paths to Illustrator file 155 | function exportPaths(folder, name, ext) { 156 | try { 157 | var aiFile = new File(folder + '/' + name + ext), 158 | expOptions = new ExportOptionsIllustrator; 159 | 160 | expOptions.path = IllustratorPathType.ALLPATHS; 161 | activeDocument.exportDocument(aiFile, ExportType.ILLUSTRATORPATHS, expOptions); 162 | return aiFile; 163 | } catch (e) { 164 | return null; 165 | } 166 | } 167 | 168 | try { 169 | main(); 170 | } catch(e) {} -------------------------------------------------------------------------------- /jsx/GeneratePreview.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | GeneratePreview.jsx for Adobe Photoshop 3 | Description: Generate JPG preview image from active document. 4 | Date: October, 2018 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Donate (optional): 10 | If you find this script helpful, you can buy me a coffee 11 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 12 | - via Donatty https://donatty.com/sergosokin 13 | - via DonatePay https://new.donatepay.ru/en/@osokin 14 | - via YooMoney https://yoomoney.ru/to/410011149615582 15 | 16 | NOTICE: 17 | Tested with Adobe Photoshop CC 2019-2022. 18 | This script is provided "as is" without warranty of any kind. 19 | Free to use, not for sale 20 | 21 | Released under the MIT license. 22 | http://opensource.org/licenses/mit-license.php 23 | 24 | Check my other scripts: https://github.com/creold 25 | */ 26 | 27 | //@target photoshop 28 | 29 | if (app.documents.length > 0) { 30 | var fName = app.activeDocument.name; 31 | var fileExt = '.jpg'; 32 | var jpegSizeMax = 1200; // for resizeDoc() function. Unit: px 33 | var newName = prepareName(app.activeDocument); 34 | var savePath = ''; 35 | // File path for saved doc 36 | try { 37 | savePath = app.activeDocument.path; 38 | } catch (e) { 39 | // File path for unsaved doc 40 | savePath = Folder.selectDialog('Select the folder for save preview image'); 41 | } 42 | } 43 | 44 | // Main function 45 | function main() { 46 | duplicateDoc(); // Duplicate Image 47 | clearAllGuides(); // Remove all guides from duplicate 48 | var doc = app.activeDocument; 49 | doc.flatten(); // Flatten Image 50 | resizeDoc(doc, jpegSizeMax); // Resize image to specific size 51 | doc.changeMode(ChangeMode.RGB); // Convert Image Mode to RGB 52 | if (savePath != null) { 53 | var fileName = decodeURI(savePath) + '/' + newName; 54 | exportJPG(doc, fileName, fileExt); 55 | } 56 | doc.close(SaveOptions.DONOTSAVECHANGES); // Close duplicate 57 | }; 58 | 59 | function prepareName(doc) { 60 | var name = decodeURI(doc.name); 61 | name = name.replace(/\s/g, '-'); // Replace all space symbols 62 | var lastDotPosition = name.lastIndexOf('.'); 63 | if (lastDotPosition > -1) { 64 | return name.slice(0, lastDotPosition); // Remove filename extension 65 | } 66 | return name; 67 | } 68 | 69 | // Duplicate Image 70 | function duplicateDoc(enabled, withDialog) { 71 | if (enabled != undefined && !enabled) return; 72 | var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); 73 | var desc1 = new ActionDescriptor(); 74 | var ref1 = new ActionReference(); 75 | ref1.putEnumerated(charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Frst')); 76 | desc1.putReference(charIDToTypeID('null'), ref1); 77 | executeAction(charIDToTypeID('Dplc'), desc1, dialogMode); 78 | } 79 | 80 | // Remove all guides from duplicate 81 | function clearAllGuides(enabled, withDialog) { 82 | if (enabled != undefined && !enabled) return; 83 | var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); 84 | var desc = new ActionDescriptor(); 85 | var ref = new ActionReference(); 86 | ref.putEnumerated(charIDToTypeID('Gd '), charIDToTypeID('Ordn'), charIDToTypeID('Al ')); 87 | desc.putReference(charIDToTypeID('null'), ref); 88 | executeAction(charIDToTypeID('Dlt '), desc, dialogMode); 89 | } 90 | 91 | // Resize image to specific size 92 | function resizeDoc(doc, size) { 93 | // If height > width resize based on height. Change DPI to 72 94 | app.preferences.rulerUnits = Units.PIXELS; 95 | if (doc.height.value > size || doc.width.value > size) { 96 | if (doc.height > doc.width) { 97 | doc.resizeImage(null, UnitValue(size, 'px'), 72, ResampleMethod.BICUBIC); 98 | } else { 99 | doc.resizeImage(UnitValue(size, 'px'), null, 72, ResampleMethod.BICUBIC); 100 | } 101 | } 102 | } 103 | 104 | function exportJPG(doc, fileName, ext) { 105 | // Web export options 106 | var options = new ExportOptionsSaveForWeb(); 107 | options.quality = 85; 108 | options.format = SaveDocumentType.JPEG; 109 | options.optimized = true; 110 | 111 | var file = File(fileName + ext); 112 | var msg = 'A file with the same name already exists in the folder "' + decodeURI(file.parent.name) + '". Replacing it will overwrite its current contents.'; 113 | if (file.exists) { 114 | var rewrite = confirm('"' + decodeURI(file.name) + '" already exists. Do you want to replace it?\n' + msg); 115 | if (!rewrite) { 116 | var counter = 2; 117 | do { 118 | var newName = fileName + '-' + fillZero(counter++, 2); 119 | file = File(newName + ext); 120 | } while (file.exists) 121 | } 122 | } 123 | doc.exportDocument(file, ExportType.SAVEFORWEB, options); 124 | alert('The file saved as:\n' + decodeURI(file.name)); 125 | } 126 | 127 | // Add zero to the file name before the indexes are less then size 128 | function fillZero(number, size) { 129 | var str = '000000000' + number; 130 | return str.slice(str.length - size); 131 | } 132 | 133 | // Run script 134 | try { 135 | main(); 136 | } catch (e) { } -------------------------------------------------------------------------------- /jsx/MultiEditText.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | MultiEditText.jsx for Adobe Photoshop 3 | Description: Bulk editing of text layer contents. Replaces content separately or with the same text 4 | Date: April, 2024 5 | Modification date: February, 2025 6 | Author: Sergey Osokin, email: hi@sergosokin.ru 7 | 8 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 9 | 10 | ******************************************************************************************* 11 | * WARNING: The script does not support the mixed appearance of characters in a text layer * 12 | ******************************************************************************************* 13 | 14 | Release notes: 15 | 0.2 Added button to reset texts, saving entered texts when switching options 16 | 0.1 Initial version 17 | 18 | Donate (optional): 19 | If you find this script helpful, you can buy me a coffee 20 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 21 | - via Donatty https://donatty.com/sergosokin 22 | - via DonatePay https://new.donatepay.ru/en/@osokin 23 | - via YooMoney https://yoomoney.ru/to/410011149615582 24 | 25 | NOTICE: 26 | Tested with Adobe Photoshop CC 2019, 2024 (Mac/Win). 27 | This script is provided "as is" without warranty of any kind. 28 | Free to use, not for sale 29 | 30 | Released under the MIT license 31 | http://opensource.org/licenses/mit-license.php 32 | 33 | Check my other scripts: https://github.com/creold 34 | */ 35 | 36 | //@target photoshop 37 | 38 | function main() { 39 | var SCRIPT = { 40 | name: 'Multi-edit Text', 41 | version: 'v0.2' 42 | }; 43 | 44 | var CFG = { 45 | width: 300, // Text area width, px 46 | height: 240, // Text area height, px 47 | ph: '', // Content display placeholder 48 | divider: '\n@@@\n', // Symbol for separating multiple text layers 49 | softBreak: '@#', // Soft line break char 50 | coordTolerance: 10, // Object alignment tolerance for sorting 51 | isMac: /mac/i.test($.os), 52 | settings: 'MET_settings' 53 | }; 54 | 55 | var doc = app.activeDocument; 56 | var currState = doc.activeHistoryState; 57 | var idx = getSelectedLayersIdx(); 58 | 59 | var texts = getTextLayers(doc.layers, idx); 60 | if (!texts.length) { 61 | alert('Texts not found\nPlease select at least 1 text object and try again', 'Script error'); 62 | return; 63 | } 64 | 65 | var isUndo = false; 66 | 67 | var sortedTexts = [].concat(texts); 68 | sortByPosition(sortedTexts, CFG.coordTolerance); 69 | 70 | var contents = extractContents(texts, CFG.softBreak); 71 | var sortedContents = extractContents(sortedTexts, CFG.softBreak); 72 | 73 | // Replace End of Text (ETX) to line break 74 | var placeholder = isEqualContents(texts, CFG.softBreak) ? 75 | texts[0].textItem.contents.replace(/\x03/g, CFG.softBreak) : CFG.ph; 76 | 77 | var tmpText = { 78 | union: '', 79 | separate: '' 80 | }; 81 | 82 | // DIALOG 83 | var win = new Window('dialog', SCRIPT.name + ' ' + SCRIPT.version); 84 | win.orientation = 'row'; 85 | win.alignChildren = ['fill', 'top']; 86 | 87 | // INPUT 88 | var input = win.add('edittext', [0, 0, CFG.width, CFG.height], placeholder, {multiline: true, scrolling: true }); 89 | input.helpTip = 'Press Tab to preview after typing.\nNew line: PC - Ctrl+Enter, Mac OS - Enter.'; 90 | input.helpTip += '\n\nInsert ' + CFG.divider.replace(/\n/g, '') + ' on a new line\nto separate text objects'; 91 | input.helpTip += '\n\nSoft line break special char: Shift+Enter'; 92 | input.active = true; 93 | 94 | // OPTIONS AND BUTTONS 95 | var opt = win.add('group'); 96 | opt.orientation = 'column'; 97 | opt.alignChildren = ['fill', 'center']; 98 | 99 | var isSeparate = opt.add('checkbox', undefined, 'Edit Separately'); 100 | isSeparate.helpTip = 'Edit each text layer\nindividually'; 101 | var isSort = opt.add('checkbox', undefined, 'List by XY'); 102 | isSort.helpTip = 'List text layers\nsorted by position'; 103 | isSort.enabled = isSeparate.value; 104 | var isReverse = opt.add('checkbox', undefined, 'Reverse Apply'); 105 | isReverse.helpTip = 'Replace the contents\nof text layers in\nreverse order'; 106 | isReverse.enabled = isSeparate.value; 107 | 108 | var cancel, ok; 109 | if (CFG.isMac) { 110 | cancel = opt.add('button', undefined, 'Cancel', { name: 'cancel' }); 111 | reset = opt.add('button', undefined, 'Reset', { name: 'reset' }); 112 | ok = opt.add('button', undefined, 'OK', { name: 'ok' }); 113 | } else { 114 | ok = opt.add('button', undefined, 'OK', { name: 'ok' }); 115 | reset = opt.add('button', undefined, 'Reset', { name: 'reset' }); 116 | cancel = opt.add('button', undefined, 'Cancel', { name: 'cancel' }); 117 | } 118 | 119 | cancel.helpTip = 'Press Esc to Close'; 120 | reset.helpTip = 'Reset to original texts'; 121 | ok.helpTip = 'Press Enter to Run'; 122 | 123 | var isPreview = opt.add('checkbox', undefined, 'Preview'); 124 | 125 | var copyright = opt.add('statictext', undefined, 'Visit Github'); 126 | copyright.justify = 'center'; 127 | 128 | // EVENTS 129 | loadSettings(); 130 | 131 | isSeparate.onClick = function () { 132 | isSort.enabled = this.value; 133 | isReverse.enabled = this.value; 134 | input.text = getInputText(placeholder); 135 | win.update(); 136 | preview(); 137 | }; 138 | 139 | isSort.onClick = function () { 140 | preview(); 141 | } 142 | 143 | isReverse.onClick = function () { 144 | input.text = reverseText(input.text, CFG.divider); 145 | win.update(); 146 | preview(); 147 | } 148 | 149 | input.onChange = function () { 150 | if (isSeparate.value) tmpText.separate = this.text; 151 | else tmpText.union = this.text; 152 | preview(); 153 | } 154 | 155 | isPreview.onClick = preview; 156 | 157 | // Insert soft line break char 158 | input.addEventListener('keydown', function (kd) { 159 | var isShift = ScriptUI.environment.keyboardState['shiftKey']; 160 | if (isShift && kd.keyName == 'Enter') { 161 | this.textselection = CFG.softBreak; 162 | kd.preventDefault(); 163 | preview(); 164 | } 165 | }); 166 | 167 | reset.onClick = function () { 168 | tmpText.separate = ''; 169 | tmpText.union = ''; 170 | input.text = getInputText(placeholder); 171 | 172 | preview(); 173 | 174 | this.active = true; 175 | this.active = false; 176 | 177 | input.active = true; 178 | win.update(); 179 | } 180 | 181 | cancel.onClick = win.close; 182 | 183 | ok.onClick = function () { 184 | if (isPreview.value && isUndo) { 185 | doc.activeHistoryState = currState; 186 | } 187 | 188 | ok.text = 'Wait...'; 189 | win.update(); 190 | 191 | changeTexts(); 192 | 193 | saveSettings(); 194 | isUndo = false; 195 | win.close(); 196 | } 197 | 198 | /** 199 | * Retrieve the text based on the configuration settings and temporary text storage 200 | * 201 | * @param {string} def - The default text to return if no valid text is found 202 | * @returns {string} - The processed text 203 | */ 204 | function getInputText(def) { 205 | var str = (isSort.value ? sortedContents : contents).join(CFG.divider); 206 | if (isSeparate.value) { 207 | return !isEmpty(tmpText.separate) ? tmpText.separate : (isReverse.value ? reverseText(str, CFG.divider) : str); 208 | } else { 209 | return !isEmpty(tmpText.union) ? tmpText.union : def; 210 | } 211 | } 212 | 213 | /** 214 | * Toggles the preview mode for text changes 215 | */ 216 | function preview() { 217 | try { 218 | if (isPreview.enabled && isPreview.value) { 219 | if (isUndo) doc.activeHistoryState = currState; 220 | else isUndo = true; 221 | changeTexts(); 222 | app.refresh(); 223 | } else if (isUndo) { 224 | doc.activeHistoryState = currState; 225 | app.refresh(); 226 | isUndo = false; 227 | } 228 | } catch (err) {} 229 | } 230 | 231 | /** 232 | * Change the text content of selected text layers based on the provided input text 233 | */ 234 | function changeTexts() { 235 | if (isEmpty(input.text)) return; 236 | 237 | // Create a regular expression for soft breaks 238 | var regex = new RegExp(CFG.softBreak, 'gmi'); 239 | 240 | // Handle separate text replacement mode 241 | if (isSeparate.value) { 242 | var srcTexts = [].concat(isSort.value ? sortedTexts : texts); 243 | // Split the input text using the configured divider 244 | var inpTexts = input.text.replace(regex, '\x03').split(CFG.divider); 245 | // Determine the minimum length to avoid out-of-bounds errors 246 | var min = Math.min(srcTexts.length, inpTexts.length); 247 | 248 | for (var i = 0; i < min; i++) { 249 | var txt = srcTexts[i]; 250 | if (txt.textItem.contents !== inpTexts[i]) { 251 | txt.textItem.contents = inpTexts[i] 252 | .replace(/[\r\n]+/gm, '\r') 253 | .replace(regex, '\x03'); 254 | } 255 | } 256 | } else { 257 | // Handle combined text replacement mode 258 | for (var i = 0, len = texts.length; i < len; i++) { 259 | var txt = texts[i]; 260 | // Replace placeholders in the input text with the original content of the text layer 261 | var str = input.text.replace(//gi, contents[i]) 262 | .replace(/[\r\n]+/gm, '\r') 263 | .replace(regex, '\x03');; 264 | if (txt.textItem.contents !== str) { 265 | txt.textItem.contents = str; 266 | } 267 | } 268 | } 269 | } 270 | 271 | win.onClose = function () { 272 | try { 273 | if (isUndo) doc.activeHistoryState = currState; 274 | } catch (err) {} 275 | } 276 | 277 | copyright.addEventListener('mousedown', function () { 278 | openURL('https://github.com/creold'); 279 | }); 280 | 281 | /** 282 | * Save UI options in Photoshop preferences 283 | */ 284 | function saveSettings() { 285 | var desc = new ActionDescriptor(); 286 | desc.putBoolean(0, isSeparate.value); 287 | desc.putBoolean(1, isSort.value); 288 | desc.putBoolean(2, isReverse.value); 289 | app.putCustomOptions(CFG.settings, desc, true); 290 | } 291 | 292 | /** 293 | * Load options from Photoshop preferences 294 | */ 295 | function loadSettings() { 296 | try { 297 | var desc = app.getCustomOptions(CFG.settings); 298 | } catch (err) {} 299 | if (typeof desc != 'undefined') { 300 | try { 301 | isSeparate.value = desc.getBoolean(0); 302 | isSort.value = desc.getBoolean(1); 303 | isSort.enabled = desc.getBoolean(0); 304 | isReverse.value = desc.getBoolean(2); 305 | isReverse.enabled = desc.getBoolean(0); 306 | input.text = getInputText(placeholder); 307 | return; 308 | } catch (err) {} 309 | } 310 | } 311 | 312 | win.center(); 313 | win.show(); 314 | } 315 | 316 | /** 317 | * Get the indexes of all selected layers in the active Photoshop document 318 | * https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-find-selected-layers-and-run-events/td-p/10269273 by Geppetto Luis 319 | * 320 | * @returns {Array} - An array containing the indexes of the selected layers 321 | */ 322 | function getSelectedLayersIdx() { 323 | var result = new Array; 324 | var ref = new ActionReference(); 325 | ref.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Trgt')); 326 | var desc = executeActionGet(ref); 327 | 328 | if (desc.hasKey(sTID('targetLayers'))) { 329 | desc = desc.getList(sTID('targetLayers')); 330 | var counter = desc.count; 331 | var result = new Array(); 332 | for (var i = 0; i < counter; i++) { 333 | try { 334 | activeDocument.backgroundLayer; 335 | result.push(desc.getReference(i).getIndex()); 336 | } catch (err) { 337 | result.push(desc.getReference(i).getIndex() + 1); 338 | } 339 | } 340 | } else { 341 | var ref = new ActionReference(); 342 | ref.putProperty(cTID('Prpr'), cTID('ItmI')); 343 | ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt')); 344 | try { 345 | activeDocument.backgroundLayer; 346 | result.push(executeActionGet(ref).getInteger(cTID('ItmI')) - 1); 347 | } catch (err) { 348 | result.push(executeActionGet(ref).getInteger(cTID('ItmI'))); 349 | } 350 | } 351 | return result; 352 | } 353 | 354 | /** 355 | * Convert a string identifier to a character identifier 356 | * 357 | * @param {string} s - The string identifier 358 | * @returns {number} - The corresponding character identifier 359 | */ 360 | function cTID(s) { 361 | return app.charIDToTypeID(s); 362 | } 363 | 364 | /** 365 | * Convert a string identifier to a type identifier 366 | * 367 | * @param {string} s - The string identifier 368 | * @returns {number} - The corresponding type identifier 369 | */ 370 | function sTID(s) { 371 | return app.stringIDToTypeID(s); 372 | } 373 | 374 | /** 375 | * Get all text layers from the given layers array that match the provided indexes 376 | * 377 | * @param {[Object|Array]} layers - The layers array to search 378 | * @param {number[]} idx - The array of indexes to match 379 | * @returns {Array} - An array containing the text layers matching the provided indexes 380 | */ 381 | function getTextLayers(layers, idx) { 382 | var result = []; 383 | 384 | for (var i = 0; i < layers.length; i++) { 385 | var lyr = layers[i]; 386 | if (lyr.kind === LayerKind.TEXT) { 387 | for (var j = 0; j < idx.length; j++) { 388 | try { 389 | activeDocument.backgroundLayer; 390 | if (lyr.itemIndex - 1 === idx[j]) { 391 | result.push(lyr); 392 | break; 393 | } 394 | } catch (err) { 395 | if (lyr.itemIndex === idx[j]) { 396 | result.push(lyr); 397 | break; 398 | } 399 | } 400 | } 401 | } else if (lyr.typename === 'LayerSet') { 402 | result = [].concat(result, getTextLayers(lyr.layers, idx)); 403 | } 404 | } 405 | 406 | return result; 407 | } 408 | 409 | /** 410 | * Sort items based on their position 411 | * 412 | * @param {(Object|Array)} coll - Collection to be sorted 413 | * @param {number} tolerance - The tolerance within which objects are considered to have the same top position 414 | */ 415 | function sortByPosition(coll, tolerance) { 416 | if (arguments.length == 1 || tolerance == undefined) { 417 | tolerance = 10; 418 | } 419 | 420 | coll.sort(function(a, b) { 421 | if (Math.abs(a.bounds[1] - b.bounds[1]) <= tolerance) { 422 | return a.bounds[0] - b.bounds[0]; 423 | } 424 | return a.bounds[1] - b.bounds[1]; 425 | }); 426 | } 427 | 428 | /** 429 | * Extract the contents of text layers from a given collection 430 | * 431 | * @param {Array} coll - The collection of text layers 432 | * @param {string} softBreak - The soft line break special char 433 | * @returns {Array} - An array containing the contents of text layers 434 | */ 435 | function extractContents(coll, softBreak) { 436 | var result = []; 437 | 438 | for (var i = 0, len = coll.length; i < len; i++) { 439 | // Replace End of Text (ETX) to line break 440 | result.push(coll[i].textItem.contents.replace(/\x03/g, softBreak)); 441 | } 442 | 443 | return result; 444 | } 445 | 446 | /** 447 | * Check if the contents of all texts in the collection are equal 448 | * 449 | * @param {Array} coll - The collection of texts to compare 450 | * @param {string} softBreak - The soft line break special char 451 | * @returns {boolean} - Returns true if all texts have equal contents, otherwise false 452 | */ 453 | function isEqualContents(coll, softBreak) { 454 | var str = coll[0].textItem.contents.replace(/\x03/g, softBreak); 455 | 456 | for (var i = 1, len = coll.length; i < len; i++) { 457 | if (coll[i].textItem.contents.replace(/\x03/g, softBreak) !== str) 458 | return false; 459 | } 460 | 461 | return true; 462 | } 463 | 464 | /** 465 | * Reverse the order of texts within a string separated by a specified divider 466 | * @param {string} str - The delimited string to reverse 467 | * @param {string} divider - The divider used to split 468 | * @returns {string} - A reversed string 469 | */ 470 | function reverseText(str, divider) { 471 | var tmp = str.split(divider); 472 | 473 | tmp.reverse(); 474 | var str = tmp.join(divider); 475 | 476 | return str; 477 | } 478 | 479 | /** 480 | * Check if a string is empty or contains only whitespace characters 481 | * 482 | * @param {string} str - The string to check for emptiness 483 | * @returns {boolean} - True if the string is empty, false otherwise 484 | */ 485 | function isEmpty(str) { 486 | return str.replace(/\s/g, '').length == 0; 487 | } 488 | 489 | /** 490 | * Open a URL in the default web browser 491 | * 492 | * @param {string} url - The URL to open in the web browser 493 | * @returns {void} 494 | */ 495 | function openURL(url) { 496 | var html = new File(Folder.temp.absoluteURI + '/aisLink.html'); 497 | html.open('w'); 498 | var htmlBody = '

'; 499 | html.write(htmlBody); 500 | html.close(); 501 | html.execute(); 502 | } 503 | 504 | try { 505 | if (!/photoshop/i.test(app.name)) { 506 | alert('Error\nRun script from Adobe Photoshop'); 507 | } else if (!documents.length) { 508 | alert('Error\nOpen a document and try again'); 509 | } else { 510 | app.activeDocument.suspendHistory('Multi-edit Text', 'main()'); 511 | } 512 | } catch (err) {} -------------------------------------------------------------------------------- /jsx/RenameArtboardAsSize.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | RenameArtboardAsSize.jsx for Adobe Photoshop 3 | Description: The script renames artboards according to their size in units from Preferences > Units & Rulers 4 | Date: January, 2024 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Release notes: 10 | 0.1 Initial version 11 | 0.1.1 Fixed convertUnits() function 12 | 13 | Donate (optional): 14 | If you find this script helpful, you can buy me a coffee 15 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 16 | - via Donatty https://donatty.com/sergosokin 17 | - via DonatePay https://new.donatepay.ru/en/@osokin 18 | - via YooMoney https://yoomoney.ru/to/410011149615582 19 | 20 | NOTICE: 21 | Tested with Adobe Photoshop CC 2019, 2024 (Mac OS). 22 | This script is provided "as is" without warranty of any kind. 23 | Free to use, not for sale 24 | 25 | Released under the MIT license 26 | http://opensource.org/licenses/mit-license.php 27 | 28 | Check my other scripts: https://github.com/creold 29 | */ 30 | 31 | //@target photoshop 32 | app.bringToFront(); 33 | 34 | function main() { 35 | var CFG = { 36 | units: getUnits(), // Active document units 37 | isSaveName: true, // Set false to overwrite the full name 38 | isRound: true, // Set true to get a round number 39 | precision: 2, // Size rounding precision 40 | isAddUnit: true, 41 | separator: '_', 42 | settings: 'RAAS_settings', 43 | }; 44 | 45 | if (!isCorrectEnv()) return; 46 | 47 | if (!app.activeDocument.layerSets.length) { 48 | alert("The document doesn't seem to have artboards", 'Script Error'); 49 | return; 50 | } 51 | 52 | invokeUI(CFG); 53 | } 54 | 55 | // Show UI 56 | function invokeUI(prefs) { 57 | var doc = app.activeDocument; 58 | 59 | var win = new Window('dialog', 'Rename Artboard As Size'); 60 | win.alignChildren = ['fill', 'fill']; 61 | 62 | // Range 63 | var rangePnl = win.add('panel', undefined, 'Artboards range'); 64 | rangePnl.alignChildren = ['fill', 'center']; 65 | rangePnl.margins = [12, 14, 10, 14]; 66 | 67 | var isAll = rangePnl.add('radiobutton', undefined, 'All artboards'); 68 | isAll.value = true; 69 | 70 | var isCurr = rangePnl.add('radiobutton', undefined, 'Active artboard'); 71 | 72 | // Options 73 | var optPnl = win.add('panel', undefined, 'Options'); 74 | optPnl.alignChildren = ['fill', 'center']; 75 | optPnl.margins = [12, 14, 10, 14]; 76 | 77 | var isSaveName = optPnl.add('checkbox', undefined, 'Add size as suffix'); 78 | isSaveName.value = prefs.isSaveName; 79 | 80 | var isRound = optPnl.add('checkbox', undefined, 'Round to integer'); 81 | isRound.value = prefs.isRound; 82 | 83 | var isAddUnit = optPnl.add('checkbox', undefined, 'Add units after size'); 84 | isAddUnit.value = prefs.isAddUnit; 85 | 86 | // Buttons 87 | var btns = win.add('group'); 88 | btns.alignChildren = ['fill', 'center']; 89 | 90 | var cancel, ok; 91 | if (/mac/i.test($.os)) { 92 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 93 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 94 | } else { 95 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 96 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 97 | } 98 | cancel.helpTip = 'Press Esc to Close'; 99 | ok.helpTip = 'Press Enter to Run'; 100 | 101 | var copyright = win.add('statictext', undefined, '\u00A9 Sergey Osokin. Visit Github'); 102 | copyright.justify = 'center'; 103 | 104 | loadSettings(); 105 | 106 | copyright.addEventListener('mousedown', function () { 107 | openURL('https://github.com/creold'); 108 | }); 109 | 110 | cancel.onClick = win.close; 111 | 112 | ok.onClick = function () { 113 | doc.suspendHistory('Rename Artboards', 'okClick()'); 114 | } 115 | 116 | function okClick() { 117 | var doc = app.activeDocument; 118 | var range = isAll.value ? doc.layerSets : [doc.activeLayer]; 119 | 120 | prefs.isSaveName = isSaveName.value; 121 | prefs.isRound = isRound.value; 122 | prefs.isAddUnit = isAddUnit.value; 123 | 124 | for (var i = 0, len = range.length; i < len; i++) { 125 | renameArtboard(range[i], prefs); 126 | } 127 | 128 | saveSettings(); 129 | win.close(); 130 | } 131 | 132 | function loadSettings() { 133 | try { 134 | var desc = app.getCustomOptions(prefs.settings); 135 | } catch (err) {} 136 | if (typeof desc != 'undefined') { 137 | try { 138 | rangePnl.children[desc.getInteger(0)].value = true; 139 | isSaveName.value = desc.getBoolean(1); 140 | isRound.value = desc.getBoolean(2); 141 | isAddUnit.value = desc.getBoolean(3); 142 | return; 143 | } catch (err) {} 144 | } 145 | 146 | compressPref = true; 147 | } 148 | 149 | function saveSettings() { 150 | var desc = new ActionDescriptor(); 151 | desc.putInteger(0, isAll.value ? 0 : 1); 152 | desc.putBoolean(1, isSaveName.value); 153 | desc.putBoolean(2, isRound.value); 154 | desc.putBoolean(3, isAddUnit.value); 155 | app.putCustomOptions(prefs.settings, desc, true); 156 | } 157 | 158 | win.center(); 159 | win.show(); 160 | } 161 | 162 | // Rename an artboard by its size 163 | function renameArtboard(ab, prefs) { 164 | var abName = ab.name; 165 | var separator = /\s/.test(abName) ? ' ' : (/-/.test(abName) ? '-' : prefs.separator); 166 | 167 | var abRect = getArtboardSize(ab); 168 | 169 | var width = convertUnits(abRect[2] - abRect[0], 'px', prefs.units); 170 | var height = convertUnits(abRect[3] - abRect[1], 'px', prefs.units); 171 | 172 | // It's probably a group of layers 173 | if (width === 0 || height === 0) return; 174 | 175 | width = prefs.isRound ? Math.round(width) : width.toFixed(prefs.precision); 176 | height = prefs.isRound ? Math.round(height) : height.toFixed(prefs.precision); 177 | 178 | var size = width + 'x' + height; 179 | if (prefs.isAddUnit) size += prefs.units; 180 | 181 | if (prefs.isSaveName) { 182 | ab.name += separator + size; 183 | } else { 184 | ab.name = size; 185 | } 186 | } 187 | 188 | // Get ruler units 189 | function getUnits() { 190 | var units = 'px'; 191 | var ruler = app.preferences.rulerUnits.toString(); 192 | var key = ruler.replace('Units.', ''); 193 | 194 | switch (key) { 195 | case 'PIXELS': units = 'px'; break; 196 | case 'INCHES': units = 'in'; break; 197 | case 'CM': units = 'cm'; break; 198 | case 'MM': units = 'mm'; break; 199 | case 'POINTS': units = 'pt'; break; 200 | case 'PICAS': units = 'pc'; break; 201 | case 'PERCENT': units = 'px'; break; 202 | } 203 | 204 | return units; 205 | } 206 | 207 | // Check the script environment 208 | function isCorrectEnv() { 209 | var args = ['app', 'document']; 210 | 211 | for (var i = 0; i < args.length; i++) { 212 | switch (args[i].toString().toLowerCase()) { 213 | case 'app': 214 | if (!/photoshop/i.test(app.name)) { 215 | alert('Wrong application\nRun script from Adobe Photoshop', 'Script error'); 216 | return false; 217 | } 218 | break; 219 | case 'document': 220 | if (!documents.length) { 221 | alert('No documents\nOpen a document and try again', 'Script error'); 222 | return false; 223 | } 224 | break; 225 | } 226 | } 227 | 228 | return true; 229 | } 230 | 231 | // Get artboard dimensions (artboar has type LayerSet) by Chuck Uebele 232 | // https://community.adobe.com/t5/photoshop-ecosystem-discussions/photoshop-scripting-artboard-size-values/m-p/13256092#M677004 233 | function getArtboardSize(layer) { 234 | try { 235 | var ref = new ActionReference(); 236 | ref.putProperty(sTID('property'), sTID('artboard')); 237 | if (layer) { 238 | ref.putIdentifier(sTID('layer'), layer.id); 239 | } else { 240 | ref.putEnumerated(sTID('layer'), sTID('ordinal'), sTID('targetEnum')); 241 | } 242 | 243 | var desc = executeActionGet(ref).getObjectValue(sTID('artboard')).getObjectValue(sTID('artboardRect')); 244 | var bounds = []; 245 | bounds[0] = desc.getUnitDoubleValue(sTID('left')); 246 | bounds[1] = desc.getUnitDoubleValue(sTID('top')); 247 | bounds[2] = desc.getUnitDoubleValue(sTID('right')); 248 | bounds[3] = desc.getUnitDoubleValue(sTID('bottom')); 249 | 250 | return bounds; 251 | } catch(err) { 252 | return [0, 0, 0, 0]; 253 | } 254 | } 255 | 256 | function sTID(s) { 257 | return app.stringIDToTypeID(s); 258 | } 259 | 260 | // Convert units of measurement 261 | function convertUnits(value, currUnits, newUnits) { 262 | UnitValue.baseUnit = UnitValue (1 / activeDocument.resolution, 'in'); 263 | var newValue = new UnitValue(value, currUnits); 264 | newValue = newValue.as(newUnits); 265 | UnitValue.baseUnit = null; 266 | return newValue; 267 | } 268 | 269 | 270 | // Open link in browser 271 | function openURL(url) { 272 | var html = new File(Folder.temp.absoluteURI + '/aisLink.html'); 273 | html.open('w'); 274 | var htmlBody = '

'; 275 | html.write(htmlBody); 276 | html.close(); 277 | html.execute(); 278 | } 279 | 280 | try { 281 | main(); 282 | } catch(err) {} -------------------------------------------------------------------------------- /jsx/SaveAll.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | SaveAll.jsx for Adobe Photoshop 3 | Description: Simple script to save all opened docs. 4 | Date: October, 2018 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Donate (optional): 10 | If you find this script helpful, you can buy me a coffee 11 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 12 | - via Donatty https://donatty.com/sergosokin 13 | - via DonatePay https://new.donatepay.ru/en/@osokin 14 | - via YooMoney https://yoomoney.ru/to/410011149615582 15 | 16 | NOTICE: 17 | Tested with Adobe Photoshop CC 2019-2022. 18 | This script is provided "as is" without warranty of any kind. 19 | Free to use, not for sale 20 | 21 | Released under the MIT license 22 | http://opensource.org/licenses/mit-license.php 23 | 24 | Check my other scripts: https://github.com/creold 25 | */ 26 | 27 | //@target photoshop 28 | 29 | var tempDoc = app.activeDocument; 30 | 31 | for (var i = app.documents.length - 1; i >= 0; i--) { 32 | var currDoc = app.documents[i]; 33 | // TRY..CATCH function for unsaved document 34 | try { 35 | if (!currDoc.saved) { 36 | app.activeDocument = currDoc; 37 | currDoc.save(); 38 | } 39 | } catch (e) {} 40 | } 41 | 42 | app.activeDocument = tempDoc; -------------------------------------------------------------------------------- /jsx/SelectShapesByColor.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | SelectShapesByColor.jsx for Adobe Photoshop 3 | Description: Select all Shape and Solid layers that have the same color as the selected layer 4 | Date: April, 2022 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Release notes: 10 | 0.1 Initial version 11 | 0.2 Added live text support 12 | 13 | Donate (optional): 14 | If you find this script helpful, you can buy me a coffee 15 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 16 | - via Donatty https://donatty.com/sergosokin 17 | - via DonatePay https://new.donatepay.ru/en/@osokin 18 | - via YooMoney https://yoomoney.ru/to/410011149615582 19 | 20 | NOTICE: 21 | Tested with Adobe Photoshop CC 2019-2022. 22 | This script is provided "as is" without warranty of any kind. 23 | Free to use, not for sale 24 | 25 | Released under the MIT license 26 | http://opensource.org/licenses/mit-license.php 27 | 28 | Check my other scripts: https://github.com/creold 29 | */ 30 | 31 | //@target photoshop 32 | 33 | function main() { 34 | if (!isCorrectEnv()) return; 35 | 36 | var inclSolid = false; // Include Adjustment Layer > Solid Color to selection 37 | 38 | var aLayer = activeDocument.activeLayer; 39 | if (!aLayer.visible) return; 40 | 41 | var sample = getColor(aLayer), // Hex 42 | matches = search(sample, inclSolid); // Array of layer ID's 43 | 44 | deselect(); 45 | 46 | forEach(matches, function (e) { 47 | selectByID(e); 48 | }); 49 | } 50 | 51 | // Check the script environment 52 | function isCorrectEnv() { 53 | var args = ['app', 'document']; 54 | 55 | for (var i = 0; i < args.length; i++) { 56 | switch (args[i].toString().toLowerCase()) { 57 | case 'app': 58 | if (!/photoshop/i.test(app.name)) { 59 | alert('Error\nRun script from Adobe Photoshop'); 60 | return false; 61 | } 62 | break; 63 | case 'document': 64 | if (!documents.length) { 65 | alert('Error\nOpen a document and try again'); 66 | return false; 67 | } 68 | break; 69 | } 70 | } 71 | 72 | return true; 73 | } 74 | 75 | // Extract color from Shape or Solid color 76 | function getColor(layer) { 77 | try { 78 | if (layer.kind == LayerKind.TEXT) { 79 | return layer.textItem.color.rgb.hexValue; 80 | } else { 81 | var desc = getLayerDescriptor(layer); 82 | var adjs = desc.getList(cTID('Adjs')); // Adjustments 83 | 84 | var clrDesc = adjs.getObjectValue(0), 85 | color = clrDesc.getObjectValue(cTID('Clr ')); 86 | 87 | var red = Math.round(color.getDouble(cTID('Rd '))), 88 | green = Math.round(color.getDouble(cTID('Grn '))), 89 | blue = Math.round(color.getDouble(cTID('Bl '))); 90 | 91 | var rgbColor = setRGBColor(red, green, blue); 92 | 93 | return rgbColor.rgb.hexValue; 94 | } 95 | } catch (e) { 96 | return ''; // Adjustments is undefined 97 | } 98 | } 99 | 100 | function getLayerDescriptor(layer) { 101 | var prev = activeDocument.activeLayer; 102 | 103 | if (layer != prev) { 104 | try { 105 | activeDocument.activeLayer = layer; 106 | } catch (e) {} 107 | } 108 | 109 | var ref = new ActionReference(); 110 | ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt')); 111 | var desc = executeActionGet(ref); 112 | 113 | activeDocument.activeLayer = prev; 114 | 115 | return desc; 116 | } 117 | 118 | // Generate solid RGB color 119 | function setRGBColor(r, g, b) { 120 | var rgb = new RGBColor(); 121 | 122 | if (r instanceof Array) { 123 | r = r[0]; 124 | g = r[1]; 125 | b = r[2]; 126 | } 127 | 128 | rgb.red = parseInt(r); 129 | rgb.green = parseInt(g); 130 | rgb.blue = parseInt(b); 131 | 132 | var sColor = new SolidColor(); 133 | sColor.rgb = rgb; 134 | 135 | return sColor; 136 | } 137 | 138 | // Search layer color matches 139 | function search(sample, inclSolid) { 140 | var layers = getLayers(activeDocument.layers), 141 | out = []; // Array of layer ID's 142 | 143 | forEach(layers, function (e) { 144 | var color = getColor(e); 145 | 146 | if (color == '') return; // The layer is not a shape or a solid color 147 | if (getLayerType(e) == 11 && !inclSolid) return; // Skip solid 148 | 149 | // Compare hex value 150 | if (color === sample) out.push(e.id); 151 | }); 152 | 153 | return out; 154 | } 155 | 156 | // Get all single layers 157 | function getLayers(collection) { 158 | var out = []; 159 | 160 | forEach(collection, function(e) { 161 | if (!e.visible) return; 162 | if (/art/i.test(e.typename)) { // ArtLayer 163 | out.push(e); 164 | } else if (/set/i.test(e.typename)) { // LayerSet 165 | out = [].concat(out, getLayers(e.layers)); 166 | } else { 167 | out = [].concat(out, getLayers(e.layers)); 168 | } 169 | }); 170 | 171 | return out; 172 | } 173 | 174 | // Get LayerKind code 175 | function getLayerType(layer) { 176 | var prev = activeDocument.activeLayer; 177 | 178 | if (layer != prev) { 179 | try { 180 | activeDocument.activeLayer = layer; 181 | } catch (e) {} 182 | } 183 | 184 | var ref = new ActionReference(); 185 | var desc = new ActionDescriptor(); 186 | ref.putProperty(sTID('property'), sTID('layerKind')); 187 | ref.putEnumerated(sTID('layer'), sTID('ordinal'), sTID('targetEnum')); 188 | var type = executeActionGet(ref).getInteger(sTID('layerKind')); 189 | 190 | activeDocument.activeLayer = prev; 191 | 192 | return type; 193 | } 194 | 195 | // Deselect all layers in the document 196 | function deselect() { 197 | var ref = new ActionReference(); 198 | var desc = new ActionDescriptor(); 199 | ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt') ); 200 | desc.putReference(cTID('null'), ref); 201 | executeAction(sTID('selectNoLayers'), desc, DialogModes.NO); 202 | } 203 | 204 | // Select layer by ID 205 | function selectByID(id) { 206 | var ref = new ActionReference(); 207 | var desc = new ActionDescriptor(); 208 | ref.putIdentifier(cTID('Lyr '), id); 209 | desc.putReference(cTID('null'), ref); 210 | desc.putEnumerated(sTID('selectionModifier'), sTID('selectionModifierType'), sTID('addToSelection')); 211 | desc.putBoolean(cTID('MkVs'), false); 212 | executeAction(cTID('slct'), desc, DialogModes.NO); 213 | } 214 | 215 | // Calls a provided callback function once for each element in an array 216 | function forEach(collection, fn) { 217 | for (var i = 0, cLen = collection.length; i < cLen; i++) { 218 | fn(collection[i]); 219 | } 220 | } 221 | 222 | function cTID(s) { 223 | return app.charIDToTypeID(s); 224 | } 225 | 226 | function sTID(s) { 227 | return app.stringIDToTypeID(s); 228 | } 229 | 230 | try { 231 | main(); 232 | } catch(e) {} -------------------------------------------------------------------------------- /jsx/TIFF2Print.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | TIFF2Print.jsx for Adobe Photoshop 3 | Description: Simple script to save a print ready file in Photoshop. 4 | Features: + Does not change the source file 5 | + Removes guides, empty vector paths before saving TIFF 6 | + Shorten measure units MM > CM > M when possible 7 | + The file name template is configured in the script code 8 | + Allows you to overwrite an existing file or save it with a new index 9 | Date: August, 2018 10 | Author: Sergey Osokin, email: hi@sergosokin.ru 11 | 12 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 13 | 14 | Release notes: 15 | 1.0 Initial version 16 | 1.1 Added ZIP compression. 17 | 18 | Donate (optional): 19 | If you find this script helpful, you can buy me a coffee 20 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 21 | - via Donatty https://donatty.com/sergosokin 22 | - via DonatePay https://new.donatepay.ru/en/@osokin 23 | - via YooMoney https://yoomoney.ru/to/410011149615582 24 | 25 | NOTICE: 26 | Tested with Adobe Photoshop CS5 (Win), CC 2017 & 2018 (Mac), CC 2018 (Win). 27 | This script is provided "as is" without warranty of any kind. 28 | Free to use, not for sale. 29 | 30 | Released under the MIT license. 31 | http://opensource.org/licenses/mit-license.php 32 | 33 | Check my other scripts: https://github.com/creold 34 | */ 35 | 36 | //@target photoshop 37 | app.bringToFront(); 38 | 39 | // Const 40 | var scriptName = 'TIFF2Print', 41 | scriptVersion = 'v.1.1', 42 | scriptSettings = scriptName + "_settings"; 43 | 44 | // Configuration 45 | if (documents.length > 0) { 46 | var originDoc = app.activeDocument, 47 | savePath = '', 48 | saveUnits = app.preferences.rulerUnits, // Save current units 49 | printExt = '.tif', // Extension of a printed file 50 | printSuffix = 'print', // Suffix before print file extension 51 | previewExt = '.jpg', // Extension of a preview file 52 | previewSuffix = 'preview', // Suffix before preview file extension 53 | divider = '-', // For suffix and to replace spaces 54 | sizeSuffix = '', // Linear size suffix 55 | colorProfile = false, //Embed color profile in print file 56 | jpegQuality = 9, // Maximum value: 12 57 | jpgSizeMax = 1200, // Unit: px. Size on the larger side 58 | compressPref; // Declare variables to initialize settings 59 | } 60 | 61 | // Main function 62 | function main() { 63 | if (!isCorrectEnv()) return; 64 | uiDialog().show(); 65 | } 66 | 67 | // Check the script environment 68 | function isCorrectEnv() { 69 | var args = ['app', 'document']; 70 | 71 | for (var i = 0; i < args.length; i++) { 72 | switch (args[i].toString().toLowerCase()) { 73 | case 'app': 74 | if (!/photoshop/i.test(app.name)) { 75 | alert('Error\nRun script from Adobe Photoshop'); 76 | return false; 77 | } 78 | break; 79 | case 'document': 80 | if (!documents.length) { 81 | alert('Error\nOpen a document and try again'); 82 | return false; 83 | } 84 | break; 85 | } 86 | } 87 | 88 | return true; 89 | } 90 | 91 | // Create dialog window 92 | function uiDialog() { 93 | var win = new Window('dialog', scriptName + ' ' + scriptVersion); 94 | win.orientation = 'column'; 95 | win.alignChildren = ['fill', 'fill']; 96 | 97 | var namePnl = win.add('panel', undefined, 'Name settings'); 98 | namePnl.alignChildren = 'left'; 99 | // Add size in file name or not 100 | var isSize = namePnl.add('checkbox', undefined, 'Add width and height (mm)'); 101 | isSize.helpTip = 'Sample: -print.tif or -210x99mm-print.tif'; 102 | isSize.value = true; 103 | // Convert unit values when possible 104 | var isShort = namePnl.add('checkbox', undefined, 'Shorten measure unit (cm/m)'); 105 | isShort.helpTip = 'If size > 1000 mm convert mm > cm > m'; 106 | isShort.value = false; 107 | 108 | // Enable/disable convertation panel 109 | isSize.onClick = function () { 110 | isShort.enabled = this.value ? true : false; 111 | } 112 | 113 | // Select image compression 114 | var compPnl = win.add('panel', undefined, 'Image compression'); 115 | compPnl.alignChildren = 'left'; 116 | compPnl.orientation = 'row'; 117 | var noneComp = compPnl.add('radiobutton', undefined, 'None'); 118 | noneComp.value = true; 119 | var lzwComp = compPnl.add('radiobutton', undefined, 'LZW'); 120 | var zipComp = compPnl.add('radiobutton', undefined, 'ZIP'); 121 | 122 | // Generate preview image 123 | var previewPan = win.add('group'); 124 | previewPan.margins = [16, 0, 0, 0]; 125 | var preview = previewPan.add('checkbox', undefined, 'Save JPG preview'); 126 | preview.helpTip = 'Save a JPG of ' + jpgSizeMax + 127 | ' px on the larger side with ' + 128 | 'the same name as the TIFF file'; 129 | preview.value = true; 130 | 131 | // Action buttons 132 | var btns = win.add('group'); 133 | btns.alignChildren = ['fill', 'fill']; 134 | var cancel = btns.add('button', undefined, 'Cancel', {name: 'cancel'}); 135 | cancel.helpTip = 'Press Esc to Close'; 136 | var ok = btns.add('button', undefined, 'OK', {name: 'ok'}); 137 | ok.helpTip = 'Press Enter to Run'; 138 | 139 | initSettings(); 140 | 141 | cancel.onClick = win.close; 142 | 143 | ok.onClick = okClick; 144 | 145 | var copyright = win.add('statictext', undefined, '\u00A9 Sergey Osokin. Visit Github'); 146 | copyright.justify = 'center'; 147 | 148 | copyright.addEventListener('mousedown', function () { 149 | openURL('https://github.com/creold'); 150 | }); 151 | 152 | function okClick() { 153 | // Setting file path 154 | try { 155 | savePath = originDoc.path; 156 | } catch (e) { 157 | // File path for unsaved document 158 | savePath = Folder.selectDialog('Select the folder for save TIFF'); 159 | if (savePath == null) { 160 | alert('You need to select a folder to save.'); 161 | return; // Return to dialog 162 | } 163 | } 164 | 165 | var compType = TIFFEncoding.NONE; 166 | if (lzwComp.value) { 167 | compType = TIFFEncoding.TIFFLZW; 168 | } else if (zipComp.value) { 169 | compType = TIFFEncoding.TIFFZIP; 170 | } 171 | 172 | // Ruler setup 173 | if (isSize.value) { 174 | if (saveUnits != 'Units.MM') { 175 | app.preferences.rulerUnits = Units.MM; 176 | } 177 | var docSize = calcSize(originDoc, isShort.value); // Calculate doc width & height value 178 | sizeSuffix = divider + docSize.w + 'x' + docSize.h + docSize.unit; // Set suffix 179 | } 180 | 181 | var nameTpl = prepareName(originDoc) + sizeSuffix; // // Name template 182 | 183 | duplicateDoc(); // Duplicate Image 184 | clearAllGuides(); // Remove all guides from duplicate 185 | 186 | var doc = app.activeDocument; 187 | doc.flatten(); // Flatten Image 188 | delPaths(doc); // Remove empty paths 189 | 190 | try { 191 | if (savePath != null) { 192 | var fileName = decodeURI(savePath) + '/' + nameTpl; 193 | // Save print file 194 | var printName = fileName + divider + printSuffix; 195 | SaveTIFF(printName, printExt, compType); 196 | } 197 | } catch (e) {} 198 | 199 | // Save preview file 200 | if (preview.value == true) { 201 | resizeDoc(doc, jpgSizeMax); // Resize before save preview 202 | doc.changeMode(ChangeMode.RGB); 203 | var previewName = fileName + divider + previewSuffix; 204 | saveJPEG(previewName, previewExt, jpegQuality); 205 | } 206 | 207 | doc.close(SaveOptions.DONOTSAVECHANGES); // Close duplicate 208 | app.preferences.rulerUnits = saveUnits; // Restore original ruler units 209 | win.close(); 210 | 211 | // Save user settings 212 | saveSettings(); 213 | } 214 | 215 | function initSettings() { 216 | try { 217 | var desc = app.getCustomOptions(scriptSettings); 218 | } catch (e) {} 219 | if (undefined != desc) { 220 | try { 221 | isSize.value = desc.getBoolean(0); 222 | isShort.value = desc.getBoolean(1); 223 | compPnl.children[desc.getInteger(2)].value = true; 224 | preview.value = desc.getBoolean(3); 225 | if (!isSize.value) isShort.enabled = false; 226 | return; 227 | } 228 | catch (e) {} 229 | } 230 | 231 | compressPref = true; 232 | } 233 | 234 | function saveSettings() { 235 | var comp = 0; 236 | if (lzwComp.value) { 237 | comp = 1; 238 | } else if (zipComp.value) { 239 | comp = 2; 240 | } 241 | var desc = new ActionDescriptor(); 242 | desc.putBoolean(0, isSize.value); 243 | desc.putBoolean(1, isShort.value); 244 | desc.putInteger(2, comp); 245 | desc.putBoolean(3, preview.value); 246 | app.putCustomOptions(scriptSettings, desc, true); 247 | } 248 | 249 | return win; 250 | } 251 | 252 | // Calculate doc width & height value 253 | function calcSize(doc, value) { 254 | var unitType = 'mm'; 255 | var width = Math.round(doc.width.value); // Remove unit type from width 256 | var height = Math.round(doc.height.value); // Remove unit type from height 257 | var newWidth = width; 258 | var newHeight = height; 259 | // Convert unit only for large layout >=1000x1000 mm 260 | if (value == true && width >= 1000 && height >= 1000) { 261 | if (isInteger(width / 1000) && isInteger(height / 1000)) { 262 | newWidth = convertUnit(width, '000'); 263 | newHeight = convertUnit(height, '000'); 264 | unitType = 'm'; 265 | } else if (isInteger(width / 10) && isInteger(height / 10)) { 266 | newWidth = convertUnit(width, '0'); 267 | newHeight = convertUnit(height, '0'); 268 | unitType = 'cm'; 269 | } 270 | } 271 | return { 'unit': unitType, 'w': newWidth, 'h': newHeight }; 272 | } 273 | 274 | function isInteger(number) { 275 | return Math.round(number) === number; 276 | } 277 | 278 | function convertUnit(number, size) { 279 | var str = number.toString(); 280 | var lastZeroPosition = str.lastIndexOf(size); 281 | if (lastZeroPosition > -1) { 282 | return str.slice(0, lastZeroPosition); 283 | } 284 | return str; 285 | } 286 | 287 | function prepareName(doc) { 288 | var name = decodeURI(doc.name); 289 | name = name.replace(/\s/g, divider); // Replace all space symbols 290 | var lastDotPosition = name.lastIndexOf('.'); 291 | if (lastDotPosition > -1) { 292 | return name.slice(0, lastDotPosition); // Remove filename extension 293 | } 294 | return name; 295 | } 296 | 297 | // Duplicate original document 298 | function duplicateDoc(enabled, withDialog) { 299 | if (enabled != undefined && !enabled) return; 300 | var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); 301 | var desc1 = new ActionDescriptor(); 302 | var ref1 = new ActionReference(); 303 | ref1.putEnumerated(charIDToTypeID('Dcmn'), charIDToTypeID('Ordn'), charIDToTypeID('Frst')); 304 | desc1.putReference(charIDToTypeID('null'), ref1); 305 | executeAction(charIDToTypeID('Dplc'), desc1, dialogMode); 306 | } 307 | 308 | // Remove all guides from duplicate 309 | function clearAllGuides(enabled, withDialog) { 310 | if (enabled != undefined && !enabled) return; 311 | var dialogMode = (withDialog ? DialogModes.ALL : DialogModes.NO); 312 | var desc = new ActionDescriptor(); 313 | var ref = new ActionReference(); 314 | ref.putEnumerated(charIDToTypeID('Gd '), charIDToTypeID('Ordn'), charIDToTypeID('Al ')); 315 | desc.putReference(charIDToTypeID('null'), ref); 316 | executeAction(charIDToTypeID('Dlt '), desc, dialogMode); 317 | } 318 | 319 | // Delete empty vector paths 320 | function delPaths(target) { 321 | for (var i = 0; i < target.pathItems.length; i++) { 322 | target.pathItems[i].remove(); 323 | } 324 | } 325 | 326 | // Resize image to specific size 327 | function resizeDoc(doc, size) { 328 | app.preferences.rulerUnits = Units.PIXELS; 329 | if (doc.height.value > size || doc.width.value > size) { 330 | // If height > width resize based on height. Change DPI to 72 331 | if (doc.height.value > doc.width.value) { 332 | doc.resizeImage(null, UnitValue(size, 'px'), 72, ResampleMethod.BICUBIC); 333 | } else { 334 | doc.resizeImage(UnitValue(size, 'px'), null, 72, ResampleMethod.BICUBIC); 335 | } 336 | } 337 | } 338 | 339 | // Save print file in TIFF format 340 | function SaveTIFF(fileName, ext, compression) { 341 | var file = saveFile(fileName, ext); 342 | tiffSaveOptions = new TiffSaveOptions(); 343 | tiffSaveOptions.alphaChannels = false; 344 | tiffSaveOptions.byteOrder = ByteOrder.IBM; 345 | tiffSaveOptions.embedColorProfile = colorProfile; 346 | tiffSaveOptions.imageCompression = compression; 347 | tiffSaveOptions.layerCompression = LayerCompression.RLE; 348 | tiffSaveOptions.layers = false; 349 | tiffSaveOptions.transparency = false; 350 | app.activeDocument.saveAs(file, tiffSaveOptions, true, Extension.LOWERCASE); 351 | alert('The file saved as:\n' + decodeURI(file.name)); 352 | } 353 | 354 | // Save preview file in JPEG format 355 | function saveJPEG(fileName, ext, quality) { 356 | if (quality > 12) quality = 12; // Because max JPG quality = 12 357 | var file = saveFile(fileName, ext); 358 | if (app.activeDocument.bitsPerChannel != BitsPerChannelType.EIGHT) { 359 | app.activeDocument.bitsPerChannel = BitsPerChannelType.EIGHT; 360 | } 361 | jpgSaveOptions = new JPEGSaveOptions(); 362 | jpgSaveOptions.embedColorProfile = false; 363 | jpgSaveOptions.formatOptions = FormatOptions.STANDARDBASELINE; 364 | jpgSaveOptions.matte = MatteType.NONE; 365 | jpgSaveOptions.quality = quality; 366 | app.activeDocument.saveAs(file, jpgSaveOptions, true, Extension.LOWERCASE); 367 | alert('The file saved as:\n' + decodeURI(file.name)); 368 | } 369 | 370 | // Check if there is already such a file 371 | function saveFile(fileName, ext) { 372 | var file = File(fileName + ext); 373 | var msg = 'A file with the same name already exists in the folder "' + 374 | decodeURI(file.parent.name) + 375 | '". Replacing it will overwrite its current contents.'; 376 | if (file.exists) { 377 | var rewrite = confirm('"' + decodeURI(file.name) + 378 | '" already exists. Do you want to replace it?\n' + msg); 379 | if (!rewrite) { 380 | var counter = 2; 381 | do { 382 | var newName = fileName + '-' + fillZero(counter++, 2); 383 | file = File(newName + ext); 384 | } while (file.exists) 385 | } 386 | } 387 | return file; // Final name for save 388 | } 389 | 390 | // Add zero to the file name before the indexes are less then size 391 | function fillZero(number, size) { 392 | var str = '000000000' + number; 393 | return str.slice(str.length - size); 394 | } 395 | 396 | // Open link in browser 397 | function openURL(url) { 398 | var html = new File(Folder.temp.absoluteURI + '/aisLink.html'); 399 | html.open('w'); 400 | var htmlBody = '

'; 401 | html.write(htmlBody); 402 | html.close(); 403 | html.execute(); 404 | } 405 | 406 | // Run script 407 | try { 408 | main(); 409 | } catch (e) {} -------------------------------------------------------------------------------- /jsx/TextBlock.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | TextBlock.jsx for Adobe Photoshop 3 | Description: Convert selected text layers into a block of text 4 | Date: March, 2025 5 | Modification date: April, 2025 6 | Author: Sergey Osokin, email: hi@sergosokin.ru 7 | 8 | Version for Adobe Illustrator: 9 | https://github.com/creold/illustrator-scripts/blob/master/md/Text.md 10 | 11 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 12 | 13 | Release notes: 14 | 0.2 Added option to center text block and hide/remove original layers 15 | 0.1 Initial version 16 | 17 | Donate (optional): 18 | If you find this script helpful, you can buy me a coffee 19 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 20 | - via Donatty https://donatty.com/sergosokin 21 | - via DonatePay https://new.donatepay.ru/en/@osokin 22 | - via YooMoney https://yoomoney.ru/to/410011149615582 23 | 24 | NOTICE: 25 | Tested with Adobe Photoshop CC 2019-2024 (Mac/Win). 26 | This script is provided "as is" without warranty of any kind. 27 | Free to use, not for sale 28 | 29 | Released under the MIT license 30 | http://opensource.org/licenses/mit-license.php 31 | 32 | Check my other scripts: https://github.com/creold 33 | */ 34 | 35 | //@target photoshop 36 | app.bringToFront(); 37 | 38 | function main() { 39 | var SCRIPT = { 40 | name: 'Text Block', 41 | version: 'v0.2' 42 | }; 43 | 44 | var CFG = { 45 | width: '300 px', // Text Block width 46 | spacing: '10 px', // Text lines spacing 47 | units: app.preferences.rulerUnits, 48 | isMac: /mac/i.test($.os), 49 | settings: 'TB_settings', 50 | }; 51 | 52 | if (!isCorrectEnv()) return; 53 | var doc = app.activeDocument; 54 | var idx = getSelectedLayersIdx(); 55 | 56 | var texts = getTextLayers(doc.layers, idx); 57 | if (texts.length < 2) { 58 | alert('Texts not found\nPlease select at least two text layers and try again', 'Script error'); 59 | return; 60 | } 61 | 62 | // Sort array by Y and X positions 63 | sortByPosition(texts, 10); 64 | 65 | // DIALOG 66 | var win = new Window('dialog', SCRIPT.name + ' ' + SCRIPT.version); 67 | win.alignChildren = ['fill', 'top']; 68 | 69 | // INPUTS 70 | var settPnl = win.add('panel', undefined, 'Block Settings'); 71 | settPnl.alignChildren = ['fill', 'top']; 72 | settPnl.margins = [10, 15, 5, 15]; 73 | 74 | var wrapper1 = settPnl.add('group'); 75 | wrapper1.alignChildren = ['left', 'center']; 76 | 77 | var wLbl = wrapper1.add('statictext', undefined, 'Width:'); 78 | wLbl.preferredSize.width = 55; 79 | 80 | var wInp = wrapper1.add('edittext', undefined, CFG.width); 81 | wInp.preferredSize.width = 80; 82 | wInp.helpTip = 'Supporterd units:\npx, pt, in, mm, cm, m, ft, yd'; 83 | wInp.active = true; 84 | 85 | var wrapper2 = settPnl.add('group'); 86 | wrapper2.alignChildren = ['left', 'center']; 87 | 88 | var spLbl = wrapper2.add('statictext', undefined, 'Spacing:'); 89 | spLbl.preferredSize.width = 55; 90 | 91 | var spInp = wrapper2.add('edittext', undefined, CFG.spacing); 92 | spInp.preferredSize.width = 80; 93 | spInp.helpTip = 'Supporterd units:\npx, pt, in, mm, cm, m, ft, yd'; 94 | 95 | var isToCenter = settPnl.add('checkbox', undefined, 'Center To Canvas'); 96 | 97 | // ORIGINAL LAYERS POST-PROCESS 98 | var origPnl = win.add('panel', undefined, 'Original Texts'); 99 | origPnl.alignChildren = ['fill', 'top']; 100 | origPnl.margins = [10, 15, 5, 15]; 101 | 102 | var isKeep = origPnl.add('radiobutton', undefined, 'Keep'); 103 | isKeep.value = true; 104 | var isHide = origPnl.add('radiobutton', undefined, 'Hide'); 105 | var isRemove = origPnl.add('radiobutton', undefined, 'Remove'); 106 | 107 | // BUTTONS 108 | var btns = win.add('group'); 109 | btns.alignChildren = ['fill', 'center']; 110 | btns.spacing = 10; 111 | 112 | var cancel, ok; 113 | if (CFG.isMac) { 114 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 115 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 116 | } else { 117 | ok = btns.add('button', undefined, 'OK', { name: 'ok' }); 118 | cancel = btns.add('button', undefined, 'Cancel', { name: 'cancel' }); 119 | } 120 | 121 | cancel.helpTip = 'Press Esc to Close'; 122 | ok.helpTip = 'Press Enter to Run'; 123 | 124 | var copyright = win.add('statictext', undefined, 'Click Here To Visit Github'); 125 | copyright.justify = 'center'; 126 | 127 | // EVENTS 128 | loadSettings(); 129 | 130 | wInp.onChange = spInp.onChange = function () { 131 | var units = parseUnits(this.text, 'px'); 132 | var num = strToNum(this.text, CFG.width); 133 | this.text = num + ' ' + units; 134 | } 135 | 136 | /** 137 | * Use Up / Down arrow keys (+ Shift) to change value 138 | */ 139 | bindStepperKeys(wInp, 0.1, 100000); 140 | bindStepperKeys(spInp, 0, 100000); 141 | 142 | cancel.onClick = win.close; 143 | 144 | ok.onClick = function () { 145 | doc.suspendHistory('TextBlock Script', 'okClick()'); 146 | } 147 | 148 | copyright.addEventListener('mousedown', function () { 149 | openURL('https://github.com/creold'); 150 | }); 151 | 152 | function okClick() { 153 | saveSettings(); 154 | 155 | app.preferences.rulerUnits = Units.PIXELS; 156 | 157 | var wUnits = parseUnits(wInp.text, 'px'); 158 | var blockWidth = convertUnits( strToNum(wInp.text, CFG.width), wUnits, 'px' ); 159 | 160 | var spUnits = parseUnits(spInp.text, 'px'); 161 | var blockSpacing = convertUnits( strToNum(spInp.text, CFG.spacing), spUnits, 'px' ); 162 | 163 | // Add a group to final output 164 | var textGroup = doc.layerSets.add(); 165 | textGroup.name = 'Text Block'; 166 | 167 | var nextTop = 0; 168 | var posLeft = texts[0].bounds[0]; 169 | var posRight = texts[0].bounds[2]; 170 | var posTop = texts[0].bounds[1]; 171 | 172 | // Create text block 173 | for (var i = 0; i < texts.length; i++) { 174 | var currText = texts[i]; 175 | var dupText = currText.duplicate(textGroup, ElementPlacement.PLACEATEND); 176 | dupText.name = currText.name; 177 | 178 | var bounds = dupText.bounds; 179 | var currWidth = bounds[2].value - bounds[0].value; 180 | var ratio = (blockWidth / currWidth) * 100; 181 | 182 | dupText.resize(ratio, ratio, AnchorPosition.TOPLEFT); 183 | 184 | bounds = dupText.bounds; 185 | var deltaX = bounds[0].value; 186 | var deltaY = bounds[1].value; 187 | 188 | dupText.translate(-deltaX, -deltaY + nextTop + blockSpacing); 189 | nextTop += bounds[3].value - bounds[1].value + blockSpacing; 190 | } 191 | 192 | // Align text block 193 | if (isToCenter.value) { 194 | alignToCenter(doc, textGroup); 195 | } else if (!isKeep.value) { 196 | textGroup.translate(posLeft, posTop); 197 | } else { 198 | textGroup.translate(posRight, posTop); 199 | } 200 | 201 | // Original layers 202 | for(var j = texts.length - 1; j >= 0; j--){ 203 | if (isHide.value) { 204 | texts[j].visible = false; 205 | } else if (isRemove.value) { 206 | texts[j].remove(); 207 | } 208 | } 209 | 210 | app.preferences.rulerUnits = CFG.units; 211 | 212 | win.close(); 213 | } 214 | 215 | /** 216 | * Handle keyboard input to shift numerical values 217 | * @param {Object} input - The input element to which the event listener will be attached 218 | * @param {number} min - The minimum allowed value for the numerical input 219 | * @param {number} max - The maximum allowed value for the numerical input 220 | * @returns {void} 221 | */ 222 | function bindStepperKeys(input, min, max) { 223 | input.addEventListener('keydown', function (kd) { 224 | var step = ScriptUI.environment.keyboardState['shiftKey'] ? 10 : 1; 225 | var units = parseUnits(this.text, 'pt'); 226 | var num = parseFloat(this.text); 227 | if (kd.keyName == 'Down' || kd.keyName == 'LeftBracket') { 228 | this.text = (min && (num - step) < min) ? min : num - step; 229 | this.text += ' ' + units; 230 | kd.preventDefault(); 231 | } 232 | if (kd.keyName == 'Up' || kd.keyName == 'RightBracket') { 233 | this.text = (max && (num + step) > max) ? max : num + step; 234 | this.text += ' ' + units; 235 | kd.preventDefault(); 236 | } 237 | }); 238 | } 239 | 240 | /** 241 | * Save UI options in Photoshop preferences 242 | */ 243 | function saveSettings() { 244 | var desc = new ActionDescriptor(); 245 | desc.putString(0, wInp.text); 246 | desc.putString(1, spInp.text); 247 | desc.putBoolean(2, isToCenter.value); 248 | desc.putBoolean(3, isKeep.value); 249 | desc.putBoolean(4, isHide.value); 250 | desc.putBoolean(5, isRemove.value); 251 | app.putCustomOptions(CFG.settings, desc, true); 252 | } 253 | 254 | /** 255 | * Load options from Photoshop preferences 256 | */ 257 | function loadSettings() { 258 | try { 259 | var desc = app.getCustomOptions(CFG.settings); 260 | } catch (err) {} 261 | if (typeof desc != 'undefined') { 262 | try { 263 | wInp.text = desc.getString(0); 264 | spInp.text = desc.getString(1); 265 | isToCenter.value = desc.getBoolean(2); 266 | isKeep.value = desc.getBoolean(3); 267 | isHide.value = desc.getBoolean(4); 268 | isRemove.value = desc.getBoolean(5); 269 | return; 270 | } catch (err) {} 271 | } 272 | } 273 | 274 | win.center(); 275 | win.show(); 276 | } 277 | 278 | /** 279 | * Checks if the script is running in the correct environment 280 | * 281 | * @returns {boolean} - Returns `true` if the environment is correct 282 | */ 283 | function isCorrectEnv() { 284 | var args = ['app', 'document']; 285 | 286 | for (var i = 0; i < args.length; i++) { 287 | switch (args[i].toString().toLowerCase()) { 288 | case 'app': 289 | if (!/photoshop/i.test(app.name)) { 290 | alert('Wrong application\nRun script from Adobe Photoshop', 'Script error'); 291 | return false; 292 | } 293 | break; 294 | case 'document': 295 | if (!documents.length) { 296 | alert('No documents\nOpen a document and try again', 'Script error'); 297 | return false; 298 | } 299 | break; 300 | } 301 | } 302 | 303 | return true; 304 | } 305 | 306 | /** 307 | * Get the indexes of all selected layers in the active Photoshop document 308 | * https://community.adobe.com/t5/photoshop-ecosystem-discussions/how-to-find-selected-layers-and-run-events/td-p/10269273 by Geppetto Luis 309 | * 310 | * @returns {Array} - An array containing the indexes of the selected layers 311 | */ 312 | function getSelectedLayersIdx() { 313 | var results = new Array; 314 | var ref = new ActionReference(); 315 | ref.putEnumerated(cTID('Dcmn'), cTID('Ordn'), cTID('Trgt')); 316 | var desc = executeActionGet(ref); 317 | 318 | if (desc.hasKey(sTID('targetLayers'))) { 319 | desc = desc.getList(sTID('targetLayers')); 320 | var counter = desc.count; 321 | var results = new Array(); 322 | for (var i = 0; i < counter; i++) { 323 | try { 324 | activeDocument.backgroundLayer; 325 | results.push(desc.getReference(i).getIndex()); 326 | } catch (err) { 327 | results.push(desc.getReference(i).getIndex() + 1); 328 | } 329 | } 330 | } else { 331 | var ref = new ActionReference(); 332 | ref.putProperty(cTID('Prpr'), cTID('ItmI')); 333 | ref.putEnumerated(cTID('Lyr '), cTID('Ordn'), cTID('Trgt')); 334 | try { 335 | activeDocument.backgroundLayer; 336 | results.push(executeActionGet(ref).getInteger(cTID('ItmI')) - 1); 337 | } catch (err) { 338 | results.push(executeActionGet(ref).getInteger(cTID('ItmI'))); 339 | } 340 | } 341 | 342 | return results; 343 | } 344 | 345 | /** 346 | * Convert a string identifier to a character identifier 347 | * 348 | * @param {string} s - The string identifier 349 | * @returns {number} - The corresponding character identifier 350 | */ 351 | function cTID(s) { 352 | return app.charIDToTypeID(s); 353 | } 354 | 355 | /** 356 | * Convert a string identifier to a type identifier 357 | * 358 | * @param {string} s - The string identifier 359 | * @returns {number} - The corresponding type identifier 360 | */ 361 | function sTID(s) { 362 | return app.stringIDToTypeID(s); 363 | } 364 | 365 | /** 366 | * Get all text layers from the given layers array that match the provided indexes 367 | * 368 | * @param {[Object|Array]} layers - The layers array to search 369 | * @param {number[]} idx - The array of indexes to match 370 | * @returns {Array} - An array containing the text layers matching the provided indexes 371 | */ 372 | function getTextLayers(layers, idx) { 373 | var results = []; 374 | 375 | for (var i = 0; i < layers.length; i++) { 376 | var lyr = layers[i]; 377 | if (lyr.kind === LayerKind.TEXT) { 378 | for (var j = 0; j < idx.length; j++) { 379 | try { 380 | activeDocument.backgroundLayer; 381 | if (lyr.itemIndex - 1 === idx[j]) { 382 | results.push(lyr); 383 | break; 384 | } 385 | } catch (err) { 386 | if (lyr.itemIndex === idx[j]) { 387 | results.push(lyr); 388 | break; 389 | } 390 | } 391 | } 392 | } else if (lyr.typename === 'LayerSet') { 393 | results = [].concat(results, getTextLayers(lyr.layers, idx)); 394 | } 395 | } 396 | 397 | return results; 398 | } 399 | 400 | /** 401 | * Sort items based on their position 402 | * 403 | * @param {(Object|Array)} coll - Collection to be sorted 404 | * @param {number} tolerance - The tolerance within which objects are considered to have the same top position 405 | */ 406 | function sortByPosition(coll, tolerance) { 407 | if (arguments.length == 1 || tolerance == undefined) { 408 | tolerance = 10; 409 | } 410 | 411 | coll.sort(function(a, b) { 412 | var boundsA = a.bounds; 413 | var boundsB = b.bounds; 414 | 415 | var yA = boundsA[1].value; 416 | var yB = boundsB[1].value; 417 | 418 | if (Math.abs(yA - yB) > tolerance) { 419 | return yA - yB; 420 | } 421 | 422 | var xA = boundsA[0].value; 423 | var xB = boundsB[0].value; 424 | 425 | if (Math.abs(xA - xB) > tolerance) { 426 | return xA - xB; 427 | } 428 | 429 | return 0; 430 | }); 431 | } 432 | 433 | /** 434 | * Parse units from a mixed string 435 | * 436 | * @param {string} str - The input string containing the numeric value and units (e.g., '10px'). 437 | * @param {string} def - The default units to be returned if no units are found in the input string 438 | * @returns {string} - The parsed units or the default units if not found 439 | */ 440 | function parseUnits(str, def) { 441 | var match = str.match(/(\d+(\.\d+)?)\s*([a-zA-Z]+)\s*[^a-zA-Z]*$/); 442 | 443 | if (match) { 444 | var units = match[3].toLowerCase(); 445 | var validUnits = ['px', 'pt', 'in', 'mm', 'cm', 'm', 'ft', 'yd']; 446 | 447 | for (var i = 0; i < validUnits.length; i++) { 448 | if (units === validUnits[i]) return units; 449 | } 450 | } 451 | 452 | return def; 453 | } 454 | 455 | /** 456 | * Convert a value from one set of units to another 457 | * 458 | * @param {string} value - The numeric value to be converted 459 | * @param {string} currUnits - The current units of the value (e.g., 'in', 'mm', 'pt') 460 | * @param {string} newUnits - The desired units for the converted value (e.g., 'in', 'mm', 'pt') 461 | * @returns {number} - The converted value in the specified units 462 | */ 463 | function convertUnits(value, currUnits, newUnits) { 464 | return UnitValue(value, currUnits).as(newUnits); 465 | } 466 | 467 | /** 468 | * Convert string to absolute number 469 | * 470 | * @param {string} str - The string to convert to a number 471 | * @param {number} def - The default value to return if the conversion fails 472 | * @returns {number} - The converted number 473 | */ 474 | function strToNum(str, def) { 475 | if (arguments.length == 1 || def == undefined) def = 1; 476 | str = str.replace(/,/g, '.').replace(/[^\d.]/g, ''); 477 | str = str.split('.'); 478 | str = str[0] ? str[0] + '.' + str.slice(1).join('') : ''; 479 | if (isNaN(str) || !str.length) return parseFloat(def); 480 | else return parseFloat(str); 481 | } 482 | 483 | /** 484 | * Center an item within the document canvas 485 | * 486 | * @param {Object} doc - The Photoshop document 487 | * @param {Object} item - The layer or group to be centered 488 | * @returns {void} 489 | */ 490 | function alignToCenter(doc, item) { 491 | var bnds = item.bounds; 492 | var width = bnds[2] - bnds[0]; 493 | var height = bnds[3] - bnds[1]; 494 | 495 | var docCenterX = doc.width / 2; 496 | var docCenterY = doc.height / 2; 497 | 498 | var currCenterX = bnds[0] + width / 2; 499 | var currCenterY = bnds[1] + height / 2; 500 | 501 | var offsetX = docCenterX - currCenterX; 502 | var offsetY = docCenterY - currCenterY; 503 | 504 | item.translate(offsetX, offsetY); 505 | } 506 | 507 | /** 508 | * Open a URL in the default web browser 509 | * 510 | * @param {string} url - The URL to open in the web browser 511 | * @returns {void} 512 | */ 513 | function openURL(url) { 514 | var html = new File(Folder.temp.absoluteURI + '/aisLink.html'); 515 | html.open('w'); 516 | var htmlBody = '

'; 517 | html.write(htmlBody); 518 | html.close(); 519 | html.execute(); 520 | } 521 | 522 | try { 523 | main(); 524 | } catch(err) {} -------------------------------------------------------------------------------- /jsx/ToggleLayersLocksByName.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | ToggleLayersLocksByName.jsx for Adobe Photoshop 3 | Description: Locks layers in the document based on the keyword in the name 4 | Date: September, 2021 5 | Author: Sergey Osokin, email: hi@sergosokin.ru 6 | 7 | Installation: https://github.com/creold/photoshop-scripts#how-to-run-scripts 8 | 9 | Donate (optional): 10 | If you find this script helpful, you can buy me a coffee 11 | - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts 12 | - via Donatty https://donatty.com/sergosokin 13 | - via DonatePay https://new.donatepay.ru/en/@osokin 14 | - via YooMoney https://yoomoney.ru/to/410011149615582 15 | 16 | NOTICE: 17 | Tested with Adobe Photoshop CC 2019. 18 | This script is provided "as is" without warranty of any kind. 19 | Free to use, not for sale 20 | 21 | Released under the MIT license 22 | http://opensource.org/licenses/mit-license.php 23 | 24 | Check my other scripts: https://github.com/creold 25 | */ 26 | 27 | //@target photoshop 28 | 29 | function main() { 30 | if (!isCorrectEnv()) return; 31 | 32 | var doc = app.activeDocument, 33 | key = '[lock]', 34 | allLayers = []; 35 | 36 | allLayers = collectAllLayers(doc, allLayers); 37 | 38 | for (var i = 0, len = allLayers.length; i < len; i++) { 39 | var lyr = allLayers[i]; 40 | if (!lyr.isBackgroundLayer && lyr.name.indexOf(key) !== -1) { 41 | lyr.allLocked = !lyr.allLocked; 42 | } 43 | } 44 | } 45 | 46 | // Check the script environment 47 | function isCorrectEnv() { 48 | var args = ['app', 'document']; 49 | 50 | for (var i = 0; i < args.length; i++) { 51 | switch (args[i].toString().toLowerCase()) { 52 | case 'app': 53 | if (!/photoshop/i.test(app.name)) { 54 | alert('Error\nRun script from Adobe Photoshop'); 55 | return false; 56 | } 57 | break; 58 | case 'document': 59 | if (!documents.length) { 60 | alert('Error\nOpen a document and try again'); 61 | return false; 62 | } 63 | break; 64 | } 65 | } 66 | 67 | return true; 68 | } 69 | 70 | function collectAllLayers(doc, allLayers) { 71 | for (var i = 0; i < doc.layers.length; i++) { 72 | var lyr = doc.layers[i]; 73 | if (lyr.typename === 'ArtLayer') { 74 | allLayers.push(lyr); 75 | } else if (lyr.typename === 'LayerSet') { 76 | allLayers.push(lyr); 77 | collectAllLayers(lyr, allLayers); 78 | } else { 79 | collectAllLayers(lyr, allLayers); 80 | } 81 | } 82 | 83 | return allLayers; 84 | } 85 | 86 | try { 87 | main(); 88 | } catch (e) {} --------------------------------------------------------------------------------