Debugging Keyshot Scripts

Greetings everyone,

I’ve been delving into the world of script development for Keyshot, and I’m loving the experience so far. However, I’ve encountered a major challenge when it comes to debugging the code. Currently, I find myself either pasting it into the Keyshot console or running the script repeatedly with logging/print statements, and it’s quite cumbersome.

I was wondering if any of you know of a way to debug Keyshot scripts in an IDE where we could use breakpoints, step-by-step execution, or other features commonly found in debuggers?

I’d greatly appreciate any insights or recommendations you might have on this matter.

Thank you in advance!

No experience in using one but if I read a bit about debuggers I see quite a few talking about PyCharm PyCharm: the Python IDE for Professional Developers by JetBrains it looks nice and clean. Quite expensive but there is a trial. I saw die-hards do the debugging using Pdb which is build-in but without a GUI.

1 Like

Pycharm looks cool, I was thinking of trying it. My main concern is if I can debug in pycharm what keyshot is executing, in a way that I could use breakpoints in pycharm that will stop keyshot on that point in the code for example, or line by line execution.

Well actually I was also thinking about how it would work and I’ve really close to 0 experience with Python.

If I’m right KS just uses the Python 3.10 (64-bit) which is installed on your machine and most of the time included in the system environments ‘Path’ variable so it can be reached from everywhere. So if your script is located in the KS script directory I think it doesn’t really matter if you start the script from KS’s console or via PyCharm. It’s mainly the location from where you run the script.

The only thing your script should do at the start is importing the KeyShot libraries so it understands the KeyShot specific commands. I reckon there’s also a way so you can have the IDE include libraries in the editor already so it can highlight commands etc.

I might be totally wrong but hope someone will slap me and correct me if so.

1 Like

Greetings!

Python scripting is indeed very fun but debugging it can be a bit difficult inside of KeyShot. This is due to how KeyShot manages its own Python interpreter. While our interpreter will load site-packages from a system-installed Python, if major and minor versions match, it is still executing via our own interpreter. Furthermore, KeyShot’s Python interpreter lives in the main thread (for implementation-specific reasons) so if this thread is suspended, so is the entirety of KeyShot.

However, if KeyShot is started from the command-line with the -script argument, then it will be possible to interact with a debugger on stdin (this is needed in order to give commands to the debugger while trapped inside it).

I’ll show a short example using Python’s built-in pdb debugging module:

if __name__ == "__main__":
  i = 0
  breakpoint()
  while i < 2:
    i += 1
  # Only needed if not using argument `-headless` or executable `keyshot_headless.exe`.
  exit(0)

Save that to “test.py”.

Figure out the absolute path to the folder containing the KeyShot executable from your installation folder (let’s call that $abspath).

Run KeyShot in either headless or GUI mode

$abspath/keyshot -script test.py               # GUI
$abspath/keyshot -headless -script test.py     # Headless *nix
$abspath/keyshot_headless.exe -script test.py  # Headless Windows

The script will enter the debugger when breakpoint() is invoked.
It will look similar to the following depending on the shell:

...a lot of text ignored...
[pyout] > <string>(4)<module>()
[pyout] (Pdb)

It will look weird because KeyShot is not supporting the debugging mode specifically!

At this point, pdb will have trapped on line 4: while i < 2:.
Inspect the i variable:

p i
[pyout] 0
[pyout] (Pdb)

Then we could create a watch point whenever the the variable changes:

display i
[pyout] display i: 0
[pyout] (Pdb)

Then we step through the execution using n (shorthand for next) and see the variable changes:

n
[pyout] > <string>(5)<module>()
[pyout] (Pdb)
n
[pyout] > <string>(4)<module>()
[pyout] display i: 1  [old: 0]
[pyout] (Pdb)
n
[pyout] > <string>(5)<module>()
[pyout] (Pdb)
n
[pyout] > <string>(4)<module>()
[pyout] display i: 2  [old: 1]
[pyout] (Pdb)
n
[pyout] > <string>(7)<module>()
[pyout] (Pdb)
n
[pyout] SystemExit: 0
[pyout] > <string>(7)<module>()
[pyout] (Pdb)

