From 9537f5f2e4b5ed64d71eca7e30a26c14843c1e40 Mon Sep 17 00:00:00 2001 From: TsXor <74352334+TsXor@users.noreply.github.com> Date: Sun, 21 Aug 2022 19:51:04 +0800 Subject: [PATCH 01/13] refactor(ActionDescriptor,ActionList,ActionReference): better type communication and completes functions in photoshop js reference Signed-off-by: TsXor --- photoshop/api/__init__.py | 6 +- photoshop/api/_actionmanager_type_binder.py | 61 ++++++ photoshop/api/action_descriptor.py | 217 ++++++++++---------- photoshop/api/action_list.py | 159 ++++++++++++-- photoshop/api/action_reference.py | 80 +++++--- photoshop/api/application.py | 7 +- 6 files changed, 369 insertions(+), 161 deletions(-) create mode 100644 photoshop/api/_actionmanager_type_binder.py diff --git a/photoshop/api/__init__.py b/photoshop/api/__init__.py index 16e39984..9fe75669 100644 --- a/photoshop/api/__init__.py +++ b/photoshop/api/__init__.py @@ -2,9 +2,9 @@ # 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.colors import CMYKColor from photoshop.api.colors import GrayColor diff --git a/photoshop/api/_actionmanager_type_binder.py b/photoshop/api/_actionmanager_type_binder.py new file mode 100644 index 00000000..a199d348 --- /dev/null +++ b/photoshop/api/_actionmanager_type_binder.py @@ -0,0 +1,61 @@ +"""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_reference import ActionReference as AR_proto +from photoshop.api.enumerations import DescValueType +from photoshop.api.enumerations import ReferenceFormType + + +class ActionDescriptor(AD_proto): + 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): + 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): + 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 d0da601f..14423e4b 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 erase(self, key: int): - """Erases a key form the descriptor.""" - self.app.erase(key) - - def fromStream(self, value: str): - """Create a descriptor from a stream of bytes. + def isEqual(self, otherDesc): + """Determines whether the descriptor is the same as another descriptor.""" + assert otherDesc.typename == "ActionDescriptor" + return self.app.isEqual(otherDesc) - for reading from disk. + def __eq__(self, other): + try: + return self.isEqual(other) + except AssertionError: + return False - """ - self.app.fromStream(value) + def erase(self, key: int): + """Erases a key from the descriptor.""" + self.erase(key) - def getBoolean(self, key: int) -> int: - """Gets the text_font of a key of type boolean. + def fromStream(self, value: str): + """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 - 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) -> int: + 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.Path: + """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) -> int: - """Gets the unit type of a key of type UnitDouble.""" + def getUnitDoubleValue(self, key: int) -> float: + """Gets the value 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,50 +165,54 @@ 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: int): + 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.Path): """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: int): - """Sets the value for a key whose type is a unit value formatted as - double.""" - self.app.putUnitDouble(key, unit_id, value) - - def toStream(self) -> str: - """Gets the entire descriptor as as stream of bytes, - for writing to disk.""" - return self.app.toSteadm() + def putUnitDouble(self, key: int, classID: int, value: float): + """Sets the value for a key whose type is a unit value formatted as a double.""" + self.app.putUnitDouble(key, classID, value) diff --git a/photoshop/api/action_list.py b/photoshop/api/action_list.py index be34b23a..72b18b38 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.Path: + """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.Path): + """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_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 51f133ed..95b04021 100644 --- a/photoshop/api/application.py +++ b/photoshop/api/application.py @@ -19,6 +19,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 @@ -352,10 +353,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 From e339e77ec93af5d95d5386e7ffd8b3412711fee6 Mon Sep 17 00:00:00 2001 From: TsXor Date: Thu, 8 Sep 2022 23:55:16 +0800 Subject: [PATCH 02/13] refactor(Session): fix Action Manager things in session I may have to correct something about Action Manager and its classes: ActionDescriptor, ActionList, ActionReference. Action Manager is NOT something to describe your action palette. Action Manager actually enables low-level access into photoshop, and all things in this repo can actually be rewritten in Action Manager. You can consider an ActionDescriptor as dict, ActionList as array, ActionReference as filters for searching. So we should NOT set an INSTANCE of ActionDescriptor as an attribute of a Session and instead we should set the ActionDescriptor CLASS (the same for ActionList, ActionReference). Signed-off-by: TsXor --- photoshop/session.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/photoshop/session.py b/photoshop/session.py index 882416d3..2954e103 100644 --- a/photoshop/session.py +++ b/photoshop/session.py @@ -122,9 +122,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 From ddc651b12c511e14e5918ec5ef028c561b78e57e Mon Sep 17 00:00:00 2001 From: TsXor Date: Fri, 2 Sep 2022 23:29:49 +0800 Subject: [PATCH 03/13] feat(action_manager): Integrated my AM types and utilities desc_value_types and ref_form_types defines classes which describes AM things and utils contains a shortcut for id conversion. Nothing breaking. All AM functions are still available. My submodule uses relative importing because absolute importing causes bigger possibility in path typos. Signed-off-by: TsXor --- photoshop/api/_actionmanager_type_binder.py | 25 +++- photoshop/api/action_manager/__init__.py | 20 +++ .../_main_types/_type_mapper.py | 64 +++++++++ .../_main_types/action_descriptor.py | 63 ++++++++ .../_main_types/action_descriptor_iterator.py | 18 +++ .../action_manager/_main_types/action_list.py | 55 +++++++ .../_main_types/action_list_iterator.py | 17 +++ .../_main_types/action_reference.py | 53 +++++++ .../_main_types/action_reference_iterator.py | 22 +++ .../desc_value_types/__init__.py | 5 + .../desc_value_types/enumerated.py | 15 ++ .../action_manager/desc_value_types/typeid.py | 13 ++ .../desc_value_types/unitdouble.py | 15 ++ photoshop/api/action_manager/jprint.py | 42 ++++++ .../action_manager/js_converter/__init__.py | 1 + .../action_manager/js_converter/__main__.py | 12 ++ .../action_manager/js_converter/convert.py | 91 ++++++++++++ .../js_converter/injection_js.py | 136 ++++++++++++++++++ .../js_converter/node_execjs.py | 14 ++ .../action_manager/ref_form_types/__init__.py | 6 + .../action_manager/ref_form_types/_marker.py | 13 ++ .../ref_form_types/identifier.py | 3 + .../action_manager/ref_form_types/index.py | 3 + .../action_manager/ref_form_types/offset.py | 3 + .../ref_form_types/referencekey.py | 55 +++++++ photoshop/api/action_manager/utils.py | 42 ++++++ 26 files changed, 803 insertions(+), 3 deletions(-) create mode 100644 photoshop/api/action_manager/__init__.py create mode 100644 photoshop/api/action_manager/_main_types/_type_mapper.py create mode 100644 photoshop/api/action_manager/_main_types/action_descriptor.py create mode 100644 photoshop/api/action_manager/_main_types/action_descriptor_iterator.py create mode 100644 photoshop/api/action_manager/_main_types/action_list.py create mode 100644 photoshop/api/action_manager/_main_types/action_list_iterator.py create mode 100644 photoshop/api/action_manager/_main_types/action_reference.py create mode 100644 photoshop/api/action_manager/_main_types/action_reference_iterator.py create mode 100644 photoshop/api/action_manager/desc_value_types/__init__.py create mode 100644 photoshop/api/action_manager/desc_value_types/enumerated.py create mode 100644 photoshop/api/action_manager/desc_value_types/typeid.py create mode 100644 photoshop/api/action_manager/desc_value_types/unitdouble.py create mode 100644 photoshop/api/action_manager/jprint.py create mode 100644 photoshop/api/action_manager/js_converter/__init__.py create mode 100644 photoshop/api/action_manager/js_converter/__main__.py create mode 100644 photoshop/api/action_manager/js_converter/convert.py create mode 100644 photoshop/api/action_manager/js_converter/injection_js.py create mode 100644 photoshop/api/action_manager/js_converter/node_execjs.py create mode 100644 photoshop/api/action_manager/ref_form_types/__init__.py create mode 100644 photoshop/api/action_manager/ref_form_types/_marker.py create mode 100644 photoshop/api/action_manager/ref_form_types/identifier.py create mode 100644 photoshop/api/action_manager/ref_form_types/index.py create mode 100644 photoshop/api/action_manager/ref_form_types/offset.py create mode 100644 photoshop/api/action_manager/ref_form_types/referencekey.py create mode 100644 photoshop/api/action_manager/utils.py diff --git a/photoshop/api/_actionmanager_type_binder.py b/photoshop/api/_actionmanager_type_binder.py index a199d348..c1e872e2 100644 --- a/photoshop/api/_actionmanager_type_binder.py +++ b/photoshop/api/_actionmanager_type_binder.py @@ -7,13 +7,24 @@ # Import local modules from photoshop.api.action_descriptor import ActionDescriptor as AD_proto +from photoshop.api.action_manager._main_types.action_descriptor import ActionDescriptor as AD_utils_proto from photoshop.api.action_list import ActionList as AL_proto +from photoshop.api.action_manager._main_types.action_list import ActionList as AL_utils_proto from photoshop.api.action_reference import ActionReference as AR_proto +from photoshop.api.action_manager._main_types.action_reference import ActionReference as AR_utils_proto from photoshop.api.enumerations import DescValueType from photoshop.api.enumerations import ReferenceFormType -class ActionDescriptor(AD_proto): +class ActionDescriptor(AD_proto, AD_utils_proto): + @classmethod + def load(cls, adict: dict) -> '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)) @@ -31,7 +42,11 @@ def getReference(self, key: int) -> "ActionReference": return ActionReference(parent=self.app.getReference(key)) -class ActionList(AL_proto): +class ActionList(AL_proto, AL_utils_proto): + @classmethod + def load(cls, alist: list) -> '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)) @@ -49,7 +64,11 @@ def getReference(self, index: int) -> "ActionReference": return ActionReference(parent=self.app.getReference(index)) -class ActionReference(AR_proto): +class ActionReference(AR_proto, AR_utils_proto): + @classmethod + def load(cls, adict: dict) -> 'ActionReference': + return super().load(adict) + def getForm(self) -> ReferenceFormType: """Gets the form of this action reference.""" return ReferenceFormType(self.app.getForm()) diff --git a/photoshop/api/action_manager/__init__.py b/photoshop/api/action_manager/__init__.py new file mode 100644 index 00000000..3af810db --- /dev/null +++ b/photoshop/api/action_manager/__init__.py @@ -0,0 +1,20 @@ +from .ref_form_types import * +from .desc_value_types import * +from .utils import * +from .js_converter import dump as dumpjs +from .jprint import * + +__all__ = [ # noqa: F405 + 'str2id', + 'id2str', + 'Enumerated', + 'TypeID', + 'UnitDouble', + 'Identifier', + 'Index', + 'Offset', + 'ReferenceKey', + 'dumpjs', + 'jprint', + 'jformat', +] \ No newline at end of file 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..3b715948 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/_type_mapper.py @@ -0,0 +1,64 @@ +from ..desc_value_types import * +from ..ref_form_types import * +from ..utils import * + +__all__ = ['unpack', 'pack', 'parsetype'] + +pytype2str = { + bool:'Boolean', + int:'Integer', + float:'Double', + str:'String', + 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: + 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] + 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 \ No newline at end of file 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..2723edee --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_descriptor.py @@ -0,0 +1,63 @@ +from ._type_mapper import * +from ..utils import * +from .action_descriptor_iterator import ActionDescriptor_Iterator +from typing import Any +from abc import ABC, abstractclassmethod + +class ActionDescriptor: + '''A vessel for my extra utils.''' + + @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 + v = v \ + if (dtype := parsetype(v)) == 'others' \ + else namespace[dtype].load(v) + new.uput(k,v) + return new + + def uget(self, key: str) -> Any: + keyid = str2id(key) + val = pack(self, keyid) + return val + + def uput(self, key: str, val: Any): + 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 __contains__(self, key): + keys = [key for key in self] + return key in keys + + def dump(self) -> dict: + #This is a dict comprehension. + ddict = {'_classID':self.classID} + ddict.update({ + key:( + value.dump() \ + if hasattr(value := self.uget(key), 'dump') \ + else value + ) for key in self + }) + 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) \ No newline at end of file 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..c00a4395 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py @@ -0,0 +1,18 @@ +from ..utils import * + +class ActionDescriptor_Iterator: + def __init__(self, psobj: 'ActionDescriptor'): + self.curobj = psobj + self.n = -1 + + def __next__(self) -> str: + self.n += 1 + try: + keyid = self.curobj.getKey(self.n) + except: + raise StopIteration + keystr = id2str(keyid) + return keystr + + def __repr__(self): + return ''%self.n \ No newline at end of file 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..126eeafb --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_list.py @@ -0,0 +1,55 @@ +from ._type_mapper import * +from ..utils import * +from .action_list_iterator import ActionList_Iterator +from typing import Any +from abc import ABC, abstractclassmethod + +class ActionList(ABC): + '''A vessel for my extra utils.''' + + @abstractclassmethod + def load(cls, alist: list, namespace: dict): # pass globals() for namespace + new = cls() + for v in alist: + v = v \ + if (dtype := parsetype(v)) == 'others' \ + else namespace[dtype].load(v) + new.uput(v) + 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: + val = pack(self, index) + return val + + def uput(self, val: Any): + typestr, args = unpack(val) + #ActionList type checking + assert True if (dtype := self.dtype) is None else 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: + #This is a list comprehension. + dlist = [ + ( + elem.dump() \ + if hasattr(elem, 'dump') \ + else elem + ) for elem in self + ] + return dlist \ No newline at end of file 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..f3df0ceb --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_list_iterator.py @@ -0,0 +1,17 @@ +from typing import Any + +class ActionList_Iterator: + def __init__(self, psobj: 'ActionList'): + self.curobj = psobj + self.n = -1 + + def __next__(self) -> Any: + self.n += 1 + try: + elem = self.curobj.uget(self.n) + except: + raise StopIteration() + return elem + + def __repr__(self): + return ''%self.n \ No newline at end of file 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..bae2ae81 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_reference.py @@ -0,0 +1,53 @@ +from .action_reference_iterator import ActionReference_Iterator +from ..ref_form_types import ReferenceKey +from abc import ABC, abstractclassmethod + +class ActionReference(ABC): + '''A vessel for my extra utils.''' + + @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: + target = self + for i in range(index+1): + try: + target = target.getContainer() + except: + raise IndexError('index out of range') + return ReferenceKey._packer(target) + + def uput(self, rkey: ReferenceKey): + 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: + target = self + 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: + rlen -= 1; break + return rlen + + def __iter__(self) -> ActionReference_Iterator: + return ActionReference_Iterator(self) \ No newline at end of file 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..f5acf8a2 --- /dev/null +++ b/photoshop/api/action_manager/_main_types/action_reference_iterator.py @@ -0,0 +1,22 @@ +from ..ref_form_types import ReferenceKey + +class ActionReference_Iterator: + def __init__(self, psobj: 'ActionReference'): + 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: + raise StopIteration + return ReferenceKey._packer(self.curobj) + + def __repr__(self): + return ''%self.n \ No newline at end of file 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..e8342afa --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/__init__.py @@ -0,0 +1,5 @@ +from .enumerated import Enumerated +from .typeid import TypeID +from .unitdouble import UnitDouble + +__all__ = ['Enumerated', 'TypeID', 'UnitDouble'] \ No newline at end of file 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..dcee9193 --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/enumerated.py @@ -0,0 +1,15 @@ +from ..utils import * +from collections import namedtuple + +Enumerated_proto = namedtuple('Enumerated_proto', ['type', 'value']) + +class Enumerated(Enumerated_proto): + @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) \ No newline at end of file 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..beeff682 --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/typeid.py @@ -0,0 +1,13 @@ +from ..utils import * +from collections import namedtuple + +TypeID_proto = namedtuple('TypeID_proto', ['string']) + +class TypeID(TypeID_proto): + @classmethod + def _packer(cls, obj, index): + typeid = id2str(obj.getClass(index)) + return cls(typeid) + def _unpacker(self): + nid = str2id(self.typeid) + return (nid,) \ No newline at end of file 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..a6fca43e --- /dev/null +++ b/photoshop/api/action_manager/desc_value_types/unitdouble.py @@ -0,0 +1,15 @@ +from ..utils import * +from collections import namedtuple + +UnitDouble_proto = namedtuple('UnitDouble_proto', ['unit', 'double']) + +class UnitDouble(UnitDouble_proto): + @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..fe263b8d --- /dev/null +++ b/photoshop/api/action_manager/jprint.py @@ -0,0 +1,42 @@ +#Format it like json! +#Just literal processing. + +def jformat(astr, indent=4): + 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 + return nstr + +def jprint(obj, indent=4): + print(jformat(repr(obj), indent=indent)) \ No newline at end of file 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..73ff17be --- /dev/null +++ b/photoshop/api/action_manager/js_converter/__init__.py @@ -0,0 +1 @@ +from .convert import dump \ No newline at end of file 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..edfbabd9 --- /dev/null +++ b/photoshop/api/action_manager/js_converter/__main__.py @@ -0,0 +1,12 @@ +import sys +from photoshop.api.action_manager.js_converter import dump +from photoshop.api.action_manager import jprint + +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]) \ No newline at end of file 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..7a5b27e1 --- /dev/null +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -0,0 +1,91 @@ +from .node_execjs import execjs +from .injection_js import injection +from ..utils import str2id, id2str, str2hash, hash2str +from ..desc_value_types import * +from ..ref_form_types import * +import json + +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']), + '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']))), + '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 not '_classID' 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'] + plist.extend( + [( + str2refgetpacker[val['type']](e) \ + if type(val := e['Value']) == dict \ + else str2refgetpacker['default'](e)\ + )for e in d2l] + ) + 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 + \ No newline at end of file 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..bfd5667d --- /dev/null +++ b/photoshop/api/action_manager/js_converter/injection_js.py @@ -0,0 +1,136 @@ +#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 + } +} + +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)} +} + +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)} +} + +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') +} +''' \ No newline at end of file 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..357cd694 --- /dev/null +++ b/photoshop/api/action_manager/js_converter/node_execjs.py @@ -0,0 +1,14 @@ +import os,re,subprocess + +# Node.js check +nodestate = os.popen('node --version') +if not re.match('^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 \ No newline at end of file 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..b15de167 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/__init__.py @@ -0,0 +1,6 @@ +from .identifier import Identifier +from .index import Index +from .offset import Offset +from .referencekey import ReferenceKey + +__all__ = ['Identifier', 'Index', 'Offset', 'ReferenceKey'] \ No newline at end of file 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..7b6556dc --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/_marker.py @@ -0,0 +1,13 @@ +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: + return False \ No newline at end of file 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..c1e9cfb0 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/identifier.py @@ -0,0 +1,3 @@ +from ._marker import marker + +Identifier = marker('Identifier') \ No newline at end of file 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..170e18c2 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/index.py @@ -0,0 +1,3 @@ +from ._marker import marker + +Index = marker('Index') \ No newline at end of file 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..0db7564b --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/offset.py @@ -0,0 +1,3 @@ +from ._marker import marker + +Offset = marker('Offset') \ No newline at end of file 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..def5a0a6 --- /dev/null +++ b/photoshop/api/action_manager/ref_form_types/referencekey.py @@ -0,0 +1,55 @@ +from ..utils import * +from ..desc_value_types import TypeID, Enumerated +from photoshop.api.enumerations import ReferenceFormType +from .identifier import Identifier +from .index import Index +from .offset import Offset +from ._marker import marker +from collections import namedtuple + +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: + get_func = None + if ftype == 'Class': + v = None + elif ftype == 'Enumerated': + v = Enumerated(id2str(obj.getEnumeratedType()), id2str(obj.getEnumeratedValue())) + 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) \ No newline at end of file diff --git a/photoshop/api/action_manager/utils.py b/photoshop/api/action_manager/utils.py new file mode 100644 index 00000000..08254d2c --- /dev/null +++ b/photoshop/api/action_manager/utils.py @@ -0,0 +1,42 @@ +from photoshop.api._core import Photoshop + +__all__ = ['str2id', 'id2str'] + +class app(Photoshop): + 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 = app() + +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() + +def str2id(psstr: str) -> str: + '''Convert charID or stringID to typeID''' + assert type(psstr) == str + if len(psstr) == 4: + typeid = str2hash(psstr) + try: + restr = converter.id2str(psstr) + except: + restr = '' + if not restr: + typeid = converter.str2id(psstr) + else: + typeid = converter.str2id(psstr) + return typeid + +def id2str(typeid: int) -> str: + '''Convert typeID to stringID''' + return converter.id2str(typeid) \ No newline at end of file From b64251853a4d86518a9a5451f2db01a6ff196511 Mon Sep 17 00:00:00 2001 From: TsXor Date: Sun, 4 Sep 2022 22:45:58 +0800 Subject: [PATCH 04/13] feat(jprint): adds 'prefix' option to add a prefix before am keywords For example, I advice to do 'import photoshop.api.action_manager as am'. Then jprint(prefix='am') should be helpful. Signed-off-by: TsXor --- photoshop/api/action_manager/jprint.py | 19 ++++++++++++++++++- .../action_manager/js_converter/__main__.py | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/photoshop/api/action_manager/jprint.py b/photoshop/api/action_manager/jprint.py index fe263b8d..b6a57956 100644 --- a/photoshop/api/action_manager/jprint.py +++ b/photoshop/api/action_manager/jprint.py @@ -1,7 +1,21 @@ #Format it like json! #Just literal processing. -def jformat(astr, indent=4): +def jformat(astr, indent=4, prefix=None): + all_am_keywords = [ # noqa: F405 + 'str2id', + 'id2str', + 'Enumerated', + 'TypeID', + 'UnitDouble', + 'Identifier', + 'Index', + 'Offset', + 'ReferenceKey', + 'dumpjs', + 'jprint', + 'jformat', + ] nstr = '' indent_level = 0 insmall = False @@ -36,6 +50,9 @@ def jformat(astr, indent=4): indent_level -= 1 char = '\n'+' '*(indent*indent_level)+char nstr += char + if not prefix is None: + for kwd in all_am_keywords: + nstr = nstr.replace(kwd, prefix+'.'+kwd) return nstr def jprint(obj, indent=4): diff --git a/photoshop/api/action_manager/js_converter/__main__.py b/photoshop/api/action_manager/js_converter/__main__.py index edfbabd9..34595e3e 100644 --- a/photoshop/api/action_manager/js_converter/__main__.py +++ b/photoshop/api/action_manager/js_converter/__main__.py @@ -9,4 +9,4 @@ print('Operation:') print(obj[0]) print('Descriptor:') - jprint(obj[1]) \ No newline at end of file + jprint(obj[1], prefix='am') \ No newline at end of file From 520018e8752ee2002457ae0aa33a963b75b9a0d8 Mon Sep 17 00:00:00 2001 From: TsXor Date: Sun, 4 Sep 2022 23:04:40 +0800 Subject: [PATCH 05/13] docs(examples): change all examples which uses Action Manager to make use of my submodule In fact, why use those ****s born by ScriptListener Plug-in which is totally unreadable? Signed-off-by: TsXor --- examples/apply_crystallize_filter_action.py | 16 ++-- examples/convert_smartobject_to_layer.py | 18 ++-- examples/emboss_action.py | 91 ++++++++++----------- examples/enable_generator.py | 2 +- examples/import_image_as_layer.py | 11 ++- examples/replace_images.py | 15 ++-- examples/session_smart_sharpen.py | 44 +++------- examples/smart_sharpen.py | 42 +++------- 8 files changed, 104 insertions(+), 135 deletions(-) diff --git a/examples/apply_crystallize_filter_action.py b/examples/apply_crystallize_filter_action.py index a22bf274..7d4b38a8 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,12 @@ 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..c5ebfc28 100644 --- a/examples/convert_smartobject_to_layer.py +++ b/examples/convert_smartobject_to_layer.py @@ -1,19 +1,23 @@ """Convert Smart object to artLayer.""" +# Import builtin 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..5869bdf1 100644 --- a/examples/emboss_action.py +++ b/examples/emboss_action.py @@ -1,62 +1,55 @@ # 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 + } + } + } + } + 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..49c39ceb 100644 --- a/examples/import_image_as_layer.py +++ b/examples/import_image_as_layer.py @@ -2,10 +2,13 @@ # Import local modules from photoshop import Session +import pathlib 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) + import_dict = { + '_classID':None, + 'null':pathlib.Path("your/image/path") # replace it with your own path here + } + import_desc = ps.ActionDescriptor.load(import_dict) + ps.app.executeAction(am.str2id("Plc "), import_desc) # `Plc` need one space in here. diff --git a/examples/replace_images.py b/examples/replace_images.py index 24642cdb..66cd28f6 100644 --- a/examples/replace_images.py +++ b/examples/replace_images.py @@ -1,5 +1,8 @@ """Replace the image of the current active layer with a new image.""" +# Import builtin modules +import pathlib + # Import third-party modules import examples._psd_files as psd # Import from examples. @@ -14,12 +17,12 @@ 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..ff279953 100644 --- a/examples/session_smart_sharpen.py +++ b/examples/session_smart_sharpen.py @@ -19,35 +19,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) - - SmartSharpen(300, 2.0, 20) + ss_dict = { + '_classID':None, + 'presetKindType':am.Enumerated(type='presetKindType', value='presetKindCustom'), + '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) \ No newline at end of file diff --git a/examples/smart_sharpen.py b/examples/smart_sharpen.py index 9fc3e5b3..f850d05e 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,15 @@ 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) - - -SmartSharpen(300, 2.0, 20) + ss_dict = { + '_classID':None, + 'presetKindType':am.Enumerated(type='presetKindType', value='presetKindCustom'), + '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) \ No newline at end of file From 37bdb4aaa9db9ae7e84e477938e050801706b66f Mon Sep 17 00:00:00 2001 From: TsXor Date: Sun, 4 Sep 2022 23:50:18 +0800 Subject: [PATCH 06/13] docs(action_manager): all equipped with doc strings Signed-off-by: TsXor --- photoshop/api/_actionmanager_type_binder.py | 3 +++ photoshop/api/action_manager/_main_types/_type_mapper.py | 4 ++++ .../api/action_manager/_main_types/action_descriptor.py | 7 ++++++- .../_main_types/action_descriptor_iterator.py | 2 ++ photoshop/api/action_manager/_main_types/action_list.py | 7 ++++++- .../api/action_manager/_main_types/action_list_iterator.py | 2 ++ .../api/action_manager/_main_types/action_reference.py | 7 ++++++- .../_main_types/action_reference_iterator.py | 2 ++ .../api/action_manager/desc_value_types/enumerated.py | 1 + photoshop/api/action_manager/desc_value_types/typeid.py | 1 + .../api/action_manager/desc_value_types/unitdouble.py | 1 + photoshop/api/action_manager/jprint.py | 6 ++++-- photoshop/api/action_manager/js_converter/convert.py | 5 +++++ photoshop/api/action_manager/js_converter/injection_js.py | 6 +++++- photoshop/api/action_manager/js_converter/node_execjs.py | 2 ++ photoshop/api/action_manager/ref_form_types/_marker.py | 3 +++ photoshop/api/action_manager/ref_form_types/identifier.py | 4 ++++ photoshop/api/action_manager/ref_form_types/index.py | 4 ++++ photoshop/api/action_manager/ref_form_types/offset.py | 4 ++++ .../api/action_manager/ref_form_types/referencekey.py | 3 +++ photoshop/api/action_manager/utils.py | 3 +++ 21 files changed, 71 insertions(+), 6 deletions(-) diff --git a/photoshop/api/_actionmanager_type_binder.py b/photoshop/api/_actionmanager_type_binder.py index c1e872e2..836d7aca 100644 --- a/photoshop/api/_actionmanager_type_binder.py +++ b/photoshop/api/_actionmanager_type_binder.py @@ -19,6 +19,7 @@ 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): @@ -45,6 +46,7 @@ def getReference(self, key: int) -> "ActionReference": 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: @@ -67,6 +69,7 @@ def getReference(self, index: int) -> "ActionReference": 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: diff --git a/photoshop/api/action_manager/_main_types/_type_mapper.py b/photoshop/api/action_manager/_main_types/_type_mapper.py index 3b715948..2f689217 100644 --- a/photoshop/api/action_manager/_main_types/_type_mapper.py +++ b/photoshop/api/action_manager/_main_types/_type_mapper.py @@ -1,3 +1,7 @@ +'''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.''' + from ..desc_value_types import * from ..ref_form_types import * from ..utils import * diff --git a/photoshop/api/action_manager/_main_types/action_descriptor.py b/photoshop/api/action_manager/_main_types/action_descriptor.py index 2723edee..b4803a7b 100644 --- a/photoshop/api/action_manager/_main_types/action_descriptor.py +++ b/photoshop/api/action_manager/_main_types/action_descriptor.py @@ -5,7 +5,9 @@ from abc import ABC, abstractclassmethod class ActionDescriptor: - '''A vessel for my extra utils.''' + '''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 @@ -23,11 +25,13 @@ def load(cls, adict: dict, namespace: dict): # pass globals() for namespace 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) @@ -44,6 +48,7 @@ def __contains__(self, key): return key in keys def dump(self) -> dict: + '''Convert an ActionDescriptor to a python object.''' #This is a dict comprehension. ddict = {'_classID':self.classID} ddict.update({ diff --git a/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py index c00a4395..9d5eee51 100644 --- a/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py @@ -1,6 +1,8 @@ from ..utils import * class ActionDescriptor_Iterator: + '''An iterator. You don't need to initialize it manually.''' + def __init__(self, psobj: 'ActionDescriptor'): self.curobj = psobj self.n = -1 diff --git a/photoshop/api/action_manager/_main_types/action_list.py b/photoshop/api/action_manager/_main_types/action_list.py index 126eeafb..c5c85b43 100644 --- a/photoshop/api/action_manager/_main_types/action_list.py +++ b/photoshop/api/action_manager/_main_types/action_list.py @@ -5,7 +5,9 @@ from abc import ABC, abstractclassmethod class ActionList(ABC): - '''A vessel for my extra utils.''' + '''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 @@ -26,10 +28,12 @@ def dtype(self) -> str: 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 assert True if (dtype := self.dtype) is None else dtype == typestr, \ @@ -44,6 +48,7 @@ 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 = [ ( diff --git a/photoshop/api/action_manager/_main_types/action_list_iterator.py b/photoshop/api/action_manager/_main_types/action_list_iterator.py index f3df0ceb..68a62bf3 100644 --- a/photoshop/api/action_manager/_main_types/action_list_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_list_iterator.py @@ -1,6 +1,8 @@ from typing import Any class ActionList_Iterator: + '''An iterator. You don't need to initialize it manually.''' + def __init__(self, psobj: 'ActionList'): self.curobj = psobj self.n = -1 diff --git a/photoshop/api/action_manager/_main_types/action_reference.py b/photoshop/api/action_manager/_main_types/action_reference.py index bae2ae81..0cadf6d0 100644 --- a/photoshop/api/action_manager/_main_types/action_reference.py +++ b/photoshop/api/action_manager/_main_types/action_reference.py @@ -3,7 +3,9 @@ from abc import ABC, abstractclassmethod class ActionReference(ABC): - '''A vessel for my extra utils.''' + '''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): @@ -19,6 +21,7 @@ def load(cls, alist: list): 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: @@ -28,6 +31,7 @@ def uget(self, index: int) -> ReferenceKey: 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) @@ -35,6 +39,7 @@ def uput(self, rkey: ReferenceKey): put_func(*args) def dump(self) -> list: + '''Convert an ActionReference to a python object.''' target = self tlist = ['!ref'] tlist.extend([elem for elem in 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 index f5acf8a2..cb487a37 100644 --- a/photoshop/api/action_manager/_main_types/action_reference_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_reference_iterator.py @@ -1,6 +1,8 @@ from ..ref_form_types import ReferenceKey class ActionReference_Iterator: + '''An iterator. You don't need to initialize it manually.''' + def __init__(self, psobj: 'ActionReference'): self.curobj = psobj self.init = True diff --git a/photoshop/api/action_manager/desc_value_types/enumerated.py b/photoshop/api/action_manager/desc_value_types/enumerated.py index dcee9193..27acc6ec 100644 --- a/photoshop/api/action_manager/desc_value_types/enumerated.py +++ b/photoshop/api/action_manager/desc_value_types/enumerated.py @@ -4,6 +4,7 @@ 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)) diff --git a/photoshop/api/action_manager/desc_value_types/typeid.py b/photoshop/api/action_manager/desc_value_types/typeid.py index beeff682..c35fa42e 100644 --- a/photoshop/api/action_manager/desc_value_types/typeid.py +++ b/photoshop/api/action_manager/desc_value_types/typeid.py @@ -4,6 +4,7 @@ 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)) diff --git a/photoshop/api/action_manager/desc_value_types/unitdouble.py b/photoshop/api/action_manager/desc_value_types/unitdouble.py index a6fca43e..e25dbf63 100644 --- a/photoshop/api/action_manager/desc_value_types/unitdouble.py +++ b/photoshop/api/action_manager/desc_value_types/unitdouble.py @@ -4,6 +4,7 @@ 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)) diff --git a/photoshop/api/action_manager/jprint.py b/photoshop/api/action_manager/jprint.py index b6a57956..38c41530 100644 --- a/photoshop/api/action_manager/jprint.py +++ b/photoshop/api/action_manager/jprint.py @@ -1,7 +1,8 @@ -#Format it like json! -#Just literal processing. +'''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', @@ -56,4 +57,5 @@ def jformat(astr, indent=4, prefix=None): return nstr def jprint(obj, indent=4): + '''Print formatted repr of an object.''' print(jformat(repr(obj), indent=indent)) \ No newline at end of file diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index 7a5b27e1..24652e41 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -1,3 +1,8 @@ +'''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)''' + from .node_execjs import execjs from .injection_js import injection from ..utils import str2id, id2str, str2hash, hash2str diff --git a/photoshop/api/action_manager/js_converter/injection_js.py b/photoshop/api/action_manager/js_converter/injection_js.py index bfd5667d..08d91374 100644 --- a/photoshop/api/action_manager/js_converter/injection_js.py +++ b/photoshop/api/action_manager/js_converter/injection_js.py @@ -1,4 +1,8 @@ -#You may turn on syntax highlighting for js here. +'''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 { diff --git a/photoshop/api/action_manager/js_converter/node_execjs.py b/photoshop/api/action_manager/js_converter/node_execjs.py index 357cd694..96352074 100644 --- a/photoshop/api/action_manager/js_converter/node_execjs.py +++ b/photoshop/api/action_manager/js_converter/node_execjs.py @@ -1,3 +1,5 @@ +'''Defines execjs, a function to run js in Node.js''' + import os,re,subprocess # Node.js check diff --git a/photoshop/api/action_manager/ref_form_types/_marker.py b/photoshop/api/action_manager/ref_form_types/_marker.py index 7b6556dc..c095b5bf 100644 --- a/photoshop/api/action_manager/ref_form_types/_marker.py +++ b/photoshop/api/action_manager/ref_form_types/_marker.py @@ -1,3 +1,6 @@ +'''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 diff --git a/photoshop/api/action_manager/ref_form_types/identifier.py b/photoshop/api/action_manager/ref_form_types/identifier.py index c1e9cfb0..82906978 100644 --- a/photoshop/api/action_manager/ref_form_types/identifier.py +++ b/photoshop/api/action_manager/ref_form_types/identifier.py @@ -1,3 +1,7 @@ +'''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') \ No newline at end of file diff --git a/photoshop/api/action_manager/ref_form_types/index.py b/photoshop/api/action_manager/ref_form_types/index.py index 170e18c2..9fbe6977 100644 --- a/photoshop/api/action_manager/ref_form_types/index.py +++ b/photoshop/api/action_manager/ref_form_types/index.py @@ -1,3 +1,7 @@ +'''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') \ No newline at end of file diff --git a/photoshop/api/action_manager/ref_form_types/offset.py b/photoshop/api/action_manager/ref_form_types/offset.py index 0db7564b..76753162 100644 --- a/photoshop/api/action_manager/ref_form_types/offset.py +++ b/photoshop/api/action_manager/ref_form_types/offset.py @@ -1,3 +1,7 @@ +'''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') \ No newline at end of file diff --git a/photoshop/api/action_manager/ref_form_types/referencekey.py b/photoshop/api/action_manager/ref_form_types/referencekey.py index def5a0a6..f6fc7260 100644 --- a/photoshop/api/action_manager/ref_form_types/referencekey.py +++ b/photoshop/api/action_manager/ref_form_types/referencekey.py @@ -1,3 +1,6 @@ +'''Defines class ReferenceKey. It handles type mapping in ActionReference. +You can initialize it with 2 arguments: desiredclass, value.''' + from ..utils import * from ..desc_value_types import TypeID, Enumerated from photoshop.api.enumerations import ReferenceFormType diff --git a/photoshop/api/action_manager/utils.py b/photoshop/api/action_manager/utils.py index 08254d2c..bf711984 100644 --- a/photoshop/api/action_manager/utils.py +++ b/photoshop/api/action_manager/utils.py @@ -1,8 +1,11 @@ +'''TypeID conversion utilities of this submodule.''' + from photoshop.api._core import Photoshop __all__ = ['str2id', 'id2str'] class app(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) From b2b2b0e464b323897a7e3e076d6fa1a33f4575c3 Mon Sep 17 00:00:00 2001 From: TsXor Date: Sun, 4 Sep 2022 23:57:57 +0800 Subject: [PATCH 07/13] fix(action_manager): patch to support py37 change patching to (try eval + except), and now run black -l 80 to avoid hound warning There will be lots of violations in hound's eye which I cannot find out locally because hound's linter is not up to date. I will have to do some shattered commits, and for these commits, I will name them hop (hound oriented programming). Signed-off-by: TsXor --- examples/apply_crystallize_filter_action.py | 5 +- examples/convert_smartobject_to_layer.py | 8 +- examples/emboss_action.py | 55 +++--- examples/import_image_as_layer.py | 14 +- examples/replace_images.py | 8 +- examples/session_smart_sharpen.py | 17 +- examples/smart_sharpen.py | 17 +- photoshop/api/_actionmanager_type_binder.py | 24 ++- photoshop/api/action_manager/__init__.py | 42 +++-- .../_main_types/_type_mapper.py | 109 ++++++------ .../_main_types/action_descriptor.py | 122 +++++++------ .../_main_types/action_descriptor_iterator.py | 31 ++-- .../action_manager/_main_types/action_list.py | 122 +++++++------ .../_main_types/action_list_iterator.py | 28 +-- .../_main_types/action_reference.py | 114 ++++++------ .../_main_types/action_reference_iterator.py | 37 ++-- .../desc_value_types/__init__.py | 3 +- .../desc_value_types/enumerated.py | 31 ++-- .../action_manager/desc_value_types/typeid.py | 27 +-- .../desc_value_types/unitdouble.py | 31 ++-- photoshop/api/action_manager/jprint.py | 116 ++++++------ .../action_manager/js_converter/__init__.py | 5 +- .../action_manager/js_converter/__main__.py | 22 ++- .../action_manager/js_converter/convert.py | 166 ++++++++++-------- .../js_converter/injection_js.py | 10 +- .../js_converter/node_execjs.py | 28 +-- .../action_manager/ref_form_types/__init__.py | 3 +- .../action_manager/ref_form_types/_marker.py | 32 ++-- .../ref_form_types/identifier.py | 7 +- .../action_manager/ref_form_types/index.py | 7 +- .../action_manager/ref_form_types/offset.py | 7 +- .../ref_form_types/referencekey.py | 112 ++++++------ photoshop/api/action_manager/utils.py | 71 ++++---- 33 files changed, 777 insertions(+), 654 deletions(-) diff --git a/examples/apply_crystallize_filter_action.py b/examples/apply_crystallize_filter_action.py index 7d4b38a8..5303a6e0 100644 --- a/examples/apply_crystallize_filter_action.py +++ b/examples/apply_crystallize_filter_action.py @@ -31,10 +31,7 @@ active_document.activeLayer = active_document.layerSets.item(len(nLayerSets)).artLayers.item(len(nArtLayers)) def applyCrystallize(cellSize): - filter_dict = { - '_classID':None, - 'ClSz':cellSize - } + filter_dict = {"_classID": None, "ClSz": cellSize} filter_desc = ps.ActionDescriptor.load(filter_dict) ps.app.executeAction(am.str2id("Crst"), filter_desc) diff --git a/examples/convert_smartobject_to_layer.py b/examples/convert_smartobject_to_layer.py index c5ebfc28..7d81f6ab 100644 --- a/examples/convert_smartobject_to_layer.py +++ b/examples/convert_smartobject_to_layer.py @@ -1,6 +1,6 @@ """Convert Smart object to artLayer.""" -# Import builtin modules +# Import built-in modules from textwrap import dedent # Import local modules @@ -10,10 +10,12 @@ # example 1 with Session() as ps: - js = dedent(""" + js = dedent( + """ var idplacedLayerConvertToLayers = stringIDToTypeID( "placedLayerConvertToLayers" ); executeAction( idplacedLayerConvertToLayers, undefined, DialogModes.NO ); - """) + """ + ) ps.app.doJavaScript(js) # example 2 diff --git a/examples/emboss_action.py b/examples/emboss_action.py index 5869bdf1..2dbc7626 100644 --- a/examples/emboss_action.py +++ b/examples/emboss_action.py @@ -2,54 +2,47 @@ 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. exec_dict = { - '_classID':None, - 'null':[ - '!ref', - am.ReferenceKey(desiredclass='action', value='Sepia Toning (layer)'), - am.ReferenceKey(desiredclass='actionSet', value='Default Actions') - ] + "_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) + app.executeAction(am.str2id("Ply "), exec_desc, ps.DialogModes.DisplayNoDialogs) # Create solid color fill layer. 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 - } - } - } + "_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) + app.executeAction(am.str2id("Mk "), filledlayer_desc, ps.DialogModes.DisplayNoDialogs) # Select mask. selectmask_dict = { - '_classID':None, - 'null':[ - '!ref', - am.ReferenceKey(desiredclass='channel', value=am.Enumerated(type='channel',value='mask')) + "_classID": None, + "null": [ + "!ref", + am.ReferenceKey(desiredclass="channel", value=am.Enumerated(type="channel", value="mask")), ], - 'makeVisible':False + "makeVisible": False, } selectmask_desc = ps.ActionDescriptor.load(selectmask_dict) - app.executeAction(am.str2id('slct'), selectmask_desc, ps.DialogModes.DisplayNoDialogs) + app.executeAction(am.str2id("slct"), selectmask_desc, ps.DialogModes.DisplayNoDialogs) app.activeDocument.activeLayer.invert() diff --git a/examples/import_image_as_layer.py b/examples/import_image_as_layer.py index 49c39ceb..45468a73 100644 --- a/examples/import_image_as_layer.py +++ b/examples/import_image_as_layer.py @@ -1,14 +1,16 @@ """Import a image as a artLayer.""" +# Import built-in modules +import pathlib + # Import local modules from photoshop import Session -import pathlib +import photoshop.api.action_manager as am with Session(action="new_document") as ps: - import_dict = { - '_classID':None, - 'null':pathlib.Path("your/image/path") # replace it with your own path here - } + # replace it with your own path here + import_dict = {"_classID": None, "null": pathlib.Path("your/image/path")} import_desc = ps.ActionDescriptor.load(import_dict) - ps.app.executeAction(am.str2id("Plc "), import_desc) # `Plc` need one space in here. + ps.app.executeAction(am.str2id("Plc "), import_desc) + # length of charID should always be 4, if not, pad with spaces diff --git a/examples/replace_images.py b/examples/replace_images.py index 66cd28f6..cec73ad0 100644 --- a/examples/replace_images.py +++ b/examples/replace_images.py @@ -1,6 +1,6 @@ """Replace the image of the current active layer with a new image.""" -# Import builtin modules +# Import built-in modules import pathlib # Import third-party modules @@ -8,6 +8,7 @@ # Import local modules from photoshop import Session +import photoshop.api.action_manager as am PSD_FILE = psd.get_psd_files() @@ -17,10 +18,7 @@ active_layer = ps.active_document.activeLayer bounds = active_layer.bounds print(f"current layer {active_layer.name}: {bounds}") - input_dict = { - '_classID':None, - 'null':pathlib.Path(PSD_FILE["red_100x200.png"]) - } + 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) diff --git a/examples/session_smart_sharpen.py b/examples/session_smart_sharpen.py index ff279953..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() @@ -20,14 +21,14 @@ def SmartSharpen(inAmount, inRadius, inNoise): ss_dict = { - '_classID':None, - 'presetKindType':am.Enumerated(type='presetKindType', value='presetKindCustom'), - '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') + "_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) + ps.app.ExecuteAction(am.str2id("smartSharpen"), ss_desc) - SmartSharpen(300, 2.0, 20) \ No newline at end of file + SmartSharpen(300, 2.0, 20) diff --git a/examples/smart_sharpen.py b/examples/smart_sharpen.py index f850d05e..d92a158a 100644 --- a/examples/smart_sharpen.py +++ b/examples/smart_sharpen.py @@ -27,14 +27,15 @@ def SmartSharpen(inAmount, inRadius, inNoise): ss_dict = { - '_classID':None, - 'presetKindType':am.Enumerated(type='presetKindType', value='presetKindCustom'), - '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') + "_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) + app.ExecuteAction(am.str2id("smartSharpen"), ss_desc) -SmartSharpen(300, 2.0, 20) \ No newline at end of file + +SmartSharpen(300, 2.0, 20) diff --git a/photoshop/api/_actionmanager_type_binder.py b/photoshop/api/_actionmanager_type_binder.py index 836d7aca..120cfc27 100644 --- a/photoshop/api/_actionmanager_type_binder.py +++ b/photoshop/api/_actionmanager_type_binder.py @@ -7,19 +7,25 @@ # Import local modules from photoshop.api.action_descriptor import ActionDescriptor as AD_proto -from photoshop.api.action_manager._main_types.action_descriptor import ActionDescriptor as AD_utils_proto from photoshop.api.action_list import ActionList as AL_proto -from photoshop.api.action_manager._main_types.action_list import ActionList as AL_utils_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.action_manager._main_types.action_reference import ActionReference as AR_utils_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''' + 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): @@ -45,8 +51,8 @@ def getReference(self, key: int) -> "ActionReference": class ActionList(AL_proto, AL_utils_proto): @classmethod - def load(cls, alist: list) -> 'ActionList': - '''Convert a python object to an ActionList''' + def load(cls, alist: list) -> "ActionList": + """Convert a python object to an ActionList""" return super().load(alist, globals()) def getType(self, index: int) -> DescValueType: @@ -68,8 +74,8 @@ def getReference(self, index: int) -> "ActionReference": class ActionReference(AR_proto, AR_utils_proto): @classmethod - def load(cls, adict: dict) -> 'ActionReference': - '''Convert a python object to an ActionReference''' + def load(cls, adict: dict) -> "ActionReference": + """Convert a python object to an ActionReference""" return super().load(adict) def getForm(self) -> ReferenceFormType: diff --git a/photoshop/api/action_manager/__init__.py b/photoshop/api/action_manager/__init__.py index 3af810db..b9ab3d35 100644 --- a/photoshop/api/action_manager/__init__.py +++ b/photoshop/api/action_manager/__init__.py @@ -1,20 +1,28 @@ -from .ref_form_types import * -from .desc_value_types import * -from .utils import * +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 .jprint import * +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', -] \ No newline at end of file + "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 index 2f689217..37016520 100644 --- a/photoshop/api/action_manager/_main_types/_type_mapper.py +++ b/photoshop/api/action_manager/_main_types/_type_mapper.py @@ -1,68 +1,73 @@ -'''Maybe the core of this submodule. +"""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.''' +This module is INTERNAL. You should not import functions from it.""" -from ..desc_value_types import * -from ..ref_form_types import * -from ..utils import * +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'] + +__all__ = ["unpack", "pack", "parsetype"] pytype2str = { - bool:'Boolean', - int:'Integer', - float:'Double', - str:'String', - Enumerated:'Enumerated', - UnitDouble:'UnitDouble', - TypeID:'Class', - 'ActionDescriptor':'Object', - 'ActionList':'List', - 'ActionReference':'Reference', + bool: "Boolean", + int: "Integer", + float: "Double", + str: "String", + Enumerated: "Enumerated", + UnitDouble: "UnitDouble", + TypeID: "Class", + "ActionDescriptor": "Object", + "ActionList": "List", + "ActionReference": "Reference", } str2pytype = { - 'Enumerated':Enumerated, - 'UnitDouble':UnitDouble, - 'Class':TypeID, + "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: - args = (val,) - return (typestr, args) + 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] - 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 + valtype = obj.getType(index) + typestr = str(valtype)[14:-4] + 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' + 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 = 'ActionList' - else: - dtype = 'others' - return dtype \ No newline at end of file + 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 index b4803a7b..1a16e715 100644 --- a/photoshop/api/action_manager/_main_types/action_descriptor.py +++ b/photoshop/api/action_manager/_main_types/action_descriptor.py @@ -1,68 +1,74 @@ -from ._type_mapper import * -from ..utils import * -from .action_descriptor_iterator import ActionDescriptor_Iterator +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod from typing import Any -from abc import ABC, abstractclassmethod -class ActionDescriptor: - '''A vessel for my extra utils. - You should not use, and cannot initialize it - because it is an abstract class.''' +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 - @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 - v = v \ - if (dtype := parsetype(v)) == 'others' \ - else namespace[dtype].load(v) - new.uput(k,v) - 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 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 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 __len__(self): - return self.count + def __iter__(self) -> ActionDescriptor_Iterator: + return ActionDescriptor_Iterator(self) - 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 __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} - ddict.update({ - key:( - value.dump() \ - if hasattr(value := self.uget(key), 'dump') \ - else value - ) for key in self - }) - return ddict + 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) \ No newline at end of file + 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 index 9d5eee51..e2ee3303 100644 --- a/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_descriptor_iterator.py @@ -1,20 +1,21 @@ -from ..utils import * +from ..utils import id2str + class ActionDescriptor_Iterator: - '''An iterator. You don't need to initialize it manually.''' + """An iterator. You don't need to initialize it manually.""" - def __init__(self, psobj: 'ActionDescriptor'): - self.curobj = psobj - self.n = -1 + 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: - raise StopIteration - keystr = id2str(keyid) - return keystr + 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 \ No newline at end of file + 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 index c5c85b43..fa56588d 100644 --- a/photoshop/api/action_manager/_main_types/action_list.py +++ b/photoshop/api/action_manager/_main_types/action_list.py @@ -1,60 +1,68 @@ -from ._type_mapper import * -from ..utils import * -from .action_list_iterator import ActionList_Iterator +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod from typing import Any -from abc import ABC, abstractclassmethod + +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: - v = v \ - if (dtype := parsetype(v)) == 'others' \ - else namespace[dtype].load(v) - new.uput(v) - 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 - assert True if (dtype := self.dtype) is None else 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 \ No newline at end of file + """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 index 68a62bf3..b269ea56 100644 --- a/photoshop/api/action_manager/_main_types/action_list_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_list_iterator.py @@ -1,19 +1,21 @@ +# Import built-in modules from typing import Any + class ActionList_Iterator: - '''An iterator. You don't need to initialize it manually.''' + """An iterator. You don't need to initialize it manually.""" - def __init__(self, psobj: 'ActionList'): - self.curobj = psobj - self.n = -1 + 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: - raise StopIteration() - return elem + 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 \ No newline at end of file + 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 index 0cadf6d0..d551ba1d 100644 --- a/photoshop/api/action_manager/_main_types/action_reference.py +++ b/photoshop/api/action_manager/_main_types/action_reference.py @@ -1,58 +1,62 @@ -from .action_reference_iterator import ActionReference_Iterator +# Import built-in modules +from abc import ABC +from abc import abstractclassmethod + from ..ref_form_types import ReferenceKey -from abc import ABC, abstractclassmethod +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: - 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.''' - target = self - 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: - rlen -= 1; break - return rlen - - def __iter__(self) -> ActionReference_Iterator: - return ActionReference_Iterator(self) \ No newline at end of file + """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 index cb487a37..2f93ac29 100644 --- a/photoshop/api/action_manager/_main_types/action_reference_iterator.py +++ b/photoshop/api/action_manager/_main_types/action_reference_iterator.py @@ -1,24 +1,25 @@ from ..ref_form_types import ReferenceKey + class ActionReference_Iterator: - '''An iterator. You don't need to initialize it manually.''' + """An iterator. You don't need to initialize it manually.""" - def __init__(self, psobj: 'ActionReference'): - self.curobj = psobj - self.init = True - self.n = -1 + 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: - raise StopIteration - return ReferenceKey._packer(self.curobj) + 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 \ No newline at end of file + 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 index e8342afa..6537c6f6 100644 --- a/photoshop/api/action_manager/desc_value_types/__init__.py +++ b/photoshop/api/action_manager/desc_value_types/__init__.py @@ -2,4 +2,5 @@ from .typeid import TypeID from .unitdouble import UnitDouble -__all__ = ['Enumerated', 'TypeID', 'UnitDouble'] \ No newline at end of file + +__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 index 27acc6ec..97607b2e 100644 --- a/photoshop/api/action_manager/desc_value_types/enumerated.py +++ b/photoshop/api/action_manager/desc_value_types/enumerated.py @@ -1,16 +1,23 @@ -from ..utils import * +# Import built-in modules from collections import namedtuple -Enumerated_proto = namedtuple('Enumerated_proto', ['type', 'value']) +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) \ No newline at end of file + """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 index c35fa42e..0fc10a19 100644 --- a/photoshop/api/action_manager/desc_value_types/typeid.py +++ b/photoshop/api/action_manager/desc_value_types/typeid.py @@ -1,14 +1,21 @@ -from ..utils import * +# Import built-in modules from collections import namedtuple -TypeID_proto = namedtuple('TypeID_proto', ['string']) +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,) \ No newline at end of file + """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 index e25dbf63..0d0ee5a9 100644 --- a/photoshop/api/action_manager/desc_value_types/unitdouble.py +++ b/photoshop/api/action_manager/desc_value_types/unitdouble.py @@ -1,16 +1,23 @@ -from ..utils import * +# Import built-in modules from collections import namedtuple -UnitDouble_proto = namedtuple('UnitDouble_proto', ['unit', 'double']) +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) + """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 index 38c41530..fe1daf4f 100644 --- a/photoshop/api/action_manager/jprint.py +++ b/photoshop/api/action_manager/jprint.py @@ -1,61 +1,63 @@ -'''Format a repr() string like json. -This is just literal processing.''' +"""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 not prefix is None: - for kwd in all_am_keywords: - nstr = nstr.replace(kwd, prefix+'.'+kwd) - return nstr + """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): - '''Print formatted repr of an object.''' - print(jformat(repr(obj), indent=indent)) \ No newline at end of file + """Print formatted repr of an object.""" + print(jformat(repr(obj), indent=indent)) diff --git a/photoshop/api/action_manager/js_converter/__init__.py b/photoshop/api/action_manager/js_converter/__init__.py index 73ff17be..b12651f5 100644 --- a/photoshop/api/action_manager/js_converter/__init__.py +++ b/photoshop/api/action_manager/js_converter/__init__.py @@ -1 +1,4 @@ -from .convert import dump \ No newline at end of file +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 index 34595e3e..d4e3709b 100644 --- a/photoshop/api/action_manager/js_converter/__main__.py +++ b/photoshop/api/action_manager/js_converter/__main__.py @@ -1,12 +1,16 @@ +# Import built-in modules import sys -from photoshop.api.action_manager.js_converter import dump + +# 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') \ No newline at end of file +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 index 24652e41..9d1ccba5 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -1,96 +1,112 @@ -'''Defines functions to parse information got on the js side into loadable form. +"""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)''' +am.dumpjs(some_js_code)""" -from .node_execjs import execjs -from .injection_js import injection -from ..utils import str2id, id2str, str2hash, hash2str -from ..desc_value_types import * -from ..ref_form_types import * +# Import built-in modules import json +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 + 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 + 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']), - 'ActionDescriptor':lambda x: parsedict(x), - 'ActionReference':lambda x: parseref(x), - 'ActionList':lambda x: parselist(x), - } + "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"]), + "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']))), - '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'])), - } + "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 not '_classID' 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 + 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 + 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'] - plist.extend( - [( - str2refgetpacker[val['type']](e) \ - if type(val := e['Value']) == dict \ - else str2refgetpacker['default'](e)\ - )for e in d2l] - ) - return plist + d2l = [tdict[str(i)] for i in range(tdict["len"])] + plist = ["!ref"] + # py37 compat + try: + exec( + """ext = [(str2refgetpacker[val["type"]](e) """ + + """if type(val := e["Value"]) == dict """ + + """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) + 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 - \ No newline at end of file + 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 index 08d91374..6d2c07a4 100644 --- a/photoshop/api/action_manager/js_converter/injection_js.py +++ b/photoshop/api/action_manager/js_converter/injection_js.py @@ -1,9 +1,11 @@ -'''Defines injection, a variable which contains js code. +# 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.''' +You may turn on syntax highlighting for js here.""" -injection = ''' +injection = """ class UnitDouble { constructor(unit,ndouble) { @@ -137,4 +139,4 @@ class ActionReference { console.log(execlogjson) console.log('END OF JSON') } -''' \ No newline at end of file +""" diff --git a/photoshop/api/action_manager/js_converter/node_execjs.py b/photoshop/api/action_manager/js_converter/node_execjs.py index 96352074..a8789a49 100644 --- a/photoshop/api/action_manager/js_converter/node_execjs.py +++ b/photoshop/api/action_manager/js_converter/node_execjs.py @@ -1,16 +1,22 @@ -'''Defines execjs, a function to run js in Node.js''' +"""Defines execjs, a function to run js in Node.js""" + +# Import built-in modules +import os +import re +import subprocess -import os,re,subprocess # Node.js check -nodestate = os.popen('node --version') -if not re.match('^v\d*\.\d*\.\d*',nodestate.read()): - raise RuntimeError('Please check if Node.js is installed to PATH!') +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 \ No newline at end of file + 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 index b15de167..6ff5b91d 100644 --- a/photoshop/api/action_manager/ref_form_types/__init__.py +++ b/photoshop/api/action_manager/ref_form_types/__init__.py @@ -3,4 +3,5 @@ from .offset import Offset from .referencekey import ReferenceKey -__all__ = ['Identifier', 'Index', 'Offset', 'ReferenceKey'] \ No newline at end of file + +__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 index c095b5bf..5a0c20b2 100644 --- a/photoshop/api/action_manager/ref_form_types/_marker.py +++ b/photoshop/api/action_manager/ref_form_types/_marker.py @@ -1,16 +1,20 @@ -'''Defines class marker. It is the class of Identifier, Index, Offset. -It is INTERNAL. You should not import or initialize it.''' +"""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: - return False \ No newline at end of file + 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 index 82906978..f716f935 100644 --- a/photoshop/api/action_manager/ref_form_types/identifier.py +++ b/photoshop/api/action_manager/ref_form_types/identifier.py @@ -1,7 +1,8 @@ -'''Defines a special object: Identifier. +"""Defines a special object: Identifier. You can give it a value by adding a number to it. -For example: id = Identifier+777''' +For example: id = Identifier+777""" from ._marker import marker -Identifier = marker('Identifier') \ No newline at end of file + +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 index 9fbe6977..0a119ff3 100644 --- a/photoshop/api/action_manager/ref_form_types/index.py +++ b/photoshop/api/action_manager/ref_form_types/index.py @@ -1,7 +1,8 @@ -'''Defines a special object: Index. +"""Defines a special object: Index. You can give it a value by adding a number to it. -For example: index = Index+1''' +For example: index = Index+1""" from ._marker import marker -Index = marker('Index') \ No newline at end of file + +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 index 76753162..8f11fef5 100644 --- a/photoshop/api/action_manager/ref_form_types/offset.py +++ b/photoshop/api/action_manager/ref_form_types/offset.py @@ -1,7 +1,8 @@ -'''Defines a special object: Offset. +"""Defines a special object: Offset. You can give it a value by adding a number to it. -For example: offset = Offset+12''' +For example: offset = Offset+12""" from ._marker import marker -Offset = marker('Offset') \ No newline at end of file + +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 index f6fc7260..ade2d258 100644 --- a/photoshop/api/action_manager/ref_form_types/referencekey.py +++ b/photoshop/api/action_manager/ref_form_types/referencekey.py @@ -1,58 +1,72 @@ -'''Defines class ReferenceKey. It handles type mapping in ActionReference. -You can initialize it with 2 arguments: desiredclass, value.''' +"""Defines class ReferenceKey. It handles type mapping in ActionReference. +You can initialize it with 2 arguments: desiredclass, value.""" -from ..utils import * -from ..desc_value_types import TypeID, Enumerated +# Import built-in modules +from collections import namedtuple + +# Import local modules from photoshop.api.enumerations import ReferenceFormType -from .identifier import Identifier -from .index import Index -from .offset import Offset + +from ..desc_value_types import Enumerated +from ..desc_value_types import TypeID +from ..utils import id2str +from ..utils import str2id from ._marker import marker -from collections import namedtuple + +# 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}, + **{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']) +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: - get_func = None - if ftype == 'Class': - v = None - elif ftype == 'Enumerated': - v = Enumerated(id2str(obj.getEnumeratedType()), id2str(obj.getEnumeratedValue())) - 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) \ No newline at end of file + @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 index bf711984..dd4e8eee 100644 --- a/photoshop/api/action_manager/utils.py +++ b/photoshop/api/action_manager/utils.py @@ -1,45 +1,56 @@ -'''TypeID conversion utilities of this submodule.''' +"""TypeID conversion utilities of this submodule.""" +# Import local modules from photoshop.api._core import Photoshop -__all__ = ['str2id', 'id2str'] + +__all__ = ["str2id", "id2str"] + class app(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) + """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 = app() + 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') + """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() + """Convert typeID to charID.""" + assert len(hex(x)) == 10 + return x.to_bytes(length=4, byteorder="big").decode() + def str2id(psstr: str) -> str: - '''Convert charID or stringID to typeID''' - assert type(psstr) == str - if len(psstr) == 4: - typeid = str2hash(psstr) - try: - restr = converter.id2str(psstr) - except: - restr = '' - if not restr: - typeid = converter.str2id(psstr) - else: - typeid = converter.str2id(psstr) - return typeid + """Convert charID or stringID to typeID""" + assert type(psstr) == str + if len(psstr) == 4: + 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 + def id2str(typeid: int) -> str: - '''Convert typeID to stringID''' - return converter.id2str(typeid) \ No newline at end of file + """Convert typeID to stringID""" + return converter.id2str(typeid) From 04e936238687a65f2c29698e6c998c6548fb5e47 Mon Sep 17 00:00:00 2001 From: TsXor Date: Thu, 8 Sep 2022 21:44:41 +0800 Subject: [PATCH 08/13] fix(action_manager,examples): fix support for Path(AliasType) and fix import picture example Signed-off-by: TsXor --- examples/import_image_as_layer.py | 25 +++++++++++++++---- photoshop/api/action_descriptor.py | 4 +-- photoshop/api/action_list.py | 4 +-- .../_main_types/_type_mapper.py | 5 ++++ photoshop/api/action_manager/jprint.py | 4 +-- .../action_manager/js_converter/convert.py | 2 ++ .../js_converter/injection_js.py | 9 +++++++ 7 files changed, 42 insertions(+), 11 deletions(-) diff --git a/examples/import_image_as_layer.py b/examples/import_image_as_layer.py index 45468a73..c841eac4 100644 --- a/examples/import_image_as_layer.py +++ b/examples/import_image_as_layer.py @@ -8,9 +8,24 @@ import photoshop.api.action_manager as am -with Session(action="new_document") as ps: - # replace it with your own path here - import_dict = {"_classID": None, "null": pathlib.Path("your/image/path")} +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) - ps.app.executeAction(am.str2id("Plc "), import_desc) - # length of charID should always be 4, if not, pad with spaces + app.executeAction(am.str2id("placeEvent"), import_desc) + + +with Session(action="new_document") as ps: + importfile(ps.app, pathlib.Path("your/image/path"), (-100, 456), (4470, 4463)) diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index 14423e4b..281575fd 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -129,7 +129,7 @@ def getObjectValue(self, key): """Implemented in _actionmanager_type_binder.ActionDescriptor""" pass - def getPath(self, key: int) -> pathlib.Path: + def getPath(self, key: int) -> pathlib.WindowsPath: """Gets the value of a key of type File.""" return pathlib.Path(self.app.getPath(key)) @@ -200,7 +200,7 @@ def putObject(self, key: int, classID: int, value): assert value.typename == "ActionDescriptor" self.app.putObject(key, classID, value) - def putPath(self, key: int, value: pathlib.Path): + def putPath(self, key: int, value: pathlib.WindowsPath): """Sets the value for a key whose type is path.""" self.app.putPath(key, str(value)) diff --git a/photoshop/api/action_list.py b/photoshop/api/action_list.py index 72b18b38..0c116ed9 100644 --- a/photoshop/api/action_list.py +++ b/photoshop/api/action_list.py @@ -91,7 +91,7 @@ def getObjectValue(self, index): """Implemented in _actionmanager_type_binder.ActionList""" pass - def getPath(self, index: int) -> pathlib.Path: + def getPath(self, index: int) -> pathlib.WindowsPath: """Gets the value of a list element of type File.""" return pathlib.Path(self.app.getPath(index)) @@ -162,7 +162,7 @@ def putObject(self, classID: int, value): assert value.typename == "ActionDescriptor" self.app.putObject(classID, value) - def putPath(self, value: pathlib.Path): + def putPath(self, value: pathlib.WindowsPath): """Appends a new value, a path.""" self.app.putPath(str(value)) diff --git a/photoshop/api/action_manager/_main_types/_type_mapper.py b/photoshop/api/action_manager/_main_types/_type_mapper.py index 37016520..36ff6346 100644 --- a/photoshop/api/action_manager/_main_types/_type_mapper.py +++ b/photoshop/api/action_manager/_main_types/_type_mapper.py @@ -2,6 +2,9 @@ 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 @@ -15,6 +18,7 @@ int: "Integer", float: "Double", str: "String", + pathlib.WindowsPath: "Path", Enumerated: "Enumerated", UnitDouble: "UnitDouble", TypeID: "Class", @@ -43,6 +47,7 @@ def unpack(val): 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. diff --git a/photoshop/api/action_manager/jprint.py b/photoshop/api/action_manager/jprint.py index fe1daf4f..6ba6a506 100644 --- a/photoshop/api/action_manager/jprint.py +++ b/photoshop/api/action_manager/jprint.py @@ -58,6 +58,6 @@ def jformat(astr, indent=4, prefix=None): return nstr -def jprint(obj, indent=4): +def jprint(obj, indent=4, prefix=None): """Print formatted repr of an object.""" - print(jformat(repr(obj), indent=indent)) + print(jformat(repr(obj), indent=indent, prefix=prefix)) diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index 9d1ccba5..e0b56958 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -5,6 +5,7 @@ # Import built-in modules import json +import pathlib from ..desc_value_types import Enumerated from ..desc_value_types import TypeID @@ -45,6 +46,7 @@ def unhead(string): "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), diff --git a/photoshop/api/action_manager/js_converter/injection_js.py b/photoshop/api/action_manager/js_converter/injection_js.py index 6d2c07a4..234d0d1f 100644 --- a/photoshop/api/action_manager/js_converter/injection_js.py +++ b/photoshop/api/action_manager/js_converter/injection_js.py @@ -30,6 +30,13 @@ class TypeID { } } +class File { + constructor(string) { + this.type = 'File' + this.string = string + } +} + function charIDToTypeID(chr) { return 'CharID_'+chr } @@ -49,6 +56,7 @@ class ActionDescriptor { 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 { @@ -63,6 +71,7 @@ class ActionList { 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 { From 2712d1c5dbc3eea26f896349df81dd49f7d7c050 Mon Sep 17 00:00:00 2001 From: TsXor Date: Sat, 10 Sep 2022 21:41:20 +0800 Subject: [PATCH 09/13] fix(action_manager/utils): 1. Requires initialization of app when needed instead of do it on import to pass import test. 2. Warns user when typeID collision happens. (And id2str will try to avoid giving user string that will cause collision.) typeID collision happens when a stringID and a charID share the same name. For example, charID 'From' -> stringID 'from' and charID 'from' -> stringID 'originalAddressAttr', so 'from' is a string that will cause collision. After scanning the cpp header "PITerminology.h" and "PIStringTerminology.h", I found 3 such strings and hardcoded them into utils.py. str2id() will not automatically correct user when user gives a collision string (because no one knows what he want it to be a stringID or an charID!), but will regard that string as stringID and throw a warning. Users can follow that warning's guide to fix error and suppress that warning. Signed-off-by: TsXor --- .../action_manager/js_converter/convert.py | 4 +- photoshop/api/action_manager/utils.py | 70 +++++++++++++++++-- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index e0b56958..429abbdf 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -85,8 +85,8 @@ def parseref(tdict): plist = ["!ref"] # py37 compat try: - exec( - """ext = [(str2refgetpacker[val["type"]](e) """ + ext = eval( + """[(str2refgetpacker[val["type"]](e) """ + """if type(val := e["Value"]) == dict """ + """else str2refgetpacker["default"](e)) for e in d2l]""" ) diff --git a/photoshop/api/action_manager/utils.py b/photoshop/api/action_manager/utils.py index dd4e8eee..faaf1676 100644 --- a/photoshop/api/action_manager/utils.py +++ b/photoshop/api/action_manager/utils.py @@ -1,5 +1,9 @@ """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 @@ -7,8 +11,9 @@ __all__ = ["str2id", "id2str"] -class app(Photoshop): - """Partially reimplement the Application class in this file to avoid circular import.""" +class AppAgent(Photoshop): + """Partially reimplement the Application class + in this file to avoid circular import.""" typename = "Application" @@ -19,7 +24,21 @@ def id2str(self, number: int) -> str: return self.app.typeIDToStringID(number) -converter = app() +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: @@ -35,10 +54,47 @@ def hash2str(x: int) -> str: 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) @@ -51,6 +107,12 @@ def str2id(psstr: str) -> str: return typeid +@requireapp def id2str(typeid: int) -> str: """Convert typeID to stringID""" - return converter.id2str(typeid) + result = converter.id2str(typeid) + try: + search = collisions["collision"].index(result) + return collisions["chr"][search] + except ValueError: + return result From 9b1a39ac0fc263eeec80dbb2e8820e9b87767e9b Mon Sep 17 00:00:00 2001 From: TsXor Date: Mon, 12 Sep 2022 13:18:53 +0800 Subject: [PATCH 10/13] style(action_manager/js_converter): hop Signed-off-by: TsXor --- photoshop/api/action_manager/js_converter/convert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index 429abbdf..f370506d 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -86,9 +86,9 @@ def parseref(tdict): # py37 compat try: ext = eval( - """[(str2refgetpacker[val["type"]](e) """ - + """if type(val := e["Value"]) == dict """ - + """else str2refgetpacker["default"](e)) for e in d2l]""" + """[(str2refgetpacker[val["type"]](e) """ + + """if type(val := e["Value"]) == dict """ + + """else str2refgetpacker["default"](e)) for e in d2l]""" ) except SyntaxError: ext = [ From 135478c785868d663542c257c91b245ab5e13441 Mon Sep 17 00:00:00 2001 From: longhao Date: Sat, 10 Sep 2022 15:16:16 +0800 Subject: [PATCH 11/13] chore(ci): update hound to ignore BLK100 Signed-off-by: longhao --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index 6c437067..97fffa92 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,5 @@ [flake8] +ignore = BLK100 # flake8-quotes: # Use double quotes as our default to comply with black, we like it and From 387387f7e02a131e2e88663c22b1f001243fdc6b Mon Sep 17 00:00:00 2001 From: TsXor Date: Mon, 12 Sep 2022 13:23:44 +0800 Subject: [PATCH 12/13] style(action_manager/js_converter): hop Signed-off-by: TsXor --- photoshop/api/action_manager/js_converter/convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photoshop/api/action_manager/js_converter/convert.py b/photoshop/api/action_manager/js_converter/convert.py index f370506d..ea114b2d 100644 --- a/photoshop/api/action_manager/js_converter/convert.py +++ b/photoshop/api/action_manager/js_converter/convert.py @@ -86,8 +86,8 @@ def parseref(tdict): # py37 compat try: ext = eval( - """[(str2refgetpacker[val["type"]](e) """ + - """if type(val := e["Value"]) == dict """ + + """[(str2refgetpacker[val["type"]](e) """ + # noqa + """if type(val := e["Value"]) == dict """ + # noqa """else str2refgetpacker["default"](e)) for e in d2l]""" ) except SyntaxError: From f77694b338516fbe25ebe7927e059a6205f0f572 Mon Sep 17 00:00:00 2001 From: TsXor <74352334+TsXor@users.noreply.github.com> Date: Sat, 3 Dec 2022 14:55:31 +0800 Subject: [PATCH 13/13] fix(action_descriptor): fix a little typo Signed-off-by: 23Xor --- photoshop/api/action_descriptor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/photoshop/api/action_descriptor.py b/photoshop/api/action_descriptor.py index 8771a1e1..fe1862bb 100644 --- a/photoshop/api/action_descriptor.py +++ b/photoshop/api/action_descriptor.py @@ -57,7 +57,7 @@ def __eq__(self, other): def erase(self, key: int): """Erases a key from the descriptor.""" - self.erase(key) + self.app.erase(key) def fromStream(self, value: str): """Creates a descriptor from a stream of bytes; for reading from disk.""" @@ -171,7 +171,7 @@ def putData(self, key: int, value: str): except BaseException: pass - def putDouble(self, key: int, value: int): + def putDouble(self, key: int, value: float): """Sets the value for a key whose type is double.""" self.app.putDouble(key, value)