We assume that, at this point, your already have started FreeCAD via Pyzo. Execute the following code (either in Pyzo\'s FreeCAD-shell or in FreeCAD\'s Python console panel):

import mymod

doc = App.newDocument()
mymod.create('obj1')
mymod.create('obj2')

doc.recompute()
``` Now, everytime you recompute one of the two created FeaturePython objects, the object\'s label and \"aaa\" will be printed in Pyzo\'s shell.

We want to quickly change the behavior of the FeaturePython object so that it prints \"bbb\" instead of \"aaa\". Open the module in Pyzo\'s editor (if not already opened) and change the string literal \"aaa\" to \"bbb\". To apply these changes, just execute that cell (which is defined by the two \"##\" comment lines) in Pyzo by pressing Ctrl+Return. Recompute the objects, and we have the desired result. Editing the method and executing the cell can now be repeated as often as desired. Debugging also works normally, e.g. using breakpoints. If you want set breakpoints below the code cell, there might be a line offset because the rest of the class definition is not updated. A nice aspect of this strategy is that the code is directly modified in the real module, so when just saving the module, restarting FreeCAD and re-opening the FCStd file, you already have the newest version without any monkey patching.

## Advanced Topics 

### Creating a Custom Tool 

![Custom tool example \"MyRunner\" in Pyzo](https://raw.githubusercontent.com/FreeCAD/FreeCAD-documentation/master/wiki/images/PyzoMyRunner.png )

Pyzo includes a Plug-In concept to extend its features without patching its source code.

See the example code in the Pyzo repository:

<https://github.com/pyzo/pyzo/blob/main/pyzo/resources/tool_examples/myRunner.py>

### Debugging Start-Up of FreeCAD Modules 

To debug the init script of a FreeCAD Mod, e.g. files such as **Mod/Draft/Init.py** or **Mod/Draft/InitGui.py** using breakpoints, we need to set a breakpoint in the code file. These init script files are not directly executed.

During startup, FreeCAD runs the Python scripts **src/App/FreeCADInit.py** and **src/Gui/FreeCADGuiInit.py**, and each of these reads the corresponding init scripts (App and Gui) of the Mod and executes them as a string with the code contents:


```python
with open(file=InstallFile, encoding="utf-8") as f:
    exec(f.read())

The interpreter has no more information about the filepath of the executed code. To include the filepath, we need to patch the lines above. FreeCAD version 0.22 or newer already contain this patch.

This is not all. The scripts src/App/FreeCADInit.py and src/Gui/FreeCADGuiInit.py are not individual files anymore but resource files in libraries in the distributed binary versions of FreeCAD. Therefore we will extract them into files and replace the data in the libraries with a short Python script that will call our new out-sourced scripts. So we could also open these new files and set breakpoints there.

The following code will extract the scripts from the libraries, patch them and place the caller code back in the libraries:

import os
import sys
import shutil


lib_dirpath = '/home/.../FreeCAD/usr/lib' # example for linux os
# lib_dirpath = r'C:\ProgramData\FreeCAD\bin' # example for windows os

restore_original_libs = False
# restore_original_libs = True


lib_dirpath = os.path.abspath(lib_dirpath)
for appgui in ['App', 'Gui']:
    if sys.platform == 'linux':
        fp = os.path.join(lib_dirpath, 'libFreeCAD{}.so'.format(appgui))
    elif sys.platform == 'win32':
        fp = os.path.join(lib_dirpath, 'FreeCAD{}.dll'.format(appgui))
    else: raise NotImplementedError()

    fp_backup = fp + '.orig'

    if restore_original_libs:
        shutil.copy(fp_backup, fp)
        continue

    if not os.path.isfile(fp_backup):
        shutil.copy(fp, fp_backup)

    with open(fp_backup, 'rb') as fd:
        dd = fd.read()

    if appgui == 'App':
        needle = b'\n# FreeCAD init module\n'
        fp_py = fp + '.FreeCADInit.py'
    else:
        needle = b'\n# FreeCAD gui init module\n'
        fp_py = fp + '.FreeCADGuiInit.py'

    i_needle = dd.index(needle)
    assert dd.find(needle, i_needle + 1) == -1

    i_start = i_needle - dd[:i_needle][::-1].index(b'\x00')
    i_end = dd.index(b'\x00', i_needle)

    dd_code = dd[i_start:i_end]

    fp_py_orig = fp_py + '.orig'
    with open(fp_py_orig, 'wb') as fd:
        fd.write(dd_code)

    loader_code = '\n'.join([
        'fp = ' + repr(fp_py),
        'with open(fp, "rt", encoding="utf-8") as fd: source = fd.read()',
        'exec(compile(source, fp, "exec"))',
        ]).format(repr(fp_py))

    dd_code_new = loader_code.encode('utf-8')
    assert len(dd_code_new) <= len(dd_code)
    dd_code_new += b'\x00' * (len(dd_code) - len(dd_code_new))

    dd_new = dd[:i_start] + dd_code_new + dd[i_end:]
    assert len(dd_new) == len(dd)

    with open(fp, 'wb') as fd:
        fd.write(dd_new)


    tt_code = dd_code.decode('utf-8')

    needle = """
                with open(file=InstallFile, encoding="utf-8") as f:
                    exec(f.read())
    """

    needle_patched = """
                with open(InstallFile, 'rt', encoding='utf-8') as f:
                    exec(compile(f.read(), InstallFile, 'exec'))
    """

    if needle in tt_code:
        # older FreeCAD versions < 0.22 need to be patched
        tt_code = tt_code.replace(needle, needle_patched)
    else:
        assert needle_patched in tt_code

    with open(fp_py, 'wt', encoding='utf-8') as fd:
        fd.write(tt_code)

We can now, for example, open Mod/Draft/Init.py in Pyzo, set a breakpoint there in line 26 and start the FreeCAD shell in Pyzo. Execution will be interrupted at the breakpoint. We can switch the stack frames, view and manipulate variables and step/continue the code execution.

Pyzo customizations developed by FreeCAD users

Workspace2 by TheMarkster et al.

https://forum.freecad.org/viewtopic.php?p=720709#p720709

A modified \"workspace\" tool that adds filters and a watchlist.

WorkspaceWatch by heda

https://forum.freecad.org/viewtopic.php?p=774260#p774260

A modified \"workspace\" tool that adds an extra treeview widget for displaying \"watched\" variables.

GitBashTool by TheMarkster

https://forum.freecad.org/viewtopic.php?p=774810#p774810

Displays a push button to open a git bash shell in the path of the file in the current editor.

Known Limitations and Issues

General issues caused by importing FreeCAD as a module in Python

Wrong scaling for the FreeCAD GUI on multiple screens with different resolutions

problem description : https://forum.freecad.org/viewtopic.php?p=774781#p774781

solution : https://forum.freecad.org/viewtopic.php?p=774803#p774803 : https://forum.freecad.org/viewtopic.php?p=774972#p774972

Fontconfig related crash when switching to the Draft workbench because of third-party software \"Graphviz\"

problem description : https://forum.freecad.org/viewtopic.php?p=776449#p776449

solution : https://forum.freecad.org/viewtopic.php?p=776751#p776751

Feedback

To ask questions about this topic, share ideas, give input, point out mistakes, etc, please write a message to the initial topic in the FreeCAD forum or create a new one.

Miscellaneous Links


documentation index > Pyzo

This page is retrieved from https://github.com/FreeCAD/FreeCAD-documentation/blob/main/wiki/Pyzo.md