Note the lines containing “display i: NEW [old: OLD]”.

Also note that KeyShot may behave with I/O errors when exitting the debugger.

Since you cannot interact easily via the GUI to introduce breakpoints, you can add them in code while testing. Like the following example:

if __name__ == "__main__":
  i = 0
  while i < 10:
    if i == 5:
      breakpoint()
    i += 1
  # Only needed if not using argument `-headless` or executable `keyshot_headless.exe`.
  exit(0)

Here we enter the debugger when i == 5:

p i
[pyout] 5
[pyout] (pdb)
c

c is shorthand for continue which resumes the program execution.

I recommend reading the documentation of pdf.
Including the section about “Debugger Commands”.
You can also get help by using the help command while trapped inside pdb.

This was just a very simple example, and I hope it might be of some help.
/Morten

2 Likes

Wow, Morten, your response is absolutely incredible! Your in-depth explanation is a game-changer for me, providing me with a clear understanding of how I can achieve what I wanted. I was previously relying on print logging, but your approach takes it to a whole new level. It’s especially valuable for handling those pesky exceptions that logging couldn’t fully address.

I can’t thank you enough for your invaluable help and guidance. You’re a lifesaver!

Many thanks once again!

2 Likes

That’s really useful info! Might be good to include it in the manual as well for those diving into Python.

1 Like

You’re very welcome, @fernando.andre.gomes.

Another approach to adding breakpoint() inside your function bodies, is to instead add it at the start of your script and add pdb breakpoints as needed.

Note that if the environment variable PYTHONBREAKPOINT is set to 0, then breakpoint() does nothing. If it isn’t set or has an empty value, then the regular behavior is kept.

Another trick is using locals() to check which variables are available in the local scope instead of having to know the names of the variables you’re looking for:

def foo(n):
  print(n**n)

if __name__ == "__main__":
  breakpoint()
  foo(2)

First stepping into foo(), listing the local variables (n=2), and continuing the program.

[pyout] > <string>(6)<module>()
[pyout] (Pdb)
s
[pyout] --Call--
[pyout] > <string>(1)foo()
[pyout] (Pdb)
locals()
[pyout] {'n': 2}
[pyout] (Pdb)
c
[pyout] 4

When trapping in the debugger, you can execute any Python expression. This can be pretty handy as you can dynamically alter the program at runtime!

This is what we did with locals(). It isn’t a pdb command.

Running the above program again, we can change what foo() does by overwriting the function with bar() that we define at runtime:

[pyout] > <string>(6)<module>()
[pyout] (Pdb)
def bar(n): print(f"bar() intercepted {n=}")
[pyout] (Pdb)
foo = bar
[pyout] (Pdb)
c
[pyout] bar() intercepted n=2

@oscar.rottink, that is a great idea. Thanks.

2 Likes

This is incredibly handy! I can’t wait to give it a test run! Hahaha

Hey, @morten.kristensen.1, not related to debugging, but do you happen to know how I could retrieve a camera node? I need to unlock and delete it. I tried using getCamera() and getCameras(), but they only return a string, not the actual camera node. Unfortunately, removeCamera() doesn’t work for locked cameras, and I need to remove some of them.

Any insights or tips would be highly appreciated. Thanks in advance!

That’s great to hear! :slight_smile:

Unfortunately, that is not possible right now. We might look into exposing camera nodes via the scene nodes functionality since that would automatically enable locking/unlocking, including other operations.

1 Like

I see…

So maybe I will need to change the script I was thinking. We have some files that comes from other departments that is a total mess, a lot of duplicated modelsets, cameras, environments and I was thinking about a cleanup script that would keep what is important to us. But most of the time the cameras from these ksp comes locked, so I will not be able to delete and keep the ones the user wants.
I think the solution would be newScene() and importing the file without cameras.
Maybe I could store the getCameraDirection() and other parameters before newScene() and recreate those cameras in this new scene, I think that would work.