shared.python.serialize

Github link: shared.python.serialize

This is a wrapper around json. It serializes your data objects and is able to load them form disk as well. It’s pretty handy.

import shared.python.serialize as pyjson
to_disk = pyjson.save(r"C:\path\file.json", {"data":0})  # save to disk
from_disk = pyjson.load(r"C:\path\file.json")  # get from saved

shared.python.file 1.0

Github link: shared.python.file


I often get mixed up with what file and folder operations are in os.path and in shutil. I also feel that the os module is not very readable. So, for the sake of readability, I like to add a few functions that help out with file and folder operations.


name()

One example of how unreadable os.path can be is getting a file name.

file_name = os.path.dirname(r"C:\path\fileName.ext")

Pretty horrible, no? I mean the function is called dirname as if we were looking a the name of a directory!

With name(), we can get the name of a file in a much more readable way. And, since we bothered with this, we can get the name of the file without the extension like so:

import shared.python.file as pyfile
file_name = pyfile.name(r"C:\path\fileName.ext", include_ext=False)

list_files()

This function will return all files within a folder. You can obviously do this in line, but its really nice to have it as a one line function.

To make this function, I started with the line that gets me the list of files:

for (dirpath, dirnames, filenames) in os.walk(path):
    files.extend([os.path.join(dirpath, x) for x in filenames])

That gives me a list of all the files in all the folders in the given path. That’s useful unless you only want the files in the path, and not in all the sub-folders. So we can add an if for that case:

for (dirpath, dirnames, filenames) in os.walk(path):
    files.extend([os.path.join(dirpath, x) for x in filenames])
    if not recursive:
        break

At this point we now have the correct list of files. This trick I learned from a friend at work. He uses the break command in interesting ways. In this case, it breaks the os.walk() command right after the first loop. This means that it skips all the sub-folders, leaving the list with the files in the base folder only.

Lastly, filter files to the given extensions. I like to feed the extensions as a list. This way, you can assemble your extensions in a loop. So assume the extensions are [“ma”, “mb”]:

for (dirpath, dirnames, filenames) in os.walk(path):
    files.extend([os.path.join(dirpath, x) for x in filenames])
    if not recursive:
        break

if extension:
    
    filtered = list()
    extenstions = pyutils.make_list(extension)
    
    for e in extenstions:
        e = "." + e.replace(".", "")
    
    for i, file_name in enumerate(files):
        add = False
        for e in extenstions:
            if file_name.endswith(e):
                add = True
                break
        if add:
            filtered.append(file_name)
    
    files = filtered

The first loop makes sure that all extensions start with a “.”.

The second loop adds the files to filtered only if they have the right extension.

And this is how the function is used:

import shared.python.file as pyfile
maya_files = pyfile.list_files(r"C:\path", extension=["ma","mb"], recursive=True)

For the complete code, please refer to the function in git (shared.python.file)


If you have any doubts or questions about any of the functions or the module itself, please add it to the comments. I’ll be happy to add any clarification to this post or to change the module itself (where it makes sense).

shared.python.system

This module is quite short, but useful when dealing with multiple users. It’s used to identify the user, IP, and computer. I also have a wrapper function for subprocess, but I’m not fully convinced that it belongs in there.

Most of the discussion around this module will be around readability. Fortunately, I have a post on Python Readability already written. So I’m going to continue under the assumption that you agree with me on the importance of readability.


run_application_async()

Let me know how you feel about this function. I added it because a friend of mine uses it that way, and we were working on a core repo together one time and I dont have a real reason to remove it. So I’ve now been adding it to every core library I write.

The reality is that Python does not really have a way to do true asynchronous processes. The only way is to launch separate Python apps. This is why I have kept the wrapper here. But I feel that if its worth making this function, its worth making a full wrapper for subprocess. But i have not done that. Maybe I should. Let me know in the comments if you think it’s worth while and I will put it on the list of modules we will add to our core libraries.

Other than that, I think it’s all pretty self explanatory. Let me know in the comments if you need any clarifications, or if I’m missing something, or if I’m just nuts.


Ok, see you next week.

// Isoparm

shared.python.utils 1.1

Github link: shared.python.utils

Hello, and welcome to our first module. In this one we will make the most generic scripts we can think of. These are meant to help us in writing Python. They should not have any other dependencies other than Python or other well established Python libraries.


make_list()

