Skip to content

Commit d4c4027

Browse files
committed
Cinema 4D Python SDK 2026.1
1 parent c720027 commit d4c4027

File tree

21 files changed

+2119
-97
lines changed

21 files changed

+2119
-97
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Py Licensing 2026 Example
2+
3+
Demonstrates how to implement licensing checks in plugins.
4+
5+
![](preview.png)
6+
7+
Licensing plugins is an important aspect of commercial plugin development but also a very complex subject. This set of examples attempts to give an overview of different licensing concepts and how they can be implemented in Python plugins for Cinema 4D.
8+
9+
In Python, the subject of licensing is complicated by the fact that Python code by definition is insecure, as source code must be sent as plain text to the interpreter to be executed. All Python *'obfuscation'* measures - be it the built in encryption of Cinema 4D `.pyp` files or third party tools such as `pyarmor` or `nuitka` - cannot prevent this. A motivated and only mildly competent attacker will always be able to easily decrypt your code and remove any licensing checks you have implemented. Due to that, licensing should always be implemented in a "keep honest users honest" manner in Python.
10+
11+
## Content
12+
13+
| File | Description |
14+
|------|-------------|
15+
| `py-licensing.pyp` | Demonstrates multiple licensing workflows for Python plugins in Cinema 4D. |
16+
| `py-licensing_simple.pyp` | A simplified version of the main licensing example, demonstrating only a single licensing concept. |
17+
18+
## Concepts not Demonstrated
19+
20+
Concepts that are important but explicitly not shown in these examples are:
21+
22+
- Online license validation with a REST or similar web service. But the general code structure provides a place with `PluginMessage` (`C4DPL_STARTACTIVITY`) and `ValidateSerial`.
23+
- Trial Periods or general license time limits encoded into license keys. The example hints at as where this could be done, but does not implement it.
24+
25+
## Concepts Demonstrated
26+
27+
- `py-licensing_simple.pyp`
28+
- Simple file based license storage in the user's preferences folder.
29+
- Restart based activation workflow.
30+
- Simple hashing scheme for license keys.
31+
- `py-licensing_simple.pyp`
32+
- Internalized license storage.
33+
- File based license storage.
34+
- Restart based activation workflow.
35+
- No-restart based activation workflow.
36+
- Simple hashing scheme for license keys.
37+
- Dynamic enabling/disabling of plugin functionality based on licensing state.
38+
- GUI based license manager dialog.
405 KB
Loading

plugins/py-licensing_2026/py-licensing_2026.pyp

Lines changed: 1209 additions & 0 deletions
Large diffs are not rendered by default.

plugins/py-licensing_2026/py-licensing_simple_2026.pyp

