[3dsmax] Documenting with sphinx-autodoc and MaxPlus issues

Hi all:

I’ve been using sphinx lately to generate documentation for my tools, but when it comes to 3ds max, I’ve run into a little stumbling block; because I need to import the MaxPlus module (naturally), during sphinx-autodoc, I get the following message, which reminds me: I can’t use MaxPlus outside of max!

    import MaxPlus
  File "C:\Users\sonictk\AppData\Local\Autodesk\3dsMax\2015 - 64bit\ENU\scripts\
virtualenv\Lib\site-packages\MaxPlus.py", line 26, in <module>
    _MaxPlus = swig_import_helper()
  File "C:\Users\sonictk\AppData\Local\Autodesk\3dsMax\2015 - 64bit\ENU\scripts\
virtualenv\Lib\site-packages\MaxPlus.py", line 22, in swig_import_helper
    _mod = imp.load_module('_MaxPlus', fp, pathname, description)
ImportError: DLL load failed: The specified module could not be found.

Having never really gotten around this even when trying to execute scripts in PyCharm for testing, I’d like to know if anyone has actually run into this problem before (importing MaxPlus outside of 3ds max), and if you managed to solve it? All I could find on the topic was here: http://forums.cgsociety.org/archive/index.php/t-1209635.html but as I’m trying to run sphinx as a seperate build process, I’d rather avoid having 3ds max open if possible (And I’m not sure if I can run .bat files from within the Listener anyway…?)

If anyone could offer any advice on this, I’d really appreciate it!

in your sphinx conf.py us mock:


import sys
from unittest import mock
sys.modules['MaxPlus'] = mock.Mock()

The error is most likely due to MaxPlus being compiled with a different version of either python or VC or both than the one sphinx is using. Either way, the mock should get you past it.

[QUOTE=TheMaxx;26794]in your sphinx conf.py us mock:


import sys
from unittest import mock
sys.modules['MaxPlus'] = mock.Mock()

The error is most likely due to MaxPlus being compiled with a different version of either python or VC or both than the one sphinx is using. Either way, the mock should get you past it.[/QUOTE]

(On 2.7 here)

from mock import Mock as MagicMock

class Mock(MagicMock):
    __all__ = ['QApplication','pyqtSignal','pyqtSlot','QObject','QAbstractItemModel','QModelIndex','QTabWidget',
        'QWebPage','QTableView','QWebView','QAbstractTableModel','Qt','QWidget','QPushButton','QDoubleSpinBox',
        'QListWidget','QDialog','QSize','QTableWidget','QMainWindow','QTreeWidget',
        'QAbstractItemDelegate','QColor','QGraphicsItemGroup','QGraphicsItem','QGraphicsPathItem',
        'QGraphicsTextItem','QGraphicsRectItem','QGraphicsScene','QGraphicsView',]

    def __init__(self, *args, **kwargs):
        super(Mock, self).__init__()


    @classmethod
    def __getattr__(cls, name):
        if name in ('__file__', '__path__'):
            return os.devnull
        else:
            return Mock

    @classmethod
    def __setattr__(*args, **kwargs):
        pass

    def __setitem__(self, *args, **kwargs):
        return

    def __getitem__(self, *args, **kwargs):
        return Mock
        

MOCK_MODULES = ['pygtk', 'gtk', 'gobject', 'argparse', 'numpy', 'pandas', 'MaxPlus']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)

However, because I actually am using MaxPlus for a ton o’ stuff, I’ll get warnings such as these:

And I’m not quite sure why the getattr isn’t handling the calls to the MaxPlus methods…but that’s causing the documentation to build incorrectly (no module index file generated, no docstrings, etc.)

Is there a way to get sphinx to build and ignore import errors? Been trying to find a quick band-aid over the past few days but haven’t really managed to get anything working…

im guessing it is because you’re using MaxPlus objects as defualt arguments so it has to evaluate them. I’m not sure if it would be worth for you, but you could move those off into the function:



def foo(bar=None):
    if bar is None:
        bar = MaxPlus.MenuManager.GetMainMenu()

Try that and see if it resolves the errors

[QUOTE=TheMaxx;26818]im guessing it is because you’re using MaxPlus objects as defualt arguments so it has to evaluate them. I’m not sure if it would be worth for you, but you could move those off into the function:



def foo(bar=None):
    if bar is None:
        bar = MaxPlus.MenuManager.GetMainMenu()

Try that and see if it resolves the errors[/QUOTE]

Hi Maxx:

Thanks, unfortunately there’s like 195+ of these (all sorts of methods that need to be mocked) so it’s a bit impractical to do it that way, I think. I tried doing it for a few procs, but they still require certain things to be returned (can’t mock all the return values sadly). I’m still trying to get the Mock to do it automatically for any class that it cannot import…

you’d have to find everything in a default argument and mock that, i’m still not sure why you Mock class isn’t working as expected. The modules just need to be importable, but not actually work.

hmmm, your class works in python 3 :confused:

edit: try this


class Mock(MagicMock):
    __all__ = ['QApplication','pyqtSignal','pyqtSlot','QObject','QAbstractItemModel','QModelIndex','QTabWidget',
        'QWebPage','QTableView','QWebView','QAbstractTableModel','Qt','QWidget','QPushButton','QDoubleSpinBox',
        'QListWidget','QDialog','QSize','QTableWidget','QMainWindow','QTreeWidget',
        'QAbstractItemDelegate','QColor','QGraphicsItemGroup','QGraphicsItem','QGraphicsPathItem',
        'QGraphicsTextItem','QGraphicsRectItem','QGraphicsScene','QGraphicsView',]
    def __init__(self, *args, **kwargs):
        super(Mock, self).__init__()
    @classmethod
    def __getattr__(cls, name):
        if name in ('__file__', '__path__'):
            return os.devnull
        else:
            return Mock()
    @classmethod
    def __setattr__(*args, **kwargs):
        pass
    def __setitem__(self, *args, **kwargs):
        return
    def __getitem__(self, *args, **kwargs):
        return Mock()