This function converts any python object into a list(). This is particularly useful when dealing with packages such as Maya and 3DsMax. Many functions in those packages will return either a list() or a str(), depending on how many entries are returned. This is super annoying. You end up having to test the result to see if its a list() every time (ugh!). After writing that check seven times, I gave up.

Another added benefit of this function is that it can help you in making functions that take one or more items as arguments. For example:

# you can call a log function with a text:
log("Tell me something")

# or with an array:
log(["Tell me this", "Tell me that"])

Here is how that function would work:

import shared.python.utils as pyutils
 
# new_list == ["string"] 
new_list = pyutils.make_list("string")

# new_list == ["string"]
new_list = pyutils.make_list(["string"])

join_lists()

Have you noticed that some functions return False when they should return an empty string? Check this example:

results = list()

for item in list_of_items:
    results =+ imported.get_value(item)

Imagine that we dont own ‘imported’, its a imported library. And imagine that get_values() returns False if there are no values to get from item. If item ever has no values to return, you would get an error that looks like this:

TypeError: can only concatenate list (not "bool") to list

The solution is simple but annoying. After writing it several times, I decided to add it to my utils module.

This is how you would use it:

import shared.python.utils as pyutils

# new_list = ["one", "two", "three", "four"]
new_list = pyutils.join_lists(["one","two"],["three", False], ["four"])

remove_duplicates()

This one exists only for readability’s sake. Its not difficult to do this in a single line, but its hard-ish to read.

# its not easy to read this:
sans_duplicates = list(set(objects))

# this is much easier:
sans_duplicates = pyutils.remove_duplicates(objects)

are_items_in_list()

I find myself looking checking a list to see if it has a bunch of items. I do this often. You can use the set.intersection() function, but it’s hard to read. It ends up looking like this:

# how am I supposed to know what this means???
if len(set(full_list).intersection(items)):
    return

# much easier to read, I think.
if pyutils.are_items_in_list(items, full_list):
    reutrn

I suspect you begin to see a pattern. I always try to make code more readable. In many cases I over do it, I know. However, when I go back to my code weeks, months and years later, I don’t struggle with it so much. So I feel it’s worth it.

I think that’s enough for today. I will deal with more of this module on the next post.

Let me know in the comments if this is a helpful format. Also, if you have any questions about any of the modules mentioned, or any of the ones not mentioned, please dont hesitate.

As always, I welcome any suggestions.

// Isoparm

shared.python Intro

In my experience, having a good core library makes a world of difference. I couldn’t even imagine not having one. I owe all of my speed of development to my core library. It’s worth spending the time to get a good library started. And then, it’s worth keeping it up. I can’t stress this enough. To me, success in any Python project starts at your core library.

You will notice I use the words ‘core’ and ‘shared’ interchangeably. I dont like the word ‘core’ because it has baggage that I sometimes don’t fully follow and I end up spending a ton of time arguing semantics, rather than code. However, it’s an accepted word in the community, so it’s sometimes easier to just use it. I go back and forth on it. Maybe we should have a discussion on it in our discussions page?

Here are the modules that I’m currently planning for our core / shared library:

  • python
  • maya
  • math
  • ui
  • startup

python

We will start with the lowest level modules. These modules are meant to help with writing Python. For example, they can be wrappers for os or subprocess, or they can be a python utilities module for making some base decorators.

maya

These modules will contain wrappers for the maya.cmds, maya.mel, pymel, openMaya, and other Maya related modules. We will try to keep the code as un-opinionated as possible. Solutions here should be as generic as possible. So try to avoid thinking in terms of tools and workflow, and think more about APIs and what you think you will need for when you are making tools.

math

There are quite a few things that I don’t like doing in Maya. Math is one of them. If I can avoid opening Maya for my solutions, then I will be able to use my code in projects that are not for Maya. Among some of the modules in here, we will have vectors, angles, filters, math functions, point clouds, etc.

ui

This is one I end up arguing over a lot. In reality, this module is here for the sake of practicality, not for the sake of Pythonic purity. One thing I really like doing, is selecting a UI scheme that can work in most situations, and them making it so I can make GUIs quickly. I even developed an auto GUI maker once. I really liked it, since it allowed me to focus on the meat of the tools and code, and not on the GUI. As we develop this module further, I’m sure you will see the benefits.

startup

Yeah, we need one of these. It’s really convenient to have a single entry point for all your Python settings and environment. Here we will set up the logger, pick which Python Host we are in, load the correct versions of plugins, etc.

