├── source
├── test
│ ├── __init__.py
│ ├── res
│ │ └── settings.ini
│ ├── examples
│ │ └── PlaneProjector_tests.c4d
│ ├── objects
│ │ ├── object_iterator_tests.c4d
│ │ ├── object_hierarchy_tests.c4d
│ │ ├── center_object_axis_tests.c4d
│ │ ├── object_hierarchy_tests.py
│ │ ├── center_object_axis_tests.py
│ │ └── object_iterator_tests.py
│ ├── maths
│ │ ├── matrix_to_listlist_tests.c4d
│ │ └── matrix_to_listlist_tests.py
│ ├── utils_tests.py
│ ├── plugins_tests.py
│ ├── maths_tests.py
│ └── objects_tests.py
└── py4dlib
│ ├── examples
│ ├── __init__.py
│ ├── Extract Polys 1.0
│ │ ├── res
│ │ │ ├── icon.tif
│ │ │ ├── strings_us
│ │ │ │ └── dialogs
│ │ │ │ │ └── IDD_DIALOG_SETTINGS.str
│ │ │ ├── c4d_symbols.h
│ │ │ └── dialogs
│ │ │ │ └── IDD_DIALOG_SETTINGS.res
│ │ └── Extract.pyp
│ ├── Regex Renamer 1.1
│ │ └── res
│ │ │ ├── icon.png
│ │ │ ├── icons
│ │ │ ├── alternative icon 01.tif
│ │ │ ├── alternative icon 02.tif
│ │ │ └── alternative icon 03.png
│ │ │ ├── tutorial.html
│ │ │ ├── strings_us
│ │ │ └── dialogs
│ │ │ │ └── IDD_DIALOG_SETTINGS.str
│ │ │ ├── c4d_symbols.h
│ │ │ └── dialogs
│ │ │ └── IDD_DIALOG_SETTINGS.res
│ ├── Plane Projector 1.2
│ │ ├── res
│ │ │ ├── icon.png
│ │ │ ├── strings_us
│ │ │ │ └── dialogs
│ │ │ │ │ └── IDD_DIALOG_SETTINGS.str
│ │ │ ├── c4d_symbols.h
│ │ │ └── dialogs
│ │ │ │ └── IDD_DIALOG_SETTINGS.res
│ │ └── icons
│ │ │ ├── alternative icon 1.png
│ │ │ ├── alternative icon 2.png
│ │ │ ├── alternative icon 3.png
│ │ │ ├── alternative icon 4.png
│ │ │ └── alternative icon 5.png
│ ├── PrintObjectHierarchy.py
│ ├── CreatePlanesFromPolygons.py
│ └── ShowPolygonNumber.py
│ ├── __init__.py
│ ├── plugins.py
│ └── utils.py
├── docs
├── img
│ └── ShowPolygonNumber.png
├── pydocs3theme
│ ├── theme.conf
│ └── static
│ │ ├── pydoctheme.css_t
│ │ ├── pygments.css_t
│ │ ├── default.css_t
│ │ └── basic.css_t
├── acknowledgements.rst
├── contributions.rst
├── license.rst
├── installation.rst
├── index.rst
├── examples.rst
├── api.rst
├── api
│ ├── plugins.rst
│ ├── mesh.rst
│ ├── utils.rst
│ ├── maths.rst
│ └── objects.rst
├── usage.rst
├── Makefile
├── make.bat
└── conf.py
├── .pydevproject
├── .gitignore
└── README.md
/source/test/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/source/test/res/settings.ini:
--------------------------------------------------------------------------------
1 | [Settings]
2 | string = some string
3 | int = 1
4 | float = 2.0
5 |
6 |
--------------------------------------------------------------------------------
/docs/img/ShowPolygonNumber.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/docs/img/ShowPolygonNumber.png
--------------------------------------------------------------------------------
/docs/pydocs3theme/theme.conf:
--------------------------------------------------------------------------------
1 | [theme]
2 | inherit = default
3 | stylesheet = pydoctheme.css
4 | pygments_style = sphinx
--------------------------------------------------------------------------------
/source/test/examples/PlaneProjector_tests.c4d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/test/examples/PlaneProjector_tests.c4d
--------------------------------------------------------------------------------
/source/test/objects/object_iterator_tests.c4d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/test/objects/object_iterator_tests.c4d
--------------------------------------------------------------------------------
/source/test/maths/matrix_to_listlist_tests.c4d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/test/maths/matrix_to_listlist_tests.c4d
--------------------------------------------------------------------------------
/source/test/objects/object_hierarchy_tests.c4d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/test/objects/object_hierarchy_tests.c4d
--------------------------------------------------------------------------------
/source/test/objects/center_object_axis_tests.c4d:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/test/objects/center_object_axis_tests.c4d
--------------------------------------------------------------------------------
/source/py4dlib/examples/Extract Polys 1.0/res/icon.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Extract Polys 1.0/res/icon.tif
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Regex Renamer 1.1/res/icon.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/res/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/res/icon.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 1.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 2.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 3.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 4.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Plane Projector 1.2/icons/alternative icon 5.png
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 01.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 01.tif
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 02.tif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 02.tif
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/andreberg/py4dlib/HEAD/source/py4dlib/examples/Regex Renamer 1.1/res/icons/alternative icon 03.png
--------------------------------------------------------------------------------
/docs/acknowledgements.rst:
--------------------------------------------------------------------------------
1 | Acknowledgements
2 | ----------------
3 |
4 | a.k.a. **The Hall of Fame**
5 |
6 | In no particular order.
7 |
8 | * Lennart Wåhlin (TCA Studios)
9 | * Per-Anders Edwards
10 | * Adam Swaab
11 | * NitroMan (Nitro4D)
12 | * Robert Leger
13 | * Niklas Rosenstein
14 | * Scott Ayers
15 |
16 | Thanks for making some of your C.O.F.F.E.E. and Python scripts
17 | free and for often not denying the ability to take a look.
18 |
--------------------------------------------------------------------------------
/docs/contributions.rst:
--------------------------------------------------------------------------------
1 | Contributions
2 | -------------
3 |
4 | I'd be very happy to accept code contributions. If you'd like to contribute
5 | please fork this repository, commit your changes to your local fork and then
6 | send me a pull request.
7 |
8 | If that's too much of a hassle, you can also go ahead and create a gist or
9 | a pastebin post (or whatever copypasta service you fancy) and send me the URL
10 | so that I can include the code manually.
11 |
--------------------------------------------------------------------------------
/.pydevproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Default
6 | python 2.7
7 |
8 | /py4dlib/source
9 |
10 |
11 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/tutorial.html:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 | Re-directing to www.regular-expressions.info
7 |
8 |
9 |
10 |
11 | Re-directing to www.regular-expressions.info (online connection needed)
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/license.rst:
--------------------------------------------------------------------------------
1 | License
2 | -------
3 |
4 | .. code::
5 |
6 | Licensed under the Apache License, Version 2.0 (the "License");
7 | you may not use this file except in compliance with the License.
8 | You may obtain a copy of the License at
9 |
10 | http://www.apache.org/licenses/LICENSE-2.0
11 |
12 | Unless required by applicable law or agreed to in writing, software
13 | distributed under the License is distributed on an "AS IS" BASIS,
14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | See the License for the specific language governing permissions and
16 | limitations under the License.
17 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ------------
3 |
4 | Take the `py4dlib` folder which you can find under `/source` in
5 | this repository, and put it under the following path::
6 |
7 | /library/python/packages/
8 |
9 | `` is the prefs directory for CINEMA 4D in your home
10 | directory and `` is the name of the operating system you are
11 | running.
12 |
13 | For example on OS X this path could be::
14 |
15 | /Users//Library/Preferences/MAXON/CINEMA 4D R/library/python/packages/osx
16 |
17 | On Windows this path could be::
18 |
19 | %APPDATA%\MAXON\CINEMA 4D R\library\python\packages\win64
20 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Extract Polys 1.0/res/strings_us/dialogs/IDD_DIALOG_SETTINGS.str:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 |
3 | DIALOGSTRINGS IDD_DIALOG_SETTINGS
4 | {
5 | IDS_BUTTON_CANCEL "Cancel";
6 | IDS_BUTTON_DOIT "Do It!";
7 | IDS_CHECK "Optimize Unused Points";
8 | IDS_CHECK1 "Hide Instead of Delete";
9 | IDS_CHECK2 "Center Axis";
10 | IDS_CHECK3 "Keep Normal Tags";
11 | IDS_CHECK4 "Delete Original Polys";
12 | IDS_CHECK5 "Try Re-Aligning Normals";
13 | IDS_DIALOG_TITLE "Dialog";
14 | IDS_DUMMY "Dummy";
15 | IDS_GROUP_BUTTONS "Buttons";
16 | IDS_GROUP_SETTINGS "Settings";
17 | IDS_GROUP_WRAPPER "Wrapper";
18 | IDS_STATIC "Group";
19 | IDS_STATIC1 "Group";
20 | }
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generic
2 |
3 | *~
4 | *.[oa]
5 | *.bak
6 | backup/
7 |
8 | # Mac OS X
9 |
10 | Desktop DF
11 | Desktop DB
12 | .Spotlight-V100
13 | .DS_Store
14 | .Trashes
15 | .com.apple.timemachine.supported
16 | .fseventsd
17 | .syncinfo
18 | .TemporaryItems
19 | ._.*
20 |
21 | # Windows
22 |
23 | Thumbs.db
24 | desktop.ini
25 |
26 | # TextMate
27 |
28 | *.tm_build_errors
29 | *.tmproj
30 |
31 | # Xcode 3
32 |
33 | build/
34 | *~.nib
35 | *.pbxuser
36 | *.perspectivev3
37 | *.mode1
38 | *.mode1v3
39 | *.mode2v3
40 |
41 | # Xcode 4
42 |
43 | xcuserdata/
44 | *.xcworkspace
45 |
46 | # Python
47 |
48 | *.pyc
49 | __pycache__/
50 |
51 | # Sphinx
52 |
53 | _build/
54 |
55 | # GitHub
56 |
57 | gh-pages/
58 |
59 | # Eclipse / Aptana
60 |
61 | *.project
62 | .settings/
63 | doc/
64 |
65 | # CodePro
66 |
67 | coverage.txt
68 | coverage/
69 |
70 | # Project Specific
71 |
72 | scripts/
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/strings_us/dialogs/IDD_DIALOG_SETTINGS.str:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 |
3 | DIALOGSTRINGS IDD_DIALOG_SETTINGS
4 | {
5 | IDS_BUTTON_CANCEL "Cancel";
6 | IDS_BUTTON_DOIT "Replace";
7 | IDS_CHECK_DOTALL "Dot all";
8 | IDS_CHECK_IGNORECASE "Ignore case";
9 | IDS_CHECK_MULTILINE "Multiline";
10 | IDS_CHECK_SELECTIONONLY "Selected objects only";
11 | IDS_CHECK_VERBOSE "Verbose";
12 | IDS_DIALOG_TITLE "Regex Renamer";
13 | IDS_DUMMY "Dummy";
14 | IDS_GROUP_BUTTONS "Buttons";
15 | IDS_GROUP_ENTRYUI "Entry UI";
16 | IDS_GROUP_SETTINGS_REGEX "Regex Settings";
17 | IDS_GROUP_SETTINGS_TARGET "Target Settings";
18 | IDS_GROUP_WRAPPER "Wrapper";
19 | IDS_STATIC_REPLACE "Replace with:";
20 | IDS_STATIC_SEARCH "Search for:";
21 | }
22 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Extract Polys 1.0/res/c4d_symbols.h:
--------------------------------------------------------------------------------
1 | //*********************************************************************\
2 | // File name : c4d_symbols.h
3 | // Description : symbol definition file for the plugin
4 | // Created at :
5 | // Created by : Resource editor
6 | // Modified by :
7 | //*********************************************************************/
8 | // WARNING : Only edit this file, if you exactly know what you are doing.
9 | // WARNING : The comments are important, too.
10 |
11 | enum
12 | {
13 | _FIRST_ELEMENT_ = 10000,
14 | // Dialog definitions of IDD_DIALOG_SETTINGS start here
15 | IDD_DIALOG_SETTINGS,
16 | IDC_BUTTON_CANCEL,
17 | IDC_BUTTON_DOIT,
18 | IDC_GROUP_WRAPPER,
19 | IDC_GROUP_SETTINGS,
20 | IDC_GROUP_BUTTONS,
21 | IDC_DUMMY,
22 | IDC_CHK_OPTIMIZE,
23 | IDC_CHK_CENTER,
24 | IDC_CHK_KEEPNORMALTAG,
25 | IDC_CHK_HIDE,
26 | IDC_CHK_DELETEORIG,
27 | IDC_CHK_RENORMALIZE,
28 | // Dialog definitions of IDD_DIALOG_SETTINGS end here
29 |
30 |
31 | // End of symbol definition
32 | _DUMMY_ELEMENT_
33 | };
34 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. py4dlib documentation master file, created by
2 | sphinx-quickstart on Tue Jul 30 19:53:32 2013.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to py4dlib's documentation!
7 | ===================================
8 |
9 | **py4dlib** is a collection of Python modules to ease development
10 | of scripts and plugins for MAXON's `CINEMA 4D `_ 3D application.
11 |
12 | It's goal is to provide essentials and convenience functions
13 | left out by CINEMA 4D's current Python SDK so that repetition and
14 | code duplication can be avoided.
15 |
16 | It includes routines for working with c4d objects, polygonal modelling,
17 | some utilities and tools for writing plugins and math routines needed for 3D work.
18 |
19 |
20 | Contents:
21 |
22 | .. toctree::
23 |
24 | installation
25 | usage
26 | contributions
27 | acknowledgements
28 | license
29 | api
30 | examples
31 |
32 |
33 | Indices and tables
34 | ==================
35 |
36 | * :ref:`genindex`
37 | * :ref:`modindex`
38 | * :ref:`search`
39 |
40 |
41 |
--------------------------------------------------------------------------------
/source/py4dlib/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # __init__.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-26.
7 | # Copyright 2012 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | __version__ = (0, 6, 5)
14 | __author__ = u'André Berg'
15 | __email__ = 'andre.bergmedia@googlemail.com'
16 | __url__ = 'http://github.com/andreberg/py4dlib'
17 | __license__ = 'Apache License, Version 2.0'
18 | __title__ = "py4dlib: re-usable components for CINEMA 4D's Python implementation"
19 |
20 |
21 | # Licensed under the Apache License, Version 2.0 (the "License");
22 | # you may not use this file except in compliance with the License.
23 | # You may obtain a copy of the License at
24 | #
25 | # http://www.apache.org/licenses/LICENSE-2.0
26 | #
27 | # Unless required by applicable law or agreed to in writing, software
28 | # distributed under the License is distributed on an "AS IS" BASIS,
29 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
30 | # See the License for the specific language governing permissions and
31 | # limitations under the License.
32 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/c4d_symbols.h:
--------------------------------------------------------------------------------
1 | //*********************************************************************\
2 | // File name : c4d_symbols.h
3 | // Description : symbol definition file for the plugin
4 | // Created at :
5 | // Created by : Resource editor
6 | // Modified by :
7 | //*********************************************************************/
8 | // WARNING : Only edit this file, if you exactly know what you are doing.
9 | // WARNING : The comments are important, too.
10 |
11 | enum
12 | {
13 | _FIRST_ELEMENT_ = 10000,
14 | // Dialog definitions of IDD_DIALOG_SETTINGS start here
15 | IDD_DIALOG_SETTINGS,
16 | IDC_GROUP_WRAPPER,
17 | IDC_GROUP_SETTINGS,
18 | IDC_STATIC_SEARCH,
19 | IDC_EDIT_SEARCH,
20 | IDC_STATIC_REPLACE,
21 | IDC_EDIT_REPLACE,
22 | IDC_CHECK_IGNORECASE,
23 | IDC_CHECK_MULTILINE,
24 | IDC_CHECK_DOTALL,
25 | IDC_CHECK_VERBOSE,
26 | IDC_GROUP_SETTINGS2,
27 | IDC_CHECK_SELECTIONONLY,
28 | IDC_GROUP_BUTTONS,
29 | IDC_BUTTON_CANCEL,
30 | IDC_BUTTON_DOIT,
31 | IDC_DUMMY,
32 | // Dialog definitions of IDD_DIALOG_SETTINGS end here
33 |
34 |
35 | // End of symbol definition
36 | _DUMMY_ELEMENT_
37 | };
38 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/res/strings_us/dialogs/IDD_DIALOG_SETTINGS.str:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 |
3 | DIALOGSTRINGS IDD_DIALOG_SETTINGS
4 | {
5 | IDS_BUTTON_CANCEL "Cancel";
6 | IDS_BUTTON_DOIT "Project";
7 | IDS_BUTTON_GETOBJ "<";
8 | IDS_BUTTON_PICK "Pick";
9 | IDS_BUTTON_PLANE "Plane";
10 | IDS_BUTTON_WPLANE "Workplane";
11 | IDS_DIALOG_TITLE "Plane Projector";
12 | IDS_DUMMY "Dummy";
13 | IDS_GROUP_BUTTONS "Buttons";
14 | IDS_GROUP_MANUAL "Manual Edit Group";
15 | IDS_GROUP_SETTINGS "Settings";
16 | IDS_GROUP_SETTINGS2 "Settings3";
17 | IDS_GROUP_WRAPPER "Wrapper";
18 | IDS_MANUAL "Manual";
19 | IDS_NORMAL "Vertex Normal";
20 | IDS_STATIC1 "Create ...............";
21 | IDS_STATIC2 "Create Buttons";
22 | IDS_STATIC_MANUAL "Manual Dir ..";
23 | IDS_STATIC_MODE "Direction .....";
24 | IDS_STATIC_SETTINGS4 "Settings4";
25 | IDS_STATIC_TARGET "Target ...............";
26 | IDS_STATIC_UPAXIS "Target Up Axis ..";
27 | IDS_X "X";
28 | IDS_X_LOCAL "Local X";
29 | IDS_Y "Y";
30 | IDS_Y_LOCAL "Local Y";
31 | IDS_Z "Z";
32 | IDS_Z_LOCAL "Local Z";
33 | }
34 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/res/c4d_symbols.h:
--------------------------------------------------------------------------------
1 | //*********************************************************************\
2 | // File name : c4d_symbols.h
3 | // Description : symbol definition file for the plugin
4 | // Created at :
5 | // Created by : Resource editor
6 | // Modified by :
7 | //*********************************************************************/
8 | // WARNING : Only edit this file, if you exactly know what you are doing.
9 | // WARNING : The comments are important, too.
10 |
11 | enum
12 | {
13 | _FIRST_ELEMENT_ = 10000,
14 | // Dialog definitions of IDD_DIALOG_SETTINGS start here
15 | IDD_DIALOG_SETTINGS,
16 | IDC_GROUP_WRAPPER,
17 | IDC_GROUP_SETTINGS,
18 | IDC_STATIC_TARGET,
19 | IDC_EDIT_TARGET,
20 | IDC_BUTTON_GETOBJ,
21 | IDC_STATIC_UPAXIS,
22 | IDC_COMBO_UPAXIS,
23 | IDC_BUTTON_PLANE,
24 | IDC_BUTTON_WPLANE,
25 | IDC_GROUP_SETTINGS2,
26 | IDC_STATIC_MODE,
27 | IDC_COMBO_MODE,
28 | IDC_GROUP_SETTINGS3,
29 | IDC_STATIC_MANUAL,
30 | IDC_GROUP_MANUAL,
31 | IDC_EDIT_X,
32 | IDC_EDIT_Y,
33 | IDC_EDIT_Z,
34 | IDC_GROUP_BUTTONS,
35 | IDC_BUTTON_CANCEL,
36 | IDC_BUTTON_DOIT,
37 | IDC_DUMMY,
38 | // Dialog definitions of IDD_DIALOG_SETTINGS end here
39 |
40 |
41 | // End of symbol definition
42 | _DUMMY_ELEMENT_
43 | };
44 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Extract Polys 1.0/res/dialogs/IDD_DIALOG_SETTINGS.res:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 | DIALOG IDD_DIALOG_SETTINGS
3 | {
4 | NAME IDS_DIALOG_TITLE; CENTER_V; ALIGN_LEFT;
5 |
6 | GROUP IDC_GROUP_WRAPPER
7 | {
8 | NAME IDS_GROUP_WRAPPER; ALIGN_TOP; SCALE_H;
9 | BORDERSIZE 4, 4, 4, 4;
10 | COLUMNS 1;
11 | SPACE 4, 4;
12 |
13 | GROUP IDC_GROUP_SETTINGS
14 | {
15 | NAME IDS_GROUP_SETTINGS; ALIGN_TOP; CENTER_H;
16 | BORDERSIZE 5, 5, 5, 5;
17 | COLUMNS 1;
18 | SPACE 4, 4;
19 |
20 | CHECKBOX IDC_CHK_OPTIMIZE { NAME IDS_CHECK; ALIGN_TOP; ALIGN_LEFT; }
21 | CHECKBOX IDC_CHK_CENTER { NAME IDS_CHECK2; ALIGN_TOP; ALIGN_LEFT; }
22 | SEPARATOR { SCALE_H; }
23 | CHECKBOX IDC_CHK_DELETEORIG { NAME IDS_CHECK4; ALIGN_TOP; ALIGN_LEFT; }
24 | GROUP IDC_STATIC
25 | {
26 | ALIGN_TOP; ALIGN_LEFT;
27 | BORDERSIZE 16, 0, 0, 0;
28 | COLUMNS 1;
29 | SPACE 4, 4;
30 |
31 | CHECKBOX IDC_CHK_HIDE { NAME IDS_CHECK1; ALIGN_TOP; ALIGN_LEFT; }
32 | }
33 | SEPARATOR { SCALE_H; }
34 | CHECKBOX IDC_CHK_KEEPNORMALTAG { NAME IDS_CHECK3; ALIGN_TOP; ALIGN_LEFT; }
35 | GROUP IDC_STATIC
36 | {
37 | NAME IDS_STATIC1; ALIGN_TOP; ALIGN_LEFT;
38 | BORDERSIZE 16, 0, 0, 0;
39 | COLUMNS 1;
40 | SPACE 4, 4;
41 |
42 | CHECKBOX IDC_CHK_RENORMALIZE { NAME IDS_CHECK5; ALIGN_TOP; ALIGN_LEFT; }
43 | }
44 | }
45 | SEPARATOR { SCALE_H; }
46 | GROUP IDC_GROUP_BUTTONS
47 | {
48 | NAME IDS_GROUP_BUTTONS; ALIGN_TOP; CENTER_H;
49 | BORDERSIZE 0, 0, 0, 0;
50 | COLUMNS 2;
51 | SPACE 4, 4;
52 |
53 | BUTTON IDC_BUTTON_CANCEL { NAME IDS_BUTTON_CANCEL; ALIGN_TOP; CENTER_H; }
54 | BUTTON IDC_BUTTON_DOIT { NAME IDS_BUTTON_DOIT; ALIGN_TOP; CENTER_H; }
55 | }
56 | GROUP IDC_DUMMY
57 | {
58 | NAME IDS_DUMMY; ALIGN_TOP; SCALE_H;
59 | BORDERSIZE 0, 0, 0, 0;
60 | COLUMNS 1;
61 | SPACE 4, 4;
62 |
63 |
64 | }
65 | }
66 | }
--------------------------------------------------------------------------------
/docs/examples.rst:
--------------------------------------------------------------------------------
1 | .. _py4dlib.examples:
2 |
3 | Examples
4 | --------
5 |
6 | ShowPolygonNumber
7 | ~~~~~~~~~~~~~~~~~
8 |
9 | Example script that shows how to get point and polygon selections
10 | and how to create objects and attach them to polygons in local and
11 | global coordinate systems.
12 |
13 |
14 | PrintObjectHierarchy
15 | ~~~~~~~~~~~~~~~~~~~~
16 |
17 | Example script that shows how to use the :py:class:`ObjectHierarchy` class.
18 |
19 | .. _Extract:
20 |
21 | Extract
22 | ~~~~~~~
23 |
24 | Example plugin that extracts selected polygons across multiple objects
25 | into new polygon objects.
26 |
27 | Basically, the same as selecting polygons on some objects,
28 | running Split, then deleting the inverse selection to keep
29 | only the selected polys. Plus a few extras.
30 |
31 | PlaneProjector
32 | ~~~~~~~~~~~~~~
33 |
34 | Example plugin that shows how to project selected points along a direction vector
35 | onto a planar target.
36 |
37 | Can snap any number of points onto planar targets along local or global X, Y, Z, a manual
38 | direction or the averaged vertex normals (basically the direction of the modelling tool's
39 | Normal setting for a group of selected points).
40 |
41 | Currently needs a working plane or a plane object as target. You can create a new working
42 | plane yourself and pick it up to be the target by pressing the pick target button (<) or
43 | you can select any number of polygons on any number of polygon objects and hit one of the
44 | Create buttons to create a new plane or workplane aligned to the polygons.
45 |
46 | Note: in R14 the workplane button aligns the workplane mode to the selection instead of
47 | creating a workplane object.
48 |
49 | As a future improvement it shouldn't be hard at all to use polygons of other objects as
50 | planar targets.
51 |
52 | RegexRenamer
53 | ~~~~~~~~~~~~
54 |
55 | Regex Renamer is a command plugin, that shows how to use the :py:class:`UserDefaults` class
56 | to provide UI state persistence between times the plugin is run.
57 |
58 | The plugin's function is to utilize Python's powerful "re" module to perform regular expression
59 | based searching and replacing within object names.
60 |
61 |
62 | Check out `http://regular-expression.info `_ to learn more
63 | about regular expressions.
64 |
65 |
--------------------------------------------------------------------------------
/source/test/objects/object_hierarchy_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # object_hierarchy_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-28.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # This file is a copy of the Python tag script
10 | # found in the C4D file of the same name.
11 | # It is provided for reference in case CINEMA 4D
12 | # is not available or the .c4d file can't be opened.
13 | #
14 | # pylint: disable-msg=F0401,W0611
15 |
16 | import os
17 | import pprint
18 |
19 | pp = pprint.PrettyPrinter(width=92)
20 | PP = pp.pprint
21 | PF = pp.pformat
22 |
23 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
24 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
25 |
26 | try:
27 | import c4d #@UnresolvedImport
28 | from c4d import documents #@UnresolvedImport
29 | except ImportError:
30 | if TESTRUN == 1:
31 | pass
32 |
33 | from py4dlib import utils, objects #@UnusedImport
34 | from py4dlib.objects import ObjectHierarchy
35 |
36 |
37 | def main():
38 | utils.ClearConsole()
39 | doc = documents.GetActiveDocument()
40 | if not doc:
41 | return None
42 | targetobj = doc.SearchObject('Target')
43 | sourceobj = doc.SearchObject('Source')
44 | if not targetobj or not sourceobj:
45 | return None
46 | oh = ObjectHierarchy(targetobj)
47 | oh.PPrint()
48 | oh.PPrint(filter_type=c4d.Onull)
49 | scene = ObjectHierarchy()
50 | PP(scene)
51 | print(scene)
52 | filtered_objs = oh.Get('Target/Source/../*')
53 | for obj in filtered_objs:
54 | print(obj.GetName())
55 |
56 |
57 | if __name__=='__main__':
58 | main()
59 |
60 |
61 | # Licensed under the Apache License, Version 2.0 (the "License");
62 | # you may not use this file except in compliance with the License.
63 | # You may obtain a copy of the License at
64 | #
65 | # http://www.apache.org/licenses/LICENSE-2.0
66 | #
67 | # Unless required by applicable law or agreed to in writing, software
68 | # distributed under the License is distributed on an "AS IS" BASIS,
69 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
70 | # See the License for the specific language governing permissions and
71 | # limitations under the License.
72 |
--------------------------------------------------------------------------------
/source/test/utils_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # test.utils_tests
4 | # project
5 | #
6 | # Created by André Berg on 2013-08-12.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | import os
14 | import unittest
15 |
16 | __version__ = (0, 1)
17 | __date__ = '2013-08-12'
18 | __updated__ = '2013-08-12'
19 |
20 |
21 | DEBUG = 0 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
22 | TESTRUN = 1 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
23 |
24 |
25 | from py4dlib import utils
26 | from py4dlib.utils import EscapeUnicode, UnescapeUnicode
27 |
28 |
29 | class Test(unittest.TestCase):
30 |
31 | def testEscapeUnescapeUnicode(self):
32 |
33 | a1 = "W\xfcrfel" # R12 latin-1 bytes
34 | u1 = u"W\xfcrfel" # R12 latin-1 unicode
35 | a2 = r"W\u00FCrfel" # R12 bytes escaped unicode
36 | a3 = "W\xc3\xbcrfel" # R13/14 utf-8 bytes
37 | u2 = u"Würfel" # R13/14 utf-8 unicode
38 |
39 | src = [a1, u1, a2, a3, u2]
40 |
41 | dst12 = []
42 | for s in src:
43 | dst12.append(EscapeUnicode(s))
44 |
45 | utils.C4D_VERSION = 13000
46 |
47 | dst13 = []
48 | for s in src:
49 | dst13.append(EscapeUnicode(s))
50 |
51 | for d in dst12:
52 | self.assertEquals(r"W\u00FCrfel", d)
53 |
54 | dst13_expected = src
55 |
56 | for i in xrange(len(dst13)):
57 | self.assertEquals(dst13[i], dst13_expected[i])
58 |
59 | utils.C4D_VERSION = 12000
60 |
61 | for j in dst12:
62 | res = UnescapeUnicode(j)
63 | self.assertEquals(res, u"W\xfcrfel")
64 |
65 |
66 | if __name__ == "__main__":
67 | #import sys;sys.argv = ['', 'Test.testName']
68 | unittest.main()
69 |
70 |
71 | # Licensed under the Apache License, Version 2.0 (the "License");
72 | # you may not use this file except in compliance with the License.
73 | # You may obtain a copy of the License at
74 | #
75 | # http://www.apache.org/licenses/LICENSE-2.0
76 | #
77 | # Unless required by applicable law or agreed to in writing, software
78 | # distributed under the License is distributed on an "AS IS" BASIS,
79 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
80 | # See the License for the specific language governing permissions and
81 | # limitations under the License.
82 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Regex Renamer 1.1/res/dialogs/IDD_DIALOG_SETTINGS.res:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 | DIALOG IDD_DIALOG_SETTINGS
3 | {
4 | NAME IDS_DIALOG_TITLE; CENTER_V; ALIGN_LEFT;
5 |
6 | GROUP IDC_GROUP_WRAPPER
7 | {
8 | NAME IDS_GROUP_WRAPPER; SCALE_V; SCALE_H;
9 | BORDERSIZE 4, 4, 4, 4;
10 | COLUMNS 1;
11 | SPACE 4, 4;
12 |
13 | GROUP IDC_GROUP_SETTINGS
14 | {
15 | NAME IDS_GROUP_ENTRYUI; SCALE_V; SCALE_H;
16 | BORDERSIZE 2, 2, 2, 2;
17 | COLUMNS 2;
18 | SPACE 4, 4;
19 |
20 | STATICTEXT IDC_STATIC_SEARCH { NAME IDS_STATIC_SEARCH; CENTER_V; ALIGN_RIGHT; }
21 | MULTILINEEDIT IDC_EDIT_SEARCH
22 | {
23 | SCALE_V; SCALE_H; SIZE 70, 0;
24 | MONOSPACED; SYNTAXCOLOR; PYTHON; STATUSBAR;
25 | }
26 | STATICTEXT IDC_STATIC_REPLACE { NAME IDS_STATIC_REPLACE; CENTER_V; ALIGN_RIGHT; }
27 | MULTILINEEDIT IDC_EDIT_REPLACE
28 | {
29 | SCALE_V; SCALE_H; SIZE 70, 0;
30 | MONOSPACED; SYNTAXCOLOR; PYTHON; STATUSBAR;
31 | }
32 | }
33 | GROUP IDC_STATIC
34 | {
35 | NAME IDS_GROUP_SETTINGS_REGEX; ALIGN_TOP; ALIGN_LEFT;
36 | BORDERSTYLE BORDER_NONE; BORDERSIZE 77, 0, 4, 0;
37 | ROWS 1;
38 |
39 | CHECKBOX IDC_CHECK_IGNORECASE { NAME IDS_CHECK_IGNORECASE; ALIGN_TOP; ALIGN_LEFT; }
40 | CHECKBOX IDC_CHECK_MULTILINE { NAME IDS_CHECK_MULTILINE; ALIGN_TOP; ALIGN_LEFT; }
41 | CHECKBOX IDC_CHECK_DOTALL { NAME IDS_CHECK_DOTALL; ALIGN_TOP; ALIGN_LEFT; }
42 | CHECKBOX IDC_CHECK_VERBOSE { NAME IDS_CHECK_VERBOSE; ALIGN_TOP; ALIGN_LEFT; }
43 | }
44 | GROUP IDC_GROUP_SETTINGS2
45 | {
46 | NAME IDS_GROUP_SETTINGS_TARGET; ALIGN_TOP; ALIGN_LEFT;
47 | BORDERSIZE 77, 0, 0, 0;
48 | COLUMNS 1;
49 | SPACE 4, 4;
50 |
51 | CHECKBOX IDC_CHECK_SELECTIONONLY { NAME IDS_CHECK_SELECTIONONLY; ALIGN_TOP; ALIGN_LEFT; }
52 | }
53 | SEPARATOR { SCALE_H; }
54 | GROUP IDC_GROUP_BUTTONS
55 | {
56 | NAME IDS_GROUP_BUTTONS; ALIGN_TOP; CENTER_H;
57 | BORDERSIZE 0, 0, 0, 0;
58 | COLUMNS 2;
59 | SPACE 4, 4;
60 |
61 | BUTTON IDC_BUTTON_CANCEL { NAME IDS_BUTTON_CANCEL; ALIGN_TOP; CENTER_H; }
62 | BUTTON IDC_BUTTON_DOIT { NAME IDS_BUTTON_DOIT; ALIGN_TOP; CENTER_H; }
63 | }
64 | GROUP IDC_DUMMY
65 | {
66 | NAME IDS_DUMMY; ALIGN_TOP; SCALE_H;
67 | BORDERSIZE 0, 0, 0, 0;
68 | COLUMNS 1;
69 | SPACE 4, 4;
70 |
71 |
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/source/py4dlib/examples/PrintObjectHierarchy.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # PrintObjectHierarchy.py
4 | # py4dlib.examples
5 | #
6 | # Created by André Berg on 2013-07-29.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # PrintObjectHierarchy
12 | #
13 | # Summary:
14 | #
15 | # Shows how to use the ObjectHierarchy class.
16 | #
17 | # pylint: disable-msg=F0401
18 |
19 | """
20 | Name-US:Print Object Hierarchy
21 | Description-US:Pretty print an indented list of the selected object hierarchy to the console.
22 | """
23 |
24 | import os
25 |
26 | __version__ = (0, 2)
27 | __date__ = '2013-07-29'
28 | __updated__ = '2013-08-08'
29 |
30 |
31 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
32 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
33 |
34 |
35 |
36 | import pprint
37 |
38 | pp = pprint.PrettyPrinter(width=92)
39 | PP = pp.pprint
40 | PF = pp.pformat
41 |
42 | try:
43 | import c4d #@UnresolvedImport
44 | from c4d import documents
45 | except ImportError:
46 | if TESTRUN == 1:
47 | pass
48 |
49 |
50 | if DEBUG:
51 | import py4dlib
52 | reload(py4dlib)
53 | reload(py4dlib.objects)
54 |
55 | from py4dlib.objects import ObjectHierarchy
56 | from py4dlib.utils import ClearConsole, UnescapeUnicode, EscapeUnicode
57 |
58 |
59 | def main(doc): # IGNORE:W0621
60 | if not doc: return None
61 | doc.StartUndo()
62 |
63 | sel = doc.GetSelection()
64 | if sel is None: return False
65 |
66 | c4d.StopAllThreads()
67 |
68 | root = doc.GetFirstObject()
69 | if not root: return False
70 |
71 | print(u"printing hierarchy starting from %s" % root.GetName())
72 |
73 | oh = ObjectHierarchy()
74 | oh.PPrint()
75 |
76 | print(u"printing Null objects starting from %s" % root.GetName())
77 |
78 | oh.PPrint(filter_type=c4d.Onull)
79 |
80 | PP(oh)
81 | print(oh)
82 |
83 | filteredObjs = oh.Get('*/*')
84 | for obj in filteredObjs:
85 | print obj.GetName()
86 |
87 | # tell C4D to update internal state
88 | c4d.EventAdd()
89 | doc.EndUndo()
90 |
91 |
92 | if __name__ == '__main__':
93 | ClearConsole()
94 | doc = documents.GetActiveDocument()
95 | main(doc)
96 |
97 |
98 |
99 | # Licensed under the Apache License, Version 2.0 (the "License");
100 | # you may not use this file except in compliance with the License.
101 | # You may obtain a copy of the License at
102 | #
103 | # http://www.apache.org/licenses/LICENSE-2.0
104 | #
105 | # Unless required by applicable law or agreed to in writing, software
106 | # distributed under the License is distributed on an "AS IS" BASIS,
107 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
108 | # See the License for the specific language governing permissions and
109 | # limitations under the License.
110 |
--------------------------------------------------------------------------------
/source/test/maths/matrix_to_listlist_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # matrix_to_listlist_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2013-08-02.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # This file is a copy of a Python script
10 | # to be run on the C4D file of the same name.
11 | #
12 | # pylint: disable-msg=F0401,W0611
13 |
14 | import os
15 |
16 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
17 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
18 |
19 | try:
20 | import c4d #@UnresolvedImport
21 | from c4d import documents #@UnresolvedImport
22 | except ImportError:
23 | if TESTRUN == 1:
24 | pass
25 |
26 | import py4dlib
27 | from py4dlib.utils import PPLLString, ClearConsole
28 | from py4dlib.maths import MatrixToListList, ListListToMatrix
29 | from py4dlib.objects import CreateObject, Select
30 |
31 | if DEBUG:
32 | reload(py4dlib)
33 | reload(py4dlib.maths)
34 | reload(py4dlib.utils)
35 |
36 |
37 | def main():
38 | ClearConsole()
39 | doc = documents.GetActiveDocument()
40 | op = doc.SearchObject("Cube")
41 | if op is not None:
42 | print("Removing old Cube...")
43 | op.Remove()
44 | print("Creating new Cube...")
45 | op = CreateObject(c4d.Ocube, "Cube")
46 | Select(op)
47 |
48 | print("")
49 |
50 | mg = op.GetMg()
51 | n_off = c4d.Vector(100, 0, 0)
52 | n_mg = c4d.Matrix(n_off, mg.v1, mg.v2, mg.v3)
53 |
54 | print("Setting new mg from manually constructed Matrix:")
55 | print(" %r" % n_mg)
56 | op.SetMg(n_mg)
57 |
58 | print("")
59 |
60 | print("Converting matrix n_mg to list:")
61 | n_mgll = MatrixToListList(n_mg, incl_off=True)
62 | print("")
63 |
64 | print("Displaying new mg as list:")
65 | print(PPLLString(n_mgll))
66 | print("")
67 |
68 | print("Setting new global matrix from list:")
69 | n_ll = [[200.0, 10.0, 0.0],
70 | [ 2.0, 0.0, 0.0],
71 | [ 0.0, 2.0, 0.0],
72 | [ 0.0, 0.0, 2.0]]
73 | print(PPLLString(n_ll))
74 | print("")
75 |
76 | print("Converting list structure to Matrix n_llmg")
77 | n_llmg = ListListToMatrix(n_ll)
78 | print(" %r" % n_llmg)
79 | print("")
80 |
81 | print("Setting n_llmg")
82 | print("")
83 | op.SetMg(n_llmg)
84 |
85 | print("assert op.GetMg().off.x == 200")
86 |
87 | assert(op.GetMg().off.x == 200)
88 | print("True")
89 |
90 | c4d.EventAdd()
91 |
92 | if __name__=='__main__':
93 | main()
94 |
95 |
96 | # Licensed under the Apache License, Version 2.0 (the "License");
97 | # you may not use this file except in compliance with the License.
98 | # You may obtain a copy of the License at
99 | #
100 | # http://www.apache.org/licenses/LICENSE-2.0
101 | #
102 | # Unless required by applicable law or agreed to in writing, software
103 | # distributed under the License is distributed on an "AS IS" BASIS,
104 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
105 | # See the License for the specific language governing permissions and
106 | # limitations under the License.
107 |
--------------------------------------------------------------------------------
/docs/pydocs3theme/static/pydoctheme.css_t:
--------------------------------------------------------------------------------
1 | @import url("default.css");
2 |
3 | body {
4 | background-color: white;
5 | margin-left: 1em;
6 | margin-right: 1em;
7 | }
8 |
9 | div.related {
10 | margin-bottom: 1.2em;
11 | padding: 0.5em 0;
12 | border-top: 1px solid #ccc;
13 | margin-top: 0.5em;
14 | }
15 |
16 | div.related a:hover {
17 | color: #0095C4;
18 | }
19 |
20 | div.related:first-child {
21 | border-top: 0;
22 | border-bottom: 1px solid #ccc;
23 | }
24 |
25 | div.sphinxsidebar {
26 | background-color: #eeeeee;
27 | border-radius: 5px;
28 | line-height: 130%;
29 | font-size: smaller;
30 | }
31 |
32 | div.sphinxsidebar h3, div.sphinxsidebar h4 {
33 | margin-top: 1.5em;
34 | }
35 |
36 | div.sphinxsidebarwrapper > h3:first-child {
37 | margin-top: 0.2em;
38 | }
39 |
40 | div.sphinxsidebarwrapper > ul > li > ul > li {
41 | margin-bottom: 0.4em;
42 | }
43 |
44 | div.sphinxsidebar a:hover {
45 | color: #0095C4;
46 | }
47 |
48 | div.sphinxsidebar input {
49 | font-family: 'Lucida Grande',Arial,sans-serif;
50 | border: 1px solid #999999;
51 | font-size: smaller;
52 | border-radius: 3px;
53 | }
54 |
55 | div.sphinxsidebar input[type=text] {
56 | max-width: 150px;
57 | }
58 |
59 | div.body {
60 | padding: 0 0 0 1.2em;
61 | }
62 |
63 | div.body p {
64 | line-height: 140%;
65 | }
66 |
67 | div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 {
68 | margin: 0;
69 | border: 0;
70 | padding: 0.3em 0;
71 | }
72 |
73 | div.body hr {
74 | border: 0;
75 | background-color: #ccc;
76 | height: 1px;
77 | }
78 |
79 | div.body pre {
80 | border-radius: 3px;
81 | border: 1px solid #ac9;
82 | }
83 |
84 | div.body div.admonition, div.body div.impl-detail {
85 | border-radius: 3px;
86 | }
87 |
88 | div.body div.impl-detail > p {
89 | margin: 0;
90 | }
91 |
92 | div.body div.seealso {
93 | border: 1px solid #dddd66;
94 | }
95 |
96 | div.body a {
97 | color: #00608f;
98 | }
99 |
100 | div.body a:visited {
101 | color: #30306f;
102 | }
103 |
104 | div.body a:hover {
105 | color: #00B0E4;
106 | }
107 |
108 | tt, pre {
109 | font-family: monospace, sans-serif;
110 | font-size: 96.5%;
111 | }
112 |
113 | div.body tt {
114 | border-radius: 3px;
115 | }
116 |
117 | div.body tt.descname {
118 | font-size: 120%;
119 | }
120 |
121 | div.body tt.xref, div.body a tt {
122 | font-weight: normal;
123 | }
124 |
125 | p.deprecated {
126 | border-radius: 3px;
127 | }
128 |
129 | table.docutils {
130 | border: 1px solid #ddd;
131 | min-width: 20%;
132 | border-radius: 3px;
133 | margin-top: 10px;
134 | margin-bottom: 10px;
135 | }
136 |
137 | table.docutils td, table.docutils th {
138 | border: 1px solid #ddd !important;
139 | border-radius: 3px;
140 | }
141 |
142 | table p, table li {
143 | text-align: left !important;
144 | }
145 |
146 | table.docutils th {
147 | background-color: #eee;
148 | padding: 0.3em 0.5em;
149 | }
150 |
151 | table.docutils td {
152 | background-color: white;
153 | padding: 0.3em 0.5em;
154 | }
155 |
156 | table.footnote, table.footnote td {
157 | border: 0 !important;
158 | }
159 |
160 | div.footer {
161 | line-height: 150%;
162 | margin-top: -2em;
163 | text-align: right;
164 | width: auto;
165 | margin-right: 10px;
166 | }
167 |
168 | div.footer a:hover {
169 | color: #0095C4;
170 | }
171 |
--------------------------------------------------------------------------------
/source/test/objects/center_object_axis_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # center_object_axis_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2013-08-02.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # This file is a copy of a Python script
10 | # to be run on the C4D file of the same name.
11 | #
12 | # pylint: disable-msg=F0401,W0611,E1103
13 |
14 | import os
15 |
16 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
17 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
18 |
19 | try:
20 | import c4d #@UnresolvedImport
21 | from c4d import documents #@UnresolvedImport
22 | except ImportError:
23 | if TESTRUN == 1:
24 | pass
25 |
26 |
27 | import py4dlib
28 | from py4dlib.utils import ClearConsole
29 | from py4dlib.maths import BBox
30 | from py4dlib.objects import CreateReplaceObject, CenterObjectAxis, MakeEditable
31 | from py4dlib.mesh import CalcGravityCenter
32 |
33 |
34 | if DEBUG:
35 | reload(py4dlib)
36 | reload(py4dlib.maths)
37 | reload(py4dlib.mesh)
38 | reload(py4dlib.utils)
39 | reload(py4dlib.objects)
40 |
41 |
42 | def main():
43 | doc = documents.GetActiveDocument()
44 | if doc is None:
45 | return False
46 |
47 | doc.StartUndo()
48 |
49 | op = CreateReplaceObject(c4d.Osphere, "TestSphere")
50 | if op is None:
51 | return False
52 |
53 | op = MakeEditable(op)
54 |
55 | which_p = 1
56 | trans = c4d.Vector(0, 100, 0)
57 |
58 | doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
59 | op.SetPoint(which_p, op.GetPoint(which_p) + trans)
60 |
61 | CenterObjectAxis(op, center="midpoint")
62 |
63 | op_mg = op.GetMg()
64 | op_name = op.GetName()
65 |
66 | bb = BBox.FromObject(op)
67 | mp = bb.midpoint
68 | cg = CalcGravityCenter(op)
69 |
70 | # no need to multiply with op_mg since we'll insert under TestSphere later
71 | mp_off = mp # * op_mg
72 | cg_off = cg # * op_mg
73 |
74 | mp_mg = c4d.Matrix(mp_off, op_mg.v1, op_mg.v2, op_mg.v3)
75 | cg_mg = c4d.Matrix(cg_off, op_mg.v1, op_mg.v2, op_mg.v3)
76 |
77 | print("midpoint in local coords = %r" % mp_off)
78 | print("midpoint in global coords = %r" % (mp_off * op_mg))
79 |
80 | print("grav center in local coords = %r" % cg_off)
81 | print("grav center in global coords = %r" % (cg_off * op_mg))
82 |
83 | mp_null = CreateReplaceObject(c4d.Onull, "%s Midpoint" % op_name)
84 | cg_null = CreateReplaceObject(c4d.Onull, "%s Gravity Center" % op_name)
85 |
86 | cg_null[c4d.NULLOBJECT_DISPLAY] = 7 # Hexagon
87 | cg_null[c4d.NULLOBJECT_RADIUS] = 125
88 |
89 | mp_null[c4d.NULLOBJECT_DISPLAY] = 2 # Circle
90 | mp_null[c4d.NULLOBJECT_RADIUS] = 100
91 |
92 | mp_null.SetMg(mp_mg)
93 | cg_null.SetMg(cg_mg)
94 |
95 | mp_null.InsertUnder(op)
96 | cg_null.InsertUnder(op)
97 |
98 | print("assert(mp_null.GetMg().off == c4d.Vector(0, 50, 0))")
99 | assert(mp_null.GetMg().off == c4d.Vector(0, 50, 0))
100 |
101 | print("True")
102 |
103 | doc.EndUndo()
104 | doc.Message(c4d.MSG_UPDATE)
105 |
106 | if __name__ == '__main__':
107 | ClearConsole()
108 | main()
109 |
110 |
111 |
112 | # Licensed under the Apache License, Version 2.0 (the "License");
113 | # you may not use this file except in compliance with the License.
114 | # You may obtain a copy of the License at
115 | #
116 | # http://www.apache.org/licenses/LICENSE-2.0
117 | #
118 | # Unless required by applicable law or agreed to in writing, software
119 | # distributed under the License is distributed on an "AS IS" BASIS,
120 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
121 | # See the License for the specific language governing permissions and
122 | # limitations under the License.
123 |
--------------------------------------------------------------------------------
/docs/api.rst:
--------------------------------------------------------------------------------
1 | API docs
2 | --------
3 |
4 | A word about the style of the API. I tried to mirror the format and style
5 | used by the original Py4D SDK so using methods from **py4dlib** should
6 | hopefully feel familiar.
7 |
8 | Concerning function/method paramaters, I also try to avoid ambiguity by using
9 | the same argument names for the same type of data.
10 |
11 | This is especially apparent for functions that expect points or polygons.
12 | I usually try set the functions up in a general way, so that you don't have to
13 | remember that you have to call the version of the function that expects a list
14 | of point indices instead of a list of points (``c4d.Vector``, to be exact).
15 |
16 | A polymorphic nature like that is usually indicated by using the parameter name ``e``
17 | (for *element* or *entity*) whenever you can pass either.
18 |
19 | In all other cases it should be apparent if you need to pass a point, a list of
20 | points, a list of point indices, an object, a matrix, vector or whatever else have you.
21 |
22 | Here's an overview of some of the default parameter names and their meaning:
23 |
24 | +---------+---------------------------------------------------------------------------+
25 | | e | the ``any`` value. stands for *element*. Refer to the function docs what |
26 | | | any means in the function's context. |
27 | +---------+---------------------------------------------------------------------------+
28 | | obj | any descendant of ``c4d.BaseObject`` incl. ``c4d.PointObject`` + children |
29 | +---------+---------------------------------------------------------------------------+
30 | | m | ``c4d.Matrix`` |
31 | +---------+---------------------------------------------------------------------------+
32 | | v | ``c4d.Vector`` |
33 | +---------+---------------------------------------------------------------------------+
34 | | p | ``c4d.Vector`` representing a point, or a polygon as ``c4d.CPolygon`` |
35 | +---------+---------------------------------------------------------------------------+
36 | | l | ``list`` |
37 | +---------+---------------------------------------------------------------------------+
38 | | li | ``list`` of ``int``, which usually represents indices into an object's |
39 | | | point list |
40 | +---------+---------------------------------------------------------------------------+
41 | | lv | ``list`` of vectors |
42 | +---------+---------------------------------------------------------------------------+
43 | | lp | ``list`` of points |
44 | +---------+---------------------------------------------------------------------------+
45 | | lli | ``list`` of ``list`` of ``int`` |
46 | +---------+---------------------------------------------------------------------------+
47 | | llf | ``list`` of ``list`` of ``float`` |
48 | +---------+---------------------------------------------------------------------------+
49 | | x, n, w | ``float`` sometimes can be called other traditional names, like *theta* |
50 | | ... | if for example an angle is represented (w for greek omega, n for number) |
51 | +---------+---------------------------------------------------------------------------+
52 | | a, b, c | a name series usually represents a series of vectors. |
53 | | ... | |
54 | +---------+---------------------------------------------------------------------------+
55 |
56 | Contents:
57 |
58 | .. toctree::
59 |
60 | api/maths
61 | api/mesh
62 | api/objects
63 | api/plugins
64 | api/utils
65 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Plane Projector 1.2/res/dialogs/IDD_DIALOG_SETTINGS.res:
--------------------------------------------------------------------------------
1 | // C4D-DialogResource
2 | DIALOG IDD_DIALOG_SETTINGS
3 | {
4 | NAME IDS_DIALOG_TITLE; CENTER_V; ALIGN_LEFT;
5 |
6 | GROUP IDC_GROUP_WRAPPER
7 | {
8 | NAME IDS_GROUP_WRAPPER; ALIGN_TOP; SCALE_H;
9 | BORDERSIZE 4, 4, 4, 4;
10 | COLUMNS 1;
11 | SPACE 4, 4;
12 |
13 | GROUP IDC_GROUP_SETTINGS
14 | {
15 | NAME IDS_GROUP_SETTINGS; ALIGN_TOP; SCALE_H;
16 | BORDERSIZE 5, 5, 5, 0;
17 | COLUMNS 3;
18 | SPACE 4, 4;
19 |
20 | STATICTEXT IDC_STATIC_TARGET { NAME IDS_STATIC_TARGET; CENTER_V; ALIGN_LEFT; }
21 | EDITTEXT IDC_EDIT_TARGET
22 | { CENTER_V; SCALE_H; SIZE 70, 0; }
23 | BUTTON IDC_BUTTON_GETOBJ { NAME IDS_BUTTON_GETOBJ; ALIGN_TOP; ALIGN_LEFT; }
24 | }
25 | GROUP IDC_STATIC
26 | {
27 | NAME IDS_STATIC_SETTINGS4; ALIGN_TOP; SCALE_H;
28 | BORDERSIZE 5, 0, 5, 0;
29 | COLUMNS 2;
30 | SPACE 4, 4;
31 |
32 | STATICTEXT IDC_STATIC_UPAXIS { NAME IDS_STATIC_UPAXIS; CENTER_V; ALIGN_LEFT; }
33 | COMBOBOX IDC_COMBO_UPAXIS
34 | {
35 | ALIGN_TOP; SCALE_H; SIZE 150, 0;
36 | CHILDS
37 | {
38 | 1, IDS_X;
39 | 2, IDS_Y;
40 | 3, IDS_Z;
41 | }
42 | }
43 | STATICTEXT IDC_STATIC { NAME IDS_STATIC1; CENTER_V; ALIGN_LEFT; }
44 | GROUP IDC_STATIC
45 | {
46 | NAME IDS_STATIC2; ALIGN_TOP; SCALE_H;
47 | BORDERSTYLE BORDER_NONE; BORDERSIZE 0, 0, 0, 0;
48 | COLUMNS 3;
49 |
50 | BUTTON IDC_BUTTON_PLANE { NAME IDS_BUTTON_PLANE; ALIGN_TOP; SCALE_H; }
51 | BUTTON IDC_BUTTON_WPLANE { NAME IDS_BUTTON_WPLANE; ALIGN_TOP; SCALE_H; }
52 | }
53 | }
54 | SEPARATOR { SCALE_H; }
55 | GROUP IDC_GROUP_SETTINGS2
56 | {
57 | NAME IDS_GROUP_SETTINGS2; ALIGN_TOP; SCALE_H;
58 | BORDERSIZE 5, 0, 5, 0;
59 | COLUMNS 2;
60 | SPACE 4, 4;
61 |
62 | STATICTEXT IDC_STATIC_MODE { NAME IDS_STATIC_MODE; CENTER_V; ALIGN_LEFT; }
63 | COMBOBOX IDC_COMBO_MODE
64 | {
65 | ALIGN_TOP; SCALE_H; SIZE 150, 0;
66 | CHILDS
67 | {
68 | 1, IDS_NORMAL;
69 | 2, IDS_MANUAL;
70 | 3, IDS_X;
71 | 4, IDS_Y;
72 | 5, IDS_Z;
73 | 6, IDS_X_LOCAL;
74 | 7, IDS_Y_LOCAL;
75 | 8, IDS_Z_LOCAL;
76 | }
77 | }
78 | }
79 | GROUP IDC_GROUP_SETTINGS3
80 | {
81 | NAME IDS_GROUP_SETTINGS2; SCALE_V; SCALE_H;
82 | BORDERSIZE 5, 0, 5, 5;
83 | COLUMNS 2;
84 | SPACE 4, 4;
85 |
86 | STATICTEXT IDC_STATIC_MANUAL { NAME IDS_STATIC_MANUAL; CENTER_V; CENTER_H; }
87 | GROUP IDC_GROUP_MANUAL
88 | {
89 | NAME IDS_GROUP_MANUAL; ALIGN_TOP; SCALE_H;
90 | BORDERSIZE 0, 0, 0, 0;
91 | COLUMNS 3;
92 | SPACE 4, 4;
93 |
94 | EDITNUMBERARROWS IDC_EDIT_X
95 | { CENTER_V; SCALE_H; SIZE 70, 0; }
96 | EDITNUMBERARROWS IDC_EDIT_Y
97 | { CENTER_V; SCALE_H; SIZE 70, 0; }
98 | EDITNUMBERARROWS IDC_EDIT_Z
99 | { CENTER_V; SCALE_H; SIZE 70, 0; }
100 | }
101 | }
102 | SEPARATOR { SCALE_H; }
103 | GROUP IDC_GROUP_BUTTONS
104 | {
105 | NAME IDS_GROUP_BUTTONS; ALIGN_TOP; CENTER_H;
106 | BORDERSIZE 0, 0, 0, 0;
107 | COLUMNS 2;
108 | SPACE 4, 4;
109 |
110 | BUTTON IDC_BUTTON_CANCEL { NAME IDS_BUTTON_CANCEL; ALIGN_TOP; CENTER_H; }
111 | BUTTON IDC_BUTTON_DOIT { NAME IDS_BUTTON_DOIT; ALIGN_TOP; CENTER_H; }
112 | }
113 | GROUP IDC_DUMMY
114 | {
115 | NAME IDS_DUMMY; ALIGN_TOP; SCALE_H;
116 | BORDERSIZE 0, 0, 0, 0;
117 | COLUMNS 1;
118 | SPACE 4, 4;
119 |
120 |
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/docs/pydocs3theme/static/pygments.css_t:
--------------------------------------------------------------------------------
1 | .highlight .hll { background-color: #ffffcc }
2 | .highlight { background: #eeffcc; }
3 | .highlight .c { color: #408090; font-style: italic } /* Comment */
4 | .highlight .err { border: 1px solid #FF0000 } /* Error */
5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */
6 | .highlight .o { color: #666666 } /* Operator */
7 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
8 | .highlight .cp { color: #007020 } /* Comment.Preproc */
9 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
10 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
11 | .highlight .gd { color: #A00000 } /* Generic.Deleted */
12 | .highlight .ge { font-style: italic } /* Generic.Emph */
13 | .highlight .gr { color: #FF0000 } /* Generic.Error */
14 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
15 | .highlight .gi { color: #00A000 } /* Generic.Inserted */
16 | .highlight .go { color: #333333 } /* Generic.Output */
17 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
18 | .highlight .gs { font-weight: bold } /* Generic.Strong */
19 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
20 | .highlight .gt { color: #0044DD } /* Generic.Traceback */
21 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
22 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
23 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
24 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */
25 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
26 | .highlight .kt { color: #902000 } /* Keyword.Type */
27 | .highlight .m { color: #208050 } /* Literal.Number */
28 | .highlight .s { color: #4070a0 } /* Literal.String */
29 | .highlight .na { color: #4070a0 } /* Name.Attribute */
30 | .highlight .nb { color: #007020 } /* Name.Builtin */
31 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
32 | .highlight .no { color: #60add5 } /* Name.Constant */
33 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
34 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
35 | .highlight .ne { color: #007020 } /* Name.Exception */
36 | .highlight .nf { color: #06287e } /* Name.Function */
37 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
38 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
39 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
40 | .highlight .nv { color: #bb60d5 } /* Name.Variable */
41 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
42 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */
43 | .highlight .mf { color: #208050 } /* Literal.Number.Float */
44 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */
45 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */
46 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */
47 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
48 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */
49 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
50 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */
51 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
52 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
53 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
54 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */
55 | .highlight .sr { color: #235388 } /* Literal.String.Regex */
56 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */
57 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */
58 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
59 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
60 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
61 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
62 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Introduction
2 | ============
3 |
4 | The `py4dlib` library is a collection of Python modules to be used
5 | with MAXON's [CINEMA 4D](http://www.maxon.net "CINEMA 4D") 3D application.
6 |
7 | It's goal is to provide essentials but also convenience functions
8 | left out by CINEMA 4D's current Python SDK so that repetition and
9 | code duplication can be avoided.
10 |
11 | It currently includes routines for working with c4d objects,
12 | polygonal modelling tasks, some utilities and tools for writing
13 | plugins plus a small library of math routines needed for 3D work.
14 |
15 | You can view the current [documentation](http://andreberg.github.io/py4dlib "py4dlib docs") online.
16 |
17 | Better yet, you can watch the [introductional video](http://vimeo.com/72108443 "Introduction to py4dlib") on Vimeo!
18 |
19 |
20 | Installation
21 | ============
22 |
23 | Take the `py4dlib` folder which you can find under `/source` in
24 | this repository, and put it under the following path:
25 |
26 | /library/python/packages/
27 |
28 | `` is the prefs directory for CINEMA 4D in your home
29 | directory and `` is the name of the operating system you are
30 | running.
31 |
32 | For example on OS X this path could be:
33 |
34 | /Users//Library/Preferences/MAXON/CINEMA 4D R/library/python/packages/osx
35 |
36 | On Windows this path could be:
37 |
38 | %APPDATA%\MAXON\CINEMA 4D R\library\python\packages\win64
39 |
40 |
41 | Usage
42 | =====
43 |
44 | Simply import any modules you'd like to use in your scripts or plugins,
45 | like you normally would:
46 |
47 | import py4dlib
48 | from py4dlib.objects import ObjectIterator
49 |
50 | To see if everything is working correctly you can try running an example
51 | script.
52 |
53 | Make a new scene, create a sphere and make it editable, then select it in
54 | the Object Manager.
55 |
56 | Open the Python script editor and run the following script:
57 |
58 | import c4d
59 | from c4d import gui
60 | #Welcome to the world of Python
61 |
62 | from py4dlib.utils import ClearConsole
63 | from py4dlib.examples import ShowPolygonNumber
64 |
65 |
66 | if __name__=='__main__':
67 | ClearConsole()
68 | doc = c4d.documents.GetActiveDocument()
69 | ShowPolygonNumber.main(doc)
70 |
71 |
72 | The result should be something like this:
73 |
74 | 
75 |
76 |
77 | See also the [Best Practices](http://andreberg.github.io/py4dlib/usage.html#best-practices "Best Practices") section in the online docs.
78 |
79 |
80 | Contributions
81 | =============
82 |
83 | I'd be very happy to accept code contributions. If you'd like to contribute
84 | please fork this repository, commit your changes to your local fork and then
85 | send me a pull request.
86 |
87 | If that’s too much of a hassle, you can also go ahead and create a gist or a
88 | pastebin post (or whatever copypasta service you fancy) and send me the URL so
89 | that I can include the code manually.
90 |
91 |
92 | Acknowledgements
93 | ================
94 |
95 | a.k.a. **The Hall of Fame**
96 |
97 | In no particular order.
98 |
99 | Lennart Wåhlin (TCA Studios)
100 | Per-Anders Edwards
101 | Adam Swaab
102 | NitroMan (Nitro4D)
103 | Robert Leger
104 | Niklas Rosenstein
105 | Scott Ayers
106 |
107 | Thanks for making some of your C.O.F.F.E.E. and Python scripts
108 | free and for often not denying the ability to take a look.
109 |
110 |
111 | License
112 | =======
113 |
114 | Licensed under the Apache License, Version 2.0 (the "License");
115 | you may not use this file except in compliance with the License.
116 | You may obtain a copy of the License at
117 |
118 | http://www.apache.org/licenses/LICENSE-2.0
119 |
120 | Unless required by applicable law or agreed to in writing, software
121 | distributed under the License is distributed on an "AS IS" BASIS,
122 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
123 | See the License for the specific language governing permissions and
124 | limitations under the License.
125 |
--------------------------------------------------------------------------------
/source/test/plugins_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # plugins_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-26.
7 | # Copyright 2012 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | import os
14 | import unittest
15 |
16 | __version__ = (0, 1)
17 | __date__ = '2012-09-26'
18 | __updated__ = '2013-08-01'
19 |
20 | from py4dlib.plugins import UserDefaults
21 |
22 | DEFAULTS = {
23 | 'triangulate': True,
24 | 'mode': 'quads',
25 | 'mindistance': 0.005
26 | }
27 |
28 | FILEPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res", "settings.ini")
29 |
30 | class PluginsTest(unittest.TestCase):
31 |
32 | def setUp(self):
33 | for apath in [FILEPATH]:
34 | if os.path.exists(apath) and not os.path.isdir(apath):
35 | print("removing %r" % apath)
36 | os.remove(apath)
37 |
38 | def tearDown(self):
39 | pass
40 |
41 | def testUserDefaultsCreationWithDefaults(self):
42 | settings = UserDefaults(filepath=FILEPATH, defaults=DEFAULTS)
43 | self.assertTrue(os.path.exists(settings.filepath))
44 | statedefaults = settings.state.defaults()
45 | self.assertDictEqual(statedefaults, DEFAULTS, 'state defaults should be %r, but is %r' % (DEFAULTS, statedefaults))
46 | expected_contents = """[DEFAULT]
47 | triangulate = True
48 | mindistance = 0.005
49 | mode = quads
50 |
51 | [Settings]
52 |
53 | """
54 | with open(FILEPATH, 'rb') as savedconfig:
55 | actual_contents = savedconfig.read()
56 | self.assertEqual(expected_contents, actual_contents, 'expected contents should be %r, but is %r' % (expected_contents, actual_contents))
57 |
58 | def testUserDefaultsCreationNoDefaults(self):
59 | settings = UserDefaults(filepath=FILEPATH)
60 | self.assertTrue(os.path.exists(settings.filepath))
61 | statedefaults = settings.state.defaults()
62 | self.assertDictEqual(statedefaults, {}, 'state defaults should be %r, but is %r' % ({}, statedefaults))
63 |
64 | def testUserDefaultsModificationAndSaving(self):
65 | settings = UserDefaults(filepath=FILEPATH)
66 | self.assertTrue(os.path.exists(settings.filepath))
67 | settings.Set('string', 'some string')
68 | settings.Set('int', 1)
69 | settings.Set('float', 2.0)
70 | settings.Save()
71 | expected_contents = """[Settings]
72 | string = some string
73 | int = 1
74 | float = 2.0
75 |
76 | """
77 | with open(FILEPATH, 'rb') as savedconfig:
78 | actual_contents = savedconfig.read()
79 | self.assertEqual(expected_contents, actual_contents, 'expected contents should be %r, but is %r' % (expected_contents, actual_contents))
80 |
81 | def testUserDefaultsReadingAndRetrieving(self):
82 | settings = UserDefaults(filepath=FILEPATH)
83 | settings.Set('string', 'some string')
84 | settings.Set('int', 1)
85 | settings.Set('float', 2.0)
86 | settings.Save()
87 | settings2 = UserDefaults(filepath=FILEPATH)
88 | self.assertTrue(os.path.exists(settings2.filepath))
89 | strval = settings2.Get('string')
90 | intval = settings2.Get('int')
91 | floatval = settings2.Get('float')
92 | expected_strval = 'some string'
93 | expected_intval = '1'
94 | expected_floatval = '2.0'
95 | self.assertEqual(strval, expected_strval, 'expected string value should be %r, but is %r' % (expected_strval, strval))
96 | self.assertEqual(intval, expected_intval, 'expected int value should be %r, but is %r' % (expected_intval, intval))
97 | self.assertEqual(floatval, expected_floatval, 'expected float value should be %r, but is %r' % (expected_floatval, floatval))
98 | self.assertNotEqual(floatval, 2.0, 'expected float value should not be %r, but is %r' % (2.0, floatval))
99 |
100 |
101 | if __name__ == "__main__":
102 | #import sys;sys.argv = ['', 'Test.testName']
103 | unittest.main()
104 |
105 |
106 | # Licensed under the Apache License, Version 2.0 (the "License");
107 | # you may not use this file except in compliance with the License.
108 | # You may obtain a copy of the License at
109 | #
110 | # http://www.apache.org/licenses/LICENSE-2.0
111 | #
112 | # Unless required by applicable law or agreed to in writing, software
113 | # distributed under the License is distributed on an "AS IS" BASIS,
114 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
115 | # See the License for the specific language governing permissions and
116 | # limitations under the License.
117 |
--------------------------------------------------------------------------------
/docs/api/plugins.rst:
--------------------------------------------------------------------------------
1 | Plugins
2 | -------
3 |
4 |
5 | .. class:: UserDefaults(filepath, defaults=None, header='Settings')
6 |
7 | Support for reading and writing settings files
8 | in .ini format.
9 |
10 | This can be used to provide state persistence
11 | for UI elements of a plugin.
12 |
13 | Examples:
14 |
15 | Initialize a new config object, modify and
16 | then save it:
17 |
18 | .. code::
19 |
20 | >>> filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res", "settings.ini")
21 | >>> defaults = UserDefaults(filepath=filepath)
22 | >>> defaults.Set('key', 'value')
23 | >>> defaults.Save()
24 |
25 | If filepath points to an existing file, it will
26 | use that file and initializing a new config object
27 | by reading from it.
28 |
29 | If you later want to read in the config file:
30 |
31 | .. code::
32 |
33 | >>> defaults.Read()
34 | >>> print(defaults.Get('setting'))
35 | value
36 | >>> print(defaults.Get('does-not-exist', default='use default instead'))
37 | use default instead
38 |
39 | :param str filepath: usually ``res/settings.ini`` relative to the
40 | source code file of the plugin, that uses the config store.
41 | :param defaults: ``dict`` default values to be used if the config
42 | file needs to be created. It is also possible to pass None
43 | here and then use the :py:func:`Set` and :py:func:`Save` functions
44 | to set inidividual settings after creation of the config object.
45 | :param str header: the name for a section in the .ini file.
46 | Usually you can get away with leaving it at the default.
47 | This will add a header ``[Settings]`` under which your
48 | settings will appear. If you have more advanced uses
49 | you are advised to modify the config parser state
50 | directly through ``self.state``.
51 |
52 | .. function:: Get(self, name, section=None, default=None, valuetype="str")
53 |
54 | Retrieve a previously stored value from the config object.
55 |
56 | :param str name: name of the setting
57 | :param str section: the section name. ``self.header`` if None.
58 | :param any default: a default value to use in case name wasn't found.
59 | :param str valuetype: type of the value to get. can be one of ``[str, bool, int, float]``.
60 | :return: ``str`` on success, None or *default* on failure.
61 | this will always return a string even if the value was
62 | stored as another type previously. So the caller is
63 | responsible for the convertion to the wanted data type.
64 |
65 | .. function:: GetInt(self, name, section=None, default=None)
66 |
67 | Retrieve a previously stored integer value from the config object.
68 |
69 | :param str name: name of the setting
70 | :param str section: the section name. ``self.header`` if None.
71 | :param any default: a default value to use in case name wasn't found.
72 | :return: ``int`` on success, None or 'default' on failure.
73 |
74 | .. function:: GetFloat(self, name, section=None, default=None)
75 |
76 | Retrieve a previously stored float value from the config object.
77 |
78 | :param str name: name of the setting
79 | :param str section: the section name. ``self.header`` if None.
80 | :param any default: a default value to use in case name wasn't found.
81 | :return: ``float`` on success, None or 'default' on failure.
82 |
83 | .. function:: GetBool(self, name, section=None, default=None)
84 |
85 | Retrieve a previously stored boolean value from the config object.
86 |
87 | :param str name: name of the setting
88 | :param str section: the section name. ``self.header`` if None.
89 | :param any default: a default value to use in case name wasn't found.
90 | :return: ``bool`` on success, None or 'default' on failure.
91 |
92 | .. function:: Set(self, name, value, section=None)
93 |
94 | Store a value in the config object for later retrieval.
95 |
96 | :param str name: name of the setting
97 | :param any value: value to set.
98 | :param str section: the section name. ``self.header`` if None.
99 | :return: True if successful, False otherwise.
100 |
101 | .. function:: Read(self)
102 |
103 | Read state from configuration file.
104 |
105 | :return: True if successful.
106 | :raise OSError: if config file couldn't be read.
107 |
108 | .. function:: Save(self, config=None, filepath=None)
109 |
110 | Save settings to a configuration file.
111 |
112 | :param ConfigParser config: the config object to save.
113 | If None, uses ``self.config`` instead.
114 | :param str filepath: allows for specifying another path
115 | than ``self.filepath`` in order to save a copy
116 | of the config object.
117 | :return: True if successful, False otherwise.
118 |
119 |
--------------------------------------------------------------------------------
/docs/usage.rst:
--------------------------------------------------------------------------------
1 | Usage
2 | -----
3 |
4 | Simply import any modules you'd like to use in your scripts or plugins,
5 | like you normally would::
6 |
7 | import py4dlib
8 | from py4dlib.objects import ObjectIterator
9 |
10 | To see if everything is working correctly you can try running an example
11 | script.
12 |
13 | Make a new scene, create a sphere and make it editable, then select it in
14 | the Object Manager.
15 |
16 | Open the Python script editor and run the following script::
17 |
18 | import c4d
19 | from c4d import gui
20 | #Welcome to the world of Python
21 |
22 | from py4dlib.utils import ClearConsole
23 | from py4dlib.examples import ShowPolygonNumber
24 |
25 |
26 | if __name__=='__main__':
27 | ClearConsole()
28 | doc = c4d.documents.GetActiveDocument()
29 | ShowPolygonNumber.main(doc)
30 |
31 | The result should be something like this:
32 |
33 | .. image:: img/ShowPolygonNumber.png
34 | :width: 422.25 px
35 | :height: 351 px
36 | :alt: ShowPolygonNumber
37 |
38 |
39 | Best Practices
40 | --------------
41 |
42 | What can you do if your plugin depends on **py4dlib** but
43 | the end user doesn't have the library installed?
44 |
45 | Well, you could try downloading the library using Python
46 | itself, but I'd caution against it. Running a plugin
47 | shouldn't start a connection to some random website to
48 | download stuff on the end user's computer.
49 |
50 | No, there are better ways to deal with this issue.
51 | For example, you could:
52 |
53 | 1) kindly ask the user to download the library herself
54 | 2) include the library with your script or plugin
55 | 3) copy and paste the methods you are using into your own script or plugin
56 |
57 | Asking The User
58 | ~~~~~~~~~~~~~~~
59 |
60 | In the first case a sensible thing you can do, is check for
61 | prerequisite dependencies at the time the plugin is actually
62 | executed and if dependencies are missing display a message to
63 | the user and politely ask to download the library.
64 |
65 | You can find a complete example in the :ref:`Extract` plugin
66 | included in the :ref:`py4dlib.examples`, but I shall give a
67 | brief overview here.
68 |
69 | At the top of your plugin set a constant indicating the state
70 | of the dependency prerequisite and wrap any py4dlib imports in
71 | a try/except block, catching ``ImportError``::
72 |
73 | PY4DLIB_FOUND = False
74 |
75 | try:
76 | from py4dlib.objects import DeselectAll, Select, GetGlobalRotation
77 | # more dependency imports ...
78 | PY4DLIB_FOUND = True
79 | except ImportError:
80 | pass
81 |
82 | Now, when the time comes to execute the plugin and do the bulk
83 | of the actual work, check if your prerequisite constant is True
84 | and if not display a message dialog, for example, like so::
85 |
86 | PY4DLIB_NOT_FOUND_MSG = """This plugin needs py4dlib which is missing.
87 |
88 | Please download and install it free of charge
89 | from http://github.com/andreberg/py4dlib.
90 | """
91 |
92 | # ...
93 |
94 | def run(self):
95 | if not PY4DLIB_FOUND:
96 | c4d.gui.MessageDialog(PY4DLIB_NOT_FOUND_MSG)
97 | return False
98 | # ...
99 |
100 | Including The Library
101 | ~~~~~~~~~~~~~~~~~~~~~
102 |
103 | In the second case you can put the py4dlib source folder into a subdirectory
104 | included in your plugin or script distribution. To import the library you
105 | can then modify ``sys.path`` at the top of your script before any py4dlib
106 | imports. For example if you have the following directory structure::
107 |
108 | icons/
109 | res/
110 | lib/
111 | Plugin.pyp
112 |
113 | you can put the ``py4dlib`` folder under ``lib/`` and then modify ``sys.path``
114 | at the top of your plugin/script, like so::
115 |
116 | import sys, os
117 | sys.path.insert(1, os.path.realpath('lib')) # use 1 not 0!
118 |
119 | Note that we are using insert here instead of append. You can also use
120 | append but in that case the user's version of py4dlib will be loaded
121 | before your included one if the user has py4dlib installed.
122 |
123 | In case you are developing a script and not a plugin, it is the same, really.
124 | It just means you have to distribute an actual folder structure where a simple
125 | file would have done. But if you want to include icons you'd have to do that
126 | anyway.
127 |
128 | Copy And Paste Methods
129 | ~~~~~~~~~~~~~~~~~~~~~~
130 |
131 | If you don't want to make the user install anything and you also don't want to
132 | include the library, then you will have to copy and paste the py4dlib methods
133 | you are using into your plugin.
134 |
135 | In that case I recommend developing your plugin or script with the usual
136 | dependency imports until you have it just right. Then before you deliver
137 | the final version to your users, create a version with just the neccessary
138 | methods inlined.
139 |
140 | Special care was given to make the py4dlib API not too self-entangled so
141 | ripping stuff out to copy+paste it into your own scripts shouldn't prove
142 | too difficult.
143 |
144 |
145 |
146 |
--------------------------------------------------------------------------------
/docs/pydocs3theme/static/default.css_t:
--------------------------------------------------------------------------------
1 | /*
2 | * default.css_t
3 | * ~~~~~~~~~~~~~
4 | *
5 | * Sphinx stylesheet -- default theme.
6 | *
7 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS.
8 | * :license: BSD, see LICENSE for details.
9 | *
10 | */
11 |
12 | @import url("basic.css");
13 |
14 | /* -- page layout ----------------------------------------------------------- */
15 |
16 | body {
17 | font-family: 'Lucida Grande', Arial, sans-serif;
18 | font-size: 100%;
19 | background-color: white;
20 | color: #000;
21 | margin: 0;
22 | padding: 0;
23 | }
24 |
25 | div.document {
26 | background-color: white;
27 | }
28 |
29 | div.documentwrapper {
30 | float: left;
31 | width: 100%;
32 | }
33 |
34 | div.bodywrapper {
35 | margin: 0 0 0 230px;
36 | }
37 |
38 | div.body {
39 | background-color: white;
40 | color: #222222;
41 | padding: 0 20px 30px 20px;
42 | }
43 |
44 | div.footer {
45 | color: #555555;
46 | width: 100%;
47 | padding: 9px 0 9px 0;
48 | text-align: center;
49 | font-size: 75%;
50 | }
51 |
52 | div.footer a {
53 | color: #555555;
54 | text-decoration: underline;
55 | }
56 |
57 | div.related {
58 | background-color: white;
59 | line-height: 30px;
60 | color: #666666;
61 | }
62 |
63 | div.related a {
64 | color: #444444;
65 | }
66 |
67 | div.sphinxsidebar {
68 | }
69 |
70 | div.sphinxsidebar h3 {
71 | font-family: 'Lucida Grande', Arial, sans-serif;
72 | color: #444444;
73 | font-size: 1.4em;
74 | font-weight: normal;
75 | margin: 0;
76 | padding: 0;
77 | }
78 |
79 | div.sphinxsidebar h3 a {
80 | color: #444444;
81 | }
82 |
83 | div.sphinxsidebar h4 {
84 | font-family: 'Lucida Grande', Arial, sans-serif;
85 | color: #444444;
86 | font-size: 1.3em;
87 | font-weight: normal;
88 | margin: 5px 0 0 0;
89 | padding: 0;
90 | }
91 |
92 | div.sphinxsidebar p {
93 | color: #444444;
94 | }
95 |
96 | div.sphinxsidebar p.topless {
97 | margin: 5px 10px 10px 10px;
98 | }
99 |
100 | div.sphinxsidebar ul {
101 | margin: 10px;
102 | padding: 0;
103 | color: #444444;
104 | }
105 |
106 | div.sphinxsidebar a {
107 | color: #444444;
108 | }
109 |
110 | div.sphinxsidebar input {
111 | border: 1px solid #444444;
112 | font-family: sans-serif;
113 | font-size: 1em;
114 | }
115 |
116 |
117 | /* for collapsible sidebar */
118 | div#sidebarbutton {
119 | background-color: #3c6e83;
120 | }
121 |
122 |
123 | /* -- hyperlink styles ------------------------------------------------------ */
124 |
125 | a {
126 | color: #0090c0;
127 | text-decoration: none;
128 | }
129 |
130 | a:visited {
131 | color: #00608f;
132 | text-decoration: none;
133 | }
134 |
135 | a:hover {
136 | text-decoration: underline;
137 | }
138 |
139 |
140 |
141 | /* -- body styles ----------------------------------------------------------- */
142 |
143 | div.body h1,
144 | div.body h2,
145 | div.body h3,
146 | div.body h4,
147 | div.body h5,
148 | div.body h6 {
149 | font-family: 'Lucida Grande', Arial, sans-serif;
150 | background-color: white;
151 | font-weight: normal;
152 | color: #1a1a1a;
153 | border-bottom: 1px solid #ccc;
154 | margin: 20px -20px 10px -20px;
155 | padding: 3px 0 3px 10px;
156 | }
157 |
158 | div.body h1 { margin-top: 0; font-size: 200%; }
159 | div.body h2 { font-size: 160%; }
160 | div.body h3 { font-size: 140%; }
161 | div.body h4 { font-size: 120%; }
162 | div.body h5 { font-size: 110%; }
163 | div.body h6 { font-size: 100%; }
164 |
165 | a.headerlink {
166 | color: #aaaaaa;
167 | font-size: 0.8em;
168 | padding: 0 4px 0 4px;
169 | text-decoration: none;
170 | }
171 |
172 | a.headerlink:hover {
173 | background-color: #aaaaaa;
174 | color: white;
175 | }
176 |
177 | div.body p, div.body dd, div.body li {
178 | text-align: justify;
179 | line-height: 130%;
180 | }
181 |
182 | div.admonition p.admonition-title + p {
183 | display: inline;
184 | }
185 |
186 | div.admonition p {
187 | margin-bottom: 5px;
188 | }
189 |
190 | div.admonition pre {
191 | margin-bottom: 5px;
192 | }
193 |
194 | div.admonition ul, div.admonition ol {
195 | margin-bottom: 5px;
196 | }
197 |
198 | div.note {
199 | background-color: #eee;
200 | border: 1px solid #ccc;
201 | }
202 |
203 | div.seealso {
204 | background-color: #ffc;
205 | border: 1px solid #ff6;
206 | }
207 |
208 | div.topic {
209 | background-color: #eee;
210 | }
211 |
212 | div.warning {
213 | background-color: #ffe4e4;
214 | border: 1px solid #f66;
215 | }
216 |
217 | p.admonition-title {
218 | display: inline;
219 | }
220 |
221 | p.admonition-title:after {
222 | content: ":";
223 | }
224 |
225 | pre {
226 | padding: 5px;
227 | background-color: #eeffcc;
228 | color: #333333;
229 | line-height: 120%;
230 | border: 1px solid #ac9;
231 | border-left: none;
232 | border-right: none;
233 | }
234 |
235 | tt {
236 | background-color: #ecf0f3;
237 | padding: 0 1px 0 1px;
238 | font-size: 0.95em;
239 | }
240 |
241 | th {
242 | background-color: #ede;
243 | }
244 |
245 | .warning tt {
246 | background: #efc2c2;
247 | }
248 |
249 | .note tt {
250 | background: #d6d6d6;
251 | }
252 |
253 | .viewcode-back {
254 | font-family: 'Lucida Grande', Arial, sans-serif;
255 | }
256 |
257 | div.viewcode-block:target {
258 | background-color: #f4debf;
259 | border-top: 1px solid #ac9;
260 | border-bottom: 1px solid #ac9;
261 | }
--------------------------------------------------------------------------------
/source/test/objects/object_iterator_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # object_iterator_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-26.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # This file is a copy of the Python tag script
10 | # found in the C4D file of the same name.
11 | # It is provided for reference if C4D is not
12 | # available or the .c4d file can't be opened.
13 | #
14 | # andre.bergmedia@googlemail.com
15 | #
16 | # pylint: disable-msg=F0401,W0611
17 |
18 | import os
19 |
20 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
21 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
22 |
23 | try:
24 | from c4d import documents #@UnresolvedImport
25 | except ImportError:
26 | if TESTRUN == 1:
27 | pass
28 |
29 | from py4dlib import utils
30 | from py4dlib.objects import ObjectIterator
31 |
32 |
33 | def main():
34 | utils.ClearConsole()
35 |
36 | doc = documents.GetActiveDocument()
37 | if not doc:
38 | return False
39 |
40 | # get some objects from the om tree...
41 | # ...at top level hierarchy
42 | group1obj = doc.SearchObject('Group1')
43 | # ... and somewhere further down
44 | cube2obj = doc.SearchObject('Cube2')
45 | if not group1obj or not cube2obj:
46 | return False
47 |
48 | indent = 4
49 |
50 | print("this should iterate through Group1's children and")
51 | print("it should stop before Group2\n")
52 |
53 | cur = []
54 | for op, lvl in ObjectIterator(group1obj):
55 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
56 | cur.append(op.GetName())
57 |
58 | assert(cur == ['Group1A', 'Cube', 'Würfel', 'Group1AA', 'Cube2',
59 | 'Wuerfel2', 'Group1B', 'Sweep-NURBS', 'Kreis', 'Heart'])
60 |
61 | print("\n")
62 | print("this should iterate starting from and including Group1")
63 | print("and it should not stop unless at the end of the object")
64 | print("manager top level\n")
65 |
66 | cur = []
67 | for op, lvl in ObjectIterator(group1obj, children_only=False):
68 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
69 | cur.append(op.GetName())
70 |
71 | assert(cur == ["Group1", "Group1A", "Cube", "Würfel", "Group1AA",
72 | "Cube2", "Wuerfel2", "Group1B", "Sweep-NURBS", "Kreis", "Heart",
73 | "Group2", "Röhre", "Zylinder", "Zylinder2", "Group3", "Group3A",
74 | "Group3AA", "Group3AB", "Kugel", "Platonischer Körper", "Group4",
75 | "Group4A", "Group4AA", "Group4AB", "Kugel2", "Platonischer Körper2"])
76 |
77 | print("\n")
78 | print("this should iterate through Group1's children only and it ")
79 | print("should stop before Cube2:\n")
80 |
81 | cur = []
82 | for op, lvl in ObjectIterator(group1obj, stop_obj=cube2obj):
83 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
84 | cur.append(op.GetName())
85 |
86 | assert(cur == ["Group1A", "Cube", "Würfel", "Group1AA"])
87 |
88 | # now test iterating within a sub hierarchy
89 |
90 | # get some more objects from the om tree...
91 | # ...at sub level hierarchy
92 | group3obj = doc.SearchObject('Group3')
93 | # ... and somewhere further down
94 | group3abobj = doc.SearchObject('Group3AB')
95 | if not group3obj or not group3abobj:
96 | return False
97 |
98 | print("\n")
99 | print("this should iterate through Group3's children and it should stop")
100 | print("before going up again through Zylinder2\n")
101 |
102 | cur = []
103 | for op, lvl in ObjectIterator(group3obj, startlvl=4):
104 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
105 | cur.append(op.GetName())
106 |
107 | assert(cur == ["Group3A", "Group3AA", "Group3AB", "Kugel", "Platonischer Körper"])
108 |
109 | print("\n")
110 | print("this should iterate starting from and including Group3 and it ")
111 | print("should not stop unless at the end of the object manager top level\n")
112 |
113 | cur = []
114 | for op, lvl in ObjectIterator(group3obj, children_only=False, startlvl=4):
115 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
116 | cur.append(op.GetName())
117 |
118 | assert(cur == ["Group3", "Group3A", "Group3AA", "Group3AB", "Kugel",
119 | "Platonischer Körper", "Group4", "Group4A", "Group4AA", "Group4AB",
120 | "Kugel2", "Platonischer Körper2"])
121 |
122 | print("\n")
123 | print("this should iterate through Group3's children only and it should ")
124 | print("stop after Group3AB\n")
125 |
126 | cur = []
127 | for op, lvl in ObjectIterator(group3obj, stop_obj=group3abobj, startlvl=4):
128 | print("%s%s" % (' ' * lvl * indent, op.GetName()))
129 | cur.append(op.GetName())
130 |
131 | assert(cur == ["Group3A", "Group3AA"])
132 |
133 |
134 | if __name__ == '__main__':
135 | main()
136 |
137 |
138 | # Licensed under the Apache License, Version 2.0 (the "License");
139 | # you may not use this file except in compliance with the License.
140 | # You may obtain a copy of the License at
141 | #
142 | # http://www.apache.org/licenses/LICENSE-2.0
143 | #
144 | # Unless required by applicable law or agreed to in writing, software
145 | # distributed under the License is distributed on an "AS IS" BASIS,
146 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
147 | # See the License for the specific language governing permissions and
148 | # limitations under the License.
149 |
--------------------------------------------------------------------------------
/source/test/maths_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # test.maths_tests
4 | # project
5 | #
6 | # Created by André Berg on 2013-08-02.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | import os
14 | import math
15 | import unittest
16 |
17 | __version__ = (0, 1)
18 | __date__ = '2013-08-02'
19 | __updated__ = '2013-08-05'
20 |
21 |
22 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
23 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
24 |
25 |
26 | try:
27 | import c4d #@UnresolvedImport
28 | except ImportError:
29 | if TESTRUN == 1:
30 | pass
31 |
32 | from py4dlib.maths import Det, UnitNormal, Transpose, VLerp, VNLerp, VSLerp
33 |
34 | eps = 0.000001
35 |
36 | def FloatEqual(a, b, places=len(str(eps))-2):
37 | return round(abs(b - a), places) == 0
38 |
39 | class VectorMock(object):
40 | """ Mock object for c4d.Vectors """
41 |
42 | def __init__(self, x, y=None, z=None):
43 | if y is None:
44 | y = x
45 | if z is None:
46 | z = x
47 | self.x = float(x)
48 | self.y = float(y)
49 | self.z = float(z)
50 |
51 | def __str__(self):
52 | return ("%r, x = %s, y = %s, z = %s" % (self, self.x, self.y, self.z))
53 |
54 | def __len__(self):
55 | return self.GetLength()
56 |
57 | def __sub__(self, other):
58 | return VectorMock(self.x - other.x, self.y - other.y, self.z - other.z)
59 |
60 | def __rsub__(self, other):
61 | return VectorMock(other.x - self.x, other.y - self.y, other.z - self.z)
62 |
63 | def __add__(self, other):
64 | return VectorMock(self.x + other.x, self.y + other.y, self.z + other.z)
65 |
66 | def __mul__(self, other):
67 | return VectorMock(self.x * other, self.y * other, self.z * other)
68 |
69 | def __rmul__(self, other):
70 | return self.__mul__(other)
71 |
72 | def __eq__(self, other):
73 | return (FloatEqual(self.x, other.x) and FloatEqual(self.y, other.y) and FloatEqual(self.z, other.z))
74 |
75 | def GetLength(self):
76 | return math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
77 |
78 | def GetNormalized(self):
79 | ool = 1.0 / len(self)
80 | return VectorMock(self.x * ool, self.y * ool, self.z * ool)
81 |
82 | def Normalize(self):
83 | ool = 1.0 / len(self)
84 | self.x *= ool
85 | self.y *= ool
86 | self.z *= ool
87 |
88 | def Dot(self, other):
89 | return float(self.x * other.x + self.y * other.y + self.z * other.z)
90 |
91 | def Cross(self, other):
92 | return VectorMock(self.y * other.z - self.z * other.y,
93 | self.z * other.x - self.x * other.z,
94 | self.x * other.y - self.y * other.x)
95 |
96 | class CPolygonMock(object):
97 | """ Mock object for c4d.CPolygons """
98 | def __init__(self, a, b, c, d=None):
99 | self.a = a
100 | self.b = b
101 | self.c = c
102 | if d is None:
103 | d = c
104 | self.d = d
105 |
106 |
107 | class Test(unittest.TestCase):
108 |
109 |
110 | def testDet(self):
111 | m = [[ 1, 22, 3],
112 | [ 5, 1, 7],
113 | [ 9, -1, 2]]
114 | det = Det(m)
115 |
116 | self.assertEquals(1133, det)
117 |
118 | m2 = [[ 1, 22, 3, 4],
119 | [ 5, 6, 7, 8],
120 | [ 9, 22, 22, 12],
121 | [13, 14, 15, 16]]
122 | det2 = Det(m2)
123 |
124 | self.assertEquals(5280, det2)
125 |
126 | def testUnitNormal(self):
127 | a = VectorMock(-100, 100, 100)
128 | b = VectorMock(-100, 100, -100)
129 | c = VectorMock(-100, -100, 100)
130 |
131 | normal = UnitNormal(a, b, c)
132 |
133 | self.assertEquals(-1.0, normal.x)
134 |
135 | def testTranspose(self):
136 | m44 = [[1, 2, 3, 4],
137 | [5, 6, 7, 8],
138 | [9, 10, 11, 12],
139 | [13, 14, 15, 16]]
140 |
141 | expected = [[1, 5, 9, 13],
142 | [2, 6, 10, 14],
143 | [3, 7, 11, 15],
144 | [4, 8, 12, 16]]
145 |
146 | m44_t = Transpose(m44)
147 | self.assertEquals(m44_t, expected)
148 |
149 | def testVLerp(self):
150 | vs = VectorMock(2, 2, 2)
151 | ve = VectorMock(4, 4, 4)
152 |
153 | vl = VLerp(vs, ve)
154 | expected = VectorMock(3.0)
155 |
156 | self.assertEquals(vl, expected)
157 |
158 | def testVNLerp(self):
159 | vs = VectorMock(2, 2, 2)
160 | ve = VectorMock(4, 4, 4)
161 |
162 | vnl = VNLerp(vs, ve)
163 | expected = VectorMock(0.6)
164 |
165 | self.assertEquals(vnl, expected)
166 | print(vnl)
167 |
168 |
169 | def testSLerp(self):
170 | vs = VectorMock(0, 1, 0)
171 | ve = VectorMock(1, 0, 0)
172 |
173 | vsl = VSLerp(vs, ve)
174 | expected = VectorMock(0.707106781187, 0.707106781187, 0.0)
175 |
176 | self.assertEquals(vsl, expected)
177 | print(vsl)
178 |
179 | if __name__ == "__main__":
180 | #import sys;sys.argv = ['', 'Test.testName']
181 | unittest.main()
182 |
183 |
184 | # Licensed under the Apache License, Version 2.0 (the "License");
185 | # you may not use this file except in compliance with the License.
186 | # You may obtain a copy of the License at
187 | #
188 | # http://www.apache.org/licenses/LICENSE-2.0
189 | #
190 | # Unless required by applicable law or agreed to in writing, software
191 | # distributed under the License is distributed on an "AS IS" BASIS,
192 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
193 | # See the License for the specific language governing permissions and
194 | # limitations under the License.
195 |
--------------------------------------------------------------------------------
/source/test/objects_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # objects_tests.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-28.
7 | # Copyright 2012 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401,W1401
12 |
13 | import os
14 | import unittest
15 | import pprint
16 |
17 | __version__ = (0, 1)
18 | __date__ = '2012-09-28'
19 | __updated__ = '2013-08-08'
20 |
21 |
22 | from py4dlib.objects import ObjectHierarchy
23 |
24 |
25 | pp = pprint.PrettyPrinter()
26 | PP = pp.pprint
27 | PF = pp.pformat
28 |
29 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
30 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
31 |
32 |
33 | DATA = {
34 | 'Source/Group1ABB': [
35 | 'Kegel1',
36 | 'Kegel2',
37 | 'Polygons1'
38 | ], 'Target/Group1/Group1A/Group1AB': [
39 | 'Cube3',
40 | 'W\ürfel3',
41 | 'Group1ABA',
42 | 'Group1ABB'
43 | ], 'Target': [
44 | 'Target',
45 | 'Group1'
46 | ], 'Target/Group1/Group1A/Group1AA': [
47 | 'Cube2',
48 | 'W\ürfel2'
49 | ], 'Source/Group1ABA/Sweep-NURBS': [
50 | 'Kreis',
51 | 'Heart'
52 | ], 'Target/Group1/Group1A': [
53 | 'Cube1',
54 | 'W\ürfel1',
55 | 'Group1AA',
56 | 'Group1AB',
57 | 'Group1AC'
58 | ], 'Source/Group1AC': [
59 | 'Cube3',
60 | 'W\ürfel3'
61 | ], 'Source/Group1AA': [
62 | 'Cube2', 'W\ürfel2'
63 | ], 'Source/Group1AB': [
64 | 'Cube3',
65 | 'W\ürfel3'
66 | ], 'Target/Group1/Group1A/Group1AC': [
67 | 'Cube3',
68 | 'W\ürfel3',
69 | 'Group1ACA'
70 | ], 'Source': [
71 | 'Source',
72 | 'Group1',
73 | 'Group1A',
74 | 'Group1AA',
75 | 'Group1AB',
76 | 'Group1ABA',
77 | 'Group1ABB',
78 | 'Group1AC',
79 | 'Group1ACA'
80 | ], 'Source/Group1ABA': [
81 | 'Sweep-NURBS'
82 | ], 'Source/Group1A': [
83 | 'Cube1',
84 | 'W\ürfel1'
85 | ], 'Target/Group1': [
86 | 'Group1A'
87 | ], 'Target/Group1/Group1A/Group1AB/Group1ABA/Sweep-NURBS': [
88 | 'Kreis',
89 | 'Heart'
90 | ], 'Target/Group1/Group1A/Group1AB/Group1ABB': [
91 | 'Kegel1',
92 | 'Kegel2',
93 | 'Polygons1'
94 | ], 'Target/Group1/Group1A/Group1AB/Group1ABA': [
95 | 'Sweep-NURBS'
96 | ]
97 | }
98 |
99 | class OHMock(ObjectHierarchy):
100 | '''ObjectHierarchy mock object'''
101 | def __init__(self, *args, **kwargs): # IGNORE:W0231
102 | # yes, we don't call/init super here to avoid 'c4d' is not defined
103 | self.sep = '/'
104 | self.entries = DATA
105 |
106 |
107 | class ObjectsTest(unittest.TestCase):
108 |
109 | def setUp(self):
110 | self.mockobj = OHMock()
111 |
112 | def testGetFullyQualifiedKeyPath(self):
113 | expected = ['Sweep-NURBS']
114 | path = 'Target/Group1/Group1A/Group1AB/Group1ABA'
115 | actual = self.mockobj.Get(path)
116 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
117 |
118 | def testGetWildcardExpandedKeyPath1(self):
119 | expected = ['Kreis', 'Heart', 'Sweep-NURBS', 'Kegel1', 'Kegel2', 'Polygons1']
120 | path = 'Target/Group1/Group1A/Group1AB/*'
121 | actual = self.mockobj.Get(path)
122 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
123 |
124 | def testGetWildcardExpandedKeyPath2(self):
125 | expected = ['Kreis', 'Heart', 'Sweep-NURBS', 'Kegel1', 'Kegel2', 'Polygons1']
126 | path = 'Target/*/*/*/*'
127 | actual = self.mockobj.Get(path)
128 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
129 |
130 | def testGetWildcardExpandedKeyPath3(self):
131 | # this also matches 'Target/Group1/Group1A/Group1AB' directly now
132 | # since the wildcard character is not after the sepaerator and thus
133 | # can evaluate to nothing
134 | expected = ['Kreis', 'Heart', 'Cube3', 'W\ürfel3', 'Group1ABA', 'Group1ABB', 'Sweep-NURBS', 'Kegel1', 'Kegel2', 'Polygons1']
135 | path = 'Target/Group1/Group1A/Group1AB*'
136 | actual = self.mockobj.Get(path)
137 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
138 |
139 | def testGetPatternExpandedKeyPath(self):
140 | expected = ['Target', 'Group1', 'Kegel1', 'Kegel2', 'Polygons1']
141 | path = '(Source/Group1ABB|Target)'
142 | actual = self.mockobj.Get(path)
143 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
144 |
145 | def testGetRePatternExpandedKeyPath(self):
146 | expected = ['Kegel1', 'Kegel2', 'Polygons1']
147 | path = '!(?!\T)(Source/Group1ABB|Target)'
148 | actual = self.mockobj.Get(path)
149 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
150 |
151 | def testGetRelativeExpandedKeyPath(self):
152 | expected = ['Cube3', 'W\ürfel3', 'Group1ABA', 'Group1ABB']
153 | path = 'Target/Group1/Group1A/Group1AB/Group1ABA/..'
154 | actual = self.mockobj.Get(path)
155 | self.assertEqual(actual, expected, 'actual should equal %r, but is %r' % (expected, actual))
156 |
157 |
158 | if __name__ == "__main__":
159 | #import sys;sys.argv = ['', 'Test.testName']
160 | unittest.main()
161 |
162 |
163 | # Licensed under the Apache License, Version 2.0 (the "License");
164 | # you may not use this file except in compliance with the License.
165 | # You may obtain a copy of the License at
166 | #
167 | # http://www.apache.org/licenses/LICENSE-2.0
168 | #
169 | # Unless required by applicable law or agreed to in writing, software
170 | # distributed under the License is distributed on an "AS IS" BASIS,
171 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
172 | # See the License for the specific language governing permissions and
173 | # limitations under the License.
--------------------------------------------------------------------------------
/source/py4dlib/examples/CreatePlanesFromPolygons.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # CreatePlanesFromPolygons.py
4 | # py4dlib.examples
5 | #
6 | # Created by André Berg on 2013-08-08.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # CreatePlanesFromPolygons
12 | #
13 | # Create a workplane or plane from a selected polygon.
14 | #
15 | # Summary:
16 | #
17 | # Example script that shows how to create a planar primitive such as a workplane
18 | # or a plane object and adjust its orientation to match the modelling axis set to
19 | # normal mode.
20 | #
21 | # pylint: disable-msg=F0401
22 |
23 | """
24 | Name-US:Create Planes From Polygons
25 | Description-US:Create plane or workingplane objects for each selected polygon on a group of selected objects.
26 | """
27 |
28 | import os
29 |
30 | __version__ = (0, 2)
31 | __date__ = '2013-08-08'
32 | __updated__ = '2013-08-08'
33 |
34 |
35 | DEBUG = 0 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
36 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
37 |
38 | try:
39 | import c4d #@UnresolvedImport
40 | from c4d import documents
41 | except ImportError:
42 | if TESTRUN == 1:
43 | pass
44 |
45 |
46 | # R14 and above no longer have a workingplane object
47 | # and I don't know yet how to manipulate the document
48 | # workingplane mode.
49 | if c4d.GetC4DVersion() > 13999:
50 | TYPE = c4d.Oplane
51 | else:
52 | TYPE = c4d.Oplane
53 |
54 |
55 | from py4dlib.utils import ClearConsole
56 | from py4dlib.maths import BuildMatrix3, IsZeroVector
57 | from py4dlib.objects import CreateObject
58 | from py4dlib.mesh import CalcPolyCentroid, CalcPolyNormal
59 |
60 |
61 | def CreatePlanesFromPolygons(op, typ):
62 | if not isinstance(op, c4d.PolygonObject):
63 | raise TypeError("E: expected c4d.PolygonObject, got %r" % (type(op)))
64 |
65 | allpolys = op.GetAllPolygons()
66 | polysel = op.GetPolygonS()
67 |
68 | selpolys = []
69 | for i, p in enumerate(allpolys):
70 | if polysel.IsSelected(i) == True:
71 | selpolys.append((i, p))
72 |
73 | if len(selpolys) == 0:
74 | print("need at least 1 polygon selected for CreatePlaneFromPolygon")
75 | return False
76 |
77 | mg = op.GetMg()
78 | op_name = op.GetName()
79 |
80 | planes = []
81 |
82 | for i, p in selpolys:
83 |
84 | pids = [p.a, p.b, p.c]
85 |
86 | p1 = p.a
87 | p2 = p.b
88 | p3 = p.c
89 | p4 = p.d
90 |
91 | plen = 3
92 | if p.c != p.d:
93 | pids.append(p4)
94 | plen = 4
95 |
96 | allp = op.GetAllPoints()
97 |
98 | pnts = []
99 | for id_ in pids:
100 | pnts.append(allp[id_])
101 |
102 | a = allp[p1]
103 | b = allp[p2]
104 | c = allp[p3]
105 | d = allp[p4]
106 |
107 | cv1 = a - b
108 | cv2 = b - c
109 | cv3 = c - d
110 | cv4 = d - a
111 | if plen == 4:
112 | cva = (cv3 - cv1)
113 | cvb = (cv4 - cv2)
114 | else:
115 | cva = cv3 - cv1
116 | cvb = cv3 - cv2
117 |
118 | if DEBUG:
119 | print("%d: a = %r, b = %r, c = %r, d = %r" % (i, a,b,c,d))
120 | print("cv1 = %r" % cv1)
121 | print("cv2 = %r" % cv2)
122 | print("cv3 = %r" % cv3)
123 | print("cv4 = %r" % cv4)
124 | print("cva = %r" % cva)
125 | print("cvb = %r" % cvb)
126 |
127 | pn = CalcPolyNormal(p, op)
128 | pc = CalcPolyCentroid(pids, op)
129 |
130 | AXIS_ZY = 1
131 |
132 | base = "x"
133 | axis = AXIS_ZY
134 |
135 | if plen == 4:
136 | cv = c4d.Vector(cva.x, cvb.y/2, cva.z)
137 | else:
138 | if cvb.y == 0:
139 | cv = cvb
140 | else:
141 | cv = cva
142 |
143 | if IsZeroVector(cv):
144 | if cv == cva:
145 | cv = cvb
146 | else:
147 | cv = cva
148 |
149 | cv ^= mg
150 |
151 | if DEBUG:
152 | #print("vmax = %r" % vmax)
153 | print("pn = %r" % pn)
154 | print("cv = %r" % cv)
155 | print("base = %r" % base)
156 |
157 | new_mg = BuildMatrix3(pn ^ mg, cv, off=(pc * mg), base=base)
158 | #new_mg.v2 = -new_mg.v2
159 |
160 | name = "%s Plane %d" % (op_name, i)
161 | plane = CreateObject(typ, name)
162 | if plane is None:
163 | return False
164 |
165 | plane.SetMg(new_mg)
166 |
167 | if typ == c4d.Oconplane:
168 | plane[c4d.CONSTRUCTIONPLANE_TYPE] = axis
169 | plane[c4d.CONSTRUCTIONPLANE_SPACING] = 10
170 | elif typ == c4d.Oplane:
171 | plane[c4d.PRIM_AXIS] = axis
172 | plane[c4d.PRIM_PLANE_WIDTH] = 75
173 | plane[c4d.PRIM_PLANE_HEIGHT] = plane[c4d.PRIM_PLANE_WIDTH]
174 | else:
175 | if DEBUG:
176 | print("Unknown plane type: %r" % typ)
177 | return False
178 |
179 | plane.Message(c4d.MSG_UPDATE)
180 | planes.append(plane)
181 |
182 | return planes
183 |
184 |
185 | def main():
186 | doc = documents.GetActiveDocument()
187 | if doc is None:
188 | return False
189 |
190 | doc.StartUndo()
191 |
192 | sel = doc.GetSelection()
193 | if sel is None:
194 | return False
195 |
196 | # loop through all objects
197 | for op in sel:
198 | if not isinstance(op, c4d.PolygonObject):
199 | if DEBUG:
200 | print("%s: not a polygon object. Skipping..." % str(op.GetName()))
201 | continue
202 |
203 | print("creating plane(s) for object: %s" % op.GetName())
204 |
205 | CreatePlanesFromPolygons(op, TYPE)
206 |
207 | # tell C4D to update internal state
208 | c4d.EventAdd()
209 | doc.EndUndo()
210 |
211 |
212 | if __name__ == '__main__':
213 | ClearConsole()
214 | main()
215 |
216 |
217 | # Licensed under the Apache License, Version 2.0 (the "License");
218 | # you may not use this file except in compliance with the License.
219 | # You may obtain a copy of the License at
220 | #
221 | # http://www.apache.org/licenses/LICENSE-2.0
222 | #
223 | # Unless required by applicable law or agreed to in writing, software
224 | # distributed under the License is distributed on an "AS IS" BASIS,
225 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
226 | # See the License for the specific language governing permissions and
227 | # limitations under the License.
228 |
--------------------------------------------------------------------------------
/docs/api/mesh.rst:
--------------------------------------------------------------------------------
1 | Mesh
2 | ----
3 |
4 | Functions for working with CINEMA 4D's point and polygon objects.
5 |
6 |
7 | .. function:: TogglePolySelection(obj)
8 |
9 | .. function:: SelectAllPolys(obj)
10 |
11 | .. function:: SelectPolys(li, obj, clearOldSel=True)
12 |
13 | Switch the selection state to 'selected' for a list of polygons.
14 | Expects a list of polygon indices.
15 |
16 | If ``clearOldSel`` is True, clears the old polygon selection.
17 | Otherwise appends to the current selection. Default is True.
18 |
19 | :return: True if the selection state was changed, or False if
20 | obj is not a ``c4d.PolygonObject``.
21 |
22 | .. function:: SelectPoints(li, obj, clearOldSel=True)
23 |
24 | Switch the selection state to 'selected' for a list of points.
25 | Expects a list of point indices.
26 |
27 | If ``clearOldSel`` is True, clears the old polygon selection.
28 | Otherwise appends to the current selection. Default is True.
29 |
30 | :return: True if the selection state was changed, or False if
31 | obj is not a ``c4d.PointObject``.
32 |
33 | .. function:: GetSelectedPoints(obj)
34 |
35 | Returns list of selected point indices.
36 |
37 | To get the actual point(s), using an ``index``
38 | from the returned list do something like
39 |
40 | .. code::
41 |
42 | allpoints = op.GetAllPoints()
43 | point = allpoints[index]
44 |
45 | .. function:: GetSelectedPolys(obj)
46 |
47 | Returns list of selected polygons indices.
48 |
49 | To get the actual polygon(s), using an ``index``
50 | from the returned list do something like
51 |
52 | .. code::
53 |
54 | allpolys = obj.GetAllPolygons()
55 | poly = allpolys[index]
56 |
57 | .. function:: GetPointsForIndices(li, obj)
58 |
59 | Return a list with the actual points from a list of point indices.
60 |
61 | If ``li`` already is of type ``list`` return the list untouched.
62 |
63 | .. function:: GetPolysForIndices(li, obj)
64 |
65 | Return a list with the actual polygons from a list of polygon indices.
66 |
67 | If ``li`` already is of type ``list`` return the list untouched.
68 |
69 | .. function:: GetIndicesForPoints(lp, obj)
70 |
71 | Return a list of point indices for all points that are equal
72 | to the vectors from lp.
73 |
74 | Warning: can be time consuming for large models, since this
75 | has to check against all points each time for every element
76 | in lp.
77 |
78 | You are better off acquiring the list of indices another way.
79 | Especially if it is just about converting a list of selected
80 | points to their indices.
81 |
82 | Use :py:func:`GetSelectedPoints` in that case.
83 |
84 | If ``lp`` already is of type ``list`` return the list untouched.
85 |
86 | .. function:: GetPolysForPoints(li, obj, strict=True, threshold=3)
87 |
88 | Returns a list of polygon indices for all polygons that have
89 | points with point indices given by ``li`` as their members.
90 |
91 | This is the same as converting between selections by holding
92 | Cmd/Ctrl when pressing the modelling mode buttons in CINEMA 4D.
93 |
94 | :param bool strict: if True, return only those polygons
95 | that are fully enclosed by all the points that make
96 | up that polygon.
97 |
98 | :param int threshold: minimum number of points that must be
99 | shared to for strict mode to be considered as enclosing
100 | a polygon. 3 includes triangles, 4 would only include
101 | quads.
102 |
103 | If ``li`` already is of type ``list`` return the
104 | list untouched.
105 |
106 | .. function:: CalcPolyCentroid(e, obj)
107 |
108 | Calculate the centroid of a polygon by averaging its vertices.
109 |
110 | :param e: can be ``c4d.CPolygon``, ``list`` representing
111 | point indices, or ``list`` representing a list
112 | of points.
113 |
114 | .. function:: CalcPolyNormal(e, obj)
115 |
116 | Calculate the orientation of face normal using Newell's method.
117 |
118 | See :py:func:`CalcVertexNormal` for an example of usage within the calling context.
119 |
120 | :param e: can be ``c4d.CPolygon``, ``list`` representing
121 | point indices, or ``list`` representing a list
122 | of points.
123 |
124 | .. function:: CalcVertexNormal(v, idx, obj)
125 |
126 | Calculate the vertex normal by averaging surrounding face normals.
127 |
128 | Usually called from a construct like the following:
129 |
130 | .. code::
131 |
132 | # calculate average vertex normal for point selection
133 | vtx_normals = []
134 |
135 | for i, point in enumerate(obj.GetAllPoints()):
136 | if pointsel.IsSelected(i):
137 | vn = CalcVertexNormal(point, i, obj)
138 | vtx_normals.append(vn)
139 |
140 | N = VAvg(vtx_normals)
141 |
142 | .. function:: CalcAverageVertexNormal(obj)
143 |
144 | Calculate the average normal of a selection of points.
145 |
146 | This gives the same normal as setting the modelling tool
147 | to "Normal" mode for an arbitrary point selection.
148 |
149 | :return: normal, or zero vector if no points selected.
150 |
151 | .. function:: CalcThreePointNormal(a, b, c)
152 |
153 | Calculate the surface normal of a three point plane.
154 |
155 | Doesn't take orientation of neighboring polygons into account.
156 |
157 | .. function:: CalcTriangleArea(p, obj)
158 |
159 | Calculate area of a triangle using ``|(v3 - v1) x (v3 - v2)|/2``.
160 |
161 | .. function:: CalcPolyArea(p, obj, normalized=False)
162 |
163 | Calculate the area of a planar polygon.
164 |
165 | .. function:: CalcBBox(e, selOnly=False, obj=None)
166 |
167 | Construct a :py:class:`BBox` for a ``c4d.PointObject``, a ``c4d.CPolygon``,
168 | or a list of polygon indices. If you have a list of point indices you can
169 | construct a BBox directly using the :py:func:`FromPointList` class method.
170 |
171 | You must supply the object the polygon list belongs to in the latter case.
172 |
173 | Note that if you are interested in the midpoint or radius only, you can
174 | use the built-in ``c4d.BaseObject.GetMp()`` and ``GetRad()`` methods
175 | respectively.
176 |
177 | :param bool selOnly: if True, use selected points
178 | only if e is a ``c4d.PointObject``. Otherwise use
179 | all points of the object.
180 |
181 | .. function:: CalcGravityCenter(obj)
182 |
183 | Calculate the center of gravity for obj.
184 |
185 | .. function:: PolyToList(p)
186 |
187 | Convert a ``c4d.CPolygon`` to a ``list`` of ``c4d.Vectors``, representing the points of the polygon.
188 |
189 | .. function:: PolyToListList(p, obj)
190 |
191 | Convert a ``c4d.CPolygon`` to a ``list`` structure.
192 |
193 | ``list`` represents a list of points comprised of a list of coordinate values.
194 |
195 | .. function:: ListToPoly(li)
196 |
197 | Convert a ``list`` of ``int`` representing indices into an object's point list to a ``c4d.CPolygon``.
198 |
199 | .. function:: ListListToPoly(lli)
200 |
201 | Convert a ``list`` structure to ``c4d.CPolygon``.
202 |
203 | ``list`` represents a list of indices that indentify points of an object.
204 |
205 |
--------------------------------------------------------------------------------
/docs/api/utils.rst:
--------------------------------------------------------------------------------
1 | Utils
2 | -----
3 |
4 | Utility toolbelt for great convenience.
5 |
6 | .. function:: ClearConsole()
7 |
8 | Clears the console across multiple CINEMA 4D versions.
9 |
10 | .. function:: FuzzyCompareStrings(a, b, limit=20)
11 |
12 | Fuzzy string comparison.
13 |
14 | Two strings are deemed equal if they have
15 | the same byte sequence for up to 'limit' chars.
16 |
17 | Limit can be an int or a percentage string
18 | like for example ``60%`` in which case 2 strings
19 | are deemed equal if at least ``60%`` relative to
20 | the longest string match.
21 |
22 | .. function:: EscapeUnicode(s)
23 |
24 | CINEMA 4D R12's CPython integration stores high-order chars (``ord > 126``)
25 | as 4-byte unicode escape sequences with upper case hex letters.
26 |
27 | For example the character ``ä`` (*LATIN SMALL LETTER A WITH DIAERESIS*)
28 | would be stored as the byte sequence ``\u00E4``. This function replaces
29 | high-order chars with a unicode escape sequence suitable for CINEMA 4D R12.
30 |
31 | If you use this function in R12 you probably need to balance each call with a
32 | call to :py:func:`UnescapeUnicode` when the time comes to use, display or
33 | compare your string to a string returned by some internal function of C4D R12.
34 |
35 | For example, if you ask an object named ``Würfel`` (German for cube)
36 | for its name::
37 |
38 | opname = op.GetName()
39 |
40 | # opname now prints as "W\ürfel" but is actually "W\u00FCrfel"
41 | # if you UnescapeUnicode now you will get a Python byte string
42 | # with proper encoding
43 |
44 | opname_unescaped = UnescapeUnicode(opname)
45 |
46 | # opname_unescaped is now "W\xfcrfel" (latin-1) and you can compare
47 | # it to other Python byte strings with no additional fuzz. If you want
48 | # to change the byte string and then pass it to C4D to set an object's
49 | # new name you would have to escape the byte string to get a string
50 | # that uses an escape sequence similar to the one shown at the beginning
51 |
52 | new_opname_escaped = EscapeUnicode(opname_unescaped)
53 |
54 | op.SetName(new_opname_escaped)
55 |
56 | # Remember this is only relevant for R12. In R13 and R14 both functions
57 | # are practically no-ops.
58 |
59 | In R13 and R14 it returns the string untouched since in those versions
60 | the CPython intergration handles Unicode encoded strings properly.
61 |
62 | .. function:: UnescapeUnicode(s)
63 |
64 | CINEMA 4D R12's CPython integration stores high-order chars (``ord > 126``)
65 | as 4-byte unicode escape sequences with upper case hex letters.
66 |
67 | This function converts unicode escape sequences used by CINEMA 4D when passing
68 | bytes (e.g. ``\u00FC`` -> ``\xfc``) to their corresponding high-order characters.
69 |
70 | It should be used in R12 only and should balance out any calls made to
71 | :py:func:`EscapeUnicode`.
72 |
73 | In R13 and R14 the string is returned untouched since in those versions
74 | the CPython intergration handles Unicode encoded strings properly.
75 |
76 | .. function:: VersionString(versionTuple)
77 |
78 | ``(x,y,z, .. n) -> 'x.y.z...n'``
79 |
80 | .. function:: PPLLString(ll)
81 |
82 | Returns a pretty-printed string of a ``list`` structure.
83 |
84 | .. function:: System(cmd, args=None)
85 |
86 | Convenience function for firing off commands to
87 | the system console. Used instead of `subprocess.call`_
88 | so that shell variables will be expanded properly.
89 |
90 | Not the same as `os.system`_ as here it captures
91 | ``stdout`` and ``stderr`` in a tuple for Python 2.5
92 | and lower or a ``namedtuple`` in 2.6 and higher.
93 | So you can use ``result[0]`` in the first case and
94 | ``result.out`` in the second.
95 |
96 | :param str cmd: a console command line string
97 | :param list args: a list of arguments that will be
98 | expanded in cmd starting with ``$0``
99 | :return: ``tuple`` or ``namedtuple``
100 |
101 | Decorators
102 | ----------
103 |
104 | .. function:: benchmark(func=None, prec=3, unit='auto', name_width=0, time_width=8)
105 |
106 | A decorator that prints the time a function takes
107 | to execute per call and cumulative total.
108 |
109 | Accepts the following keyword arguments
110 |
111 | :param str unit: time unit for display. one of ``[auto, us, ms, s, m]``.
112 | :param int prec: radix point precision.
113 | :param int name_width: width of the right-aligned function name field.
114 | :param int time_width: width of the right-aligned time value field.
115 |
116 | For convenience you can also set attributes on the benchmark
117 | function itself with the same name as the keyword arguments
118 | and the value of those will be used instead. This saves you
119 | from having to call the decorator with the same arguments each
120 | time you use it. Just set, for example, ``benchmark.prec = 5``
121 | after the import and before you use it for the first time.
122 |
123 | Usage example:
124 |
125 | .. code::
126 |
127 | @benchmark
128 | def factorial(x):
129 | ''' Return factorial of x. '''
130 | result = 1
131 | for i in range(x):
132 | result = result * (i + 1)
133 | return result
134 |
135 | Output:
136 |
137 | .. code::
138 |
139 | -> factorial() @ 001: 10.000 us, total: 10.000 us
140 | -> factorial() @ 002: 22.000 us, total: 32.000 us
141 |
142 | Output for ``@benchmark(unit='ms', time_width=6)``:
143 |
144 | .. code::
145 |
146 | -> factorial() @ 001: 0.009 ms, total: 0.009 ms
147 | -> factorial() @ 002: 0.023 ms, total: 0.032 ms
148 |
149 | .. function:: require(*args, **kwargs)
150 |
151 | Decorator that enforces types for function/method args.
152 |
153 | Two ways to specify which types are required for each arg.
154 |
155 | 1) 2-tuples, where first member specifies arg index or arg name,
156 | second member specifies a type or a tuple of types.
157 | 2) kwargs style, e.g. ``argname=types`` where ``types`` again can
158 | be a type or a tuple of types.
159 |
160 | None is always a valid type, to allow for optional args.
161 |
162 | Usage example:
163 |
164 | .. code::
165 |
166 | @require(x=int, y=float)
167 | def func(x, y):
168 | return x / y
169 |
170 | .. function:: deprecated(level=1, since=None, info=None)
171 |
172 | Can be used to mark functions as deprecated.
173 |
174 | :param int level: severity level.
175 | 0 = warnings.warn(category=DeprecationWarning)
176 | 1 = warnings.warn_explicit(category=DeprecationWarning)
177 | 2 = raise DeprecationWarning()
178 | :param string since: the version where deprecation was introduced.
179 | :param string info: additional info. normally used to refer to the new
180 | function now favored in place of the deprecated one.
181 |
182 | .. function:: cache(func)
183 |
184 | Classic cache decorator.
185 |
186 | .. function:: memoize(func)
187 |
188 | Classic memoization decorator.
189 |
190 |
191 | .. _subprocess.call: http://docs.python.org/library/subprocess.html?highlight=subprocess.call#subprocess.call
192 | .. _os.system: http://docs.python.org/library/os.html?highlight=os.system#os.system
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/py4dlib.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/py4dlib.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/py4dlib"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/py4dlib"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/ShowPolygonNumber.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # ShowPolygonNumber.py
4 | # py4dlib.examples
5 | #
6 | # Created by André Berg on 2013-07-29.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # ShowPolygonNumber
12 | #
13 | # Create and attach text splines to polygons.
14 | #
15 | # Summary:
16 | #
17 | # Example script that shows how to get point and polygon selections
18 | # and how to create objects and attach them to polygons in local and
19 | # global coordinate systems.
20 | #
21 | # pylint: disable-msg=F0401
22 |
23 | """
24 | Name-US:Show Polygon Number
25 | Description-US:Create and attach text splines indicating the polygon number to each selected polygon.
26 | """
27 |
28 | import os
29 |
30 | __version__ = (0, 2)
31 | __date__ = '2013-07-29'
32 | __updated__ = '2013-08-08'
33 |
34 |
35 | DEBUG = 0 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
36 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
37 |
38 | try:
39 | import c4d #@UnresolvedImport
40 | from c4d import documents
41 | except ImportError:
42 | if TESTRUN == 1:
43 | pass
44 |
45 |
46 | if DEBUG:
47 | import py4dlib
48 | reload(py4dlib.mesh)
49 | reload(py4dlib.maths)
50 |
51 |
52 | from py4dlib.maths import BuildMatrix3, IsZeroVector, BBox
53 | from py4dlib.mesh import CalcPolyNormal, CalcPolyCentroid, CalcPolyArea, PolyToListList
54 | from py4dlib.mesh import GetSelectedPoints, GetSelectedPolys
55 | from py4dlib.objects import CreateObject, InsertUnderNull
56 | from py4dlib.utils import ClearConsole, PPLLString
57 |
58 |
59 | # group text spline objects under op else insert at root
60 | GROUP_UNDER = True
61 | TEXT_SIZE = 1
62 |
63 | def main(doc): # IGNORE:W0621
64 | doc.StartUndo()
65 |
66 | sel = doc.GetSelection()
67 | if sel is None: return False
68 |
69 | c4d.StopAllThreads()
70 |
71 | # loop through all objects
72 | for op in sel:
73 | if not isinstance(op, c4d.PolygonObject):
74 | if DEBUG:
75 | print("%s: not a polygon object. Skipping..." % str(op.GetName()))
76 | continue
77 | print("object name: %s" % op.GetName())
78 |
79 | pointsel = op.GetPointS()
80 | pointselcnt = pointsel.GetCount()
81 | pointcnt = op.GetPointCount()
82 | allpoints = op.GetAllPoints()
83 |
84 | print("number of selected points = %s (%s total)" % (pointselcnt, pointcnt))
85 |
86 | polysel = op.GetPolygonS()
87 | polyselcnt = polysel.GetCount()
88 | polycnt = op.GetPolygonCount()
89 | allpolys = op.GetAllPolygons()
90 |
91 |
92 | print("number of selected polygons = %s (%s total)" % (polyselcnt, polycnt))
93 |
94 | pnts = GetSelectedPoints(op)
95 | plys = GetSelectedPolys(op)
96 |
97 | if len(plys) == 0:
98 | # nothing selected? use all polys
99 | plys = list(xrange(0, polycnt))
100 |
101 | print("selected points = %s" % pnts)
102 | print("selected polys = %s" % plys)
103 |
104 | for pnt in pnts:
105 | print("%d: %s" % (pnt, allpoints[pnt]))
106 |
107 | pmarks = []
108 |
109 | op_mg = op.GetMg()
110 | op_name = op.GetName()
111 |
112 | pgrp_name = "%s - Polygon #s" % op_name
113 | pgrp = doc.SearchObject(pgrp_name)
114 | if pgrp:
115 | pgrp.Remove()
116 |
117 | for ply in plys:
118 | poly = allpolys[ply]
119 |
120 | a = allpoints[poly.a]
121 | b = allpoints[poly.b]
122 | c = allpoints[poly.c]
123 | d = allpoints[poly.d]
124 |
125 | pids = [a, b, c]
126 |
127 | plen = 3
128 | if c != d:
129 | pids.append(d)
130 | plen = 4
131 |
132 | cv1 = a - b
133 | cv2 = b - c
134 | cv3 = c - d
135 | cv4 = d - a
136 | if plen == 4:
137 | cva = (cv3 - cv1)
138 | cvb = (cv4 - cv2)
139 | else:
140 | cva = cv3 - cv1
141 | cvb = cv3 - cv2
142 |
143 | if plen == 4:
144 | cv = c4d.Vector(cva.x, cvb.y/2, cva.z)
145 | else:
146 | if cvb.y == 0:
147 | cv = cvb
148 | else:
149 | cv = cva
150 |
151 | if IsZeroVector(cv):
152 | if cv == cva:
153 | cv = cvb
154 | else:
155 | cv = cva
156 |
157 | AXIS_ZY = 1
158 |
159 | base = "x"
160 | axis = AXIS_ZY
161 |
162 | if DEBUG:
163 | print("pids = %r" % pids)
164 | print("%d: %s, points as list:" % (ply, poly))
165 | print("%s" % (PPLLString(PolyToListList(poly, op))))
166 |
167 | # calculate polygon normals
168 | pnormal = CalcPolyNormal(poly, op)
169 | if DEBUG: print("normal: %s" % (pnormal))
170 |
171 | # calculate polygon area and bounding box
172 | parea = CalcPolyArea(poly, op)
173 | pbb = BBox.FromPolygon(poly, op)
174 | pbb_slen = pbb.size.GetLength()
175 | parea = (parea / pbb_slen / 2.0) * TEXT_SIZE
176 |
177 | if DEBUG:
178 | print("pbb_slen: %s" % pbb_slen)
179 | print("area: %s" % (parea))
180 |
181 | # create text spline objects
182 | pname = "%d" % ply
183 |
184 | pmark = CreateObject(c4d.Osplinetext, pname)
185 | pmark[c4d.PRIM_TEXT_TEXT] = pname # Text
186 | pmark[c4d.PRIM_TEXT_HEIGHT] = parea # Font Height
187 | pmark[c4d.PRIM_PLANE] = axis # Orientation
188 |
189 | if GROUP_UNDER:
190 | ppos = CalcPolyCentroid(poly, op)
191 | else:
192 | # put in scene globally and don't group under op
193 | ppos = CalcPolyCentroid(poly, op) * op_mg
194 |
195 | # match position and orientation
196 | pmg = BuildMatrix3(pnormal, cv, off=ppos, base=base)
197 | pmg.v2 = -pmg.v2
198 |
199 | # create translation matrix to center the text letters
200 | tm = c4d.utils.MatrixMove(c4d.Vector(0, -parea/3, 0))
201 | pmg *= tm
202 |
203 | pmark.SetMg(pmg)
204 | pmarks.append(pmark)
205 |
206 | # group spline text objects under null for each op
207 | pgrp = InsertUnderNull(pmarks, name=pgrp_name)
208 |
209 | if GROUP_UNDER:
210 | pgrp.InsertUnder(op)
211 |
212 | # tell C4D to update internal state
213 | c4d.EventAdd()
214 | doc.EndUndo()
215 |
216 |
217 | if __name__ == '__main__':
218 | ClearConsole()
219 | doc = documents.GetActiveDocument()
220 | main(doc)
221 |
222 |
223 |
224 | # Licensed under the Apache License, Version 2.0 (the "License");
225 | # you may not use this file except in compliance with the License.
226 | # You may obtain a copy of the License at
227 | #
228 | # http://www.apache.org/licenses/LICENSE-2.0
229 | #
230 | # Unless required by applicable law or agreed to in writing, software
231 | # distributed under the License is distributed on an "AS IS" BASIS,
232 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
233 | # See the License for the specific language governing permissions and
234 | # limitations under the License.
235 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\py4dlib.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\py4dlib.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # py4dlib documentation build configuration file, created by
4 | # sphinx-quickstart on Tue Jul 30 19:53:32 2013.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | sys.path.insert(0, os.path.abspath('../source'))
20 |
21 | # -- General configuration -----------------------------------------------------
22 |
23 | # If your documentation needs a minimal Sphinx version, state it here.
24 | #needs_sphinx = '1.0'
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode']
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'py4dlib'
44 | copyright = u'2013, André Berg'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = '0.6.5'
52 | # The full version, including alpha/beta/rc tags.
53 | release = '0.6.5'
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = ['_build']
68 |
69 | # The reST default role (used for this markup: `text`) to use for all documents.
70 | #default_role = None
71 |
72 | # If true, '()' will be appended to :func: etc. cross-reference text.
73 | #add_function_parentheses = True
74 |
75 | # If true, the current module name will be prepended to all description
76 | # unit titles (such as .. function::).
77 | #add_module_names = True
78 |
79 | # If true, sectionauthor and moduleauthor directives will be shown in the
80 | # output. They are ignored by default.
81 | #show_authors = False
82 |
83 | # The name of the Pygments (syntax highlighting) style to use.
84 | pygments_style = 'sphinx'
85 |
86 | # A list of ignored prefixes for module index sorting.
87 | #modindex_common_prefix = []
88 |
89 | # If true, keep warnings as "system message" paragraphs in the built documents.
90 | #keep_warnings = False
91 |
92 |
93 | # -- Options for HTML output ---------------------------------------------------
94 |
95 | # The theme to use for HTML and HTML Help pages. See the documentation for
96 | # a list of builtin themes.
97 | html_theme = 'pydocs3theme'
98 |
99 | # Theme options are theme-specific and customize the look and feel of a theme
100 | # further. For a list of options available for each theme, see the
101 | # documentation.
102 | #html_theme_options = {}
103 |
104 | # Add any paths that contain custom themes here, relative to this directory.
105 | html_theme_path = ['.']
106 |
107 | # The name for this set of Sphinx documents. If None, it defaults to
108 | # " v documentation".
109 | #html_title = None
110 |
111 | # A shorter title for the navigation bar. Default is the same as html_title.
112 | #html_short_title = None
113 |
114 | # The name of an image file (relative to this directory) to place at the top
115 | # of the sidebar.
116 | #html_logo = None
117 |
118 | # The name of an image file (within the static path) to use as favicon of the
119 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
120 | # pixels large.
121 | #html_favicon = None
122 |
123 | # Add any paths that contain custom static files (such as style sheets) here,
124 | # relative to this directory. They are copied after the builtin static files,
125 | # so a file named "default.css" will overwrite the builtin "default.css".
126 | html_static_path = ['_static']
127 |
128 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
129 | # using the given strftime format.
130 | #html_last_updated_fmt = '%b %d, %Y'
131 |
132 | # If true, SmartyPants will be used to convert quotes and dashes to
133 | # typographically correct entities.
134 | #html_use_smartypants = True
135 |
136 | # Custom sidebar templates, maps document names to template names.
137 | #html_sidebars = {}
138 |
139 | # Additional templates that should be rendered to pages, maps page names to
140 | # template names.
141 | #html_additional_pages = {}
142 |
143 | # If false, no module index is generated.
144 | #html_domain_indices = True
145 |
146 | # If false, no index is generated.
147 | #html_use_index = True
148 |
149 | # If true, the index is split into individual pages for each letter.
150 | #html_split_index = False
151 |
152 | # If true, links to the reST sources are added to the pages.
153 | #html_show_sourcelink = True
154 |
155 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
156 | #html_show_sphinx = True
157 |
158 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
159 | #html_show_copyright = True
160 |
161 | # If true, an OpenSearch description file will be output, and all pages will
162 | # contain a tag referring to it. The value of this option must be the
163 | # base URL from which the finished HTML is served.
164 | #html_use_opensearch = ''
165 |
166 | # This is the file name suffix for HTML files (e.g. ".xhtml").
167 | #html_file_suffix = None
168 |
169 | # Output file base name for HTML help builder.
170 | htmlhelp_basename = 'py4dlibdoc'
171 |
172 |
173 | # -- Options for LaTeX output --------------------------------------------------
174 |
175 | latex_elements = {
176 | # The paper size ('letterpaper' or 'a4paper').
177 | #'papersize': 'letterpaper',
178 |
179 | # The font size ('10pt', '11pt' or '12pt').
180 | #'pointsize': '10pt',
181 |
182 | # Additional stuff for the LaTeX preamble.
183 | #'preamble': '',
184 | }
185 |
186 | # Grouping the document tree into LaTeX files. List of tuples
187 | # (source start file, target name, title, author, documentclass [howto/manual]).
188 | latex_documents = [
189 | ('index', 'py4dlib.tex', u'py4dlib Documentation',
190 | u'André Berg', 'manual'),
191 | ]
192 |
193 | # The name of an image file (relative to this directory) to place at the top of
194 | # the title page.
195 | #latex_logo = None
196 |
197 | # For "manual" documents, if this is true, then toplevel headings are parts,
198 | # not chapters.
199 | #latex_use_parts = False
200 |
201 | # If true, show page references after internal links.
202 | #latex_show_pagerefs = False
203 |
204 | # If true, show URL addresses after external links.
205 | #latex_show_urls = False
206 |
207 | # Documents to append as an appendix to all manuals.
208 | #latex_appendices = []
209 |
210 | # If false, no module index is generated.
211 | #latex_domain_indices = True
212 |
213 |
214 | # -- Options for manual page output --------------------------------------------
215 |
216 | # One entry per manual page. List of tuples
217 | # (source start file, name, description, authors, manual section).
218 | man_pages = [
219 | ('index', 'py4dlib', u'py4dlib Documentation',
220 | [u'André Berg'], 1)
221 | ]
222 |
223 | # If true, show URL addresses after external links.
224 | #man_show_urls = False
225 |
226 |
227 | # -- Options for Texinfo output ------------------------------------------------
228 |
229 | # Grouping the document tree into Texinfo files. List of tuples
230 | # (source start file, target name, title, author,
231 | # dir menu entry, description, category)
232 | texinfo_documents = [
233 | ('index', 'py4dlib', u'py4dlib Documentation',
234 | u'André Berg', 'py4dlib', 'One line description of project.',
235 | 'Miscellaneous'),
236 | ]
237 |
238 | # Documents to append as an appendix to all manuals.
239 | #texinfo_appendices = []
240 |
241 | # If false, no module index is generated.
242 | #texinfo_domain_indices = True
243 |
244 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
245 | #texinfo_show_urls = 'footnote'
246 |
247 | # If true, do not generate a @detailmenu in the "Top" node's menu.
248 | #texinfo_no_detailmenu = False
249 |
--------------------------------------------------------------------------------
/docs/pydocs3theme/static/basic.css_t:
--------------------------------------------------------------------------------
1 | /**
2 | * Sphinx stylesheet -- basic theme
3 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 | */
5 |
6 | /* -- main layout ----------------------------------------------------------- */
7 |
8 | div.clearer {
9 | clear: both;
10 | }
11 |
12 | /* -- relbar ---------------------------------------------------------------- */
13 |
14 | div.related {
15 | width: 100%;
16 | font-size: 90%;
17 | }
18 |
19 | div.related h3 {
20 | display: none;
21 | }
22 |
23 | div.related ul {
24 | margin: 0;
25 | padding: 0 0 0 10px;
26 | list-style: none;
27 | }
28 |
29 | div.related li {
30 | display: inline;
31 | }
32 |
33 | div.related li.right {
34 | float: right;
35 | margin-right: 5px;
36 | }
37 |
38 | /* -- sidebar --------------------------------------------------------------- */
39 |
40 | div.sphinxsidebarwrapper {
41 | padding: 10px 5px 0 10px;
42 | word-wrap: break-word;
43 | }
44 |
45 | div.sphinxsidebar {
46 | float: left;
47 | width: 230px;
48 | margin-left: -100%;
49 | font-size: 90%;
50 | }
51 |
52 | div.sphinxsidebar ul {
53 | list-style: none;
54 | }
55 |
56 | div.sphinxsidebar ul ul,
57 | div.sphinxsidebar ul.want-points {
58 | margin-left: 20px;
59 | list-style: square;
60 | }
61 |
62 | div.sphinxsidebar ul ul {
63 | margin-top: 0;
64 | margin-bottom: 0;
65 | }
66 |
67 | div.sphinxsidebar form {
68 | margin-top: 10px;
69 | }
70 |
71 | div.sphinxsidebar input {
72 | border: 1px solid #98dbcc;
73 | font-family: sans-serif;
74 | font-size: 1em;
75 | }
76 |
77 | img {
78 | border: 0;
79 | }
80 |
81 | /* -- search page ----------------------------------------------------------- */
82 |
83 | ul.search {
84 | margin: 10px 0 0 20px;
85 | padding: 0;
86 | }
87 |
88 | ul.search li {
89 | padding: 5px 0 5px 20px;
90 | background-image: url(file.png);
91 | background-repeat: no-repeat;
92 | background-position: 0 7px;
93 | }
94 |
95 | ul.search li a {
96 | font-weight: bold;
97 | }
98 |
99 | ul.search li div.context {
100 | color: #888;
101 | margin: 2px 0 0 30px;
102 | text-align: left;
103 | }
104 |
105 | ul.keywordmatches li.goodmatch a {
106 | font-weight: bold;
107 | }
108 |
109 | /* -- index page ------------------------------------------------------------ */
110 |
111 | table.contentstable {
112 | width: 90%;
113 | }
114 |
115 | table.contentstable p.biglink {
116 | line-height: 150%;
117 | }
118 |
119 | a.biglink {
120 | font-size: 1.3em;
121 | }
122 |
123 | span.linkdescr {
124 | font-style: italic;
125 | padding-top: 5px;
126 | font-size: 90%;
127 | }
128 |
129 | /* -- general index --------------------------------------------------------- */
130 |
131 | table.indextable td {
132 | text-align: left;
133 | vertical-align: top;
134 | }
135 |
136 | table.indextable dl, table.indextable dd {
137 | margin-top: 0;
138 | margin-bottom: 0;
139 | }
140 |
141 | table.indextable tr.pcap {
142 | height: 10px;
143 | }
144 |
145 | table.indextable tr.cap {
146 | margin-top: 10px;
147 | background-color: #f2f2f2;
148 | }
149 |
150 | img.toggler {
151 | margin-right: 3px;
152 | margin-top: 3px;
153 | cursor: pointer;
154 | }
155 |
156 | /* -- general body styles --------------------------------------------------- */
157 |
158 | a.headerlink {
159 | visibility: hidden;
160 | }
161 |
162 | h1:hover > a.headerlink,
163 | h2:hover > a.headerlink,
164 | h3:hover > a.headerlink,
165 | h4:hover > a.headerlink,
166 | h5:hover > a.headerlink,
167 | h6:hover > a.headerlink,
168 | dt:hover > a.headerlink {
169 | visibility: visible;
170 | }
171 |
172 | div.body p.caption {
173 | text-align: inherit;
174 | }
175 |
176 | div.body td {
177 | text-align: left;
178 | }
179 |
180 | .field-list ul {
181 | padding-left: 1em;
182 | }
183 |
184 | .first {
185 | margin-top: 0 !important;
186 | }
187 |
188 | p.rubric {
189 | margin-top: 30px;
190 | font-weight: bold;
191 | }
192 |
193 | /* -- sidebars -------------------------------------------------------------- */
194 |
195 | div.sidebar {
196 | margin: 0 0 0.5em 1em;
197 | border: 1px solid #ddb;
198 | padding: 7px 7px 0 7px;
199 | background-color: #ffe;
200 | width: 40%;
201 | float: right;
202 | }
203 |
204 | p.sidebar-title {
205 | font-weight: bold;
206 | }
207 |
208 | /* -- topics ---------------------------------------------------------------- */
209 |
210 | div.topic {
211 | border: 1px solid #ccc;
212 | padding: 7px 7px 0 7px;
213 | margin: 10px 0 10px 0;
214 | }
215 |
216 | p.topic-title {
217 | font-size: 1.1em;
218 | font-weight: bold;
219 | margin-top: 10px;
220 | }
221 |
222 | /* -- admonitions ----------------------------------------------------------- */
223 |
224 | div.admonition {
225 | margin-top: 10px;
226 | margin-bottom: 10px;
227 | padding: 7px;
228 | }
229 |
230 | div.admonition dt {
231 | font-weight: bold;
232 | }
233 |
234 | div.admonition dl {
235 | margin-bottom: 0;
236 | }
237 |
238 | p.admonition-title {
239 | margin: 0px 10px 5px 0px;
240 | font-weight: bold;
241 | }
242 |
243 | div.body p.centered {
244 | text-align: center;
245 | margin-top: 25px;
246 | }
247 |
248 | /* -- tables ---------------------------------------------------------------- */
249 |
250 | table.docutils {
251 | border: 0 solid #dce;
252 | border-collapse: collapse;
253 | }
254 |
255 | table.docutils td, table.docutils th {
256 | padding: 2px 5px 2px 5px;
257 | border-left: 0;
258 | background-color: #eef;
259 | }
260 |
261 | table.docutils td p.last, table.docutils th p.last {
262 | margin-bottom: 0;
263 | }
264 |
265 | table.field-list td, table.field-list th {
266 | border: 0 !important;
267 | }
268 |
269 | table.footnote td, table.footnote th {
270 | border: 0 !important;
271 | }
272 |
273 | table.docutils th {
274 | border-top: 1px solid #cac;
275 | background-color: #ede;
276 | }
277 |
278 | th {
279 | text-align: left;
280 | padding-right: 5px;
281 | }
282 |
283 | th.head {
284 | text-align: center;
285 | }
286 |
287 | /* -- other body styles ----------------------------------------------------- */
288 |
289 | dl {
290 | margin-bottom: 15px;
291 | }
292 |
293 | dd p {
294 | margin-top: 0px;
295 | }
296 |
297 | dd ul, dd table {
298 | margin-bottom: 10px;
299 | }
300 |
301 | dd {
302 | margin-top: 3px;
303 | margin-bottom: 10px;
304 | margin-left: 30px;
305 | }
306 |
307 | dt:target, .highlight {
308 | background-color: #fbe54e;
309 | }
310 |
311 | dl.glossary dt {
312 | font-weight: bold;
313 | font-size: 1.1em;
314 | }
315 |
316 | .field-list ul {
317 | margin: 0;
318 | padding-left: 1em;
319 | }
320 |
321 | .field-list p {
322 | margin: 0;
323 | }
324 |
325 | .refcount {
326 | color: #060;
327 | }
328 |
329 | .optional {
330 | font-size: 1.3em;
331 | }
332 |
333 | .versionmodified {
334 | font-style: italic;
335 | }
336 |
337 | p.deprecated, p.deprecated-removed {
338 | background-color: #ffe4e4;
339 | border: 1px solid #f66;
340 | padding: 7px
341 | }
342 |
343 | .system-message {
344 | background-color: #fda;
345 | padding: 5px;
346 | border: 3px solid red;
347 | }
348 |
349 | .footnote:target {
350 | background-color: #ffa;
351 | }
352 |
353 | .impl-detail {
354 | margin-top: 10px;
355 | margin-bottom: 10px;
356 | padding: 7px;
357 | border: 1px solid #ccc;
358 | }
359 |
360 | .impl-detail .compound-first {
361 | margin-top: 0;
362 | }
363 |
364 | .impl-detail .compound-last {
365 | margin-bottom: 0;
366 | }
367 |
368 | /* -- code displays --------------------------------------------------------- */
369 |
370 | pre {
371 | overflow: auto;
372 | overflow-y: hidden;
373 | }
374 |
375 | td.linenos pre {
376 | padding: 5px 0px;
377 | border: 0;
378 | background-color: transparent;
379 | color: #aaa;
380 | }
381 |
382 | table.highlighttable {
383 | margin-left: 0.5em;
384 | }
385 |
386 | table.highlighttable td {
387 | padding: 0 0.5em 0 0.5em;
388 | }
389 |
390 | tt.descname {
391 | background-color: transparent;
392 | font-weight: bold;
393 | font-size: 1.2em;
394 | }
395 |
396 | tt.descclassname {
397 | background-color: transparent;
398 | }
399 |
400 | tt.xref, a tt {
401 | background-color: transparent;
402 | font-weight: bold;
403 | }
404 |
405 | h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt {
406 | background-color: transparent;
407 | }
408 |
409 | /* -- math display ---------------------------------------------------------- */
410 |
411 | img.math {
412 | vertical-align: middle;
413 | }
414 |
415 | div.body div.math p {
416 | text-align: center;
417 | }
418 |
419 | span.eqno {
420 | float: right;
421 | }
422 |
423 | /* -- printout stylesheet --------------------------------------------------- */
424 |
425 | @media print{
426 | div.document,
427 | div.documentwrapper,
428 | div.bodywrapper {
429 | margin: 0 !important;
430 | width: 100%;
431 | }
432 |
433 | div.sphinxsidebar,
434 | div.related,
435 | div.footer,
436 | #top-link {
437 | display: none;
438 | }
439 | }
440 |
--------------------------------------------------------------------------------
/docs/api/maths.rst:
--------------------------------------------------------------------------------
1 | Maths
2 | -----
3 |
4 |
5 | .. class:: BBox
6 |
7 | Calculate various bounding box metrics from a list of points,
8 | such as min, max, midpoint, radius and size.
9 |
10 | .. classmethod:: FromObject(cls, obj, selOnly=False)
11 |
12 | Returns a new BBox object with all points from the passed object.
13 |
14 | :param bool selOnly: use selected points only instead of all points.
15 |
16 | :raise ValueError: if the object has no points.
17 |
18 | .. classmethod:: FromPointList(cls, lst)
19 |
20 | Returns a new BBox object with all points from a list added.
21 |
22 | Elements of lst must be of type ``c4d.Vector``.
23 |
24 | :raise ValueError: if the list is empty.
25 |
26 | .. classmethod:: FromPolygon(cls, poly, obj)
27 |
28 | Returns a new BBox object with all points from the passed polygon.
29 |
30 | .. function:: AddPoint(p)
31 |
32 | Add metrics from point p.
33 |
34 | .. function:: AddPoints(lst)
35 |
36 | Add metrics from a list of points.
37 |
38 | .. function:: GetMax()
39 |
40 | Return max bounds vector.
41 |
42 | .. function:: GetMin()
43 |
44 | Return min bounds vector.
45 |
46 | .. function:: GetMp()
47 |
48 | Return midpoint vector.
49 |
50 | .. function:: GetRad()
51 |
52 | Return radius vector.
53 |
54 | .. function:: GetSize()
55 |
56 | Return size vector.
57 |
58 | .. class:: Plane(pos, n)
59 |
60 | Represents a plane defined by positional offset and normal direction.
61 |
62 | .. function:: SetN(self, newN)
63 |
64 | Sets the normal of the plane to ``newN``.
65 |
66 | .. function:: SetPos(self, newPos)
67 |
68 | Sets the positional offset of the plane to ``newPos``.
69 |
70 | .. function:: SideAsString(self, d)
71 |
72 | Given distance ``d`` return a string indicating the residence
73 | of the point that correlates to the given distance.
74 |
75 | Used together with :py:func:`PointResidence`::
76 |
77 | SideAsString(PointResidence(self, p))
78 |
79 | :return: either ``front``, ``back`` or ``onplane``
80 |
81 | .. function:: PointResidence(self, p)
82 |
83 | Define the resident direction of a point with respect
84 | to the plane.
85 |
86 | The point can be either in front of the plane (+1), on the
87 | plane (0) or at the back of the plane (-1).
88 |
89 |
90 | .. function:: PointDistance(self, p, signed=True)
91 |
92 | Calculate distance from a point p to the plane.
93 |
94 | :param bool signed: set to True if you want the signed distance.
95 |
96 | A signed distance can be useful to determine if the point is located
97 | in the half space from the backside of the plane or in the half space
98 | on the front.
99 |
100 | .. function:: LineIntersection(self, p, d=None)
101 |
102 | Calculate intersection point with a line starting at position p
103 | and pointing in the direction d.
104 |
105 | :param c4d.Vector d: direction of the line. If None, the normal
106 | of the plane will be used instead.
107 |
108 | :return: ``c4d.Vector`` representing the intersection point, or
109 | None if an intersection isn't possible (parallel directions).
110 |
111 |
112 | .. function:: FloatEqual(a, b, places=8)
113 |
114 | Same as ``c4d.utils.FloatTolerantCompare`` just a shorter function name.
115 |
116 | .. function:: MAbs(m)
117 |
118 | ``abs()`` each component vector of matrix m.
119 |
120 | .. function:: VDeg(v, isHPB=False)
121 |
122 | Convert each component of vector v to degrees.
123 |
124 | .. function:: VRad(v, isHPB=False)
125 |
126 | Convert each component of vector v to radians.
127 |
128 | .. function:: VAvg(lv)
129 |
130 | Calculate the average of a list of vectors.
131 |
132 | .. function:: VAbsMin(v)
133 |
134 | Return min component of a vector using ``abs(x) < abs(y)`` comparisons.
135 |
136 | .. function:: VAbsMax(v)
137 |
138 | Return max component of a vector using ``abs(x) > abs(y)`` comparisons.
139 |
140 | .. function:: VBoundaryLerp(lv, t=0.5)
141 |
142 | Interpolate linearily between a list of vectors, such that
143 | the resulting vector points to the weighted midpoint in the
144 | vector space defined by the boundaries max X to min X and
145 | max Y to min Y.
146 |
147 | :param float t: the weighting coefficient.
148 |
149 | :return: None if len(lst) is 0 or if the angle between
150 | the two max/min vectors is greater than 180 degrees.
151 |
152 | .. function:: VLerp(startv, endv, t=0.5)
153 |
154 | Linear interpolation between 2 vectors.
155 |
156 | Same as ``c4d.utils.VectorMix``.
157 |
158 | .. function:: VNLerp(startv, endv, t=0.5)
159 |
160 | Normalized linear interpolation between 2 vectors.
161 |
162 | .. function:: VSLerp(startv, endv, t=0.5)
163 |
164 | Spherical linear interpolation between 2 vectors.
165 |
166 | .. function:: BuildMatrix(v, off=None, order="zyx")
167 |
168 | Builds a new orthonormal basis from a direction and (optionally) an offset vector using John F. Hughes and Thomas Möller's method.
169 |
170 | If ``off`` is None, off will default to a zero vector.
171 |
172 | .. function:: BuildMatrix2(v, off=None, base="z")
173 |
174 | Builds a new orthonormal basis from a direction and (optionally) an offset vector using base aligned cross products.
175 |
176 | If ``off`` is None, off will default to a zero vector.
177 |
178 | :param str base: the base component 'v' represents. Must be one of ``x, y, z, -x, -y, -z``
179 |
180 |
181 | .. function:: BuildMatrix3(v, v2, off=None, base="z")
182 |
183 | Builds a new orthonormal basis from 2 direction
184 | and (optionally) an offset vector using cross products.
185 |
186 | :param str base: the base component 'v' represents.
187 |
188 | .. function:: GetMulP(m, v)
189 |
190 | Multiply a matrix with a vector representing a point.
191 |
192 | Same as ``c4d.Matrix.Mul(v)``.
193 |
194 | .. function:: GetMulV(m, v)
195 |
196 | Multiply a matrix with a vector representing a direction.
197 |
198 | Same as ``c4d.Matrix.MulV(v)``
199 |
200 | .. function:: Det(m)
201 |
202 | Determinant of an ``n x n`` matrix.
203 |
204 | m can be of type ``c4d.Matrix`` when ``n = 3``
205 | or ``list`` when ``n = 3`` or ``n = 4`` .
206 |
207 | .. function:: Transpose(e)
208 |
209 | Transpose matrix e in row-major format to column-major.
210 |
211 | ``e`` can be of type ``list`` structure or ``c4d.Matrix``.
212 |
213 | .. function:: ListToMatrix(lv)
214 |
215 | Convert a list of 3 or 4 ``c4d.Vector`` to ``c4d.Matrix``.
216 |
217 | .. function:: ListListToMatrix(lli)
218 |
219 | Convert a ``list`` structure, representing a list of list
220 | of coordinate values to a ``c4d.Matrix``.
221 |
222 | See :py:func:`MatrixToListList` to find out which list corresponds
223 | to which matrix component.
224 |
225 | .. function:: MatrixToListList(m, inclOff=False)
226 |
227 | Convert a ``c4d.Matrix`` to a ``list`` structure.
228 |
229 | The structure layout is generally in row-major format,
230 | and the ordering the same as the order required for
231 | constructing a ``c4d.Matrix`` by hand:
232 |
233 | .. code::
234 |
235 | [[off.x, off.y, off.z],
236 | [v1.x, v1.y, v1.z],
237 | [v2.x, v2.y, v2.z],
238 | [v3.x, v3.y, v3.z]]
239 |
240 | .. function:: UnitNormal(a, b, c)
241 |
242 | Calculate unit normal of a planar surface.
243 |
244 | :raise ValueError: if magnitude <= 0.0
245 |
246 | .. function:: IsPointInTriangle(p, a, b, c)
247 |
248 | Returns True if the point p is inside the triangle given by points a, b, and c.
249 |
250 | .. function:: IsColinear(lv)
251 |
252 | Given a list of vectors check if they all share the same coordinates
253 | in at least 2 dimensions.
254 |
255 | :return: True if all the vectors in the list are co-linear.
256 |
257 | .. function:: IsZeroVector(v)
258 |
259 | Uses float tolerant component comparison to check if v is a zero vector.
260 |
261 | .. function:: LineLineDistance(p1a, p1b, p2a, p2b)
262 |
263 | Computes the smallest distance between two 3D lines.
264 |
265 | :return: ``tuple`` of two ``c4d.Vectors`` which are the points on each of the
266 | two input lines that, when connected, form a segment which represents the
267 | shortest distance between the two lines.
268 |
269 | .. function:: WrapPi(theta)
270 |
271 | Wraps an angle theta in range ``-pi..pi`` by adding the correct multiple of 2 pi.
272 |
273 | .. function:: SafeAcos(x)
274 |
275 | Same as ``math.acos(x)`` but if x is out of range, it is *clamped* to the
276 | nearest valid value. The value returned is in range ``0..pi``, the same as
277 | the standard `math.acos`_ function.
278 |
279 |
280 | .. _math.acos: http://docs.python.org/2/library/math.html?highlight=math.acos#math.acos
281 |
--------------------------------------------------------------------------------
/docs/api/objects.rst:
--------------------------------------------------------------------------------
1 | Objects
2 | -------
3 |
4 | Functions for working with CINEMA 4D's objects.
5 |
6 | .. class:: ObjectIterator(start_obj, stop_obj=None, children_only=True, startlvl=-1)
7 |
8 | Iterator over specific objects in the object manager tree.
9 |
10 | Using a depth first traversal scheme, return a tuple in the form
11 | ``(op, lvl)``, where op is a ``c4d.BaseObject`` representing the current
12 | object and lvl is an integer indicating the current depth level.
13 |
14 | :param c4d.BaseObject start_obj: the object whose hierarchy should be iterated over
15 | :param c4d.BaseObject stop_obj: an object or a list of objects at which traversal
16 | should stop (optional)
17 | :param bool children_only: if True, iterate through the sub-hierarchy under
18 | startobj and stop as soon as startobj's parent or
19 | stopobj (if given) is reached. This excludes startobj
20 | from the iteration.
21 | :param int startlvl: base indentation level
22 |
23 | .. class:: ObjectEntry(op, lvl=-1, parents=None)
24 |
25 | Wraps ``c4d.BaseObject`` and makes them hashable,
26 | so they can be used as keys in dictionaries.
27 |
28 | :param c4d.BaseObject op: the object to wrap.
29 | :param int lvl: the depth level within the hierarchy.
30 | :param list parents: a list of parent objects
31 |
32 | .. class:: ObjectHierarchy(root_obj=None, filter_type=None, children_only=False)
33 |
34 | Represents a hierarchical group structure in the object manager.
35 |
36 | Can be used to create a Pythonic snapshot of the current scene
37 | so as to provide easy access to specifc sets of objects.
38 |
39 | Starting with root object stores a list of ``c4d.BaseObjects``
40 | for each depth level in a dictionary. Each list is indexed by a
41 | concatenation of its parent names. The concat character is a
42 | forward slash, which forms a Unix like filepath as seen with
43 | the object manager's address bar widget.
44 |
45 | Additionally, a small subset of X-Path like functionality is
46 | provided with the ``Get()`` function, namely the subset that
47 | coincides with syntax for wildcard and regular epxression
48 | expansion. This makes it easy to select a subset of objects,
49 | based on parent-name relationships.
50 |
51 | :param c4d.Otype filter_type: only recognize objects of this c4d type
52 | :param bool children_only: see :py:class:`ObjectIterator`
53 |
54 | .. function:: PPrint(stopobj=None, filtertype=None, tabsize=4)
55 |
56 | Print an indented, tree-like representation of an object manager hierarchy.
57 |
58 | .. function:: Get(path, strict=True)
59 |
60 | Get a list of ``c4d.BaseObject`` for the key path given by 'path'.
61 |
62 | Key path can contain wildcards (``*`` or ``?``) or regular expression
63 | syntax. Prepend a ``!`` to ``path`` if you want to forego wildcard expansion
64 | and thus ensure it is used as a verbatim regular expression pattern instead.
65 |
66 | Note that if ``strict`` is True, ``path`` must match the whole key it is
67 | tested against. Otherwise it is sufficient if the path is contained by
68 | the key.
69 |
70 | Returns a list of all objects for which ``path``, expanded, matches a
71 | concatenated parent path.
72 |
73 | Returns an empty list if no objects could be located for ``path``.
74 |
75 | .. function:: Select(obj)
76 |
77 | .. function:: SelectAdd(obj)
78 |
79 | Same as :py:func:`Select` but uses a slightly different mechanism.
80 |
81 | See also ``BaseDocument.SetSelection(sel, mode)``.
82 |
83 | .. function:: SelectGroupMembers(grp)
84 |
85 | .. function:: SelectObjects(objs)
86 |
87 | .. function:: DeselectAll(inObjMngr=False)
88 |
89 | Not the same as ``BaseSelect.DeselectAll()``.
90 |
91 | :param bool inObjMngr: if True, run the deselect command for the
92 | Object Manager, else the general one for the editor viewport.
93 |
94 | .. function:: GroupObjects(objs, name="Group")
95 |
96 | ``CallCommand`` based grouping of objects from a list.
97 | Generally unreliable, because selection state matters.
98 |
99 | Use :py:func:`InsertUnderNull` for better effect.
100 |
101 | .. function:: GroupSelected(name="Group")
102 |
103 | ``CallCommand`` based grouping of selected objects.
104 | Generally unreliable, because selection state matters.
105 |
106 | Use :py:func:`InsertUnderNull` for better effect.
107 |
108 | .. function:: RecurseBranch(obj)
109 |
110 | .. function:: GetNextObject(obj, stop_objs=None)
111 |
112 | Return the next object in the hierarchy using a depth-first traversal scheme.
113 |
114 | If stop_objs is a ``c4d.BaseObject`` or a list of ``c4d.BaseObjects`` and the next
115 | operation would encounter this object (or the first object in the list) None
116 | will be returned. This is so that this function can be used in a while loop.
117 |
118 | .. function:: GetActiveObjects(doc)
119 |
120 | Same as ``BaseDocument.GetSelection()``, while
121 | GetSelection also selects tags and materials.
122 |
123 | .. function:: FindObject(name, start=None, matchfunc=None, *args, **kwargs)
124 |
125 | Find object with name 'name'.
126 |
127 | :param c4d.BaseObject start: object from where the search should begin.
128 | Can also be a ``str`` representing the name of a ``c4d.BaseObject``.
129 | :param function matchfunc: can be used to customize the matching logic
130 | by providing the name of a custom function. This function
131 | will be passed a potential candidate object plus any
132 | remaining args. It should return True or False.
133 |
134 | .. function:: FindObjects(name=None, uip=None)
135 |
136 | Find all objects in the scene, either with the name ``name``
137 | and/or the unique IP ``uip``.
138 |
139 | :return: list with matched objects or empty list if no match.
140 |
141 | .. function:: CreateObject(typ, name, undo=True)
142 |
143 | Create a object of type 'typ', with name 'name'.
144 |
145 | This calls ``c4d.StopAllThreads()`` internally.
146 |
147 | .. function:: CreateReplaceObject(typ, name)
148 |
149 | Create object with name 'name' removing and replacing any object with the same name.
150 |
151 | This calls :py:func:`CreateObject` internally.
152 |
153 | .. function:: UniqueSequentialName(name_base, template=u'%(name)s.%(num)s')
154 |
155 | Return a new sequential name based on a naming template and a
156 | base name such that the name uniquely identifies an object in
157 | the scene.
158 |
159 | By default, mimicks the names generated by CINEMA 4D when
160 | multiple objects of the same type are created in quick succession.
161 |
162 | For example if the scene had the following objects::
163 |
164 | Cube
165 | Cube.1
166 | Cube.12
167 |
168 | Using the default template, the function would return ``Cube.13``
169 | as a new name.
170 |
171 | .. function:: InsertUnderNull(objs, grp=None, name="Group", copy=False)
172 |
173 | Inserts objects under a group (null) object, optionally creating the group.
174 |
175 | Note: currently does not reset obj's coordinate frame
176 | to that of the new parent.
177 |
178 | :param c4d.BaseObject objs: can be a single object or a list of objects
179 | :param c4d.BaseObject grp: the group to place the objects under. If None
180 | a new null object will be created.
181 | :param str name: name for the new group
182 | :param bool copy: copy the objects if True
183 |
184 | .. function:: GetGlobalPosition(obj)
185 |
186 | .. function:: GetGlobalRotation(obj)
187 |
188 | .. function:: GetGlobalScale(obj)
189 |
190 | .. function:: SetGlobalPosition(obj, pos)
191 |
192 | .. function:: SetGlobalRotation(obj, rot)
193 |
194 | Please remember, like most 3D engines
195 | CINEMA 4D handles rotation in radians.
196 |
197 | Example for ``H=10, P=20, B=30``:
198 |
199 | .. code::
200 |
201 | import c4d
202 | from c4d import utils
203 | # ...
204 | hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30))
205 | SetGlobalRotation(obj, hpb) # object's rotation is 10, 20, 30
206 |
207 |
208 | .. function:: SetGlobalScale(obj, scale)
209 |
210 | .. function:: SetAxisRotation(obj, rot, local=False)
211 |
212 | Set the rotation of the object axis (i.e. keeping points in place).
213 |
214 | :param obj: object
215 | :param rot: vector
216 |
217 | Courtesy of Scott Ayers (`source `_)
218 |
219 |
220 | .. function:: CenterObjectAxis(obj, center="midpoint")
221 |
222 | Center the object's axis.
223 |
224 | This is equivalent to moving the object to the new center
225 | and then moving all the points back to the old spot.
226 |
227 | :param str center: can be ``midpoint`` to use the center
228 | of the object's bounding box, or ``gravity`` to use the
229 | object's center of gravity. The difference is that in the
230 | latter case single points at extreme distances from the
231 | object's core aren't given as much weight.
232 |
233 |
234 | .. function:: ObjectAxisFromVector(v)
235 |
236 | Same as ``c4d.utils.HPBToMatrix(c4d.utils.VectorToHPB(v))``.
237 |
238 | .. function:: MakeEditable(obj, clone=False)
239 |
240 | Run the Make Editible command on an object or a list of objects.
241 |
242 | :param bool clone: if True, return editable clones of the
243 | input.
244 |
245 | :return: editable object or list of editable objects
246 | Returns False if the document of the input objects
247 | can't be found.
--------------------------------------------------------------------------------
/source/py4dlib/plugins.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # plugins.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-26.
7 | # Copyright 2012 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | '''py4dlib.plugins -- functions for helping you write py4d plugins.'''
14 |
15 | import os
16 | import ConfigParser
17 |
18 | __version__ = (0, 5)
19 | __date__ = '2012-09-26'
20 | __updated__ = '2013-08-13'
21 |
22 | DEBUG = 0 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
23 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
24 |
25 |
26 | class UserDefaults(object):
27 | """
28 | Support for reading and writing settings files
29 | in .ini format.
30 |
31 | This can be used to provide state persistence
32 | for UI elements of a plugin.
33 |
34 | Examples:
35 |
36 | Initialize a new config object, modify and
37 | then save it:
38 |
39 | >>> filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res", "settings.ini")
40 | >>> defaults = UserDefaults(filepath=filepath)
41 | >>> defaults.Set('key', 'value')
42 | >>> defaults.Save()
43 |
44 | If filepath points to an existing file, it will
45 | use that file and initializing a new config object
46 | by reading from it.
47 |
48 | If you later want to read in the config file:
49 |
50 | >>> defaults.Read()
51 | >>> print defaults.Get('setting')
52 | value
53 | >>> print defaults.Get('does-not-exist', default='use default instead')
54 | use default instead
55 | """
56 |
57 | def __init__(self, filepath, defaults=None, header='Settings'):
58 | """
59 | Initializes a new user defaults object by either reading or creating
60 | a settings file at 'filepath'.
61 |
62 | :param filepath: ``str``
63 | usually ``res/settings.ini``relative to the
64 | source code file of the plugin, that uses the config store.
65 | :param defaults: ``dict`` default values to be used if the config
66 | file needs to be created. It is also possible to pass None
67 | here and then use the :py:func:`Set` and :py:func:`Save` functions
68 | to set inidividual settings after creation of the config object.
69 | :param header: ``str`` the name for a section in the .ini file.
70 | Usually you can get away with leaving it at the default.
71 | This will add a header "[Settings]" under which your
72 | settings will appear. If you have more advanced uses
73 | you are advised to modify the config parser state
74 | directly through ``self.state``.
75 | """
76 | super(UserDefaults, self).__init__()
77 | if not isinstance(filepath, basestring):
78 | raise TypeError("E: param 'filepath': expected type 'str', but got %s" % type(filepath))
79 | if not isinstance(header, basestring):
80 | raise TypeError("E: param 'header': expected type 'str', but got %s" % type(header))
81 | if defaults and not isinstance(defaults, dict):
82 | raise TypeError("E: param 'defaults': expected type 'dict', but got %s" % type(defaults))
83 | if defaults and not os.path.exists(filepath):
84 | # check if any values in defaults are not of type string
85 | # since ConfigParser needs string - always.
86 | _defaults = defaults.copy()
87 | for k, v in _defaults.iteritems():
88 | if not isinstance(v, basestring):
89 | defaults[k] = str(v)
90 | config = ConfigParser.ConfigParser(defaults)
91 | else:
92 | config = ConfigParser.ConfigParser()
93 | self.state = config
94 | self.filepath = filepath
95 | self.header = header
96 | if os.path.exists(filepath):
97 | if DEBUG:
98 | print("reading existing settings file at path %r" % filepath)
99 | self.Read()
100 | else:
101 | self.state.add_section(header)
102 | self.Save(config, filepath)
103 | if DEBUG:
104 | print("saving new state %r to settings file at path %r" % (self.state, filepath))
105 |
106 | def __str__(self, *args, **kwargs):
107 | return ("%r, header = %s, filepath = %s, state = %s" %
108 | (self, self.header, self.filepath, self.state))
109 |
110 | def Get(self, name, section=None, default=None, valuetype="str"):
111 | """
112 | Retrieve a previously stored value from the config object.
113 |
114 | :param str name: name of the setting
115 | :param str section: the section name. ``self.header`` if None.
116 | :param any default: a default value to use in case name wasn't found.
117 | :param str valuetype: type of the value to get. can be one of ``[str, bool, int, float]``.
118 | :return: ``valuetype`` on success, None or 'default' on failure.
119 | """
120 | if section is None:
121 | section = self.header
122 | result = default
123 | try:
124 | if valuetype == "bool":
125 | result = self.state.getboolean(section, name)
126 | elif valuetype == "int":
127 | result = self.state.getint(section, name)
128 | elif valuetype == "float":
129 | result = self.state.getfloat(section, name)
130 | else:
131 | result = self.state.get(section, name)
132 | except ConfigParser.NoOptionError, noe: # IGNORE:W0612 @UnusedVariable
133 | pass
134 | except Exception, e:
135 | print("*** Caught exception while getting %r: %s" % (name, e))
136 | return result
137 |
138 | def GetInt(self, name, section=None, default=None):
139 | """
140 | Retrieve a previously stored integer value from the config object.
141 |
142 | :param str name: name of the setting
143 | :param str section: the section name. ``self.header`` if None.
144 | :param any default: a default value to use in case name wasn't found.
145 | :return: ``int`` on success, None or 'default' on failure.
146 | """
147 | return self.Get(name, section=section, default=default, valuetype="int")
148 |
149 | def GetFloat(self, name, section=None, default=None):
150 | """
151 | Retrieve a previously stored float value from the config object.
152 |
153 | :param str name: name of the setting
154 | :param str section: the section name. ``self.header`` if None.
155 | :param any default: a default value to use in case name wasn't found.
156 | :return: ``float`` on success, None or 'default' on failure.
157 | """
158 | return self.Get(name, section=section, default=default, valuetype="float")
159 |
160 | def GetBool(self, name, section=None, default=None):
161 | """
162 | Retrieve a previously stored boolean value from the config object.
163 |
164 | :param str name: name of the setting
165 | :param str section: the section name. ``self.header`` if None.
166 | :param any default: a default value to use in case name wasn't found.
167 | :return: ``bool`` on success, None or 'default' on failure.
168 | """
169 | return self.Get(name, section=section, default=default, valuetype="bool")
170 |
171 | def Set(self, name, value, section=None): #@ReservedAssignment
172 | """
173 | Store a value in the config object for later retrieval.
174 |
175 | :param name: ``str`` name of the setting
176 | :param value: ``any`` value to set.
177 | :param section: ``str`` the section name. ``self.header`` if None.
178 | :return: True if successful, False otherwise.
179 | """
180 | if section is None:
181 | section = self.header
182 | result = False
183 | try:
184 | self.state.set(section, name, str(value))
185 | result = True
186 | except Exception, e: # IGNORE:W0703
187 | print("*** Caught exception while setting %r to %r: %s" % (name, value, e))
188 | return result
189 |
190 | def Read(self):
191 | """
192 | Read state from configuration file.
193 |
194 | :return: True if successful or None, if config file couldn't be read.
195 | """
196 | read_ok = self.state.read(self.filepath)
197 | if len(read_ok) == 0:
198 | if DEBUG:
199 | print("could not read config file at path %r" % self.filepath)
200 | return None
201 | return True
202 |
203 | def Save(self, config=None, filepath=None):
204 | """
205 | Save settings to a configuration file.
206 |
207 | :param config: ``ConfigParser``
208 | the config object to save.
209 | If None, uses ``self.config`` instead.
210 | :param filepath: ``str``
211 | allows for specifying another path
212 | than ``self.filepath`` in order to save a copy
213 | of the config object.
214 | :return: True if successful, False otherwise.
215 | """
216 | if filepath is None:
217 | filepath = self.filepath
218 | elif not isinstance(filepath, basestring):
219 | raise TypeError("E: param 'filepath': expected type 'str', but got %s" % type(filepath))
220 | if config and not isinstance(config, ConfigParser.ConfigParser):
221 | raise TypeError("E: param 'config': expected type 'ConfigParser', but got %r" % config)
222 | else:
223 | config = self.state
224 | result = False
225 | try:
226 | with open(filepath, 'wb') as configfile:
227 | config.write(configfile)
228 | result = True
229 | except Exception, e: # IGNORE:W0703
230 | print("*** Caught exception while writing config: %r ***" % e)
231 | return result
232 |
233 |
234 | # Licensed under the Apache License, Version 2.0 (the "License");
235 | # you may not use this file except in compliance with the License.
236 | # You may obtain a copy of the License at
237 | #
238 | # http://www.apache.org/licenses/LICENSE-2.0
239 | #
240 | # Unless required by applicable law or agreed to in writing, software
241 | # distributed under the License is distributed on an "AS IS" BASIS,
242 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
243 | # See the License for the specific language governing permissions and
244 | # limitations under the License.
245 |
--------------------------------------------------------------------------------
/source/py4dlib/utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # utils.py
4 | # py4dlib
5 | #
6 | # Created by André Berg on 2012-09-28.
7 | # Copyright 2012 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401
12 |
13 | '''py4dlib.utils -- utility toolbelt for great convenience.'''
14 |
15 | import os
16 | import warnings
17 |
18 | __version__ = (0, 6)
19 | __date__ = '2012-09-27'
20 | __updated__ = '2013-08-12'
21 |
22 |
23 | DEBUG = 1 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
24 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
25 |
26 |
27 | from subprocess import Popen, PIPE
28 | from functools import wraps, partial
29 |
30 | try:
31 | import c4d #@UnresolvedImport
32 | C4D_VERSION = c4d.GetC4DVersion()
33 | except ImportError:
34 | # TestRunLevel is defined by the Pydev PyUnit launcher to be 1
35 | if TESTRUN == 1:
36 | # define hard coded version here so test scripts
37 | # can set the desired version after importing py4dlib.utils
38 | C4D_VERSION = 12043
39 |
40 |
41 | def ClearConsole():
42 | version = c4d.GetC4DVersion()
43 | if version > 12999:
44 | cmd = 13957 # R14 (and R13?)
45 | else:
46 | cmd = 1024314 # R12
47 | c4d.CallCommand(cmd)
48 |
49 |
50 | def FuzzyCompareStrings(a, b, limit=20):
51 | """ Fuzzy string comparison.
52 |
53 | Two strings are deemed equal if they have
54 | the same byte sequence for up to 'limit' chars.
55 |
56 | Limit can be an int or a percentage string
57 | like for example '60%' in which case 2 strings
58 | are deemed equal if at least '60%' relative to
59 | the longest string match.
60 | """
61 | result = True
62 | maxchars = limit
63 | if isinstance(limit, basestring):
64 | try:
65 | maxp = int(limit[:-1]) # chop off '%'
66 | maxlen = max(len(a), len(b)) # percentage relative to longest str
67 | maxchars = int((maxp/100.0) * maxlen)
68 | except:
69 | raise ValueError("E: param 'limit' must be one of [str, int] " +
70 | "where str indicates a percentage, e.g. '75%'.")
71 | idx = 0
72 | for char in a:
73 | if idx >= maxchars:
74 | break
75 | try:
76 | if char != b[idx]:
77 | result = False
78 | except IndexError:
79 | result = False
80 | idx += 1
81 | return result
82 |
83 |
84 | def EscapeUnicode(s):
85 | ur""" CINEMA 4D R12's CPython integration stores high-order chars (``ord > 126``)
86 | as 4-byte unicode escape sequences with upper case hex letters.
87 |
88 | For example the character ``ä`` (LATIN SMALL LETTER A WITH DIAERESIS)
89 | would be stored as the byte sequence ``\u00E4``. This function replaces
90 | high-order chars with a unicode escape sequence suitable for CINEMA 4D.
91 |
92 | If you use this function in R12 you need to balance each call with a
93 | call to :py:func:`UnescapeUnicode` when the time comes to use or display
94 | the string.
95 |
96 | In R13 and R14 it returns the string untouched since in those versions
97 | the CPython intergration handles Unicode encoded strings properly.
98 | """
99 | result = ""
100 | if C4D_VERSION <= 12999:
101 | try:
102 | s = s.decode('utf-8').encode('latin-1')
103 | except UnicodeDecodeError:
104 | pass
105 | except UnicodeEncodeError:
106 | pass
107 | for b in s:
108 | if ord(b) > 126:
109 | result += r"\u%04X" % (ord(b),)
110 | else:
111 | result += b
112 | if DEBUG:
113 | print("result = %r" % result)
114 | else:
115 | return s
116 | return result
117 |
118 |
119 | def UnescapeUnicode(s):
120 | ur""" CINEMA 4D R12's CPython integration stores high-order chars (``ord > 126``)
121 | as 4-byte unicode escape sequences with upper case hex letters.
122 |
123 | This function converts unicode escape sequences used by CINEMA 4D when passing
124 | bytes (e.g. ``\u00FC`` -> ``\xfc``) to their corresponding high-order characters.
125 |
126 | It should be used in R12 only and should balance out any calls made to
127 | :py:func:`EscapeUnicode`.
128 |
129 | In R13 and R14 the string is returned untouched since in those versions
130 | the CPython intergration handles Unicode encoded strings properly.
131 | """
132 | if C4D_VERSION <= 12999:
133 | try:
134 | return s.decode('unicode_escape')
135 | except UnicodeEncodeError:
136 | return s
137 | else:
138 | try:
139 | return s.decode("utf-8")
140 | except UnicodeDecodeError:
141 | return s
142 | except UnicodeEncodeError:
143 | return s
144 |
145 |
146 | def VersionString(versionTuple):
147 | """ (x,y,z .. n) -> 'x.y.z...n' """
148 | return '.'.join(str(x) for x in versionTuple)
149 |
150 |
151 | def PPLLString(ll):
152 | """ Returns a pretty-printed string of a ``list`` structure. """
153 | s = " " + repr(ll)[1:-2]
154 | lines = s.split('],')
155 | result = '],\n'.join(lines)
156 | return result + ']'
157 |
158 |
159 | def System(cmd, args=None):
160 | '''
161 | Convenience function for firing off commands to
162 | the System console. Used instead of `subprocess.call`_
163 | so that shell variables will be expanded properly.
164 |
165 | Not the same as `os.system`_ as here it captures
166 | returns ``stdout`` and ``stderr`` in a tuple in
167 | Python 2.5 and lower or a ``namedtuple`` in 2.6
168 | and higher. So you can use ``result[0]`` in the
169 | first case and ``result.out`` in the second.
170 |
171 | :param cmd: a console command line
172 | :type cmd: ``string``
173 | :param args: a list of arguments that
174 | will be expanded in cmd
175 | starting with ``$0``
176 | :type args: ``list``
177 | :return: ``tuple`` or ``namedtuple``
178 | '''
179 | if args is None:
180 | fullcmd = cmd
181 | else:
182 | args = ["'{}'".format(s.replace(r'\\', r'\\\\')
183 | .replace("'", r"\'")) for s in args]
184 | fullcmd = "%s %s" % (cmd, ' '.join(args))
185 | out, err = Popen(fullcmd, stdout=PIPE, shell=True).communicate()
186 | System.out = out
187 | System.err = err
188 | try:
189 | from collections import namedtuple
190 | StdStreams = namedtuple('StdStreams', ['out', 'err'])
191 | return StdStreams(out=out, err=err)
192 | except ImportError:
193 | return (out, err)
194 |
195 |
196 | def benchmark(func=None, prec=3, unit='auto', name_width=0, time_width=8):
197 | """
198 | A decorator that prints the time a function takes
199 | to execute per call and cumulative total.
200 |
201 | Accepts the following keyword arguments:
202 |
203 | :param unit: ``str`` time unit for display. one of `[auto, us, ms, s, m]`.
204 | :param prec: ``int`` radix point precision.
205 | :param name_width: ``int`` width of the right-aligned function name field.
206 | :param time_width: ``int`` width of the right-aligned time value field.
207 |
208 | For convenience you can also set attributes on the benchmark
209 | function itself with the same name as the keyword arguments
210 | and the value of those will be used instead. This saves you
211 | from having to call the decorator with the same arguments each
212 | time you use it. Just set, for example, ``benchmark.prec = 5``
213 | after the import and before you use it for the first time.
214 | """
215 | import time
216 | if hasattr(benchmark, 'prec'):
217 | prec = getattr(benchmark, 'prec')
218 | if hasattr(benchmark, 'unit'):
219 | unit = getattr(benchmark, 'unit')
220 | if hasattr(benchmark, 'name_width'):
221 | name_width = getattr(benchmark, 'name_width')
222 | if hasattr(benchmark, 'time_width'):
223 | time_width = getattr(benchmark, 'time_width')
224 | if func is None:
225 | return partial(benchmark, prec=prec, unit=unit,
226 | name_width=name_width, time_width=time_width)
227 | @wraps(func)
228 | def wrapper(*args, **kwargs): # IGNORE:W0613
229 | def _get_unit_mult(val, unit):
230 | multipliers = {'us': 1000000.0, 'ms': 1000.0, 's': 1.0, 'm': (1.0 / 60.0)}
231 | if unit in multipliers:
232 | mult = multipliers[unit]
233 | else: # auto
234 | if val >= 60.0:
235 | unit = "m"
236 | elif val >= 1.0:
237 | unit = "s"
238 | elif val <= 0.001:
239 | unit = "us"
240 | else:
241 | unit = "ms"
242 | mult = multipliers[unit]
243 | return (unit, mult)
244 | t = time.clock()
245 | res = func(*args, **kwargs)
246 | td = (time.clock() - t)
247 | wrapper.total += td
248 | wrapper.count += 1
249 | tt = wrapper.total
250 | cn = wrapper.count
251 | tdu, tdm = _get_unit_mult(td, unit)
252 | ttu, ttm = _get_unit_mult(tt, unit)
253 | td *= tdm
254 | tt *= ttm
255 | print(" -> {0:>{8}}() @ {1:>03}: {3:>{7}.{2}f} {4:>2}, total: {5:>{7}.{2}f} {6:>2}"
256 | .format(func.__name__, cn, prec, td, tdu, tt, ttu, time_width, name_width))
257 | return res
258 | wrapper.total = 0
259 | wrapper.count = 0
260 | return wrapper
261 |
262 |
263 | def require(*args, **kwargs):
264 | '''
265 | Decorator that enforces types for function/method args.
266 |
267 | Two ways to specify which types are required for each arg.
268 |
269 | 1) 2-tuples, where first member specifies arg index or arg name,
270 | second member specifies a type or a tuple of types.
271 | 2) kwargs style, e.g. `argname`=`types` where `types` again can
272 | be a type or a tuple of types.
273 |
274 | None is always a valid type, to allow for optional args.
275 | '''
276 | _required = []
277 | _args = args
278 | _kwargs = kwargs
279 | def wrapper(func):
280 | if hasattr(func, "wrapped_args"):
281 | wrapped_args = getattr(func, "wrapped_args")
282 | else:
283 | code = func.func_code
284 | wrapped_args = list(code.co_varnames[:code.co_argcount])
285 | @wraps(func)
286 | def wrapped_fn(*args, **kwargs):
287 | def _check_type(_funcname, _locator, _arg, _types):
288 | if _arg is not None and not isinstance(_arg, _types):
289 | pluralstr = "one " if isinstance(_types, (list, tuple)) else "" # IGNORE:W0311
290 | raise TypeError("E: for %s(): param %r must be %sof %r, but is %r" % # IGNORE:W0311
291 | (_funcname, str(_locator), pluralstr, _types, type(_arg)))
292 | def _get_index(param_name):
293 | codeobj_varnames = wrapped_args
294 | try:
295 | _idx = codeobj_varnames.index(param_name)
296 | return _idx
297 | except ValueError:
298 | raise NameError(param_name)
299 | for i in _args:
300 | locator = i[0]
301 | types = i[1]
302 | idx = None
303 | if isinstance(locator, int):
304 | idx = locator
305 | elif isinstance(locator, basestring):
306 | name = locator
307 | idx = _get_index(name)
308 | if name in kwargs:
309 | _check_type(func.__name__, name, kwargs[name], types)
310 | if idx >= len(args):
311 | break
312 | _check_type(func.__name__, idx, args[idx], types)
313 | for k, v in _kwargs.iteritems():
314 | name = k
315 | types = v
316 | if name in kwargs:
317 | _check_type(func.__name__, name, kwargs[name], types)
318 | else:
319 | idx = _get_index(name)
320 | if idx >= len(args):
321 | break
322 | _check_type(func.__name__, name, args[idx], types)
323 | return func(*args, **kwargs)
324 | wrapped_fn.wrapped_args = wrapped_args
325 | return wrapped_fn
326 | return wrapper
327 |
328 |
329 | def deprecated(level=1, since=None, info=None):
330 | """This decorator can be used to mark functions as deprecated.
331 |
332 | :param int level: severity level.
333 | 0 = warnings.warn(category=DeprecationWarning)
334 | 1 = warnings.warn_explicit(category=DeprecationWarning)
335 | 2 = raise DeprecationWarning()
336 | :param string since: the version where deprecation was introduced.
337 | :param string info: additional info. normally used to refer to the new
338 | function now favored in place of the deprecated one.
339 | """
340 | def __decorate(func):
341 | if since is None:
342 | msg = 'Method %s() is deprecated.' % func.__name__
343 | else:
344 | msg = 'Method %s() has been deprecated since version %s.' % (func.__name__, str(since))
345 | if info:
346 | msg += ' ' + info
347 | @wraps(func)
348 | def __wrapped(*args, **kwargs): # IGNORE:C0111
349 | if level <= 0:
350 | warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
351 | func(*args, **kwargs)
352 | elif level == 1:
353 | warnings.warn_explicit(msg, category=DeprecationWarning,
354 | filename=func.func_code.co_filename,
355 | lineno=func.func_code.co_firstlineno + 1)
356 | elif level >= 2:
357 | raise DeprecationWarning(msg)
358 | return __wrapped
359 | return __decorate
360 |
361 |
362 | def cache(func):
363 | """Classic cache decorator."""
364 | saved = {}
365 | @wraps(func)
366 | def wrapped_fn(*args):
367 | if args in saved:
368 | return saved[args]
369 | result = func(*args)
370 | saved[args] = result
371 | return result
372 | return wrapped_fn
373 |
374 |
375 | def memoize(func):
376 | """Classic memoization decorator."""
377 | mem_cache = {}
378 | @wraps(func)
379 | def wrapped_fn(*args, **kw):
380 | key = (args, tuple(sorted(kw.items())))
381 | if key not in mem_cache:
382 | mem_cache[key] = func(*args, **kw)
383 | return mem_cache[key]
384 | return wrapped_fn
385 |
386 |
387 | # Licensed under the Apache License, Version 2.0 (the "License");
388 | # you may not use this file except in compliance with the License.
389 | # You may obtain a copy of the License at
390 | #
391 | # http://www.apache.org/licenses/LICENSE-2.0
392 | #
393 | # Unless required by applicable law or agreed to in writing, software
394 | # distributed under the License is distributed on an "AS IS" BASIS,
395 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
396 | # See the License for the specific language governing permissions and
397 | # limitations under the License.
398 |
--------------------------------------------------------------------------------
/source/py4dlib/examples/Extract Polys 1.0/Extract.pyp:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Extract.pyp
4 | # py4dlib.examples
5 | #
6 | # Created by André Berg on 2013-08-02.
7 | # Copyright 2013 Berg Media. All rights reserved.
8 | #
9 | # andre.bergmedia@googlemail.com
10 | #
11 | # pylint: disable-msg=F0401,E1101,W0232
12 |
13 | '''
14 | Extract -- extracts selected polygons into a new polygon object.
15 |
16 | Summary:
17 |
18 | Same as selecting polygons on some objects,
19 | running Split, then deleting the inverse
20 | selection to keep only the selected polys.
21 | '''
22 |
23 | import os
24 | import time
25 |
26 | __all__ = []
27 | __version__ = (1, 0)
28 | __date__ = '2013-08-02'
29 | __updated__ = '2013-08-02'
30 |
31 |
32 | DEBUG = 0 or ('DebugLevel' in os.environ and os.environ['DebugLevel'] > 0)
33 | TESTRUN = 0 or ('TestRunLevel' in os.environ and os.environ['TestRunLevel'] > 0)
34 |
35 | PY4DLIB_FOUND = False
36 |
37 | if DEBUG:
38 | import pprint
39 | pp = pprint.PrettyPrinter(width=200)
40 | PP = pp.pprint
41 | PF = pp.pformat
42 |
43 | try:
44 | import c4d #@UnresolvedImport
45 | from c4d import plugins, bitmaps, gui, documents
46 | except ImportError:
47 | if TESTRUN == 1:
48 | pass
49 |
50 | try:
51 | from py4dlib.objects import DeselectAll, Select, GetGlobalRotation
52 | from py4dlib.objects import SetGlobalRotation, SetAxisRotation
53 | from py4dlib.objects import SelectObjects, InsertUnderNull
54 | from py4dlib.mesh import TogglePolySelection, GetSelectedPolys
55 | PY4DLIB_FOUND = True
56 | except ImportError:
57 | pass
58 |
59 |
60 | # ----------------------------------------------
61 | # GLOBALS
62 | # ----------------------------------------------
63 |
64 | CR_YEAR = time.strftime("%Y")
65 |
66 | PLUGIN_VERSION = '.'.join(str(x) for x in __version__)
67 | PLUGIN_NAME = "Extract"
68 | PLUGIN_HELP = "Extracts polygon selections from a model, where contiguous surfaces are scattered over multiple polygon objects."
69 | PLUGIN_ABOUT = """(C) %s Andre Berg (Berg Media)
70 | All rights reserved.
71 |
72 | Version %s
73 |
74 | Same as selecting polygons on some objects,
75 | running Split, then deleting the inverse
76 | selection to keep only the selected polys.
77 | Plus a few extras. Works with multiple object
78 | selections.
79 |
80 | Use at your own risk!
81 |
82 | It is recommended to try out the plugin
83 | on a spare copy of your data first.
84 | """ % (CR_YEAR, PLUGIN_VERSION)
85 |
86 | PY4DLIB_NOT_FOUND_MSG = """This plugin needs py4dlib which is missing.
87 |
88 | Please download and install it free of charge
89 | from http://github.com/andreberg/py4dlib.
90 | """
91 |
92 | # -------------------------------------------
93 | # PLUGING IDS
94 | # -------------------------------------------
95 |
96 | # unique ID
97 | ID_EXTRACT = 1026898
98 |
99 | # Element IDs
100 | IDD_DIALOG_SETTINGS = 10001
101 | IDC_BUTTON_CANCEL = 10002
102 | IDC_BUTTON_DOIT = 10003
103 | IDC_GROUP_WRAPPER = 10004
104 | IDC_GROUP_SETTINGS = 10005
105 | IDC_GROUP_BUTTONS = 10006
106 | IDC_DUMMY = 10007
107 | IDC_CHK_OPTIMIZE = 10008
108 | IDC_CHK_CENTER = 10009
109 | IDC_CHK_KEEPNORMALTAG = 10010
110 | IDC_CHK_HIDE = 10011
111 | IDC_CHK_DELETEORIG = 10012
112 | IDC_CHK_RENORMALIZE = 10013
113 | IDC_MENU_ABOUT = 30001
114 |
115 | # String IDs
116 | IDS_DIALOG_TITLE = PLUGIN_NAME
117 | IDS_MENU_INFO = "Info"
118 | IDS_MENU_ABOUT = "About..."
119 |
120 |
121 | # ------------------------------------------------------
122 | # User Interface
123 | # ------------------------------------------------------
124 |
125 | class ExtractDialog(gui.GeDialog):
126 |
127 | def CreateLayout(self):
128 | plugins.GeResource().Init(os.path.dirname(os.path.abspath(__file__)))
129 | self.LoadDialogResource(IDD_DIALOG_SETTINGS, flags=c4d.BFH_SCALEFIT)
130 |
131 | # Menu
132 | self.MenuFlushAll()
133 | self.MenuSubBegin(IDS_MENU_INFO)
134 | self.MenuAddString(IDC_MENU_ABOUT, IDS_MENU_ABOUT)
135 | self.MenuSubEnd()
136 |
137 | self.MenuFinished()
138 |
139 | self.SetTitle(IDS_DIALOG_TITLE)
140 |
141 | return True
142 |
143 | def InitValues(self):
144 | deleteorig = False
145 | keepnormal = False
146 | self.SetBool(IDC_CHK_OPTIMIZE, True)
147 | self.SetBool(IDC_CHK_CENTER, True)
148 | self.SetBool(IDC_CHK_DELETEORIG, deleteorig)
149 | self.SetBool(IDC_CHK_HIDE, False)
150 | self.SetBool(IDC_CHK_KEEPNORMALTAG, keepnormal)
151 | self.SetBool(IDC_CHK_RENORMALIZE, False)
152 |
153 | self.Enable(IDC_CHK_RENORMALIZE, keepnormal)
154 | self.Enable(IDC_CHK_HIDE, deleteorig)
155 | return True
156 |
157 | def Disable(self, ID):
158 | return self.Enable(ID, False)
159 |
160 | def Command(self, ID, msg):
161 | if ID == IDC_BUTTON_DOIT:
162 | scriptvars = {
163 | 'optimize': self.GetBool(IDC_CHK_OPTIMIZE),
164 | 'nodelete': self.GetBool(IDC_CHK_HIDE),
165 | 'centeraxis': self.GetBool(IDC_CHK_CENTER),
166 | 'keepnormaltags': self.GetBool(IDC_CHK_KEEPNORMALTAG),
167 | 'renormalize': self.GetBool(IDC_CHK_RENORMALIZE),
168 | 'deleteorig': self.GetBool(IDC_CHK_DELETEORIG)
169 | }
170 | script = ExtractScript(scriptvars)
171 | if DEBUG:
172 | print("do it: %r" % msg)
173 | print("script = %r" % script)
174 | print("scriptvars = %r" % scriptvars)
175 | try:
176 | return script.run()
177 | except Exception as e:
178 | if DEBUG or TESTRUN:
179 | print(e)
180 | finally:
181 | c4d.StatusClear()
182 | elif ID == IDC_BUTTON_CANCEL:
183 | if DEBUG:
184 | print("cancel: %r" % msg)
185 | self.Close()
186 | elif ID == IDC_MENU_ABOUT:
187 | c4d.gui.MessageDialog(PLUGIN_ABOUT)
188 | elif ID == IDC_CHK_DELETEORIG:
189 | self.Enable(IDC_CHK_HIDE, (self.GetBool(IDC_CHK_DELETEORIG)))
190 | elif ID == IDC_CHK_KEEPNORMALTAG:
191 | self.Enable(IDC_CHK_RENORMALIZE, self.GetBool(IDC_CHK_KEEPNORMALTAG))
192 | else:
193 | if DEBUG:
194 | print("ID = %s" % ID)
195 |
196 | return True
197 |
198 |
199 |
200 | # ------------------------------------------------------
201 | # Command Script
202 | # ------------------------------------------------------
203 |
204 | class ExtractScript(object):
205 |
206 | def __init__(self, scriptvars=None):
207 | super(ExtractScript, self).__init__()
208 | self.data = scriptvars
209 |
210 | def hideDelete(self, objs, doc):
211 | # Hide/Delete
212 | if objs is None:
213 | raise ValueError("objs can't be None")
214 | if not isinstance(objs, list):
215 | objs = [objs]
216 | settings = c4d.BaseContainer()
217 | if self.data['nodelete'] == True:
218 | kmd = c4d.MCOMMAND_HIDESELECTED
219 | verb = "hide selected"
220 | else:
221 | for obj in objs:
222 | doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)
223 | kmd = c4d.MCOMMAND_DELETE
224 | verb = "delete"
225 | retval = c4d.utils.SendModelingCommand(
226 | command=kmd, list=objs,
227 | mode=c4d.MODIFY_POLYGONSELECTION,
228 | bc=settings, doc=doc
229 | )
230 | if DEBUG:
231 | if retval is False:
232 | print("%s failed." % verb)
233 | elif retval is True:
234 | print("%s successful." % verb)
235 | return retval
236 |
237 | def run(self):
238 | if not PY4DLIB_FOUND:
239 | c4d.gui.MessageDialog(PY4DLIB_NOT_FOUND_MSG)
240 | return False
241 |
242 | doc = documents.GetActiveDocument()
243 | doc.StartUndo()
244 |
245 | sel = doc.GetSelection()
246 | if sel is None or len(sel) == 0:
247 | return False
248 |
249 | for op in sel:
250 | if not isinstance(op, c4d.PolygonObject):
251 | c4d.gui.MessageDialog("Please make sure your selection includes polygon objects only.")
252 | return False
253 |
254 | c4d.StatusSetSpin()
255 | timestart = c4d.GeGetMilliSeconds()
256 |
257 | grps = []
258 | cpys = []
259 | i = 0 # IGNORE:W0612
260 |
261 | c4d.StopAllThreads()
262 | DeselectAll(True)
263 |
264 | # loop through all objects
265 | for op in sel:
266 | polysel = GetSelectedPolys(op)
267 | if len(polysel) == 0:
268 | print("skipping %s because no polygon selection" % op.GetName())
269 | continue
270 |
271 | cpy = op.GetClone()
272 | doc.AddUndo(c4d.UNDOTYPE_NEW, cpy)
273 | doc.InsertObject(cpy)
274 | cpys.append(cpy)
275 |
276 | if self.data['deleteorig']:
277 | self.hideDelete(op, doc)
278 |
279 | Select(cpy)
280 |
281 | # unfortunately we need to kill the normal tags
282 | # since the points get shuffled
283 | if self.data['keepnormaltags'] == False:
284 | cpy.KillTag(c4d.Tnormal)
285 |
286 | # now deselect selected poly and select unselected
287 | TogglePolySelection(cpy)
288 | modobjs = []
289 |
290 | retval = self.hideDelete([cpy], doc)
291 | if isinstance(retval, list):
292 | modobjs.extend(retval)
293 | elif isinstance(retval, c4d.BaseObject):
294 | modobjs.append(retval)
295 | elif retval is False:
296 | return False
297 |
298 | # Optimize Unused Points
299 | settings = c4d.BaseContainer()
300 | if self.data['optimize'] == True:
301 | settings[c4d.MDATA_OPTIMIZE_TOLERANCE] = 0.1
302 | settings[c4d.MDATA_OPTIMIZE_POINTS] = False
303 | settings[c4d.MDATA_OPTIMIZE_POLYGONS] = False
304 | settings[c4d.MDATA_OPTIMIZE_UNUSEDPOINTS] = True
305 | retval = c4d.utils.SendModelingCommand(
306 | command=c4d.MCOMMAND_OPTIMIZE, list=[op, cpy],
307 | mode=c4d.MODIFY_POLYGONSELECTION,
308 | bc=settings, doc=doc
309 | )
310 | if retval is False:
311 | if DEBUG: print("optimize failed")
312 | return False
313 | elif retval is True:
314 | if DEBUG: print("optimize successful")
315 |
316 | if DEBUG:
317 | print("modobjs = %r" % modobjs)
318 |
319 | # Center cpy axis
320 | if self.data['centeraxis'] == True:
321 | c4d.CallCommand(1011982) # center axis to...
322 | c4d.EventAdd()
323 |
324 | i += 1
325 | DeselectAll(True)
326 | # end for
327 |
328 | if i == 0:
329 | print("Nothing to do...")
330 | return False
331 |
332 | # Create collecting group
333 | op = sel[0]
334 | try:
335 | parent = op.GetUp()
336 | parentname = parent.GetName()
337 | prot = GetGlobalRotation(parent)
338 | except Exception: # IGNORE:W0703
339 | parent = None
340 | parentname = "Objects"
341 | prot = c4d.Vector(0,0,0)
342 | grpname = "Detached %s" % parentname
343 |
344 | # get parent pos and rot (if applicable)
345 | pmg = op.GetUpMg()
346 |
347 | # Add cpy to group
348 | SelectObjects(cpys)
349 | grp = InsertUnderNull(cpys, grp=None, name=grpname)
350 | gmg = grp.GetMg()
351 |
352 | if DEBUG:
353 | print("pmg = %r" % pmg)
354 | print("gmg = %r" % gmg)
355 |
356 | # calc current offset to world 0
357 | # (since our new copies are created
358 | # relative to world zero), then add
359 | # the difference to pmg and use the
360 | # result as new pos for grp
361 | wdiff = gmg.off - c4d.Vector(0,0,0)
362 | newoff = pmg.off + wdiff
363 | gmg.off = newoff
364 |
365 | if DEBUG:
366 | print("wdiff = %r" % wdiff)
367 | print("newoff = %r" % newoff)
368 | print("gmg = %r" % gmg)
369 |
370 | grp.SetMg(gmg)
371 | if parent is not None: # if it has a parent
372 | SetGlobalRotation(grp, prot)
373 |
374 | if (self.data['keepnormaltags'] == True and
375 | self.data['renormalize'] == True):
376 | # since CINEMA 4D performs object axis
377 | # transform ("Y Up") when importing OBJ files
378 | # we need to recreate this manually for the
379 | # normal data to make sense again, e.g.
380 | # rotate the object axis by B=90
381 | for op in grp.GetChildren():
382 | if op.GetTag(c4d.Tnormal) is not None:
383 | theta = c4d.utils.Rad(90)
384 | trot = c4d.Vector(0, 0, theta)
385 | SetAxisRotation(op, trot - prot)
386 |
387 | if DEBUG:
388 | print("grp = %r" % grp)
389 | print("grps = %r" % grps)
390 | print("cpys = %r" % cpys)
391 |
392 | doc.SetMode(c4d.Mmodel)
393 |
394 | c4d.StatusClear()
395 |
396 | # tell C4D to update internal state
397 | c4d.EventAdd()
398 | doc.EndUndo()
399 |
400 | timeend = int(c4d.GeGetMilliSeconds() - timestart)
401 | timemsg = "Extract: finished in " + str(timeend) + " milliseconds"
402 | print(timemsg)
403 |
404 | return True
405 |
406 |
407 | # ----------------------------------------------------
408 | # Main
409 | # ----------------------------------------------------
410 | class ExtractMain(plugins.CommandData):
411 |
412 | dialog = None
413 |
414 | def Execute(self, doc): # IGNORE:W0613
415 | # create the dialog
416 | if self.dialog is None:
417 | self.dialog = ExtractDialog()
418 | return self.dialog.Open(c4d.DLG_TYPE_ASYNC, pluginid=ID_EXTRACT, defaultw=230)
419 |
420 | def RestoreLayout(self, secref):
421 | # manage nonmodal dialog
422 | if self.dialog is None:
423 | self.dialog = ExtractDialog()
424 | return self.dialog.Restore(pluginid=ID_EXTRACT, secret=secref)
425 |
426 |
427 |
428 | if __name__ == "__main__":
429 | thispath = os.path.dirname(os.path.abspath(__file__))
430 | icon = bitmaps.BaseBitmap()
431 | icon.InitWith(os.path.join(thispath, "res/", "icon.tif"))
432 | plugins.RegisterCommandPlugin(
433 | ID_EXTRACT,
434 | PLUGIN_NAME,
435 | 0,
436 | icon,
437 | PLUGIN_HELP,
438 | ExtractMain()
439 | )
440 | print("%s v%s loaded. (C) %s Andre Berg" % (PLUGIN_NAME, PLUGIN_VERSION, CR_YEAR))
441 |
442 |
443 | # Licensed under the Apache License, Version 2.0 (the "License");
444 | # you may not use this file except in compliance with the License.
445 | # You may obtain a copy of the License at
446 | #
447 | # http://www.apache.org/licenses/LICENSE-2.0
448 | #
449 | # Unless required by applicable law or agreed to in writing, software
450 | # distributed under the License is distributed on an "AS IS" BASIS,
451 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
452 | # See the License for the specific language governing permissions and
453 | # limitations under the License.
454 |
--------------------------------------------------------------------------------