I've created a heavily problematic code that works.
Summary: It looks through a folder structure, for png files and tries to replicate this structure in Photoshop. Files are mapped layers and Folders to Groups. I am using comtypes
to access Photoshop and create the groups and layers.
The issue I faced with, is that you can't access Photoshop Layer Objects directly (instead you have to go through the whole structure). E.g. to access
new
|
-- folder
|-- sub_folder
|-- random_file
in order to access the random_file, you have to access new, then folder, then sub_folder
!
The following code works and it was developed through an intense mental state of concentration (and some trial and error). I believe it's horrendous though! As I am trying to learn how to be a better programmer and learn from the best, I though to ask your help: Can somebody guide me in order to re-factor this beast? What can I do better and what's the way to go with?
# Algorithm (?):
# ---------
# Starting from given top folder structure .
# traverse downwards and create
# photoshop groups named as the folders
# import all the png files under this
# as linked layers and name them accordingly
# traverse another layer deeper
# and do the same
# Import layers from Misc as Background and Shadow
os.chdir(ROOT_PATH + '/Misc')
import_all_pngs_as_layers(new_doc, new_doc, ROOT_PATH + '/Misc')
os.chdir(ROOT_PATH) # Revert the working path
duplicate = False
subdir = False
for root, dd, ff in os.walk('.'):
path = root.split(os.sep)
if not os.path.basename(root) == '.': # ignore parent
if os.path.dirname(root).replace(".\\", "") in IGNORED_DIRS:
pass
elif not os.path.dirname(root) == '.' and not os.path.dirname(
root).replace(".\\", "") in IGNORED_DIRS:
# print('I am a subdir {} of the dir {}'.format(
# os.path.basename(root),
# os.path.dirname(root).replace(".\\", "")))
create_group_named(
new_doc, os.path.basename(root),
new_doc.LayerSets(os.path.dirname(root).replace(".\\", "")))
elif not os.path.basename(root) in IGNORED_DIRS:
# print("Create TOP LEVEL layer group named",
# os.path.basename(root))
create_group_named(
new_doc,
os.path.basename(root)) # Create a group named 'subdir'
if len(ff) > 1:
for filename in ff:
if filename.endswith('.png'):
for item in GROUPED_LAYERS:
# print(item)
# print(item in filename)
if item in filename:
# print(
# 'lets create a group {} and put the layer{} under it in folder {}'
# .format(item, filename, os.path.basename(root)))
os.chdir(os.path.realpath(root))
try:
new_doc.LayerSets(
os.path.basename(root)).LayerSets(item)
except:
ng = create_group_named(
new_doc, item,
new_doc.LayerSets(os.path.basename(root)))
create_layer_from_file(
new_doc, filename, ng,
os.path.realpath(filename))
else:
# print(new_doc.LayerSets(os.path.basename(root)))
create_layer_from_file(
new_doc, filename,
new_doc.LayerSets(
os.path.basename(root)).LayerSets(item),
os.path.realpath(filename))
duplicate = True
os.chdir(ROOT_PATH)
if duplicate:
pass
duplicate = False
else:
os.chdir(os.path.realpath(root))
# print('Rest files import as layers {} under {}'.format(
# filename, os.path.basename(root)))
if os.path.basename(
root) in IGNORED_DIRS or os.path.dirname(
root).replace(".\\", "") in IGNORED_DIRS:
pass
elif not os.path.dirname(root) == '.':
# print('layer {} on main group {} on group {}'
# .format(filename, os.path.dirname(root).replace(".\\",""), os.path.basename(root)))
create_layer_from_file(
new_doc, filename,
new_doc.LayerSets(
os.path.dirname(root).replace(
".\\", "")).LayerSets(
os.path.basename(root)),
os.path.realpath(filename))
else:
create_layer_from_file(
new_doc, filename,
new_doc.LayerSets[os.path.basename(root)],
os.path.realpath(filename))
os.chdir(ROOT_PATH)
else:
pass
For completeness here's the rest of the program:
import comtypes.client as ct
psApp = ct.CreateObject('Photoshop.Application')
new_doc = psApp.Documents.Add(600, 800, 72, "new-psb-test", 2, 1, 1)
# they are ignored as those are the directories on the root level
# and I explicitly import them and their layers before the loop
IGNORED_DIRS = ['Misc', 'Wheel_Merged']
# If the files (inside the group/folder Accessories)
# include the following in their names then they need to be
# sub- grouped under that name: e.g. Grille_01, 22_Grille, Grille0 all need to be
# layers under the Group named Grille
GROUPED_LAYERS = ['Body_Side', 'Grille']
def create_group_named(doc, layer_set, under=''):
""" Create a New LayerSet (aka Group) in the Photoshop
document (doc).
Args:
doc (obj): The Photoshop Document Instance
under (obj): The Group Object Instance
(e.g. if you want a subgroup under Lights then give that
object as a under name)
layer_set (str): The name of the new Layer Set
Returns:
new_layer_set (obj): The LayerSet (Group) Object
"""
if not under: # add a top level group
new_layer_set = doc.layerSets.Add()
else: # add subgroup
new_layer_set = under.LayerSets.Add()
new_layer_set.name = layer_set
return new_layer_set
def paste_file_as_linked_layer(path):
""" Import a file as a photoshop (smart) linked layer
Args:
path(str): The exact path of the image including extension
Returns:
whatever execute action returns (TBC)
"""
idPlc = psApp.charIDToTypeID("Plc ")
desc11 = ct.CreateObject("Photoshop.ActionDescriptor")
idIdnt = psApp.charIDToTypeID("Idnt")
desc11.putInteger(idIdnt, 2)
# Open the file (path)
idnull = psApp.charIDToTypeID("null")
desc11.putPath(idnull, path)
# set its type as a linked payer
idLnkd = psApp.charIDToTypeID("Lnkd")
desc11.putBoolean(idLnkd, True)
idFTcs = psApp.charIDToTypeID("FTcs")
idQCSt = psApp.charIDToTypeID("QCSt")
idQcsa = psApp.charIDToTypeID("Qcsa")
desc11.putEnumerated(idFTcs, idQCSt, idQcsa)
idOfst = psApp.charIDToTypeID("Ofst")
desc12 = ct.CreateObject('Photoshop.ActionDescriptor')
idHrzn = psApp.charIDToTypeID("Hrzn")
idRlt = psApp.charIDToTypeID("#Rlt")
desc12.putUnitDouble(idHrzn, idRlt, 0)
idVrtc = psApp.charIDToTypeID("Vrtc")
idRlt = psApp.charIDToTypeID("#Rlt")
desc12.putUnitDouble(idVrtc, idRlt, 0)
idOfst = psApp.charIDToTypeID("Ofst")
# put the object in an offset space of 0,0
desc11.putObject(idOfst, idOfst, desc12)
# 'return' of the function
# is the placement of the linked layer
f = psApp.executeAction(idPlc, desc11, 3)
def create_layer_from_file(doc, layer_name, layer_set, path):
""" Create new Layer from File nested under a LayerSet
Args:
doc (obj): The working Photoshop file
layer_name (str): The given name for the Layer
layer_set (obj): the LayerSet object that the Layer is nested under
path (str): the full Path of the file (including the extension)
Returns:
"""
psApp.activeDocument = doc
layer = layer_set.artLayers.Add()
layer.name = layer_name # Rename Layer
doc.activeLayer = layer # Select Layer
paste_file_as_linked_layer(os.path.realpath(path).replace('\\', '/'))
return layer
1 Answer 1
I spent sometime with the code
you've currently posted @wizofe, I'm not sure if it'll ever be the same again...
Side note; I heard it rumored that the character limits are just a bit more relaxed than on other sub-stacks... or in other-words this may get a bit verbose, or in other-other-words it could be another one of those posts so a snack and drink is a solid choice.
utils_photoshop/__init__.py
I didn't have insight as to how you where organizing code so I'm making up file and directory names for later pseudo-
import
ing.
#!/usr/bin/env python
import comtypes.client as ct
__license__ = """
See selection from author of question: https://codereview.stackexchange.com/q/219028/197446
"""
## Note from S0AndS0; repeated `new_doc` __will__ cause all manor of headaches
#def import_all_pngs_as_layers(new_doc, new_doc, path):
def import_all_pngs_as_layers(new_doc, path):
"""
# Warning: this function is missing from current version of the posted question
"""
pass
def create_named_group(doc, layer_set, under = ''):
"""
Create a New LayerSet (aka Group) in the Photoshop document (doc).
## Arguments
- doc (obj): The Photoshop Document Instance
- under (obj): The Group Object Instance
(e.g. if you want a subgroup under Lights then give that
object as a under name)
- layer_set (str): The name of the new Layer Set
## Returns: new_layer_set (obj): The LayerSet (Group) Object
"""
if not under: # add a top level group
new_layer_set = doc.layerSets.Add()
else: # add subgroup
new_layer_set = under.LayerSets.Add()
new_layer_set.name = layer_set
return new_layer_set
def paste_file_as_linked_layer(path, psApp):
"""
Import a file as a Photoshop (smart) linked layer
## Arguments
- path (str): The exact path of the image including extension
- psApp (object): an instance of `ct.CreateObject('Photoshop.Application')`
## Returns: whatever execute action returns (TBC)
"""
idPlc = psApp.charIDToTypeID('Plc ')
desc11 = ct.CreateObject('Photoshop.ActionDescriptor')
idIdnt = psApp.charIDToTypeID('Idnt')
desc11.putInteger(idIdnt, 2)
# Open the file (path)
idnull = psApp.charIDToTypeID('null')
desc11.putPath(idnull, path)
# set its type as a linked payer
idLnkd = psApp.charIDToTypeID('Lnkd')
desc11.putBoolean(idLnkd, True)
idFTcs = psApp.charIDToTypeID('FTcs')
idQCSt = psApp.charIDToTypeID('QCSt')
idQcsa = psApp.charIDToTypeID('Qcsa')
desc11.putEnumerated(idFTcs, idQCSt, idQcsa)
idOfst = psApp.charIDToTypeID('Ofst')
desc12 = ct.CreateObject('Photoshop.ActionDescriptor')
idHrzn = psApp.charIDToTypeID('Hrzn')
idRlt = psApp.charIDToTypeID('#Rlt')
desc12.putUnitDouble(idHrzn, idRlt, 0)
idVrtc = psApp.charIDToTypeID('Vrtc')
idRlt = psApp.charIDToTypeID('#Rlt')
desc12.putUnitDouble(idVrtc, idRlt, 0)
idOfst = psApp.charIDToTypeID('Ofst')
# put the object in an offset space of 0,0
desc11.putObject(idOfst, idOfst, desc12)
# _`return`_ of this function is placement of the linked layer
f = psApp.executeAction(idPlc, desc11, 3)
def create_layer_from_file(doc, layer_name, layer_set, path, psApp):
"""
Create new Layer from File nested under a LayerSet
## Arguments
- doc (obj): The working Photoshop file
- layer_name (str): The given name for the Layer
- layer_set (obj): the LayerSet object that the Layer is nested under
- path (str): the full Path of the file (including the extension)
- psApp (object): an instance of `ct.CreateObject('Photoshop.Application')`
## Returns: `layer`, an instance of `layer_set.artLayers.Add()`
"""
psApp.activeDocument = doc
layer = layer_set.artLayers.Add()
layer.name = layer_name # Rename Layer
doc.activeLayer = layer # Select Layer
paste_file_as_linked_layer(os.path.realpath(path).replace('\\', '/'), psApp)
return layer
def scrubbed_dirname(path):
"""
## Returns: path with any `.\\` removed
"""
## Note from S0AndS0; careful with writing code like this,
## it can _spaghettify_ a code-base to incomprehensible levels.
## Generally this is a sign that there is something that could
## be done better with whatever is using functions like these.
## Or in other-words, consider Ctrl^f "scrubbed_dirname" to be
## a _highlighter_ for places that code may be getting _hairy_.
## However, this is a balance of what is worth the time as well
## as how far feature-creep plans to take this project.
return os.path.dirname(path).replace('.\\', '')
def notice(msg, verbosity = 0, *f_list, **f_kwargs):
"""
Prints formatted notices if `--verbosity` is greater than `0`
## Arguments
- msg (str): The string to print, possibly with formatting
- f_list (list): A list of arguments to pass to `format()`
- f_kwargs (dict): Key word arguments to pass to `format()`
## Returns: None
"""
## Note from S0AndS0; when ya get to the point that adding
## checks for other levels of verbosity, or maybe are considering
## passing something like `v_threshold` and `v_current`, it might
## be better to look for libraries that better express such intent.
if verbosity <= 0:
return
if f_list and f_kwargs:
print(msg.format(*f_list, **f_kwargs))
elif f_list:
print(msg.format(*f_list))
elif f_kwargs:
print(msg.format(**f_kwargs))
else:
print(msg)
As noted the import_all_pngs_as_layers
function was missing from your question, not sure what it's special sauce is but that's okay as my internal parser is more tolerant than Python's. If this omission was intentional I'd advise editing in a real __license__
reference that expresses your views on edits, use, etc. instead of keeping the herbs-n-spices a secret.
The paste_file_as_linked_layer
and create_layer_from_file
functions received edits to allow for passing about psApp
, I get that this is not ideal but more on that later.
The scrubbed_dirname
is the convenience function that I commented about earlier, and much like the above code's comment-block states, this is something to be very careful with! These kinds of functions can be much like tribbles, one or two isn't too bad but given time and feature-creep maneuvering through a code-base can become a slog.
The notice
function is another example of a convenience function that replaces the commented print()
statements with something that outputs conditionally. But like the commented block states, it's probably a good idea to look into a library if there's more features wanted out of something like that. I didn't because I'm not about to assume you'll want to (if allowed) pip install something
just to use my suggestions, that and notice
'll show some fancy argument passing in the next code block.
I may have messed-up your documentation styling @wizofe (mainly because I use MarkDown just about everywhere), so my apologies on that point. And except for otherwise commented within the code-blocks you've done very well in dividing up portions of the problem space into manageable chunks. Not a whole lot of ground-breaking I can do in other-words.
png_to_photoshop.py
Much like some of those pocket monsters, pressing b will cancel this evolution...
#!/usr/bin/env python
import sys
import argparse
import comtypes.client as ct
import warnings
from textwrap import dedent
from utils_photoshop import (
create_layer_from_file,
create_named_group,
import_all_pngs_as_layers,
notice,
paste_file_as_linked_layer,
scrubbed_dirname)
#
# _Boilerplate stuff_
#
__description__ = """
This script recursively traverses folder structure starting with given path and creates;
- Photoshop groups named as each sub-directory, within which are
- all the png files linked to layers and name accordingly
> Sub-directories are treated similarly if found
"""
__license__ = """
See selection from author of question: https://codereview.stackexchange.com/q/219028/197446
"""
__examples__ = """
Add PNG files from: /home/bill/Pictures/funny-cats/
png_to_photoshop.py --basedir /home/bill/Pictures/funny-cats/\\
--img_ext png\\
--misc_dir YarnCollection\\
--ignored_dirs YarnCollection Unicorns\\
--layers_group costumed surprised adorbs\\
--verbosity 9000
"""
__author__ = ('wizofe', 'https://codereview.stackexchange.com/users/199289/wizofe')
## Note to editors, please add yourself to the following
__editors__ = [
('S0AndS0', 'https://codereview.stackexchange.com/users/197446/s0ands0'),
]
## Note from S0AndS0; you will usually see the reverse of the following
## at the bottom of files that maybe run or imported, in this case I am
## being explicit as to how this file may be used without modification.
if __name__ != '__main__':
raise NotImplementedError("Try running as a script, eg. python file-name.py --help")
#
# Parse the command line arguments into an `args_dict`
#
parser = argparse.ArgumentParser(description = __description__, epilog = __examples__, allow_abbrev = False)
## Example of adding command-line argument
# parser.add_argument('--foo', default = 'Fooed', type = str, help = 'Foo is defaulted to -> %(default)s')
parser.add_argument('--about', action = 'store_true', help = 'Prints info about this script and exits')
parser.add_argument('--license', action = 'store_true', help = 'Prints script license and exits')
parser.add_argument('--verbosity',
type = int,
default = 0,
help = "How verbose to be during execution, default %(default)s")
parser.add_argument('--img_ext',
default = 'png',
help = "Image extension to import into Photoshop with this script, default %(default)s")
parser.add_argument('--base_dir',
required = True,
help = 'Base directory to recursively parse for PNG files into Photoshop layers')
parser.add_argument('--misc_dir',
required = True,
help = 'Base directory to recursively parse for PNG files into Photoshop layers')
parser.add_argument('--ignored_dirs',
nargs='+',
default = ['Misc', 'Wheel_Merged'],
help = "List of sub-directory names to ignore while _walking_ base_dir, default %(default)s")
## Note from S0AndS0; ya may want to play with the following `help`
## to get output of `script-name.py --help` looking more acceptable.
parser.add_argument('--layers_group',
nargs='+',
default = ['Body_Side', 'Grille'],
help = textwrap.dedent("""
List of association names between file names, and Photoshop sub-groups, default %(default)s.
For example "{name}_01, 22_{name}, {name}0" could be grouped to "{name}",
via something like...
script-name.py --layers_group {name}
""".format(name = 'Grille')))
args_dict = vars(parser.parse_args())
#
# Ways to prematurely exit from script, note `--help` takes care of itself
#
if args_dict['license']:
print(__license__)
sys.exit()
if args_dict['about']:
message = """
This script and related code was brought to you by
Author: {name} of {url}
""".format(name = __author__[0], url = __author__[1])
if __editors__:
message += '\n\n\tand...\n'
for name, url in __editors__:
message += "\t{name} -> {url}".format(**{'name': name, 'url': url})
print(dedent(message))
sys.exit()
## Note from S0AndS0; I moved the following two assignments and
## modified the `paste_file_as_linked_layer` and `create_layer_from_file`
## functions to pass `psApp` about, because I figure a future self
## or reader will want to customize it a bit more. This is less than
## ideal, so see the posted notes for some thoughts on the future.
psApp = ct.CreateObject('Photoshop.Application')
new_doc = psApp.Documents.Add(600, 800, 72, 'new-psb-test', 2, 1, 1)
# Import layers from Misc as Background and Shadow
misc_path = os.path.join(args_dict['base_dir'], args_dict['misc_dir'])
os.chdir(misc_path)
import_all_pngs_as_layers(new_doc = new_doc, path = misc_path)
os.chdir(args_dict['base_dir']) # Revert the working path
duplicate = False
subdir = False
img_ext = ".{ext}".format(ext = args_dict['img_ext'])
for root, dd, ff in os.walk(args_dict['base_dir']):
## Note from S0AndS0; where is `path` being used?
## Or is this _zombie_/_vestigial_ code?
path = root.split(os.sep)
root_dir_basename = os.path.basename(root)
if root_dir_basename == '.': # ignore parent
pass
if scrubbed_dirname(root) in args_dict['ignored_dirs']:
pass
if not os.path.dirname(root) == '.' and not scrubbed_dirname(root) in args_dict['ignored_dirs']:
notice(msg = "I am a subdir {} of the dir {}",
verbosity = args_dict['verbosity'],
root_dir_basename,
scrubbed_dirname(root))
create_named_group(
doc = new_doc,
layer_set = root_dir_basename,
under = new_doc.LayerSets(scrubbed_dirname(root)))
elif not root_dir_basename in args_dict['ignored_dirs']:
notice(msg = "Creating TOP LEVEL layer group {name}",
verbosity = args_dict['verbosity'],
name = root_dir_basename)
create_named_group(
doc = new_doc,
layer_set = root_dir_basename)
else: # Uncaught state!
warnings.warn("\n".join([
'How did I get here?',
"\tWhat did you give me?",
"\t... uh-oh, I think I will pass on these questions and current state with...",
"\troot_dir_basename -> {path}".format(path = root_dir_basename),
]))
pass
## Note from S0AndS0; the following might be better if higher
## up in the execution stack of for loop, unless there is a
## reason to have `create_named_group` fire on empty `ff`
if len(ff) <= 0:
pass
for filename in ff:
if not filename.endswith(img_ext):
pass
for item in args_dict['layers_group']:
notice(msg = "{item} in {filename}",
verbosity = args_dict['verbosity'],
item = item,
filename = filename)
if item in filename:
notice(msg = "Creating group {group} to place layer {layer} within folder {folder}",
verbosity = args_dict['verbosity'],
group = item,
layer = filename,
folder = root_dir_basename)
os.chdir(os.path.realpath(root))
try:
new_doc.LayerSets(root_dir_basename).LayerSets(item)
except:
named_group = create_named_group(
doc = new_doc,
layer_set = item,
under = new_doc.LayerSets(root_dir_basename))
create_layer_from_file(
doc = new_doc,
layer_name = filename,
layer_set = named_group,
path = os.path.realpath(filename),
psApp = psApp)
else:
notice(msg = new_doc.LayerSets(root_dir_basename), verbosity = args_dict['verbosity'])
create_layer_from_file(
doc = new_doc,
layer_name = filename,
layer_set = new_doc.LayerSets(root_dir_basename).LayerSets(item),
path = os.path.realpath(filename),
psApp = psApp)
duplicate = True
os.chdir(args_dict['base_dir'])
if duplicate:
duplicate = False
pass
if root_dir_basename in args_dict['ignored_dirs'] or scrubbed_dirname(root) in args_dict['ignored_dirs']:
pass
os.chdir(os.path.realpath(root))
notice(msg = "Resting files imported as layers {layer} under {folder}",
verbosity = args_dict['verbosity'],
layer = filename,
folder = root_dir_basename)
if not os.path.dirname(root) == '.':
notice(msg = "layer {layer} on main group {m_group} on group {group}",
verbosity = args_dict['verbosity'],
layer = filename,
m_group = os.path.dirname(root).replace('.\\', ''),
group = root_dir_basename)
create_layer_from_file(
doc = new_doc,
layer_name = filename,
layer_set = new_doc.LayerSets(scrubbed_dirname(root)).LayerSets(root_dir_basename),
path = os.path.realpath(filename),
psApp = psApp)
else:
create_layer_from_file(
doc = new_doc,
layer_name = filename,
layer_set = new_doc.LayerSets[root_dir_basename],
path = os.path.realpath(filename),
psApp = psApp)
os.chdir(args_dict['base_dir'])
There'll be somethings that'll likely look familiar in the above code block, and some things that are diff
erent; I'll encourage readers to pull-out interesting bits and try'em in scriptlets because as it is neither the question's code nor what I've so far posted will function.
If there are questions on why I've written some-thing some-way, feel free to ask in the comments. Basically all I've added so far is some argparse
tricks and re-factored your code to be a little less cyclomaticly complex
(it's still a bit high as it is), really you where on the right track @wizofe with using pass
often and I've just tried to follow along with it.
The observant readers may also be noting my use of quotes, while Python doesn't care one way or another on
'some string'
vs"some string"
, I personally find it easier to read moons later when there's consistent use that means something about the contents. In this case single-quotes don't have anything fancy going on where as double quotes may have formatting stuffed in somewhere.
Moving on to what could be further improved, and on track with what I was commenting earlier about and intermediate structure for organizing some of this. Some of these for
loops might be easier to expand upon as Iterator
s, eg...
#!/usr/bin/env python
import sys
import argparse
import comtypes.client as ct
import warnings
from textwrap import dedent
from utils_photoshop import (
create_layer_from_file,
create_named_group,
import_all_pngs_as_layers,
notice,
paste_file_as_linked_layer,
scrubbed_dirname)
from collections import Iterator
class DirWalker_PhotoshopRanger(Dict, Iterator):
"""
DirWalker_PhotoshopRanger is a hybrid class between a `dict`ionary and `Iterator`
After modifications your _mileage_ may be improved
"""
def __init__(base_dir, misc_dir, layers_group, ignored_dirs, verbosity = 0, **kwargs):
super(DirWalker_PhotoshopRanger, self).__init__(**kwargs)
self.update({
'base_dir': base_dir,
'misc_dir': misc_dir,
'layers_group': layers_group,
'ignored_dirs': ignored_dirs,
'verbosity': verbosity},
'dir_steps': os.walk(base_dir),
'duplicate': False,
'subdir': False,
'grouped_layers': {},
'psApp': ct.CreateObject('Photoshop.Application'),
'misc_path': os.path.join(base_dir, misc_dir))
os.chdir(self['misc_path'])
self.update(new_doc = self['psApp'].Documents.Add(600, 800, 72, 'new-psb-test', 2, 1, 1))
self.update(misc_layers = import_all_pngs_as_layers(new_doc = self['new_doc'], path = self['misc_path']))
# Revert the working path
os.chdir(self['base_dir'])
self.update()
def __iter__(self):
return self
def next(self):
try:
root, dd, ff = self['dir_steps'].next()
except (StopIteration):
self.throw(GeneratorExit)
if scrubbed_dirname(root) in self['ignored_dirs']:
pass
root_dir_basename = os.path.basename(root)
if root_dir_basename == '.': # ignore parent
pass
## ... do more stuff before returning `self`
return self
#
# Python 2/3 compatibility stuff
#
def throw(self, type = None, traceback = None):
raise StopIteration
__next__ = next
... which may give ya the controls to flatten things into categories
(first sub-directory name, or similar) via something like self['categories'].update(hash_path: container_object)
or self['categories'].append(container_object)
In the future you might want to consider pulling some of the functions from utils_photoshop/__init__.py
into the DirWalker_PhotoshopRanger
class
. Namely those that I modified to pass psApp
about, instead maybe hav'em use self['psApp']
.
Incorporating portions of pre-existing code from the main for
loop into the suggested iterator will require a little editing, eg. ...doc = new_doc,...
would become ...doc = self['new_doc'],...
, and later maybe removed if things like the create_layer_from_file
function are pulled into the class
. That last state might begin to look something like...
#!/usr/bin/env python
## ... other import and initialization stuff
class DirWalker_PhotoshopRanger(Dict, Iterator):
# ... things like __init__ above
def create_named_group(self, layer_set, under = ''):
"""
Create a New LayerSet (aka Group) in the Photoshop document (self['new_doc']).
## Arguments
- layer_set (str): The name of the new Layer Set
- under (obj): The Group Object Instance, optional
## Returns: new_layer_set (obj): The LayerSet (Group) Object
"""
if not under: # add a top level group
new_layer_set = self['new_doc'].layerSets.Add()
else: # add subgroup
new_layer_set = under.LayerSets.Add()
new_layer_set.name = layer_set
return new_layer_set
def next(self):
try:
root, dd, ff = self['dir_steps'].next()
except (StopIteration):
self.throw(GeneratorExit)
root_dir_basename = os.path.basename(root)
if root_dir_basename == '.': # ignore parent
pass
if scrubbed_dirname(root) in self['ignored_dirs']:
pass
if not os.path.dirname(root) == '.' and not scrubbed_dirname(root) in self['ignored_dirs']:
notice(msg = "Searching sub-directory {subdir} of the directory {directory}",
verbosity = self['verbosity'],
subdir = oot_dir_basename,
directory = scrubbed_dirname(root))
self.create_named_group(
layer_set = root_dir_basename,
under = self['new_doc'].LayerSets(scrubbed_dirname(root)))
## ... do more stuff before returning `self`
return self
## ... other class stuff
if __name__ == __main__:
import argparse
#
# Parse the command line arguments into an `args_dict`
#
parser = argparse.ArgumentParser(description = __description__, epilog = __examples__, allow_abbrev = False)
# parser.add_argument('--foo', default = 'Fooed', type = str, help = "Foo is defaulted to -> %(default)s")
## ... more argparsy goodness
args_dict = vars(parser.parse_args())
#
# Initialize the DirWalker
#
f_line = ".-.".join('_' for _ in range(9))
dir_walker = DirWalker_PhotoshopRanger(**args_dict)
for i, state in enumerate(dir_walker):
notice(msg = "{f_line}\n{count}".format(f_line = f_line, count = i),
verbosity = 9001)
for k, v in state.items():
notice(msg = "{key} -> {value}".format(key = k, value = v),
verbosity = 9002)
notice(msg = "{f_line}".format(f_line = f_line), verbosity = 9001)
If written carefully these structures can be very performant on memory use and other system resources. Though yet again I'll warn that a code-base can get hairy with'em, in different ways than with convenience functions, but with very similar effect. Additionally it maybe of use to know that functions that use yield
operate very similarly to iterators, and be called generators
, eg...
def range_summinator(start, end):
increment = 1
if end < start:
increment = -1
while start != end:
start += increment
yield start
r = range_summinator(0, 2)
r.next() # -> 1
r.next() # -> 2
r.next()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
Okay I think this is a good pausing point to mull things over, and perhaps find bugs within my own suggestions, gotta say it was kinda a challenge without testing it in it's entirety. I may update this as thoughts percolate, and if comments/questions are posted.
Resources useful in writing (and maybe expanding upon) above are not limited to the following;
how-to-pass-and-parse-a-list-of-strings-from-command-line-with-argparse-argument Q&A from StackOverflow
simple-argparse-example-wanted-1-argument-3-results Q&A from StackOverflow
raise-warning-in-python-without-interrupting-program Q&A from StackOverflow
terminating-a-python-script Q&A from StackOverflow
textwrap.dedent
Python documentationargparse
Python documentationIterator
Python documentationwarnings
Python documentation
argparse
would allow for extending into a full command line utility, aside from those nits it's looking okay for the most part. In particular, thoseif os.path.basename(...).replace(...
lines maybe easier to debug later if it where a convenience function. As for accessingrandom_file
withinsub_folder
, if ya don't like nesting, maybe try an intermediate structure to organize things the way ya like, eg.pandas
,dict
, etc. \$\endgroup\$argparse
as I did in a similar tool I developed after this :) Can you give me examples for the terms: - 'convenience function': How I can implement one for theif os.path.basename(...).replace(...)
? - My big trouble was to create such an 'intermediate structure' for my pre-existing folder structure/schema. An example on that would be very awesome (I am happy to see one both usingpandas
and adict
Thanks again :) \$\endgroup\$random_file
object, is something that I kinda covered over on math.stack. Though there'd be some modification I believe the resulting code maybe shorter than what's demonstrated there. Hintfirst_to_compute
would instead contain a searchstr
ing orlist
, maybewalk
ing in ayield
ing fashion; bonus points for readable recursiveness. \$\endgroup\$