diff --git a/examples/apply_crystallize_filter_action.py b/examples/apply_crystallize_filter_action.py index a22bf274..5303a6e0 100644 --- a/examples/apply_crystallize_filter_action.py +++ b/examples/apply_crystallize_filter_action.py @@ -7,6 +7,8 @@ References: https://github.com/lohriialo/photoshop-scripting-python/blob/master/ApplyCrystallizeFilterAction.py +Now the submodule action_manager provides a more friendly way to deal with action manager. +Read document for details. """ # Import third-party modules @@ -14,6 +16,7 @@ # Import local modules from photoshop import Session +import photoshop.api.action_manager as am PSD_FILE = psd.get_psd_files() @@ -28,13 +31,9 @@ active_document.activeLayer = active_document.layerSets.item(len(nLayerSets)).artLayers.item(len(nArtLayers)) def applyCrystallize(cellSize): - cellSizeID = ps.app.CharIDToTypeID("ClSz") - eventCrystallizeID = ps.app.CharIDToTypeID("Crst") - - filterDescriptor = ps.ActionDescriptor - filterDescriptor.putInteger(cellSizeID, cellSize) - - ps.app.executeAction(eventCrystallizeID, filterDescriptor) + filter_dict = {"_classID": None, "ClSz": cellSize} + filter_desc = ps.ActionDescriptor.load(filter_dict) + ps.app.executeAction(am.str2id("Crst"), filter_desc) applyCrystallize(25) print("Apply crystallize done.") diff --git a/examples/convert_smartobject_to_layer.py b/examples/convert_smartobject_to_layer.py index a0022d9a..7d81f6ab 100644 --- a/examples/convert_smartobject_to_layer.py +++ b/examples/convert_smartobject_to_layer.py @@ -1,19 +1,25 @@ """Convert Smart object to artLayer.""" +# Import built-in modules +from textwrap import dedent + # Import local modules from photoshop import Session +import photoshop.api.action_manager as am # example 1 with Session() as ps: - js = """ -var idplacedLayerConvertToLayers = stringIDToTypeID( "placedLayerConvertToLayers" ); -executeAction( idplacedLayerConvertToLayers, undefined, DialogModes.NO ); -""" + js = dedent( + """ + var idplacedLayerConvertToLayers = stringIDToTypeID( "placedLayerConvertToLayers" ); + executeAction( idplacedLayerConvertToLayers, undefined, DialogModes.NO ); + """ + ) ps.app.doJavaScript(js) # example 2 with Session() as ps: - descriptor = ps.ActionDescriptor - idplacedLayerConvertToLayers = ps.app.stringIDToTypeID("placedLayerConvertToLayers") - ps.app.executeAction(idplacedLayerConvertToLayers, descriptor) + # The event id used in executeAction differs across Photoshop versions. + # Look up eventid list or record with ScriptListener Plug-in to decide which eventid to use. + ps.app.executeAction(am.str2id("placedLayerConvertToLayers"), None) diff --git a/examples/emboss_action.py b/examples/emboss_action.py index 76bee439..2dbc7626 100644 --- a/examples/emboss_action.py +++ b/examples/emboss_action.py @@ -1,62 +1,48 @@ # Import local modules from photoshop import Session +import photoshop.api.action_manager as am with Session() as ps: app = ps.app for index, x in enumerate(range(50)): # Execute an existing action from action palette. - idPly = app.charIDToTypeID("Ply ") - desc8 = ps.ActionDescriptor() - idnull = app.charIDToTypeID("null") - ref3 = ps.ActionReference() - idActn = app.charIDToTypeID("Actn") - ref3.putName(idActn, "Sepia Toning (layer)") - idASet = app.charIDToTypeID("ASet") - ref3.PutName(idASet, "Default Actions") - desc8.putReference(idnull, ref3) - app.executeAction(idPly, desc8, ps.DialogModes.DisplayNoDialogs) + exec_dict = { + "_classID": None, + "null": [ + "!ref", + am.ReferenceKey(desiredclass="action", value="Sepia Toning (layer)"), + am.ReferenceKey(desiredclass="actionSet", value="Default Actions"), + ], + } + exec_desc = ps.ActionDescriptor.load(exec_dict) + app.executeAction(am.str2id("Ply "), exec_desc, ps.DialogModes.DisplayNoDialogs) # Create solid color fill layer. - idMk = app.charIDToTypeID("Mk ") - desc21 = ps.ActionDescriptor() - idNull = app.charIDToTypeID("null") - ref12 = ps.ActionReference() - idContentLayer1 = app.stringIDToTypeID("contentLayer") - ref12.putClass(idContentLayer1) - desc21.putReference(idNull, ref12) - idUsng = app.charIDToTypeID("Usng") - desc22 = ps.ActionDescriptor() - idType = app.charIDToTypeID("Type") - desc23 = ps.ActionDescriptor() - idClr = app.charIDToTypeID("Clr ") - desc24 = ps.ActionDescriptor() - idRd = app.charIDToTypeID("Rd ") - desc24.putDouble(idRd, index) - idGrn = app.charIDToTypeID("Grn ") - desc24.putDouble(idGrn, index) - idBl = app.charIDToTypeID("Bl ") - desc24.putDouble(idBl, index) - idRGBC = app.charIDToTypeID("RGBC") - desc23.putObject(idClr, idRGBC, desc24) - idSolidColorLayer = app.StringIDToTypeID("solidColorLayer") - desc22.putObject(idType, idSolidColorLayer, desc23) - idContentLayer2 = app.StringIDToTypeID("contentLayer") - desc21.putObject(idUsng, idContentLayer2, desc22) - app.executeAction(idMk, desc21, ps.DialogModes.DisplayNoDialogs) + filledlayer_dict = { + "_classID": None, + "null": ["!ref", am.ReferenceKey(desiredclass="contentLayer", value=None)], + "using": { + "_classID": "contentLayer", + "type": { + "_classID": "solidColorLayer", + "color": {"_classID": "RGBColor", "red": index, "grain": index, "blue": index}, # noqa + }, + }, + } + filledlayer_desc = ps.ActionDescriptor.load(filledlayer_dict) + app.executeAction(am.str2id("Mk "), filledlayer_desc, ps.DialogModes.DisplayNoDialogs) # Select mask. - idSlct = app.charIDToTypeID("slct") - desc38 = ps.ActionDescriptor() - idNull1 = app.charIDToTypeID("null") - ref20 = ps.ActionReference() - idChnl1 = app.charIDToTypeID("Chnl") - idChnl2 = app.charIDToTypeID("Chnl") - idMsk = app.charIDToTypeID("Msk ") - ref20.putEnumerated(idChnl1, idChnl2, idMsk) - desc38.putReference(idNull1, ref20) - idMkVs = app.charIDToTypeID("MkVs") - desc38.putBoolean(idMkVs, False) - app.executeAction(idSlct, desc38, ps.DialogModes.DisplayNoDialogs) + selectmask_dict = { + "_classID": None, + "null": [ + "!ref", + am.ReferenceKey(desiredclass="channel", value=am.Enumerated(type="channel", value="mask")), + ], + "makeVisible": False, + } + selectmask_desc = ps.ActionDescriptor.load(selectmask_dict) + app.executeAction(am.str2id("slct"), selectmask_desc, ps.DialogModes.DisplayNoDialogs) app.activeDocument.activeLayer.invert() diff --git a/examples/enable_generator.py b/examples/enable_generator.py index 9a2750e1..a0d86a23 100644 --- a/examples/enable_generator.py +++ b/examples/enable_generator.py @@ -5,6 +5,6 @@ with Session() as ps: plugin_name = "generator-assets-dummy-menu" - generatorDesc = ps.ActionDescriptor + generatorDesc = ps.ActionDescriptor() generatorDesc.putString(ps.app.stringIDToTypeID("name"), plugin_name) ps.app.executeAction(ps.app.stringIDToTypeID("generateAssets"), generatorDesc) diff --git a/examples/import_image_as_layer.py b/examples/import_image_as_layer.py index ed027ee1..c841eac4 100644 --- a/examples/import_image_as_layer.py +++ b/examples/import_image_as_layer.py @@ -1,11 +1,31 @@ """Import a image as a artLayer.""" +# Import built-in modules +import pathlib + # Import local modules from photoshop import Session +import photoshop.api.action_manager as am + + +def importfile(app, path: pathlib.WindowsPath, position: (float, float), size: (float, float)): # noqa + px, py = position + sx, sy = size + import_dict = { + "null": path, + "freeTransformCenterState": am.Enumerated(type="quadCenterState", value="QCSAverage"), + "offset": { + "horizontal": am.UnitDouble(unit="distanceUnit", double=px), + "vertical": am.UnitDouble(unit="distanceUnit", double=py), + "_classID": "offset", + }, + "width": am.UnitDouble(unit="percentUnit", double=sx), + "height": am.UnitDouble(unit="percentUnit", double=sy), + "_classID": None, + } + import_desc = ps.ActionDescriptor.load(import_dict) + app.executeAction(am.str2id("placeEvent"), import_desc) with Session(action="new_document") as ps: - desc = ps.ActionDescriptor - desc.putPath(ps.app.charIDToTypeID("null"), "your/image/path") - event_id = ps.app.charIDToTypeID("Plc ") # `Plc` need one space in here. - ps.app.executeAction(ps.app.charIDToTypeID("Plc "), desc) + importfile(ps.app, pathlib.Path("your/image/path"), (-100, 456), (4470, 4463)) diff --git a/examples/replace_images.py b/examples/replace_images.py index 24642cdb..cec73ad0 100644 --- a/examples/replace_images.py +++ b/examples/replace_images.py @@ -1,10 +1,14 @@ """Replace the image of the current active layer with a new image.""" +# Import built-in modules +import pathlib + # Import third-party modules import examples._psd_files as psd # Import from examples. # Import local modules from photoshop import Session +import photoshop.api.action_manager as am PSD_FILE = psd.get_psd_files() @@ -14,12 +18,9 @@ active_layer = ps.active_document.activeLayer bounds = active_layer.bounds print(f"current layer {active_layer.name}: {bounds}") - input_file = PSD_FILE["red_100x200.png"] - replace_contents = ps.app.stringIDToTypeID("placedLayerReplaceContents") - desc = ps.ActionDescriptor - idnull = ps.app.charIDToTypeID("null") - desc.putPath(idnull, input_file) - ps.app.executeAction(replace_contents, desc) + input_dict = {"_classID": None, "null": pathlib.Path(PSD_FILE["red_100x200.png"])} + input_desc = ps.ActionDescriptor.load(input_dict) + ps.app.executeAction(am.str2id("placedLayerReplaceContents"), input_desc) # replaced image. active_layer = ps.active_document.activeLayer diff --git a/examples/session_smart_sharpen.py b/examples/session_smart_sharpen.py index 0e2c29ab..9e89cef8 100644 --- a/examples/session_smart_sharpen.py +++ b/examples/session_smart_sharpen.py @@ -11,6 +11,7 @@ # Import local modules from photoshop import Session +import photoshop.api.action_manager as am PSD_FILE = psd.get_psd_files() @@ -19,35 +20,15 @@ with Session(file_path, action="open") as ps: def SmartSharpen(inAmount, inRadius, inNoise): - idsmart_sharpen_id = ps.app.stringIDToTypeID(ps.EventID.SmartSharpen) - desc37 = ps.ActionDescriptor() - - idpresetKind = ps.app.stringIDToTypeID(ps.EventID.PresetKind) - idpresetKindType = ps.app.stringIDToTypeID(ps.EventID.PresetKindType) - idpresetKindCustom = ps.app.stringIDToTypeID(ps.EventID.PresetKindCustom) - desc37.putEnumerated(idpresetKind, idpresetKindType, idpresetKindCustom) - idAmnt = ps.app.charIDToTypeID("Amnt") - idPrc = ps.app.charIDToTypeID("Rds ") - desc37.putUnitDouble(idAmnt, idPrc, inAmount) - - idRds = ps.app.charIDToTypeID("Rds ") - idPxl = ps.app.charIDToTypeID("#Pxl") - desc37.putUnitDouble(idRds, idPxl, inRadius) - - idnoiseReduction = ps.app.stringIDToTypeID("noiseReduction") - idPrc = ps.app.charIDToTypeID("#Prc") - desc37.putUnitDouble(idnoiseReduction, idPrc, inNoise) - - idblur = ps.app.charIDToTypeID("blur") - idblurType = ps.app.stringIDToTypeID("blurType") - idGsnB = ps.app.charIDToTypeID("GsnB") - desc37.putEnumerated(idblur, idblurType, idGsnB) - - ps.app.ExecuteAction(idsmart_sharpen_id, desc37) - - docRef = ps.active_document - nlayerSets = docRef.layerSets - nArtLayers = docRef.layerSets.item(nlayerSets.length) - docRef.activeLayer = nArtLayers.artLayers.item(nArtLayers.artLayers.length) + ss_dict = { + "_classID": None, + "presetKindType": am.Enumerated(type="presetKindType", value="presetKindCustom"), # noqa + "amount": am.UnitDouble(unit="radius", double=inAmount), + "radius": am.UnitDouble(unit="pixelsUnit", double=inRadius), + "noiseReduction": am.UnitDouble(unit="percentUnit", double=inNoise), + "blur": am.Enumerated(type="blurType", value="gaussianBlur"), + } + ss_desc = ps.ActionDescriptor.load(ss_dict) + ps.app.ExecuteAction(am.str2id("smartSharpen"), ss_desc) SmartSharpen(300, 2.0, 20) diff --git a/examples/smart_sharpen.py b/examples/smart_sharpen.py index 9fc3e5b3..d92a158a 100644 --- a/examples/smart_sharpen.py +++ b/examples/smart_sharpen.py @@ -11,6 +11,7 @@ # Import local modules import photoshop.api as ps +import photoshop.api.action_manager as am app = ps.Application() @@ -25,32 +26,16 @@ def SmartSharpen(inAmount, inRadius, inNoise): - idsmart_sharpen_id = app.stringIDToTypeID(ps.EventID.SmartSharpen) - desc37 = ps.ActionDescriptor() - - idpresetKind = app.stringIDToTypeID(ps.EventID.PresetKind) - idpresetKindType = app.stringIDToTypeID(ps.EventID.PresetKindType) - idpresetKindCustom = app.stringIDToTypeID(ps.EventID.PresetKindCustom) - desc37.putEnumerated(idpresetKind, idpresetKindType, idpresetKindCustom) - - idAmnt = app.charIDToTypeID("Amnt") - idPrc = app.charIDToTypeID("Rds ") - desc37.putUnitDouble(idAmnt, idPrc, inAmount) - - idRds = app.charIDToTypeID("Rds ") - idPxl = app.charIDToTypeID("#Pxl") - desc37.putUnitDouble(idRds, idPxl, inRadius) - - idnoiseReduction = app.stringIDToTypeID("noiseReduction") - idPrc = app.charIDToTypeID("#Prc") - desc37.putUnitDouble(idnoiseReduction, idPrc, inNoise) - - idblur = app.charIDToTypeID("blur") - idblurType = app.stringIDToTypeID("blurType") - idGsnB = app.charIDToTypeID("GsnB") - desc37.putEnumerated(idblur, idblurType, idGsnB) - - app.ExecuteAction(idsmart_sharpen_id, desc37) + ss_dict = { + "_classID": None, + "presetKindType": am.Enumerated(type="presetKindType", value="presetKindCustom"), # noqa + "amount": am.UnitDouble(unit="radius", double=inAmount), + "radius": am.UnitDouble(unit="pixelsUnit", double=inRadius), + "noiseReduction": am.UnitDouble(unit="percentUnit", double=inNoise), + "blur": am.Enumerated(type="blurType", value="gaussianBlur"), + } + ss_desc = ps.ActionDescriptor.load(ss_dict) + app.ExecuteAction(am.str2id("smartSharpen"), ss_desc) SmartSharpen(300, 2.0, 20) diff --git a/photoshop/api/__init__.py b/photoshop/api/__init__.py index 4bfddeb3..040b71f5 100644 --- a/photoshop/api/__init__.py +++ b/photoshop/api/__init__.py @@ -1,9 +1,9 @@ """Python API for Photoshop.""" # Import local modules from photoshop.api import constants -from photoshop.api.action_descriptor import ActionDescriptor -from photoshop.api.action_list import ActionList -from photoshop.api.action_reference import ActionReference +from photoshop.api._actionmanager_type_binder import ActionDescriptor +from photoshop.api._actionmanager_type_binder import ActionList +from photoshop.api._actionmanager_type_binder import ActionReference from photoshop.api.application import Application from photoshop.api.batch_options import BatchOptions from photoshop.api.colors import CMYKColor diff --git a/photoshop/api/_actionmanager_type_binder.py b/photoshop/api/_actionmanager_type_binder.py new file mode 100644 index 00000000..120cfc27 --- /dev/null +++ b/photoshop/api/_actionmanager_type_binder.py @@ -0,0 +1,89 @@ +"""This file enables type communication without circular import. + +We just put type-irrelevant codes in one file then inherit it in this file and add type-relevant codes. + +""" + + +# Import local modules +from photoshop.api.action_descriptor import ActionDescriptor as AD_proto +from photoshop.api.action_list import ActionList as AL_proto +from photoshop.api.action_manager._main_types.action_descriptor import ( + ActionDescriptor as AD_utils_proto, +) +from photoshop.api.action_manager._main_types.action_list import ( + ActionList as AL_utils_proto, +) +from photoshop.api.action_manager._main_types.action_reference import ( + ActionReference as AR_utils_proto, +) +from photoshop.api.action_reference import ActionReference as AR_proto +from photoshop.api.enumerations import DescValueType +from photoshop.api.enumerations import ReferenceFormType + + +class ActionDescriptor(AD_proto, AD_utils_proto): + @classmethod + def load(cls, adict: dict) -> "ActionDescriptor": + """Convert a python object to an ActionDescriptor""" + return super().load(adict, globals()) + + def __init__(self, parent=None, classID=None): + self.classID = classID + super().__init__(parent=parent) + + def getType(self, key: int) -> DescValueType: + """Gets the type of a key.""" + return DescValueType(self.app.getType(key)) + + def getObjectValue(self, key: int) -> "ActionDescriptor": + """Gets the value of a key of type object.""" + return ActionDescriptor(parent=self.app.getObjectValue(key)) + + def getList(self, key: int) -> "ActionList": + """Gets the value of a key of type list.""" + return ActionList(parent=self.app.getList(key)) + + def getReference(self, key: int) -> "ActionReference": + """Gets the value of a key of type ActionReference.""" + return ActionReference(parent=self.app.getReference(key)) + + +class ActionList(AL_proto, AL_utils_proto): + @classmethod + def load(cls, alist: list) -> "ActionList": + """Convert a python object to an ActionList""" + return super().load(alist, globals()) + + def getType(self, index: int) -> DescValueType: + """Gets the type of a list element.""" + return DescValueType(self.app.getType(index)) + + def getObjectValue(self, index: int) -> "ActionDescriptor": + """Gets the value of a list element of type object.""" + return ActionDescriptor(parent=self.app.getObjectValue(index)) + + def getList(self, index: int) -> "ActionList": + """Gets the value of a list element of type list.""" + return ActionList(parent=self.app.getList(index)) + + def getReference(self, index: int) -> "ActionReference": + """Gets the value of a list element of type ActionReference.""" + return ActionReference(parent=self.app.getReference(index)) + + +class ActionReference(AR_proto, AR_utils_proto): + @classmethod + def load(cls, adict: dict) -> "ActionReference": + """Convert a python object to an ActionReference""" + return super().load(adict) + + def getForm(self) -> ReferenceFormType: + """Gets the form of this action reference.""" + return ReferenceFormType(self.app.getForm()) + + def getContainer(self) -> "ActionReference": + """Gets a reference contained in this reference. + Container references provide additional pieces to the reference. + This looks like another reference, but it is actually part of the same reference.""" + return ActionReference(parent=self.app.getContainer()) diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index e651b38e..fe1862bb 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -1,4 +1,4 @@ -"""A record of key-text_font pairs for actions. +"""A record of key-data pairs for actions. such as those included on the Adobe Photoshop Actions menu. The ActionDescriptor class is part of the Action @@ -8,27 +8,32 @@ """ # Import built-in modules -from pathlib import Path +from abc import ABC +from abc import abstractmethod +import pathlib +import warnings + +# Import third-party modules +import comtypes # Import local modules from photoshop.api._core import Photoshop -from photoshop.api.action_list import ActionList -from photoshop.api.action_reference import ActionReference -from photoshop.api.enumerations import DescValueType -class ActionDescriptor(Photoshop): - """A record of key-value pairs for actions, such as those included on the Adobe Photoshop Actions menu. +DATAFUNC_WARN = """fromSteam, toStream, getData, putData is seldom performed successfully (even in native js, for +example https://github.com/drsong2000/CRYENGINE/blob/release/Tools/photoshop/UltimateTextureSaver/Install/xtools/xlib/ +xml/atn2bin.jsx#L753-L769), and is deprecated. In fact, searching in github shows that these 4 functions are barely +used in regular photoshop scripting. If you have found the criteria of these functions, please open an issue.""" - The ActionDescriptor class is part of the Action Manager functionality. - For more details on the Action Manager, see the Photoshop Scripting Guide. - """ +class ActionDescriptor(Photoshop, ABC): + """This object provides a dictionary-style mechanism for storing data as key-value pairs. It can be used for + low-level access into Photoshop.""" object_name = "ActionDescriptor" - def __init__(self): - super().__init__() + def __init__(self, parent: comtypes.client.lazybind.Dispatch = None): + super().__init__(parent=parent) @property def count(self): @@ -39,119 +44,117 @@ def clear(self): """Clears the descriptor.""" self.app.clear() + def isEqual(self, otherDesc): + """Determines whether the descriptor is the same as another descriptor.""" + assert otherDesc.typename == "ActionDescriptor" + return self.app.isEqual(otherDesc) + + def __eq__(self, other): + try: + return self.isEqual(other) + except AssertionError: + return False + def erase(self, key: int): - """Erases a key form the descriptor.""" + """Erases a key from the descriptor.""" self.app.erase(key) def fromStream(self, value: str): - """Create a descriptor from a stream of bytes. - - for reading from disk. - - """ - self.app.fromStream(value) + """Creates a descriptor from a stream of bytes; for reading from disk.""" + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + self.app.fromStream(bytes.decode(value)) + except BaseException: + pass - def getBoolean(self, key: int) -> int: - """Gets the text_font of a key of type boolean. - - Args: - key (str): key of type boolean. + def toStream(self) -> str: + """Gets the entire descriptor as a stream of bytes, for writing to disk.""" + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + return self.app.toStream() + except BaseException: + return None - Returns: - bool: The text_font of a key of type boolean. + def getKey(self, index: int) -> int: + """Gets the ID of the Nth key, provided by index.""" + return self.app.getKey(index) - """ + def getBoolean(self, key: int) -> bool: + """Gets the value of a key of type boolean.""" return self.app.getBoolean(key) - def getClass(self, key): - """Gets the text_font of a key of type class. - - Args: - key (str): The key of type class. - - Returns: - int: The text_font of a key of type class. - - """ + def getClass(self, key: int) -> int: + """Gets the value of a key of type class.""" return self.app.getClass(key) - def getData(self, key: int) -> int: + def getData(self, key: int) -> str: """Gets raw byte data as a string value.""" - return self.app.getData(key) + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + return self.app.getData(key) + except BaseException: + return None def getDouble(self, key: int) -> float: """Gets the value of a key of type double.""" return self.app.getDouble(key) - def getEnumerationType(self, index: int) -> int: + def getEnumerationType(self, key: int) -> int: """Gets the enumeration type of a key.""" - return self.app.getEnumerationType(index) + return self.app.getEnumerationType(key) - def getEnumerationValue(self, index: int) -> int: + def getEnumerationValue(self, key: int) -> int: """Gets the enumeration value of a key.""" - return self.app.getEnumerationValue(index) + return self.app.getEnumerationValue(key) - def getInteger(self, index: int) -> int: + def getInteger(self, key: int) -> int: """Gets the value of a key of type integer.""" - return self.app.getInteger(index) - - def getKey(self, index: int) -> int: - """Gets the ID of the key provided by index.""" - return self.app.getKey(index) + return self.app.getInteger(key) - def getLargeInteger(self, index: int) -> int: + def getLargeInteger(self, key: int) -> int: """Gets the value of a key of type large integer.""" - return self.app.getLargeInteger(index) + return self.app.getLargeInteger(key) - def getList(self, index: int) -> ActionList: - """Gets the value of a key of type list.""" - return ActionList(self.app.getList(index)) + @abstractmethod + def getList(self, key): + """Implemented in _actionmanager_type_binder.ActionDescriptor""" + pass def getObjectType(self, key: int) -> int: """Gets the class ID of an object in a key of type object.""" return self.app.getObjectType(key) - def getObjectValue(self, key: int) -> int: - """Get the class ID of an object in a key of type object.""" - return self.app.getObjectValue(key) + @abstractmethod + def getObjectValue(self, key): + """Implemented in _actionmanager_type_binder.ActionDescriptor""" + pass - def getPath(self, key: int) -> Path: - """Gets the value of a key of type.""" - return Path(self.app.getPath(key)) + def getPath(self, key: int) -> pathlib.WindowsPath: + """Gets the value of a key of type File.""" + return pathlib.Path(self.app.getPath(key)) - def getReference(self, key: int) -> ActionReference: - """Gets the value of a key of type.""" - return ActionReference(self.app.getReference(key)) + @abstractmethod + def getReference(self, key): + """Implemented in _actionmanager_type_binder.ActionDescriptor""" + pass def getString(self, key: int) -> str: - """Gets the value of a key of type.""" + """Gets the value of a key of type string.""" return self.app.getString(key) - def getType(self, key: int) -> DescValueType: - """Gets the type of a key.""" - return DescValueType(self.app.getType(key)) + @abstractmethod + def getType(self, key): + """Implemented in _actionmanager_type_binder.ActionDescriptor""" + pass def getUnitDoubleType(self, key: int) -> int: """Gets the unit type of a key of type UnitDouble.""" return self.app.getUnitDoubleType(key) - def getUnitDoubleValue(self, key: int) -> float: + def getUnitDoubleValue(self, key: int) -> int: """Gets the unit type of a key of type UnitDouble.""" return self.app.getUnitDoubleValue(key) - def hasKey(self, key: int) -> bool: - """Checks whether the descriptor contains the provided key.""" - return self.app.hasKey(key) - - def isEqual(self, otherDesc) -> bool: - """Determines whether the descriptor is the same as another descriptor. - - Args: - otherDesc (.action_descriptor.ActionDescriptor): - - """ - return self.app.isEqual(otherDesc) - def putBoolean(self, key: int, value: bool): """Sets the value for a key whose type is boolean.""" self.app.putBoolean(key, value) @@ -162,45 +165,55 @@ def putClass(self, key: int, value: int): def putData(self, key: int, value: str): """Puts raw byte data as a string value.""" - self.app.putData(key, value) + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + self.app.putData(key, value) + except BaseException: + pass def putDouble(self, key: int, value: float): """Sets the value for a key whose type is double.""" self.app.putDouble(key, value) - def putEnumerated(self, key: int, enum_type: int, value: int): + def putEnumerated(self, key: int, enumType: int, value: int): """Sets the enumeration type and value for a key.""" - self.app.putEnumerated(key, enum_type, value) + self.app.putEnumerated(key, enumType, value) def putInteger(self, key: int, value: int): """Sets the value for a key whose type is integer.""" - self.app.putInteger(key, value) + if value.bit_length() <= 32: + self.app.putInteger(key, value) + else: + self.app.putLargeInteger(key, value) def putLargeInteger(self, key: int, value: int): """Sets the value for a key whose type is large integer.""" self.app.putLargeInteger(key, value) - def putList(self, key: int, value: ActionList): + def putList(self, key: int, value): """Sets the value for a key whose type is an ActionList object.""" + assert value.typename == "ActionList" self.app.putList(key, value) - def putObject(self, key: int, class_id: int, value): - """Sets the value for a key whose type is an object.""" - self.app.putObject(key, class_id, value) + def putObject(self, key: int, classID: int, value): + """Sets the value for a key whose type is an object, represented by an Action Descriptor.""" + assert value.typename == "ActionDescriptor" + self.app.putObject(key, classID, value) - def putPath(self, key: int, value: str): + def putPath(self, key: int, value: pathlib.WindowsPath): """Sets the value for a key whose type is path.""" - self.app.putPath(key, value) + self.app.putPath(key, str(value)) - def putReference(self, key: int, value: ActionReference): + def putReference(self, key: int, value): """Sets the value for a key whose type is an object reference.""" + assert value.typename == "ActionReference" self.app.putReference(key, value) def putString(self, key: int, value: str): """Sets the value for a key whose type is string.""" self.app.putString(key, value) - def putUnitDouble(self, key: int, unit_id: int, value: float): + def putUnitDouble(self, key: int, unit_id: int, value: int): """Sets the value for a key whose type is a unit value formatted as double.""" self.app.putUnitDouble(key, unit_id, value) diff --git a/photoshop/api/action_list.py b/photoshop/api/action_list.py index be34b23a..0c116ed9 100644 --- a/photoshop/api/action_list.py +++ b/photoshop/api/action_list.py @@ -4,54 +4,177 @@ """ + +# Import built-in modules +from abc import ABC +from abc import abstractmethod +import pathlib +import warnings + +# Import third-party modules +import comtypes + # Import local modules from photoshop.api._core import Photoshop -class ActionList(Photoshop): - """The list of commands that comprise an Action. +DATAFUNC_WARN = """fromSteam, toStream, getData, putData is seldom performed successfully (even in native js, for +example https://github.com/drsong2000/CRYENGINE/blob/release/Tools/photoshop/UltimateTextureSaver/Install/xtools/xlib/ +xml/atn2bin.jsx#L753-L769), and is deprecated. In fact, searching in github shows that these 4 functions are barely +used in regular photoshop scripting. If you have found the criteria of these functions, please open an issue.""" - (such as an Action created using the Actions palette in the Adobe Photoshop application). - The action list object is part of the Action Manager functionality. - For details on using the Action Manager, see the Photoshop Scripting Guide. - """ +class ActionList(Photoshop, ABC): + """This object provides an array-style mechanism for storing data. It can be used for low-level access into Photoshop. + This object is ideal when storing data of the same type. All items in the list must be of the same type.""" object_name = "ActionList" - def __init__(self, parent=None): + def __init__(self, parent: comtypes.client.lazybind.Dispatch = None): super().__init__(parent=parent) @property def count(self): return self.app.count - def getBoolean(self, index): + def clear(self): + """Clears the list.""" + self.app.clear() + + def getBoolean(self, index: int) -> bool: + """Gets the value of a list element of type boolean.""" return self.app.getBoolean(index) - def getClass(self, index): + def getClass(self, index: int) -> int: + """Gets the value of a list element of type class.""" return self.app.getClass(index) - def getData(self, index): - return self.app.getData(index) + def getData(self, index: int) -> bytes: + """Gets raw byte data as a string value.""" + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + return self.app.getData(index) + except BaseException: + return None - def getDouble(self, index): + def getDouble(self, index: int) -> float: + """Gets the value of a list element of type double.""" return self.app.getDouble(index) - def getEnumerationType(self, index): + def getEnumerationType(self, index: int) -> int: + """Gets the enumeration type of a list element.""" return self.app.getEnumerationType(index) - def getEnumerationValue(self, index): + def getEnumerationValue(self, index: int) -> int: + """Gets the enumeration value of a list element.""" return self.app.getEnumerationValue(index) - def getInteger(self, index): + def getInteger(self, index: int) -> int: + """Gets the value of a list element of type integer.""" return self.app.getInteger(index) - def getLargeInteger(self, index): + def getLargeInteger(self, index: int) -> int: + """Gets the value of a list element of type large integer.""" return self.app.getLargeInteger(index) + @abstractmethod def getList(self, index): - return self.app.getList(index) + """Implemented in _actionmanager_type_binder.ActionList""" + pass - def getObjectType(self, index): + def getObjectType(self, index: int) -> int: + """Gets the class ID of a list element of type object.""" return self.app.getObjectType(index) + + @abstractmethod + def getObjectValue(self, index): + """Implemented in _actionmanager_type_binder.ActionList""" + pass + + def getPath(self, index: int) -> pathlib.WindowsPath: + """Gets the value of a list element of type File.""" + return pathlib.Path(self.app.getPath(index)) + + @abstractmethod + def getReference(self, index): + """Implemented in _actionmanager_type_binder.ActionList""" + pass + + def getString(self, index: int) -> str: + """Gets the value of a list element of type string.""" + return self.app.getString(index) + + @abstractmethod + def getType(self, index): + """Implemented in _actionmanager_type_binder.ActionList""" + pass + + def getUnitDoubleType(self, index: int) -> int: + """Gets the unit value type of a list element of type Double.""" + return self.app.getUnitDoubleType(index) + + def getUnitDoubleValue(self, index: int) -> float: + """Gets the unit value of a list element of type double.""" + return self.app.getUnitDoubleValue(index) + + def putBoolean(self, value: bool): + """Appends a new value, true or false.""" + self.app.putBoolean(value) + + def putClass(self, value: int): + """Appends a new value, a class or data type.""" + self.app.putClass(value) + + def putData(self, value: bytes): + """Appends a new value, a string containing raw byte data.""" + warnings.warn(DATAFUNC_WARN, category=PendingDeprecationWarning) + try: + self.app.putData(value) + except BaseException: + pass + + def putDouble(self, value: float): + """Appends a new value, a double.""" + self.app.putDouble(value) + + def putEnumerated(self, enumType: int, value: int): + """Appends a new value, an enumerated (constant) value.""" + self.app.putEnumerated(enumType, value) + + def putInteger(self, value: int): + """Appends a new value, an integer.""" + if value.bit_length() <= 32: + self.app.putInteger(value) + else: + self.app.putLargeInteger(value) + + def putLargeInteger(self, value: int): + """Appends a new value, a large integer.""" + self.app.putLargeInteger(value) + + def putList(self, value): + """Appends a new value, a nested action list.""" + assert value.typename == "ActionList" + self.app.putList(value) + + def putObject(self, classID: int, value): + """Appends a new value, an object.""" + assert value.typename == "ActionDescriptor" + self.app.putObject(classID, value) + + def putPath(self, value: pathlib.WindowsPath): + """Appends a new value, a path.""" + self.app.putPath(str(value)) + + def putReference(self, value): + """Appends a new value, a reference to an object created in the script.""" + assert value.typename == "ActionReference" + self.app.putReference(value) + + def putString(self, value: str): + """Appends a new value, a string.""" + self.app.putString(value) + + def putUnitDouble(self, classID: int, value: float): + """Appends a new value, a unit/value pair""" + self.app.putUnitDouble(classID, value) diff --git a/photoshop/api/action_manager/__init__.py b/photoshop/api/action_manager/__init__.py new file mode 100644 index 00000000..b9ab3d35 --- /dev/null +++ b/photoshop/api/action_manager/__init__.py @@ -0,0 +1,28 @@ +from .desc_value_types import Enumerated +from .desc_value_types import TypeID +from .desc_value_types import UnitDouble +from .jprint import jformat +from .jprint import jprint +from .js_converter import dump as dumpjs +from .ref_form_types import Identifier +from .ref_form_types import Index +from .ref_form_types import Offset +from .ref_form_types import ReferenceKey +from .utils import id2str +from .utils import str2id + + +__all__ = [ # noqa: F405 + "str2id", + "id2str", + "Enumerated", + "TypeID", + "UnitDouble", + "Identifier", + "Index", + "Offset", + "ReferenceKey", + "dumpjs", + "jprint", + "jformat", +] diff --git a/photoshop/api/action_manager/_main_types/_type_mapper.py b/photoshop/api/action_manager/_main_types/_type_mapper.py new file mode 100644 index 00000000..36ff6346 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/_type_mapper.py @@ -0,0 +1,78 @@ +"""Maybe the core of this submodule. +Handles almost all type mappings. (Some else are in ReferenceKey.) +This module is INTERNAL. You should not import functions from it.""" + +# Import built-in modules +import pathlib + +from ..desc_value_types import Enumerated +from ..desc_value_types import TypeID +from ..desc_value_types import UnitDouble +from ..utils import id2str + + +__all__ = ["unpack", "pack", "parsetype"] + +pytype2str = { + bool: "Boolean", + int: "Integer", + float: "Double", + str: "String", + pathlib.WindowsPath: "Path", + Enumerated: "Enumerated", + UnitDouble: "UnitDouble", + TypeID: "Class", + "ActionDescriptor": "Object", + "ActionList": "List", + "ActionReference": "Reference", +} + +str2pytype = { + "Enumerated": Enumerated, + "UnitDouble": UnitDouble, + "Class": TypeID, +} + + +def unpack(val): + vtype = val.typename if hasattr(val, "typename") else type(val) + typestr = pytype2str[vtype] + try: + args = val._unpacker() + except BaseException: + args = (val,) + return (typestr, args) + + +def pack(obj, index): # "index" means id of key string or list index. + valtype = obj.getType(index) + typestr = str(valtype)[14:-4] + typestr = "Path" if typestr == "Alias" else typestr + if typestr == "Data": + # No plan to support RawType because it seldom runs successfully + # and is seldom used in regular scripting. + return None + if typestr in str2pytype: + pytype = str2pytype[typestr] + val = pytype._packer(obj, index) + elif typestr == "Object": + val = obj.getObjectValue(index) + val.classID = id2str(obj.getObjectType(index)) + else: + get_func = getattr(obj, "get" + typestr) + val = get_func(index) + return val + + +def parsetype(obj): + if type(obj) == dict: + dtype = "ActionDescriptor" + elif type(obj) == list: + first = obj[0] if obj else None + if first == "!ref": + dtype = "ActionReference" + else: + dtype = "ActionList" + else: + dtype = "others" + return dtype diff --git a/photoshop/api/action_manager/_main_types/action_descriptor.py b/photoshop/api/action_manager/_main_types/action_descriptor.py new file mode 100644 index 00000000..1a16e715 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_descriptor.py @@ -0,0 +1,74 @@ +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod +from typing import Any + +from ..utils import id2str +from ..utils import str2id +from ._type_mapper import pack +from ._type_mapper import parsetype +from ._type_mapper import unpack +from .action_descriptor_iterator import ActionDescriptor_Iterator + + +class ActionDescriptor(ABC): + """A vessel for my extra utils. + You should not use, and cannot initialize it + because it is an abstract class.""" + + @abstractclassmethod + def load(cls, adict: dict, namespace: dict): # pass globals() for namespace + clsid = adict["_classID"] if "_classID" in adict else None + new = cls(classID=clsid) + for k, v in adict.items(): + if k == "_classID": + continue + # py37 compat + try: + val = eval(r'v if (dtype := parsetype(v)) == "others" else namespace[dtype].load(v)') # noqa + except SyntaxError: + val = v if parsetype(v) == "others" else namespace[parsetype(v)].load(v) + new.uput(k, val) + return new + + def uget(self, key: str) -> Any: + """Get a value of a key in an ActionDescriptor, no matter its type.""" + keyid = str2id(key) + val = pack(self, keyid) + return val + + def uput(self, key: str, val: Any): + """Put a value of a key into an ActionDescriptor, no matter its type.""" + keyid = str2id(key) + typestr, args = unpack(val) + put_func = getattr(self, "put" + typestr) + put_func(keyid, *args) + + def __len__(self): + return self.count + + def __iter__(self) -> ActionDescriptor_Iterator: + return ActionDescriptor_Iterator(self) + + def items(self) -> ((str, Any)): + keyids = (self.getKey(n) for n in range(len(self))) + return ((id2str(keyid), pack(self, keyid)) for keyid in keyids) + + def __contains__(self, key): + keys = [key for key in self] + return key in keys + + def dump(self) -> dict: + """Convert an ActionDescriptor to a python object.""" + # This is a dict comprehension. + ddict = {"_classID": self.classID} + ext = {k: (v.dump() if hasattr(v, "dump") else v) for k, v in self.items()} + ddict.update(ext) + return ddict + + def _unpacker(self) -> tuple: + value = self + if self.classID is None: + raise RuntimeError("Do not use old methods and new methods mixedly.") + clsid = str2id(self.classID) + return (clsid, value) diff --git a/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py new file mode 100644 index 00000000..e2ee3303 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py @@ -0,0 +1,21 @@ +from ..utils import id2str + + +class ActionDescriptor_Iterator: + """An iterator. You don't need to initialize it manually.""" + + def __init__(self, psobj): + self.curobj = psobj + self.n = -1 + + def __next__(self) -> str: + self.n += 1 + try: + keyid = self.curobj.getKey(self.n) + except BaseException: + raise StopIteration + keystr = id2str(keyid) + return keystr + + def __repr__(self): + return "" % self.n diff --git a/photoshop/api/action_manager/_main_types/action_list.py b/photoshop/api/action_manager/_main_types/action_list.py new file mode 100644 index 00000000..fa56588d --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_list.py @@ -0,0 +1,68 @@ +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod +from typing import Any + +from ._type_mapper import pack +from ._type_mapper import parsetype +from ._type_mapper import unpack +from .action_list_iterator import ActionList_Iterator + + +class ActionList(ABC): + """A vessel for my extra utils. + You should not use, and cannot initialize it + because it is an abstract class.""" + + @abstractclassmethod + def load(cls, alist: list, namespace: dict): # pass globals() for namespace + new = cls() + for v in alist: + # py37 compat + try: + val = eval(r'v if (dtype := parsetype(v)) == "others" else namespace[dtype].load(v)') # noqa + except SyntaxError: + val = v if parsetype(v) == "others" else namespace[parsetype(v)].load(v) + new.uput(val) + return new + + @property + def dtype(self) -> str: + if len(self) == 0: + return None + valtype = self.getType(0) + typestr = str(valtype)[14:-4] + return typestr + + def uget(self, index: int) -> Any: + """Get an element in an ActionList, no matter its type.""" + val = pack(self, index) + return val + + def uput(self, val: Any): + """Put an element into an ActionList, no matter its type.""" + typestr, args = unpack(val) + # ActionList type checking + # py37 compat + try: + assert eval( + r"True if (dtype := self.dtype) is None else dtype == typestr" + ), "ActionList can only hold things of the same type" + except SyntaxError: + assert ( + True if self.dtype is None else self.dtype == typestr + ), "ActionList can only hold things of the same type" + put_func = getattr(self, "put" + typestr) + put_func(*args) + + def __len__(self): + return self.count + + def __iter__(self) -> ActionList_Iterator: + return ActionList_Iterator(self) + + def dump(self) -> list: + """Convert an ActionList to a python object.""" + # This is a list comprehension. + dlist = [(elem.dump() if hasattr(elem, "dump") else elem) for elem in self] + return dlist diff --git a/photoshop/api/action_manager/_main_types/action_list_iterator.py b/photoshop/api/action_manager/_main_types/action_list_iterator.py new file mode 100644 index 00000000..b269ea56 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_list_iterator.py @@ -0,0 +1,21 @@ +# Import built-in modules +from typing import Any + + +class ActionList_Iterator: + """An iterator. You don't need to initialize it manually.""" + + def __init__(self, psobj): + self.curobj = psobj + self.n = -1 + + def __next__(self) -> Any: + self.n += 1 + try: + elem = self.curobj.uget(self.n) + except BaseException: + raise StopIteration() + return elem + + def __repr__(self): + return "" % self.n diff --git a/photoshop/api/action_manager/_main_types/action_reference.py b/photoshop/api/action_manager/_main_types/action_reference.py new file mode 100644 index 00000000..d551ba1d --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_reference.py @@ -0,0 +1,62 @@ +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod + +from ..ref_form_types import ReferenceKey +from .action_reference_iterator import ActionReference_Iterator + + +class ActionReference(ABC): + """A vessel for my extra utils. + You should not use, and cannot initialize it + because it is an abstract class.""" + + @abstractclassmethod + def load(cls, alist: list): + new = cls() + # pack into a list if is a single key + alist = [alist] if type(alist) == ReferenceKey else alist + for rkey in alist: + if rkey == "!ref": + continue + new.uput(rkey) + return new + + def uget(self, index: int) -> ReferenceKey: + """Get a key in an ActionReference as ReferenceKey, no matter its type.""" + target = self + for _i in range(index + 1): + try: + target = target.getContainer() + except BaseException: + raise IndexError("index out of range") + return ReferenceKey._packer(target) + + def uput(self, rkey: ReferenceKey): + """Put a ReferenceKey into an ActionReference, no matter its type.""" + assert type(rkey) == ReferenceKey + ftype, dcls, v = rkey._unpacker() + put_func = getattr(self, "put" + ftype) + args = (dcls,) if v is None else (dcls, *v) + put_func(*args) + + def dump(self) -> list: + """Convert an ActionReference to a python object.""" + tlist = ["!ref"] + tlist.extend([elem for elem in self]) + return tlist + + def __len__(self): + rlen = 1 + target = self + while True: + try: + target = target.getContainer() + rlen += 1 + except BaseException: + rlen -= 1 + break + return rlen + + def __iter__(self) -> ActionReference_Iterator: + return ActionReference_Iterator(self) diff --git a/photoshop/api/action_manager/_main_types/action_reference_iterator.py b/photoshop/api/action_manager/_main_types/action_reference_iterator.py new file mode 100644 index 00000000..2f93ac29 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_reference_iterator.py @@ -0,0 +1,25 @@ +from ..ref_form_types import ReferenceKey + + +class ActionReference_Iterator: + """An iterator. You don't need to initialize it manually.""" + + def __init__(self, psobj): + self.curobj = psobj + self.init = True + self.n = -1 + + def __next__(self) -> ReferenceKey: + self.n += 1 + if self.init: + self.init = False + return ReferenceKey._packer(self.curobj) + self.curobj = self.curobj.getContainer() + try: + self.curobj.getContainer() + except BaseException: + raise StopIteration + return ReferenceKey._packer(self.curobj) + + def __repr__(self): + return "" % self.n diff --git a/photoshop/api/action_manager/desc_value_types/__init__.py b/photoshop/api/action_manager/desc_value_types/__init__.py new file mode 100644 index 00000000..6537c6f6 --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/__init__.py @@ -0,0 +1,6 @@ +from .enumerated import Enumerated +from .typeid import TypeID +from .unitdouble import UnitDouble + + +__all__ = ["Enumerated", "TypeID", "UnitDouble"] diff --git a/photoshop/api/action_manager/desc_value_types/enumerated.py b/photoshop/api/action_manager/desc_value_types/enumerated.py new file mode 100644 index 00000000..97607b2e --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/enumerated.py @@ -0,0 +1,23 @@ +# Import built-in modules +from collections import namedtuple + +from ..utils import id2str +from ..utils import str2id + + +Enumerated_proto = namedtuple("Enumerated_proto", ["type", "value"]) + + +class Enumerated(Enumerated_proto): + """You can initialize an Enumerated object with 2 arguments: type, value.""" + + @classmethod + def _packer(cls, obj, index): + type = id2str(obj.getEnumerationType(index)) + value = id2str(obj.getEnumerationValue(index)) + return cls(type, value) + + def _unpacker(self): + typeid = str2id(self.type) + valueid = str2id(self.value) + return (typeid, valueid) diff --git a/photoshop/api/action_manager/desc_value_types/typeid.py b/photoshop/api/action_manager/desc_value_types/typeid.py new file mode 100644 index 00000000..0fc10a19 --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/typeid.py @@ -0,0 +1,21 @@ +# Import built-in modules +from collections import namedtuple + +from ..utils import id2str +from ..utils import str2id + + +TypeID_proto = namedtuple("TypeID_proto", ["string"]) + + +class TypeID(TypeID_proto): + """You can initialize a TypeID object with 1 argument: string.""" + + @classmethod + def _packer(cls, obj, index): + typeid = id2str(obj.getClass(index)) + return cls(typeid) + + def _unpacker(self): + nid = str2id(self.typeid) + return (nid,) diff --git a/photoshop/api/action_manager/desc_value_types/unitdouble.py b/photoshop/api/action_manager/desc_value_types/unitdouble.py new file mode 100644 index 00000000..0d0ee5a9 --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/unitdouble.py @@ -0,0 +1,23 @@ +# Import built-in modules +from collections import namedtuple + +from ..utils import id2str +from ..utils import str2id + + +UnitDouble_proto = namedtuple("UnitDouble_proto", ["unit", "double"]) + + +class UnitDouble(UnitDouble_proto): + """You can initialize a UnitDouble object with 2 arguments: unit, double.""" + + @classmethod + def _packer(cls, obj, index): + unit = id2str(obj.getUnitDoubleType(index)) + double = obj.getUnitDoubleValue(index) + return cls(unit, double) + + def _unpacker(self): + unitid = str2id(self.unit) + double = self.double + return (unitid, double) diff --git a/photoshop/api/action_manager/jprint.py b/photoshop/api/action_manager/jprint.py new file mode 100644 index 00000000..6ba6a506 --- /dev/null +++ b/photoshop/api/action_manager/jprint.py @@ -0,0 +1,63 @@ +"""Format a repr() string like json. +This is just literal processing.""" + + +def jformat(astr, indent=4, prefix=None): + """Formats a repr() string.""" + all_am_keywords = [ # noqa: F405 + "str2id", + "id2str", + "Enumerated", + "TypeID", + "UnitDouble", + "Identifier", + "Index", + "Offset", + "ReferenceKey", + "dumpjs", + "jprint", + "jformat", + ] + nstr = "" + indent_level = 0 + insmall = False + insquote = False + indquote = False + aftercomma = False + for i in range(len(astr)): + char = astr[i] + if aftercomma: + aftercomma = False + if char == " ": + continue + if char == "(": + insmall = True + if char == ")": + insmall = False + if char == '"': + insquote = not insquote + if char == "'": + indquote = not indquote + if insquote or indquote: + nstr += char + continue + if char in ",[]{}": + if char == "," and not insmall: + char = char + "\n" + " " * (indent * indent_level) + aftercomma = True + if char in "[{": + indent_level += 1 + char = char + "\n" + " " * (indent * indent_level) + if char in "]}": + indent_level -= 1 + char = "\n" + " " * (indent * indent_level) + char + nstr += char + if prefix is not None: + for kwd in all_am_keywords: + nstr = nstr.replace(kwd, prefix + "." + kwd) + return nstr + + +def jprint(obj, indent=4, prefix=None): + """Print formatted repr of an object.""" + print(jformat(repr(obj), indent=indent, prefix=prefix)) diff --git a/photoshop/api/action_manager/js_converter/__init__.py b/photoshop/api/action_manager/js_converter/__init__.py new file mode 100644 index 00000000..b12651f5 --- /dev/null +++ b/photoshop/api/action_manager/js_converter/__init__.py @@ -0,0 +1,4 @@ +from .convert import dump + + +__all__ = ["dump"] diff --git a/photoshop/api/action_manager/js_converter/__main__.py b/photoshop/api/action_manager/js_converter/__main__.py new file mode 100644 index 00000000..d4e3709b --- /dev/null +++ b/photoshop/api/action_manager/js_converter/__main__.py @@ -0,0 +1,16 @@ +# Import built-in modules +import sys + +# Import local modules +from photoshop.api.action_manager import jprint +from photoshop.api.action_manager.js_converter import dump + + +if __name__ == "__main__": + for obj in dump(sys.stdin.read()): + print("==========") + print("Executed an object:") + print("Operation:") + print(obj[0]) + print("Descriptor:") + jprint(obj[1], prefix="am") diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py new file mode 100644 index 00000000..ea114b2d --- /dev/null +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -0,0 +1,114 @@ +"""Defines functions to parse information got on the js side into loadable form. +Use with following: (You need to install Node.js to PATH!) +import photoshop.api.action_manager as am +am.dumpjs(some_js_code)""" + +# Import built-in modules +import json +import pathlib + +from ..desc_value_types import Enumerated +from ..desc_value_types import TypeID +from ..desc_value_types import UnitDouble +from ..ref_form_types import Identifier +from ..ref_form_types import Index +from ..ref_form_types import Offset +from ..ref_form_types import ReferenceKey +from ..utils import id2str +from ..utils import str2hash +from .injection_js import injection +from .node_execjs import execjs + + +def toid(string): + head = str(string)[:7] + if head == "CharID_": + out = TypeID(id2str(str2hash(string[7:].ljust(4)))) + elif head == "StrnID_": + out = TypeID(string[7:]) + else: + out = string + return out + + +def unhead(string): + head = str(string)[:7] + if head == "CharID_": + out = id2str(str2hash(string[7:].ljust(4))) + elif head == "StrnID_": + out = string[7:] + else: + out = string + return out + + +str2getpacker = { + "UnitDouble": lambda x: UnitDouble(unhead(x["unit"]), x["double"]), + "Enumerated": lambda x: Enumerated(unhead(x["enumtype"]), unhead(x["enumval"])), + "TypeID": lambda x: toid(x["string"]), + "File": lambda x: pathlib.Path(x["string"]), + "ActionDescriptor": lambda x: parsedict(x), + "ActionReference": lambda x: parseref(x), + "ActionList": lambda x: parselist(x), +} +str2refgetpacker = { + "default": lambda x: ReferenceKey(unhead(x["DesiredClass"]), unhead(x["Value"])), + "Enumerated": lambda x: ReferenceKey( + unhead(x["DesiredClass"]), + Enumerated(unhead(x["Value"]["enumtype"]), unhead(x["Value"]["enumval"])), # noqa + ), + "Identifier": lambda x: ReferenceKey(unhead(x["DesiredClass"]), Identifier + int(x["Value"])), + "Index": lambda x: ReferenceKey(unhead(x["DesiredClass"]), Index + int(x["Value"])), + "Offset": lambda x: ReferenceKey(unhead(x["DesiredClass"]), Offset + int(x["Value"])), + "Property": lambda x: ReferenceKey(unhead(x["DesiredClass"]), toid(x["Value"])), +} + + +def parsedict(tdict): + if "_classID" not in tdict: + tdict["_classID"] = None + else: + tdict["_classID"] = unhead(tdict["_classID"]) + pdict = {unhead(k): (str2getpacker[v["type"]](v) if type(v) == dict else v) for k, v in tdict.items()} + del pdict["type"] + return pdict + + +def parselist(tdict): + d2l = [tdict[str(i)] for i in range(tdict["len"])] + plist = [(str2getpacker[e["type"]](e) if type(e) == dict else e) for e in d2l] + return plist + + +def parseref(tdict): + d2l = [tdict[str(i)] for i in range(tdict["len"])] + plist = ["!ref"] + # py37 compat + try: + ext = eval( + """[(str2refgetpacker[val["type"]](e) """ + # noqa + """if type(val := e["Value"]) == dict """ + # noqa + """else str2refgetpacker["default"](e)) for e in d2l]""" + ) + except SyntaxError: + ext = [ + (str2refgetpacker[e["Value"]["type"]](e) if type(e["Value"]) == dict else str2refgetpacker["default"](e)) + for e in d2l + ] + plist.extend(ext) + return plist + + +def json2obj(jsont): + obj_init = json.loads(jsont) + obj_desc = parsedict(obj_init["ActionDescriptor"]) if "ActionDescriptor" in obj_init else None + obj_operation = unhead(obj_init["Operation"]) + obj_option = obj_init["Option"] + return (obj_operation, obj_desc, obj_option) + + +def dump(jst): + jsi = injection + "\n" + jst + jsont = execjs(jsi) + objs = [json2obj(j) for j in jsont.split("END OF JSON") if j != "\n"] + return objs diff --git a/photoshop/api/action_manager/js_converter/injection_js.py b/photoshop/api/action_manager/js_converter/injection_js.py new file mode 100644 index 00000000..234d0d1f --- /dev/null +++ b/photoshop/api/action_manager/js_converter/injection_js.py @@ -0,0 +1,151 @@ +# flake8: noqa + +"""Defines injection, a variable which contains js code. +These js code implements Photoshop functions, and lures a piece of js code +to output all its information on executing executeAction function. +You may turn on syntax highlighting for js here.""" + +injection = """ + +class UnitDouble { + constructor(unit,ndouble) { + this.type = 'UnitDouble' + this.unit = unit + this.double = ndouble + } +} + +class Enumerated { + constructor(enumtype,enumval) { + this.type = 'Enumerated' + this.enumtype = enumtype + this.enumval = enumval + } +} + +class TypeID { + constructor(string) { + this.type = 'TypeID' + this.string = string + } +} + +class File { + constructor(string) { + this.type = 'File' + this.string = string + } +} + +function charIDToTypeID(chr) { + return 'CharID_'+chr +} +function stringIDToTypeID(str) { + return 'StrnID_'+str +} + +class ActionDescriptor { + constructor() {this.type = 'ActionDescriptor'} + putInteger(key,val) {this[key] = val} + putDouble(key,val) {this[key] = val} + putUnitDouble(key,unit,ndouble) {this[key] = new UnitDouble(unit,ndouble)} + putString(key,val) {this[key] = val} + putBoolean(key,val) {this[key] = val} + putEnumerated(key,enumtype,enumval) {this[key] = new Enumerated(enumtype,enumval)} + putObject(key,psclass,val) {val['_classID'] = psclass; this[key] = val} + putReference(key,val) {this[key] = val} + putList(key,val) {this[key] = val} + putClass(key,val) {this[key] = new TypeID(val)} + putPath(key,val) {this[key] = val} +} + +class ActionList { + constructor() {this.type = 'ActionList'; this.len = 0} + putInteger(val) {this.len += 1; this[this.len-1] = val} + putDouble(val) {this.len += 1; this[this.len-1] = val} + putUnitDouble(unit,ndouble) {this.len += 1; this[this.len-1] = new UnitDouble(unit,ndouble)} + putString(val) {this.len += 1; this[this.len-1] = val} + putBoolean(val) {this.len += 1; this[this.len-1] = val} + putEnumerated(enumtype,enumval) {this.len += 1; this[this.len-1] = new Enumerated(enumtype,enumval)} + putObject(psclass,val) {this.len += 1; val['_classID'] = psclass; this[this.len-1] = val} + putReference(val) {this.len += 1; this[this.len-1] = val} + putList(val) {this.len += 1; this[this.len-1] = val} + putClass(val) {this.len += 1; this[this.len-1] = new TypeID(val)} + putPath(val) {this.len += 1; this[this.len-1] = val} +} + +class ActionReference { + constructor() {this.type = 'ActionReference'; this.len = 0} + putClass(dcls) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Class', + 'Value':null, + } + } + putEnumerated(dcls,enumtype,enumval) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Enumerated', + 'Value':new Enumerated(enumtype,enumval), + } + } + putIdentifier(dcls,val) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Identifier', + 'Value':val, + } + } + putIndex(dcls,val) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Index', + 'Value':val, + } + } + putName(dcls,val) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Name', + 'Value':val, + } + } + putOffset(dcls,val) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Offset', + 'Value':val, + } + } + putProperty(dcls,val) { + this.len += 1 + this[this.len-1] = { + 'DesiredClass':dcls, + 'FormType':'Property', + 'Value':val, + } + } +} + +var DialogModes = new Object +DialogModes.ALL = 'All' +DialogModes.ERROR = 'Error' +DialogModes.NO = 'No' + +function executeAction(operate,desc,exeoption) { + execlogdict = {} + execlogdict['Operation'] = operate + execlogdict['ActionDescriptor'] = desc + execlogdict['Option'] = exeoption + execlogjson = JSON.stringify(execlogdict, null, 4) + console.log(execlogjson) + console.log('END OF JSON') +} +""" diff --git a/photoshop/api/action_manager/js_converter/node_execjs.py b/photoshop/api/action_manager/js_converter/node_execjs.py new file mode 100644 index 00000000..a8789a49 --- /dev/null +++ b/photoshop/api/action_manager/js_converter/node_execjs.py @@ -0,0 +1,22 @@ +"""Defines execjs, a function to run js in Node.js""" + +# Import built-in modules +import os +import re +import subprocess + + +# Node.js check +nodestate = os.popen("node --version") +if not re.match(r"^v\d*\.\d*\.\d*", nodestate.read()): + raise RuntimeError("Please check if Node.js is installed to PATH!") + + +def execjs(jst): + tmpjs = jst.replace('"""', '"') + run = subprocess.run("node", input=tmpjs, capture_output=True, text=True) + result = run.stdout + err = run.stderr + if err: + raise RuntimeError(err) + return result diff --git a/photoshop/api/action_manager/ref_form_types/__init__.py b/photoshop/api/action_manager/ref_form_types/__init__.py new file mode 100644 index 00000000..6ff5b91d --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/__init__.py @@ -0,0 +1,7 @@ +from .identifier import Identifier +from .index import Index +from .offset import Offset +from .referencekey import ReferenceKey + + +__all__ = ["Identifier", "Index", "Offset", "ReferenceKey"] diff --git a/photoshop/api/action_manager/ref_form_types/_marker.py b/photoshop/api/action_manager/ref_form_types/_marker.py new file mode 100644 index 00000000..5a0c20b2 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/_marker.py @@ -0,0 +1,20 @@ +"""Defines class marker. It is the class of Identifier, Index, Offset. +It is INTERNAL. You should not import or initialize it.""" + + +class marker: + def __init__(self, name, value=0): + self.name = name + self.value = value + + def __add__(self, other): + return type(self)(self.name, self.value + other) + + def __repr__(self): + return "%s+%d" % (self.name, self.value) + + def __eq__(self, other): + try: + return self.name == other.name and self.value == other.value + except BaseException: + return False diff --git a/photoshop/api/action_manager/ref_form_types/identifier.py b/photoshop/api/action_manager/ref_form_types/identifier.py new file mode 100644 index 00000000..f716f935 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/identifier.py @@ -0,0 +1,8 @@ +"""Defines a special object: Identifier. +You can give it a value by adding a number to it. +For example: id = Identifier+777""" + +from ._marker import marker + + +Identifier = marker("Identifier") diff --git a/photoshop/api/action_manager/ref_form_types/index.py b/photoshop/api/action_manager/ref_form_types/index.py new file mode 100644 index 00000000..0a119ff3 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/index.py @@ -0,0 +1,8 @@ +"""Defines a special object: Index. +You can give it a value by adding a number to it. +For example: index = Index+1""" + +from ._marker import marker + + +Index = marker("Index") diff --git a/photoshop/api/action_manager/ref_form_types/offset.py b/photoshop/api/action_manager/ref_form_types/offset.py new file mode 100644 index 00000000..8f11fef5 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/offset.py @@ -0,0 +1,8 @@ +"""Defines a special object: Offset. +You can give it a value by adding a number to it. +For example: offset = Offset+12""" + +from ._marker import marker + + +Offset = marker("Offset") diff --git a/photoshop/api/action_manager/ref_form_types/referencekey.py b/photoshop/api/action_manager/ref_form_types/referencekey.py new file mode 100644 index 00000000..ade2d258 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/referencekey.py @@ -0,0 +1,72 @@ +"""Defines class ReferenceKey. It handles type mapping in ActionReference. +You can initialize it with 2 arguments: desiredclass, value.""" + +# Import built-in modules +from collections import namedtuple + +# Import local modules +from photoshop.api.enumerations import ReferenceFormType + +from ..desc_value_types import Enumerated +from ..desc_value_types import TypeID +from ..utils import id2str +from ..utils import str2id +from ._marker import marker + +# Identifier, Index, Offset are used by getting them in globals(). +from .identifier import Identifier # noqa: F401 +from .index import Index # noqa: F401 +from .offset import Offset # noqa: F401 + + +psreftype2str = { + **{vtype.value: str(vtype)[27:-4] for vtype in ReferenceFormType}, + **{vtype: str(vtype)[27:-4] for vtype in ReferenceFormType}, +} + +ReferenceKey_proto = namedtuple("ReferenceKey", ["desiredclass", "value"]) + + +class ReferenceKey(ReferenceKey_proto): + @classmethod + def _packer(cls, obj): + ftype = psreftype2str[obj.getForm()] + dcls = id2str(obj.getDesiredClass()) + try: + get_func = getattr(obj, "get" + ftype) + except BaseException: + get_func = None + if ftype == "Class": + v = None + elif ftype == "Enumerated": + v = Enumerated( + id2str(obj.getEnumeratedType()), + id2str(obj.getEnumeratedValue()), + ) # noqa + elif ftype == "Property": + v = TypeID(id2str(obj.getProperty())) + elif ftype == "Name": + v = get_func() + elif ftype in ("Identifier", "Index", "Offset"): + v = globals()[ftype] + get_func() + return cls(dcls, v) + + def _unpacker(self): + dcls = str2id(self.desiredclass) + value = self.value + if value is None: + v = value + ftype = "Class" + elif type(value) == TypeID: + v = value._unpacker() + ftype = "Property" + elif type(value) == marker: + v = (value.value,) + ftype = value.name + elif type(value) == Enumerated: + v = value._unpacker() + ftype = "Enumerated" + elif type(value) == str: + v = (value,) + ftype = "Name" + return (ftype, dcls, v) diff --git a/photoshop/api/action_manager/utils.py b/photoshop/api/action_manager/utils.py new file mode 100644 index 00000000..faaf1676 --- /dev/null +++ b/photoshop/api/action_manager/utils.py @@ -0,0 +1,118 @@ +"""TypeID conversion utilities of this submodule.""" + +# Import built-in modules +import warnings +from functools import wraps + +# Import local modules +from photoshop.api._core import Photoshop + + +__all__ = ["str2id", "id2str"] + + +class AppAgent(Photoshop): + """Partially reimplement the Application class + in this file to avoid circular import.""" + + typename = "Application" + + def str2id(self, string: str) -> int: + return self.app.stringIDToTypeID(string) + + def id2str(self, number: int) -> str: + return self.app.typeIDToStringID(number) + + +converter = None + + +def requireapp(func): + """A simple decorator that initializes the global + app instance when the decorated function is called.""" + + @wraps(func) + def wrapped(*args, **kwargs): + global converter + if converter is None: + converter = AppAgent() + return func(*args, **kwargs) + + return wrapped + + +def str2hash(x: str) -> int: + """Convert charID to typeID.""" + assert len(x) == 4 + x = x.replace(" ", "\x20") + return int.from_bytes(bytes(x, encoding="utf-8"), byteorder="big") + + +def hash2str(x: int) -> str: + """Convert typeID to charID.""" + assert len(hex(x)) == 10 + return x.to_bytes(length=4, byteorder="big").decode() + + +# Current approach of str2id is not perfect, it will face some "collisions". +# This means, if there exists a charID which is the same as another stringID, +# it will cause a "collision". +# For example charID 'From' -> stringID 'from' +# and charID 'from' -> stringID 'originalAddressAttr' +# We can know when this will happen by prescanning the cpp header +# "PITerminology.h" and "PIStringTerminology.h". +# Prescanned collisions are stored here. If you suffer from collisions that +# str2id cannot handle, please open an issue. + +collisions = { + "chr": ("Cpy ", "From", "Type"), + "str": ("copyEvent", "originalAddressAttr", "class"), + "collision": ("copy", "from", "type"), +} + +COLLISION_WARNING = """ +You are using a string that is a collision of stringID and charID. +On this situation, str2id will consider this string as stringID. If you encounter COMerror +on app.executeAction() later, you can try to replace this string(%s) with its corresponding +charID(%s). If you are sure you want to use it as stringID and suppress this warning, +replace this string(%s) with its corresponding stringID(%s). +If this warning doesn't solve you problem, please open an issue.""" + + +@requireapp +def str2id(psstr: str) -> str: + """Convert charID or stringID to typeID""" + assert type(psstr) == str + if len(psstr) == 4: + try: + search = collisions["collision"].index(psstr) + warnstr = COLLISION_WARNING % ( + repr(collisions["collision"][search]), + repr(collisions["chr"][search]), + repr(collisions["collision"][search]), + repr(collisions["str"][search]), + ) + warnings.warn(warnstr, category=RuntimeWarning) + except ValueError: + pass # I know what I am doing. + typeid = str2hash(psstr) + try: + restr = converter.id2str(typeid) + except BaseException: + restr = "" + if not restr: + typeid = converter.str2id(psstr) + else: + typeid = converter.str2id(psstr) + return typeid + + +@requireapp +def id2str(typeid: int) -> str: + """Convert typeID to stringID""" + result = converter.id2str(typeid) + try: + search = collisions["collision"].index(result) + return collisions["chr"][search] + except ValueError: + return result diff --git a/photoshop/api/action_reference.py b/photoshop/api/action_reference.py index de9d6b81..eba5eba4 100644 --- a/photoshop/api/action_reference.py +++ b/photoshop/api/action_reference.py @@ -8,68 +8,82 @@ with an ActionDescriptor. """ -# Import local modules -from photoshop.api._core import Photoshop -from photoshop.api.enumerations import ReferenceFormType +# Import built-in modules +from abc import ABC +from abc import abstractmethod -class ActionReference(Photoshop): - """Contains data describing a referenced Action. +# Import third-party modules +import comtypes - The action reference object is part of the Action Manager functionality. - For details on using the Action Manager, see the Photoshop Scripting Guide. +# Import local modules +from photoshop.api._core import Photoshop - """ +class ActionReference(Photoshop, ABC): object_name = "ActionReference" - def __init__(self, parent=None): + def __init__(self, parent: comtypes.client.lazybind.Dispatch = None): super().__init__(parent=parent) + @abstractmethod def getContainer(self): - return self.app.getContainer() + """Implemented in _actionmanager_type_binder.ActionReference""" + pass - def getDesiredClass(self): + def getDesiredClass(self) -> int: + """Gets a number representing the class of the object.""" return self.app.getDesiredClass() def getEnumeratedType(self) -> int: + """Gets the enumeration type.""" return self.app.getEnumeratedType() def getEnumeratedValue(self) -> int: + """Gets the enumeration value.""" return self.app.getEnumeratedValue() - def getForm(self) -> ReferenceFormType: - """Gets the form of this action reference.""" - return ReferenceFormType(self.app.getForm()) + @abstractmethod + def getForm(self): + """Implemented in _actionmanager_type_binder.ActionReference""" + pass def getIdentifier(self) -> int: - """Gets the identifier value for a reference whose form is - identifier.""" + """Gets the identifier value for a reference whose form is identifier.""" return self.app.getIdentifier() def getIndex(self) -> int: - """Gets the index value for a reference in a list or array,""" + """Gets the index value for a reference in a list or array.""" return self.app.getIndex() - def putName(self, key, value): - return self.app.putName(key, value) + def getName(self) -> str: + """Gets the name of a reference.""" + return self.app.getName() + + def getOffset(self) -> int: + """Gets the offset of the object’s index value.""" + return self.app.getOffset() + + def getProperty(self) -> int: + return self.app.getProperty() + + def putClass(self, desiredClass: int): + self.app.putClass(desiredClass) - def putClass(self, value): - return self.app.putClass(value) + def putEnumerated(self, desiredClass: int, enumType: int, value: int): + self.app.putEnumerated(desiredClass, enumType, value) - def putEnumerated(self, desired_class, enum_type, value): - """Puts an enumeration type and ID into a reference along with the - desired class for the reference.""" - return self.app.putEnumerated(desired_class, enum_type, value) + def putIdentifier(self, desiredClass: int, value: int): + self.app.putIdentifier(desiredClass, value) - def putIdentifier(self, desired_class, value): - return self.app.putIdentifier(desired_class, value) + def putIndex(self, desiredClass: int, value: int): + self.app.putIndex(desiredClass, value) - def putIndex(self, desired_class, value): - return self.app.putIndex(desired_class, value) + def putName(self, desiredClass: int, value: str): + self.app.putName(desiredClass, value) - def putOffset(self, desired_class, value): - return self.app.putOffset(desired_class, value) + def putOffset(self, desiredClass: int, value: int): + self.app.putOffset(desiredClass, value) - def putProperty(self, desired_class, value): - return self.app.putProperty(desired_class, value) + def putProperty(self, desiredClass: int, value: int): + self.app.putProperty(desiredClass, value) diff --git a/photoshop/api/application.py b/photoshop/api/application.py index 681874dc..048447bd 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -18,6 +18,7 @@ from typing import Optional # Import local modules +from photoshop.api._actionmanager_type_binder import ActionDescriptor from photoshop.api._artlayer import ArtLayer from photoshop.api._core import Photoshop from photoshop.api._document import Document @@ -351,10 +352,12 @@ def eraseCustomOptions(self, key): self.app.eraseCustomOptions(key) def executeAction(self, event_id, descriptor, display_dialogs=2): - return self.app.executeAction(event_id, descriptor, display_dialogs) + comobj = self.app.executeAction(event_id, descriptor, display_dialogs) + return ActionDescriptor(parent=comobj) def executeActionGet(self, reference): - return self.app.executeActionGet(reference) + comobj = self.app.executeActionGet(reference) + return ActionDescriptor(parent=comobj) def featureEnabled(self, name): """Determines whether the feature diff --git a/photoshop/session.py b/photoshop/session.py index d5976e1b..34deb522 100644 --- a/photoshop/session.py +++ b/photoshop/session.py @@ -123,9 +123,9 @@ def __init__( self._active_document = None self.app: Application = Application(version=ps_version) - self.ActionReference: ActionReference = ActionReference() - self.ActionDescriptor: ActionDescriptor = ActionDescriptor() - self.ActionList: ActionList = ActionList() + self.ActionReference: ActionReference = ActionReference + self.ActionDescriptor: ActionDescriptor = ActionDescriptor + self.ActionList: ActionList = ActionList self.EventID = EventID self.SolidColor = SolidColor self.TextItem = TextItem