Lines changed: 414 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Contains the symbol definitions for non description resources used in the py-licensing example.
2+
enum
3+
{
4+
// Resource ID for the License Manager dialog - we use the plugin ID of the License Manager
5+
// plugin. Using the plugin ID is not a necessity, we could also define it as 1000 or so, but
6+
// good practice IMHO. We could also register a dedicated plugin ID for the dialog.
7+
DLG_LICENSE_MANAGER = 1066811,
8+
9+
// Gadget and other IDs
10+
11+
// Groups and Tabs
12+
ID_GRP_MAIN = 1000,
13+
ID_TAB_GROUPS,
14+
ID_GRP_DATA,
15+
ID_GRP_LICENSE,
16+
17+
// Data tab gadgets
18+
ID_EDT_DATA_SALT,
19+
ID_EDT_DATA_FILE_KEY,
20+
ID_EDT_DATA_USER_ID,
21+
ID_EDT_DATA_USER_NAME,
22+
ID_EDT_DATA_USER_SURNAME,
23+
ID_EDT_DATA_SYSTEM_ID,
24+
ID_EDT_DATA_PRODUCT_VERSION,
25+
ID_EDT_DATA_PRODUCT_ID,
26+
ID_EDT_DATA_ACCOUNT_LICENSES,
27+
28+
// License tab gadgets
29+
ID_CMB_LICENSE_TYPE,
30+
ID_CMB_LICENSE_TYPE_COMMERCIAL,
31+
ID_CMB_LICENSE_TYPE_DEMO,
32+
ID_CMB_LICENSE_TYPE_EDUCATIONAL,
33+
ID_CMB_LICENSE_TYPE_NFR,
34+
ID_CMB_LICENSE_STORAGE_MODE,
35+
ID_CMB_LICENSE_STORAGE_MODE_FILE,
36+
ID_CMB_LICENSE_STORAGE_MODE_SYSTEM,
37+
ID_CMB_LICENSE_ACTIVATION_MODE,
38+
ID_CMB_LICENSE_ACTIVATION_MODE_RESTART,
39+
ID_CMB_LICENSE_ACTIVATION_MODE_NO_RESTART,
40+
ID_CHK_LICENSE_USE_SYSTEM_ID,
41+
ID_CHK_LICENSE_USE_PRODUCT_ID,
42+
ID_EDT_LICENSE_KEY,
43+
ID_BTN_LICENSE_GENERATE,
44+
ID_ICN_LICENSE_KEY_VALID,
45+
ID_BTN_LICENSE_ACTIVATE,
46+
ID_BTN_LICENSE_DEACTIVATE,
47+
48+
// The help area to the right
49+
ID_EDT_HELP,
50+
51+
// Custom strings
52+
IDS_HLP_DATA = 2000,
53+
IDS_HLP_LICENSE,
54+
IDS_DLG_RESTART_REQUIRED,
55+
56+
// End of symbol definition
57+
_DUMMY_ELEMENT_
58+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Contains the IDs for the Olicenseablecube description.
2+
#ifndef _OLICENSEABLECUBE_H_
3+
#define _OLICENSEABLECUBE_H_
4+
5+
enum
6+
{
7+
// The IDs for the three cube dimensions and the license button.
8+
PY_LICENSABLE_CUBE_HEIGHT = 1000,
9+
PY_LICENSABLE_CUBE_WIDTH,
10+
PY_LICENSABLE_CUBE_DEPTH,
11+
PY_LICENSABLE_GET_LICENSE,
12+
};
13+
14+
#endif
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Contains the UI description of the Olicenseablecube object.
2+
CONTAINER Olicenseablecube
3+
{
4+
NAME Olicenseablecube;
5+
INCLUDE Obase;
6+
7+
GROUP ID_OBJECTPROPERTIES
8+
{
9+
// The interface for the three dimension parameters of the cube.
10+
REAL PY_LICENSABLE_CUBE_HEIGHT { UNIT METER; MIN 0.0; }
11+
REAL PY_LICENSABLE_CUBE_WIDTH { UNIT METER; MIN 0.0; }
12+
REAL PY_LICENSABLE_CUBE_DEPTH { UNIT METER; MIN 0.0; }
13+
14+
SEPARATOR { }
15+
16+
// A button to get a license for this object.
17+
BUTTON PY_LICENSABLE_GET_LICENSE { }
18+
}
19+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Contains the GUI definition for the License Manager dialog.
2+
DIALOG DLG_LICENSE_MANAGER
3+
{
4+
NAME IDS_LICENSE_MANAGER;
5+
SCALE_H; SCALE_V;
6+
7+
// Main group containing all other groups and gadgets.
8+
GROUP ID_GRP_MAIN
9+
{
10+
ALLOW_WEIGHTS;
11+
BORDERSIZE 8, 8, 8, 8;
12+
COLUMNS 2;
13+
SCALE_H; SCALE_V;
14+
SPACE 4, 4;
15+
16+
// Tab group containing the different tabs.
17+
TAB ID_TAB_GROUPS
18+
{
19+
SCALE_H; SCALE_V;
20+
21+
// The "Data" tab.
22+
GROUP ID_GRP_DATA
23+
{
24+
SCALE_H; SCALE_V;
25+
BORDERSIZE 4, 4, 4, 4;
26+
COLUMNS 2;
27+
NAME IDS_GRP_DATA;
28+
SPACE 4, 4;
29+
30+
STATICTEXT { NAME IDS_LBL_DATA_SALT; CENTER_V; ALIGN_LEFT; }
31+
EDITTEXT ID_EDT_DATA_SALT { SCALE_H; }
32+
33+
STATICTEXT { NAME IDS_LBL_DATA_FILE_KEY; CENTER_V; ALIGN_LEFT; }
34+
EDITTEXT ID_EDT_DATA_FILE_KEY { SCALE_H; }
35+
36+
STATICTEXT { NAME IDS_LBL_DATA_USER_NAME; CENTER_V; ALIGN_LEFT; }
37+
GROUP
38+
{
39+
SCALE_H; COLUMNS 2;
40+
SPACE 4, 4;
41+
42+
EDITTEXT ID_EDT_DATA_USER_NAME { SCALE_H; }
43+
EDITTEXT ID_EDT_DATA_USER_SURNAME { SCALE_H; }
44+
}
45+
46+
STATICTEXT { NAME IDS_LBL_DATA_USER_ID; CENTER_V; ALIGN_LEFT; }
47+
EDITTEXT ID_EDT_DATA_USER_ID { SCALE_H; }
48+
49+
STATICTEXT { NAME IDS_LBL_DATA_SYSTEM_ID; CENTER_V; ALIGN_LEFT; }
50+
EDITTEXT ID_EDT_DATA_SYSTEM_ID { SCALE_H; }
51+
52+
STATICTEXT { NAME IDS_LBL_DATA_PRODUCT_ID; CENTER_V; ALIGN_LEFT; }
53+
EDITTEXT ID_EDT_DATA_PRODUCT_ID { SCALE_H; }
54+
55+
STATICTEXT { NAME IDS_LBL_DATA_PRODUCT_VERSION; CENTER_V; ALIGN_LEFT; }
56+
EDITTEXT ID_EDT_DATA_PRODUCT_VERSION { SCALE_H; }
57+
58+
STATICTEXT { NAME IDS_LBL_DATA_ACCOUNT_LICENSES; ALIGN_TOP; ALIGN_LEFT; }
59+
MULTILINEEDIT ID_EDT_DATA_ACCOUNT_LICENSES { SCALE_H; SCALE_V; SIZE 300, 50; READONLY; }
60+
}
61+
62+
// The "License" tab.
63+
GROUP ID_GRP_LICENSE
64+
{
65+
SCALE_H; SCALE_V;
66+
BORDERSIZE 4, 4, 4, 4;
67+
COLUMNS 1;
68+
NAME IDS_GRP_LICENSE;
69+
SPACE 4, 4;
70+
71+
GROUP // The fields in the license tab.
72+
{
73+
SCALE_H; SCALE_V;
74+
COLUMNS 2;
75+
SPACE 4, 4;
76+
77+
STATICTEXT { NAME IDS_LBL_LICENSE_TYPE; CENTER_V; ALIGN_LEFT; }
78+
COMBOBOX ID_CMB_LICENSE_TYPE
79+
{
80+
SCALE_H;
81+
82+
CHILDS
83+
{
84+
ID_CMB_LICENSE_TYPE_COMMERCIAL, IDS_CMB_LICENSE_TYPE_COMMERCIAL;
85+
ID_CMB_LICENSE_TYPE_DEMO, IDS_CMB_LICENSE_TYPE_DEMO;
86+
ID_CMB_LICENSE_TYPE_EDUCATIONAL, IDS_CMB_LICENSE_TYPE_EDUCATIONAL;
87+
ID_CMB_LICENSE_TYPE_NFR, IDS_CMB_LICENSE_TYPE_NFR;
88+
}
89+
}
90+
91+
STATICTEXT { NAME IDS_LBL_LICENSE_STORAGE_MODE; CENTER_V; ALIGN_LEFT; }
92+
COMBOBOX ID_CMB_LICENSE_STORAGE_MODE
93+
{
94+
SCALE_H;
95+
96+
CHILDS
97+
{
98+
ID_CMB_LICENSE_STORAGE_MODE_FILE, IDS_CMB_LICENSE_STORAGE_MODE_FILE;
99+
ID_CMB_LICENSE_STORAGE_MODE_SYSTEM, IDS_CMB_LICENSE_STORAGE_MODE_SYSTEM;
100+
}
101+
}
102+
103+
STATICTEXT { NAME IDS_LBL_LICENSE_ACTIVATION_MODE; CENTER_V; ALIGN_LEFT; }
104+
COMBOBOX ID_CMB_LICENSE_ACTIVATION_MODE
105+
{
106+
SCALE_H;
107+
108+
CHILDS
109+
{
110+
ID_CMB_LICENSE_ACTIVATION_MODE_RESTART, IDS_CMB_LICENSE_ACTIVATION_MODE_RESTART;
111+
ID_CMB_LICENSE_ACTIVATION_MODE_NO_RESTART, IDS_CMB_LICENSE_ACTIVATION_MODE_NO_RESTART;
112+
}
113+
}
114+
115+
STATICTEXT { NAME IDS_LBL_LICENSE_USE_SYSTEM_ID; CENTER_V; ALIGN_LEFT; }
116+
CHECKBOX ID_CHK_LICENSE_USE_SYSTEM_ID { SCALE_H; }
117+
118+
STATICTEXT { NAME IDS_LBL_LICENSE_USE_PRODUCT_ID; CENTER_V; ALIGN_LEFT; }
119+
CHECKBOX ID_CHK_LICENSE_USE_PRODUCT_ID { SCALE_H; }
120+
121+
STATICTEXT { NAME IDS_LBL_LICENSE_KEY; CENTER_V; ALIGN_LEFT; }
122+
GROUP
123+
{
124+
SCALE_H;
125+
COLUMNS 2;
126+
SPACE 4, 4;
127+
128+
EDITTEXT ID_EDT_LICENSE_KEY { SCALE_H; }
129+
BITMAPBUTTON ID_ICN_LICENSE_KEY_VALID { ICONID1 300000230; }
130+
}
131+
132+
}
133+
134+
GROUP // The buttons in the license tab.
135+
{
136+
SCALE_H;
137+
COLUMNS 4;
138+
SPACE 4, 4;
139+
140+
BUTTON ID_BTN_LICENSE_GENERATE { NAME IDS_BTN_LICENSE_GENERATE; ALIGN_LEFT; }
141+
STATICTEXT { SCALE_H; } // Filler
142+
BUTTON ID_BTN_LICENSE_ACTIVATE { NAME IDS_BTN_LICENSE_ACTIVATE; ALIGN_RIGHT; }
143+
BUTTON ID_BTN_LICENSE_DEACTIVATE { NAME IDS_BTN_LICENSE_DEACTIVATE; ALIGN_RIGHT; }
144+
}
145+
}
146+
}
147+
148+
MULTILINEEDIT ID_EDT_HELP { SCALE_H; SCALE_V; SIZE 0, 0; READONLY; WORDWRAP; }
149+
}
150+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Contains the global - i.e., neither description nor dialog specific - string definitions used in
2+
// the py-licensing example.
3+
STRINGTABLE
4+
{
5+
IDS_HLP_DATA "Displays the licensing information exposed by Cinema 4D and the data defined by the plugin.
6+
7+
Salt 1: The serial generation secret of the plugin, it is used to sign license keys generated for your plugin. This is a static value which must be kept secret.
8+
9+
Salt 2: The file encryption secret of the plugin, it is used to encrypt license files created for your plugin. This is a static value which must be kept secret.
10+
11+
User Name: The name and surname of the user associated with the license that is currently active in Cinema 4D.
12+
13+
User ID: The unique identifier of the user associated with the license that is currently active in Cinema 4D.
14+
15+
System ID: The unique identifier of the system (hardware) the license that is currently active in Cinema 4D is bound to.
16+
17+
Product ID: The unique identifier of the product the license that is currently active in Cinema 4D is bound to. This value can contain modifiers such as 'commercial' or 'beta' and is also different for Cinema 4D (app), the Commandline app, a Teams Render Client, etc. It is often not a good idea to bind plugins directly to the user using a specific Product ID. Instead we could look for certain modifiers such as 'commercial' being present in the Product ID. See also https://developers.maxon.net/docs/cpp/2026_0_0/page_maxonapi_pluginlicensing.html.
18+
19+
Product Version: The version of Cinema 4D that is currently running.
20+
21+
Account Licenses: A table of all product IDs, i.e., licenses, the user has currently in his or her account, including the number of seats available for each license.";
22+
23+
24+
IDS_HLP_LICENSE "Contains the settings and output for generating a license key for your plugin.
25+
26+
When you 'just want to run' the example, simply press the 'Activate' button to activate a the currently generated license (and possibly change the activation mode before).
27+
License Type: The type of license to generate.
28+
29+
Storage Mode: Defines where the license key is stored. It can either be stored in a license file on disk or directly in Cinema 4D. Both storage forms are encrypted.
30+
31+
Activation Mode: Defines whether a restart of Cinema 4D is required to apply the licensing changes made. If 'Requires Restart' is selected, the user will be prompted to restart Cinema 4D when activating or deactivating a license. If 'No Restart Required' is selected, the changes will be applied immediately without requiring a restart.
32+
33+
Bind to System ID: If the generated license key should be bound to the given system ID of the user (i.e., what is currently shown in the Data section of this dialog).
34+
35+
Bind to Product ID: If the generated license key should be bound to the given product ID of the user (i.e., what is currently shown in the Data section of this dialog).
36+
37+
License Key: The generated license key for your plugin based on the selected settings.
38+
39+
Generate: Generates a licenses key for the current settings (but does not activate it).
40+
41+
Activate: Activates the license key shown in the License Key field.
42+
43+
Deactivate: Deactivates all licenses for the plugin (both a possibly present license file and any license stored in Cinema 4D).";
44+
45+
IDS_DLG_RESTART_REQUIRED "A restart of Cinema 4D is required to apply the licensing changes which have been made. Do you want to restart now?";
46+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Contains the string table for the Olicenseablecube description.
2+
STRINGTABLE Olicenseablecube
3+
{
4+
Olicenseablecube "Licensable Cube";
5+
6+
PY_LICENSABLE_CUBE_HEIGHT "Height";
7+
PY_LICENSABLE_CUBE_WIDTH "Width";
8+
PY_LICENSABLE_CUBE_DEPTH "Depth";
9+
PY_LICENSABLE_GET_LICENSE "Get License";
10+
}

0 commit comments

Comments
 (0)