Alright, enough rambling. Next post, we will start on shared.python.utils.py

// Isoparm

Env Setup – Part 1.2 – Connect PyCharm To Maya

In order for our dev environment to be complete, we need to have PyCharm debug code in Maya. This is something that you ‘could’ live without. I recommend you have this done because it will make your life much easier.

I like to use MayaCharm. It’s a PyCharm plugin that has a Python interpreter for PyCharm that emulates Maya. Also, it can connect directly with Maya to use PyCharm as a debugger. It’s all kinds of wonderful.

If you are interested on the Github page, or want to see the docs, here they are MayaCharm documentation.

For the quick and easy install, it’s best if you do it from inside PyCharm.

Go to File / Settings …

pyCharm_maya_connect_01

On the Search Bar, type “MayaCharm“. Only one plugin will come up. Install it and restart PyCharm.

You are not done yet. You still have to set up your Python interpreter for your current PyCharm project. So go back to your Settings Window ( File / Settings … ). Click on the “Project Interpreter” drop down, and select “Show All…

pyCharm_maya_connect_02

A new window appears. Here you need to point PyCharm to mayapy.exe (which is the Maya Python interpreter).

pyCharm_maya_connect_03
Click the “+” button
pyCharm_maya_connect_04
Select the System Interpreter on the left, and click on the “…” button.
pyCharm_maya_connect_05
Navigate to mayapy.exe in your Maya install folder

Click “OK” and you should now see the mayapy.exe interpreter added to your Project Interpreters Window:

pyCharm_maya_connect_06

Depending on your version of PyCharm, you might need to install the Python Packaging Tools. If it asks for it, do so.

At this point you need to restart PyCharm again. Even if you restarted it for the Python Packaging Tools, you need to restart anyway. If you don’t, the Active Maya SDK section of the Settings Window will be empty and you wont be able to continue. So

pyCharm_maya_connect_07

You now have to tell Maya to listen for incoming commands from PyCharm. So navigate to your userSetup.py file. This file is normally located here: “C:\Users\…user…\OneDrive\Documents\maya\2018\scripts

Open it and add these lines:

import maya.cmds as cmds

if not cmds.commandPort(':4434', query=True):
    cmds.commandPort(name=':4434')

Notice how the port name in the code is 4434, and the port in the MayaCharm Settings Window is also 4434. These number HAVE to match in order for the connection to work.

Alternatively, you could run these lines directly from the Script Editor in Maya if you wanted. This will work as well, but you will have to do it every time you open Maya. On the one hand, you don’t worry about Maya keeping the port open when you are not using PyCharm, and on the other, you have to run the command every time you want to debug with PyCharm. Ultimately, the choice is yours.

You are done with the Maya / PyCharm set up!

pyCharm_maya_connect_08

In the “Run” menu at the top of PyCharm, there are now 3 new lines. These will send the commands to Maya.

Try it out.

highlight a print command and watch it print in the Maya Script History.

I hope this worked out for you guys. If not, leave me a note with what problems you have and we will troubleshoot it together.

// Isoparm

Env Setup – Part 1.1 – Connect PyCharm And Github

Lets setup PyCharm / Github.

You need a Github account, so make one.

Make a new Github Repository. The project I will be starting out using is this one: https://github.com/isoparms/dis.shared. I recommend you create a similar one.

github_newRepo
github_newRepo_options

Yay, you have a repository! Now we will clone it to your system.

Open GitBash (it got installed when you installed Git).

this is the clone command:

git clone https://github.com/isoparms/dis.shared C:\code_folder\disorder_project\shared

Once cloned, you can pull (get latest from Github) with this command:

cd C:\code_folder\disorder_project\shared
git pull

Alright, you have your repository set up. Now we move to PyCharm.

Make a File \ New Project

Use the same folder as the one you used for your repository.

Set up Git: File \ Settings

pyCharm_git_connect_01
pyCharm_git_connect_02

Cool, we are almost done. Now make a change, add a file, etc. Then press Ctrl + K. This is how you commit or check in, so memorize it.

pyCharm_git_connect_03

PyCharm will prompt you for your account and password. Make sure you make your settings global.

Now you are done.

Make sure you make all changes repository through PyCharm. Add, remove, edit, and even copy and paste files using PyCharm. That way, Github will know what you are doing at all times, so all you have to do, is commit every now and then.

Enjoy!

//Isoparm

Next: Connect PyCharm To Maya