[QUOTE=TheMaxx;26890]you’d have to find everything in a default argument and mock that, i’m still not sure why you Mock class isn’t working as expected. The modules just need to be importable, but not actually work.

hmmm, your class works in python 3 :confused:

edit: try this


class Mock(MagicMock):
    __all__ = ['QApplication','pyqtSignal','pyqtSlot','QObject','QAbstractItemModel','QModelIndex','QTabWidget',
        'QWebPage','QTableView','QWebView','QAbstractTableModel','Qt','QWidget','QPushButton','QDoubleSpinBox',
        'QListWidget','QDialog','QSize','QTableWidget','QMainWindow','QTreeWidget',
        'QAbstractItemDelegate','QColor','QGraphicsItemGroup','QGraphicsItem','QGraphicsPathItem',
        'QGraphicsTextItem','QGraphicsRectItem','QGraphicsScene','QGraphicsView',]
    def __init__(self, *args, **kwargs):
        super(Mock, self).__init__()
    @classmethod
    def __getattr__(cls, name):
        if name in ('__file__', '__path__'):
            return os.devnull
        else:
            return Mock()
    @classmethod
    def __setattr__(*args, **kwargs):
        pass
    def __setitem__(self, *args, **kwargs):
        return
    def __getitem__(self, *args, **kwargs):
        return Mock()

[/QUOTE]

I actually have this right now:


class Mock(MagicMock):
    __all__ = ['QApplication','pyqtSignal','pyqtSlot','QObject','QAbstractItemModel','QModelIndex','QTabWidget',
        'QWebPage','QTableView','QWebView','QAbstractTableModel','Qt','QWidget','QPushButton','QDoubleSpinBox',
        'QListWidget','QDialog','QSize','QTableWidget','QMainWindow','QTreeWidget',
        'QAbstractItemDelegate','QColor','QGraphicsItemGroup','QGraphicsItem','QGraphicsPathItem',
        'QGraphicsTextItem','QGraphicsRectItem','QGraphicsScene','QGraphicsView', 'MaxPlus']

    def __init__(self, *args, **kwargs):
        super(Mock, self).__init__()


    @classmethod
    def __getattr__(cls, name):
        if name in ('__file__', '__path__'):
            return os.devnull
        else:
            return Mock()

    @classmethod
    def __setattr__(*args, **kwargs):
        pass

    def __setitem__(self, *args, **kwargs):
        return

    def __getitem__(self, *args, **kwargs):
        return Mock()

    @classmethod
    def GetMainMenu(cls):
        return Mock()

    @classmethod
    def GetSceneDir(cls):
        return Mock()

    @classmethod
    def GetRenderOutputDir(cls):
        return Mock()


    @classmethod
    def Get3DSMAXVersion(cls):
        return 1114123264

    @classmethod
    def EvalMAXScript(cls, *args):
        return Mock()

    @classmethod
    def Get(*args):
        return Mock()



MOCK_MODULES = ['pygtk', 'gtk', 'gobject', 'argparse', 'numpy', 'pandas', 'MaxPlus']
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)


autodoc_mock_imports = [
'MaxPlus',
'_MaxPlus',
]

Problem is, now all my assert checks are generating the warnings during compile time.

As an aside, is there any way I can redirect warning output to a log file as well? I’m just running

make html > buildlog.txt

and in my conf.py I have a quick class to redirect sys.stdout:

class Logger(object):
    def __init__(self):
        self.terminal = sys.stdout
        self.log = open("buildLog.txt", "a")

    def write(self, message):
        self.terminal.write(message)
        self.log.write(message)  

sys.stdout = Logger()

Which works for print statements and the like, but not warnings…those still only show up in the console.

Thanks for the help so far man! Appreciate it!

i’ll have to see if i can replicate this. As far as I knew sphinx just imported the module and got the docstring, but never called or evaluated any functions.

On a side note, i see you’re building a menu. Are/were you able to set the state of a menu item? like enable/disable etc?

EDIT:

I have the following and my docs build without issue


    parent = MaxPlus.MenuBuilder(menu)
    assert isinstance(parent, MaxPlus.MenuBuilder), 'something'

I am using py3 to build the docs however so I’m thinking it handles things differently. I’ll try with py2 in a bit

[QUOTE=TheMaxx;26910]i’ll have to see if i can replicate this. As far as I knew sphinx just imported the module and got the docstring, but never called or evaluated any functions.

On a side note, i see you’re building a menu. Are/were you able to set the state of a menu item? like enable/disable etc?

EDIT:

I have the following and my docs build without issue


    parent = MaxPlus.MenuBuilder(menu)
    assert isinstance(parent, MaxPlus.MenuBuilder), 'something'

I am using py3 to build the docs however so I’m thinking it handles things differently. I’ll try with py2 in a bit[/QUOTE]

hmm, I’ll try with Python 3 in a bit…gotta pip install all the things first T__T

And regarding enabling/disabling MenuItems; no, that’s on my list of ‘nice-to-have’ things to get working in the UI for now…It’s so annoying sometimes to run into these roadblocks with MaxPlus =\ Was thinking I would possibly find a way to do it through MAXScript even though the MenuItems were made through the MenuManager Python class…

Just to update this thread; I finally got around to taking a closer look at this as I get ready to release my toolkit, and fixed the build running by removing/passing over AssertionErrors, and mocking other methods that it was failing on.

The documentation now builds without issues. Thanks!