├── .gitignore ├── LICENSE ├── README.md ├── cinema4d_connector ├── project │ └── projectdefinition.txt ├── res │ ├── c4d_symbols.h │ ├── description │ │ ├── pref_websocket_json_ce.h │ │ └── pref_websocket_json_ce.res │ └── strings_en-US │ │ ├── c4d_strings.str │ │ └── description │ │ └── pref_websocket_json_ce.str └── source │ ├── main.cpp │ ├── websocket_json_codeexchange.cpp │ ├── websocket_json_codeexchange.h │ ├── websocket_json_preference.cpp │ └── websocket_json_preference.h ├── documentation.md ├── image └── code_exchange_c4d.jpg └── project └── projectdefinition.txt /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .vs/ 3 | _obj/ 4 | generated/ 5 | __MACOSX/ 6 | *.eclipse/ 7 | *.xcodeproj/ 8 | 9 | 10 | *.sln 11 | *.vcxproj 12 | *.vcxproj.filters 13 | *.cbp 14 | *.props 15 | *project.pbxproj 16 | *SConscript 17 | *.DS_Store 18 | 19 | *.ilk 20 | *.pdb 21 | *.xdl64 22 | *.xdl64.manifest 23 | *.xlib -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cinema 4D Connector - Cinema 4D Plugin 2 | 3 | > :warning: This an archived legacy repository which is not actively maintained. 4 | 5 | > :warning: **As of Cinema 4D 2023.2, this plugin is no longer required and supported**, as its functionality has been integrated into Cinema 4D. For more information please read the [Cinema 4D Connector documentation](https://help.maxon.net/c4d/en-us/#html/5896.html#codeexchangesendtoIDE). 6 | 7 | Provides a plugin for Cinema 4D to exchange code between the Script Manager and external code editors. 8 | 9 | The solution is server-based and requires a matching implementation on the side of the code editor. Such matching implementation is being provided with the Visual Studio Code extension `Cinema 4D Connector`. 10 | 11 | ## Installation 12 | 13 | To use all the features it is necessary to install the following two extensions: 14 | 15 | - The Cinema 4D plugin, downloadable [here](https://github.com/Maxon-Computer/Cinema-4D-Legacy-Visual-Studio-Code-Bridge-Plugin/releases). Once downloaded, extract the archive to the Cinema 4D S26+ plugins folder. You then need to activate the extension in the Cinema 4D preferences in the `Extensions | Code Exchange` menu, activate the WebSocket Json checkbox. 16 | 17 | - The `Cinema 4D Connector` extension for Visual Studio Code, directly accessible in the Visual Studio code marketplace, or download it [here](https://github.com/Maxon-Computer/Cinema-4D-Legacy-Visual-Studio-Code-Bridge-Plugin/releases). 18 | 19 | ## Features 20 | 21 | In-depth documentation can be found in [Cinema 4D Connector - Documentation](https://github.com/Maxon-Computer/Cinema-4D-Legacy-Visual-Studio-Code-Bridge-Plugin/blob/main/documentation.md). 22 | 23 | ## Requirements 24 | 25 | - Cinema 4D S26. 26 | 27 | ## Known Issues 28 | 29 | If settings are not present in the preferences, you do not have the correct version of Cinema 4D. 30 | 31 | ## License 32 | 33 | This extension is licensed under the [Apache 2.0 License](LICENSE). 34 | -------------------------------------------------------------------------------- /cinema4d_connector/project/projectdefinition.txt: -------------------------------------------------------------------------------- 1 | // Supported platforms - can be [Win32;Win64;OSX;Android;Linux;iOS] 2 | Platform=Win64;OSX;Linux 3 | 4 | // Type of project - can be [Lib;DLL;App] 5 | Type=DLL 6 | 7 | // Enable unity builds for the following directories 8 | unity=; 9 | 10 | // API dependencies 11 | APIS=cinema.framework;core.framework;math.framework;crypt.framework;python.framework;misc.framework;network.framework;cinema_hybrid.framework 12 | 13 | // Legacy C4D component 14 | C4D=true 15 | 16 | stylecheck.level=3 // must be set after c4d=true 17 | 18 | 19 | ModuleId=net.sdk.maxon.cinema4d_connector 20 | -------------------------------------------------------------------------------- /cinema4d_connector/res/c4d_symbols.h: -------------------------------------------------------------------------------- 1 | #ifndef C4D_SYMBOLS_H__ 2 | #define C4D_SYMBOLS_H__ 3 | 4 | enum 5 | { 6 | IDS_WEBSOCKET_JSON_CE = 1000, 7 | }; 8 | 9 | #endif // C4D_SYMBOLS_H__ -------------------------------------------------------------------------------- /cinema4d_connector/res/description/pref_websocket_json_ce.h: -------------------------------------------------------------------------------- 1 | #ifndef PREFS_WEBSOCKET_JSON_CE__ 2 | #define PREFS_WEBSOCKET_JSON_CE__ 3 | 4 | enum 5 | { 6 | PREFS_WEBSOCKET_JSON_CE_GRP = 1000, 7 | PREFS_WEBSOCKET_JSON_CE_PORT, 8 | }; 9 | 10 | #endif // PREFS_WEBSOCKET_JSON_CE__ 11 | -------------------------------------------------------------------------------- /cinema4d_connector/res/description/pref_websocket_json_ce.res: -------------------------------------------------------------------------------- 1 | CONTAINER pref_websocket_json_ce 2 | { 3 | NAME pref_websocket_json_ce; 4 | 5 | GROUP PREFS_WEBSOCKET_JSON_CE_GRP 6 | { 7 | DEFAULT 1; 8 | COLUMNS 1; 9 | 10 | LONG PREFS_WEBSOCKET_JSON_CE_PORT {} 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /cinema4d_connector/res/strings_en-US/c4d_strings.str: -------------------------------------------------------------------------------- 1 | // C4D-StringResource 2 | // Identifier Text 3 | 4 | STRINGTABLE 5 | { 6 | IDS_WEBSOCKET_JSON_CE "WebSocket Json"; 7 | } 8 | -------------------------------------------------------------------------------- /cinema4d_connector/res/strings_en-US/description/pref_websocket_json_ce.str: -------------------------------------------------------------------------------- 1 | STRINGTABLE pref_websocket_json_ce 2 | { 3 | pref_websocket_json_ce "WebSocket JSON Settings"; 4 | 5 | PREFS_WEBSOCKET_JSON_CE_PORT "Port"; 6 | } 7 | -------------------------------------------------------------------------------- /cinema4d_connector/source/main.cpp: -------------------------------------------------------------------------------- 1 | #include "c4d_plugin.h" 2 | #include "c4d_resource.h" 3 | #include "websocket_json_preference.h" 4 | #include "maxon/code_exchange.h" 5 | 6 | ::Bool PluginStart() 7 | { 8 | if (!RegisterWebSocketJsonCodeExchangePreferences()) 9 | return false; 10 | 11 | return true; 12 | } 13 | 14 | void PluginEnd() 15 | { 16 | 17 | } 18 | 19 | ::Bool PluginMessage(::Int32 id, void* data) 20 | { 21 | switch (id) 22 | { 23 | case C4DPL_INIT_SYS: 24 | { 25 | if (g_resource.Init() == false) 26 | return false; 27 | return true; 28 | break; 29 | } 30 | } 31 | 32 | return false; 33 | } 34 | -------------------------------------------------------------------------------- /cinema4d_connector/source/websocket_json_codeexchange.cpp: -------------------------------------------------------------------------------- 1 | #include "maxon/interface.h" 2 | #include "maxon/hierarchyobject.h" 3 | #include "maxon/parser.h" 4 | #include "maxon/datadictionary.h" 5 | #include "maxon/registrybase.h" 6 | #include "maxon/application.h" 7 | #include "maxon/valuereceiver.h" 8 | #include "maxon/errortypes.h" 9 | #include "maxon/system_process.h" 10 | #include "maxon/basearray.h" 11 | #include "maxon/spinlock.h" 12 | #include "maxon/timer.h" 13 | 14 | #include "maxon/code_exchange.h" 15 | #include "maxon/network_websocket.h" 16 | #include "maxon/py_element.h" 17 | #include "maxon/py_element_manager.h" 18 | 19 | #include "websocket_json_codeexchange.h" 20 | #include "websocket_json_preference.h" 21 | #include "c4d_baseobject.h" 22 | #include "c4d_general.h" 23 | 24 | namespace maxon 25 | { 26 | 27 | NetworkWebSocketServerRef g_wsServer = NetworkWebSocketServerRef::NullValue(); 28 | // console output is added to this string buffer. This String buffer is flush and sent ##consoleOutputInterval second. 29 | String g_consolOutputBuffer; 30 | Spinlock g_consolOutputBufferLock; 31 | 32 | // Time in second of the timer responsible to send the content of g_consolOutputBuffer to all connected IDEs. 33 | const Float32 consoleOutputInterval = 1; 34 | 35 | /// ----- Utilities functions ------ 36 | 37 | static Result JsonStringToDataDict(const String& str) 38 | { 39 | iferr_scope; 40 | 41 | if (str.IsEmpty()) 42 | return IllegalArgumentError(MAXON_SOURCE_LOCATION, "Passed string is empty."_s); 43 | 44 | const ParserRef jsonParser = ParserClasses::JsonParser().Create() iferr_return; 45 | maxon::SingleValueReceiver readData; 46 | // In case of miss-formated string we just return an empty dict 47 | iferr (jsonParser.ReadString(str, PARSERFLAGS::NONE, GetUtf8DefaultDecoder(), readData)) 48 | { 49 | return {}; 50 | } 51 | 52 | return readData.Get().GetValue(); 53 | } 54 | 55 | static Result DataDictToJsonString(const DataDictionary& dict) 56 | { 57 | iferr_scope; 58 | 59 | if (dict.IsEmpty()) 60 | return maxon::IllegalArgumentError(MAXON_SOURCE_LOCATION, "Passed dict is empty."_s); 61 | 62 | const maxon::ParserRef jsonParser = maxon::ParserClasses::JsonParser().Create() iferr_return; 63 | 64 | String out; 65 | jsonParser.Write(dict, out, false) iferr_return; 66 | return out; 67 | } 68 | 69 | static Id GetActionId(const DataDictionary& incomingMsg) 70 | { 71 | iferr_scope_handler 72 | { 73 | return Id(); 74 | }; 75 | 76 | String actionStr = incomingMsg.Get(CODEEXCHANGE::ACTION.ToString()) iferr_return; 77 | Id action; 78 | action.Init(actionStr) iferr_return; 79 | 80 | return action; 81 | } 82 | 83 | /// ----- OnMessage functions ------ 84 | 85 | static Result GetScriptContent(const DataDictionary& inData, DataDictionary& outDict) 86 | { 87 | iferr_scope; 88 | 89 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::SET_SCRIPT_CONTENT) iferr_return; 90 | String scriptPath = inData.Get(CODEEXCHANGE::SCRIPT_PATH.ToString()) iferr_return; 91 | 92 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 93 | Opt pyOptElem = pyManager.FindPyElementByPath(scriptPath) iferr_return; 94 | 95 | String out; 96 | if (pyOptElem.HasValue()) 97 | { 98 | PythonElementBaseRef pyElem = pyOptElem.GetValue() iferr_return; 99 | out = pyElem.GetScript() iferr_return; 100 | } 101 | 102 | if (out.IsEmpty()) 103 | return OK; 104 | 105 | outDict.Set(CODEEXCHANGE::VALUE, out) iferr_return; 106 | outDict.Set(CODEEXCHANGE::SCRIPT_PATH.ToString(), scriptPath) iferr_return; 107 | 108 | return OK; 109 | } 110 | 111 | static Result LoadInScriptManager(const DataDictionary& inData, const NetworkWebSocketConnectionRef& webSocket) 112 | { 113 | iferr_scope; 114 | 115 | String scriptPath = inData.Get(CODEEXCHANGE::SCRIPT_PATH.ToString()) iferr_return; 116 | String scriptContent = inData.Get(CODEEXCHANGE::VALUE.ToString()) iferr_return; 117 | 118 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 119 | Opt pyOptElem = pyManager.FindPyElementByPath(scriptPath) iferr_return; 120 | 121 | if (pyOptElem.HasValue()) 122 | { 123 | PythonElementBaseRef pyElem = pyOptElem.GetValue() iferr_return; 124 | const PythonElementScriptRef& castedPyElem = Cast(pyElem); 125 | if (castedPyElem) 126 | { 127 | castedPyElem.SetScript(scriptContent) iferr_return; 128 | castedPyElem.ShowInScriptManager() iferr_return; 129 | return OK; 130 | } 131 | } 132 | if (scriptPath.StartsWith("c4dfs"_s)) 133 | { 134 | 135 | ExecuteOnMainThread([scriptPath, scriptContent]() 136 | { 137 | ::String name = Url(scriptPath).GetName(); 138 | BaseList2D* scriptOp = CreateNewPythonScript(name, scriptContent); 139 | if (MAXON_UNLIKELY(scriptOp == nullptr)) 140 | return false; 141 | 142 | Int32 scriptId = GetDynamicScriptID(scriptOp); 143 | if (MAXON_UNLIKELY(scriptId == NOTOK)) 144 | return false; 145 | 146 | SetActiveScriptObject(scriptId); 147 | 148 | return true; 149 | }); 150 | return OK; 151 | } 152 | else if (scriptPath.StartsWith("file"_s)) 153 | { 154 | ExecuteOnMainThread([scriptPath]() 155 | { 156 | const Filename file = MaxonConvert(scriptPath); 157 | BaseList2D* scriptOp = LoadPythonScript(file); 158 | if (MAXON_UNLIKELY(scriptOp == nullptr)) 159 | return false; 160 | 161 | return true; 162 | }); 163 | return OK; 164 | } 165 | else if (scriptPath.StartsWith("untitled"_s)) 166 | { 167 | ExecuteOnMainThread([scriptContent, webSocket]() 168 | { 169 | iferr_scope_handler 170 | { 171 | return false; 172 | }; 173 | 174 | ::String name = ""; 175 | BaseList2D * scriptOp = CreateNewPythonScript(name, scriptContent); 176 | if (MAXON_UNLIKELY(scriptOp == nullptr)) 177 | return false; 178 | 179 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 180 | Opt newPyOptElem = pyManager.FindPyElementByBaseList2D(scriptOp) iferr_return; 181 | PythonElementBaseRef pyElem = newPyOptElem.GetValue() iferr_return; 182 | 183 | DataDictionary outDict; 184 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::SET_SCRIPT_CONTENT) iferr_return; 185 | outDict.Set(CODEEXCHANGE::VALUE, scriptContent) iferr_return; 186 | outDict.Set(CODEEXCHANGE::SCRIPT_PATH.ToString(), pyElem.GetPath()) iferr_return; 187 | String out = DataDictToJsonString(outDict) iferr_return; 188 | webSocket.Send(out) iferr_return; 189 | return true; 190 | }); 191 | } 192 | 193 | return OK; 194 | } 195 | 196 | static Result SetScriptContent(const DataDictionary& inData) 197 | { 198 | iferr_scope; 199 | 200 | // Script Path not defined, so create a new script 201 | String scriptPath = inData.Get(CODEEXCHANGE::SCRIPT_PATH.ToString(), ""_s); 202 | String scriptContent = inData.Get(CODEEXCHANGE::VALUE.ToString()) iferr_return; 203 | 204 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 205 | Opt pyOptElem = pyManager.FindPyElementByPath(scriptPath) iferr_return; 206 | 207 | if (pyOptElem.HasValue()) 208 | { 209 | PythonElementBaseRef pyElem = pyOptElem.GetValue() iferr_return; 210 | pyElem.SetScript(scriptContent) iferr_return; 211 | } 212 | 213 | return OK; 214 | } 215 | 216 | static Result ExecuteScript(const DataDictionary& inData) 217 | { 218 | iferr_scope; 219 | 220 | String scriptPath = inData.Get(CODEEXCHANGE::SCRIPT_PATH.ToString()) iferr_return; 221 | Bool debug = inData.Get(CODEEXCHANGE::DEBUG.ToString(), false); 222 | String scriptContent = inData.Get(CODEEXCHANGE::VALUE.ToString()) iferr_return; 223 | 224 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 225 | Opt pyOptElem = pyManager.FindPyElementByPath(scriptPath) iferr_return; 226 | 227 | if (pyOptElem.HasValue()) 228 | { 229 | PythonElementBaseRef pyElem = pyOptElem.GetValue() iferr_return; 230 | const PythonElementScriptRef& castedPyElem = Cast(pyElem); 231 | if (castedPyElem) 232 | { 233 | castedPyElem.SetScript(scriptContent) iferr_return; 234 | castedPyElem.Execute(debug) iferr_return; 235 | } 236 | } 237 | else if (debug) 238 | { 239 | // Debug === only file, therefor if we can't find it means we need to import it first 240 | ExecuteOnMainThread([scriptPath]() 241 | { 242 | iferr_scope_handler 243 | { 244 | return false; 245 | }; 246 | 247 | BaseList2D * scriptOp = LoadPythonScript(MaxonConvert(scriptPath)); 248 | if (MAXON_UNLIKELY(scriptOp == nullptr)) 249 | return false; 250 | 251 | PyElementManagerRef pyManager = PyElementManager().Create() iferr_return; 252 | Opt newPyOptElem = pyManager.FindPyElementByBaseList2D(scriptOp) iferr_return; 253 | PythonElementBaseRef pyElem = newPyOptElem.GetValue() iferr_return; 254 | 255 | pyElem.Execute(true) iferr_return; 256 | 257 | return true; 258 | }); 259 | 260 | } 261 | else 262 | { 263 | // Creates a temporary Python script and execute it 264 | BaseList2D* scriptOp = static_cast(AllocListNode(ID_PYTHONSCRIPT)); 265 | if (MAXON_UNLIKELY(scriptOp == nullptr)) 266 | return NullptrError(MAXON_SOURCE_LOCATION); 267 | 268 | if (MAXON_UNLIKELY(GetScriptHead(0) == nullptr)) 269 | return NullptrError(MAXON_SOURCE_LOCATION); 270 | 271 | scriptOp->InsertUnderLast(GetScriptHead(0)); 272 | 273 | maxon::PythonElementScriptRef newItem = maxon::PyScriptElementFactory().Create(scriptOp) iferr_return; 274 | if (MAXON_UNLIKELY(!newItem)) 275 | return NullptrError(MAXON_SOURCE_LOCATION); 276 | 277 | newItem.SetScript(scriptContent) iferr_return; 278 | newItem.Execute(debug) iferr_return; 279 | 280 | scriptOp->Remove(); 281 | BaseList2D::Free(scriptOp); 282 | } 283 | 284 | return OK; 285 | } 286 | 287 | /// ----- WebSocket Messages ------ 288 | 289 | static Result OnConnected(const NetworkWebSocketConnectionRef& webSocket, const DataDictionary& request) 290 | { 291 | iferr_scope; 292 | 293 | WebSocketJsonCodeExchangeRef ce = WebSocketJsonCodeExchange().Create() iferr_return; 294 | BaseArray>* websockets = ce.GetWebSockets(); 295 | if (MAXON_UNLIKELY(websockets == nullptr)) 296 | return OK; 297 | 298 | websockets->Append(webSocket) iferr_return; 299 | 300 | return OK; 301 | } 302 | 303 | static void OnDisconnected(const NetworkWebSocketConnectionRef& webSocket) 304 | { 305 | iferr_scope_handler 306 | { 307 | return; 308 | }; 309 | 310 | WebSocketJsonCodeExchangeRef ce = WebSocketJsonCodeExchange().Create() iferr_return; 311 | BaseArray>* websockets = ce.GetWebSockets(); 312 | if (MAXON_UNLIKELY(websockets == nullptr)) 313 | return; 314 | 315 | Int32 cnt = -1; 316 | for (auto const& sockRef : *websockets) 317 | { 318 | cnt++; 319 | if (!sockRef) 320 | continue; 321 | 322 | const NetworkWebSocketConnectionRef sock = sockRef; 323 | 324 | if (sock == webSocket) 325 | { 326 | break; 327 | } 328 | } 329 | 330 | if (cnt != -1 && cnt < websockets->GetCount()) 331 | { 332 | websockets->Erase(cnt) iferr_return; 333 | } 334 | 335 | return; 336 | } 337 | 338 | static Result OnMessage(const NetworkWebSocketConnectionRef& webSocket, WEBSOCKET_OPCODE opCode, const BaseArray& data) 339 | { 340 | iferr_scope; 341 | 342 | if (opCode != WEBSOCKET_OPCODE::TEXT) 343 | return OK; 344 | 345 | String inMsg = String(data.ToBlock()); 346 | DataDictionary inData = JsonStringToDataDict(inMsg) iferr_return; 347 | if (inData.IsEmpty()) 348 | return OK; 349 | 350 | Id action = GetActionId(inData); 351 | DataDictionary outDict; // To feed with value to send 352 | 353 | // Send CODEEXCHANGE::C4D2IDE::LOAD_SCRIPT_CONTENT 354 | if (action == CODEEXCHANGE::IDE2C4D::GET_SCRIPT_CONTENT) 355 | { 356 | GetScriptContent(inData, outDict) iferr_return; 357 | } 358 | 359 | else if (action == CODEEXCHANGE::IDE2C4D::LOAD_IN_SCRIPT_MANAGER) 360 | { 361 | LoadInScriptManager(inData, webSocket) iferr_return; 362 | return OK; 363 | } 364 | 365 | else if (action == CODEEXCHANGE::IDE2C4D::SET_SCRIPT_CONTENT) 366 | { 367 | SetScriptContent(inData) iferr_return; 368 | return OK; 369 | } 370 | 371 | else if (action == CODEEXCHANGE::IDE2C4D::EXECUTE_SCRIPT) 372 | { 373 | ExecuteScript(inData) iferr_return; 374 | return OK; 375 | } 376 | 377 | else if (action == CODEEXCHANGE::IDE2C4D::GET_PID) 378 | { 379 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::GET_PID) iferr_return; 380 | UInt pid = SystemProcessInterface::GetCurrentProcessId(); 381 | outDict.Set(CODEEXCHANGE::VALUE, pid) iferr_return; 382 | } 383 | 384 | else if (action == CODEEXCHANGE::IDE2C4D::GET_PATH) 385 | { 386 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::GET_PATH) iferr_return; 387 | Url url = Application::GetUrl(APPLICATION_URLTYPE::STARTUP_DIR) iferr_return; 388 | String path = url.GetSystemPath() iferr_return; 389 | outDict.Set(CODEEXCHANGE::VALUE, path) iferr_return; 390 | } 391 | 392 | String out = DataDictToJsonString(outDict) iferr_return; 393 | webSocket.Send(out) iferr_return; 394 | 395 | return OK; 396 | } 397 | 398 | static Result OnHandShake(const NetworkWebSocketConnectionRef& webSocket, const DataDictionary& request) 399 | { 400 | iferr_scope; 401 | return ""_s; 402 | } 403 | 404 | /// ----- Interface implementation ------ 405 | 406 | class WebSocketJsonCodeExchangeImpl : public Component 407 | { 408 | /// This Implementation is not allowed to be re-used and it act as a singleton 409 | MAXON_COMPONENT(FINAL_SINGLETON); 410 | 411 | private: 412 | /// Store the list of connection, returned by GetWebSockets() 413 | BaseArray> _websockets; 414 | 415 | /// Store if the WebSocket server is running, returned by IsRunning() 416 | Bool _isRunning = false; 417 | TimerRef _timer; 418 | 419 | public: 420 | MAXON_METHOD Result Start() 421 | { 422 | iferr_scope; 423 | if (g_wsServer) 424 | { 425 | Stop() iferr_return; 426 | } 427 | 428 | g_wsServer = NetworkWebSocketServerClass().Create() iferr_return; 429 | 430 | // create and start server 431 | auto localAddr = maxon::NetworkIpAddrPort(127, 0, 0, 1, GetPortFromPreference()); 432 | g_wsServer.ObservableHandshake().AddObserver(OnHandShake) iferr_return; 433 | g_wsServer.ObservableConnected().AddObserver(OnConnected) iferr_return; 434 | g_wsServer.ObservableDisconnected().AddObserver(OnDisconnected) iferr_return; 435 | g_wsServer.ObservableMessage().AddObserver(OnMessage) iferr_return; 436 | 437 | g_wsServer.StartWebServer(localAddr, false, "c4d_py_code_exchange"_s) iferr_return; 438 | _isRunning = true; 439 | 440 | auto g_TimerFunction = [this]() 441 | { 442 | iferr_scope_handler 443 | { 444 | return; 445 | }; 446 | String contentToSend; 447 | g_consolOutputBufferLock.Lock(); 448 | contentToSend = g_consolOutputBuffer; 449 | g_consolOutputBuffer = ""_s; 450 | g_consolOutputBufferLock.Unlock(); 451 | 452 | for (auto const& sockRef : _websockets) 453 | { 454 | if (!sockRef) 455 | continue; 456 | 457 | const NetworkWebSocketConnectionRef sock = sockRef; 458 | 459 | DataDictionary outDict; 460 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::CONSOLE) iferr_return; 461 | outDict.Set(CODEEXCHANGE::VALUE, contentToSend) iferr_return; 462 | 463 | String out = DataDictToJsonString(outDict) iferr_return; 464 | iferr (sock.Send(out)) 465 | continue; 466 | } 467 | }; 468 | 469 | _timer = TimerInterface::AddPeriodicTimer(maxon::Seconds(consoleOutputInterval), g_TimerFunction, maxon::JOBQUEUE_CURRENT) iferr_return; 470 | 471 | return OK; 472 | } 473 | 474 | MAXON_METHOD Result Stop() 475 | { 476 | iferr_scope; 477 | if (_timer) 478 | _timer.CancelAndWait(); 479 | 480 | if (g_wsServer) 481 | { 482 | // All connection must be closed or g_wsServer.StopWebServer() is waiting forever 483 | for (auto const& sockRef : _websockets) 484 | { 485 | if (!sockRef) 486 | continue; 487 | 488 | const NetworkWebSocketConnectionRef sock = sockRef; 489 | if (sock.GetState() == WEBSOCKETSTATE::CONNECTED) 490 | { 491 | sock.Close() iferr_return; 492 | } 493 | } 494 | 495 | _websockets.Flush(); 496 | g_wsServer.StopWebServer() iferr_return; 497 | g_wsServer = nullptr; 498 | } 499 | 500 | _isRunning = false; 501 | return OK; 502 | } 503 | 504 | MAXON_METHOD InternedId GetLanguage() const 505 | { 506 | return CodeExchangeLanguageId::Python; 507 | } 508 | 509 | MAXON_METHOD String GetName() const 510 | { 511 | return "WebSocket Json"_s; 512 | } 513 | 514 | MAXON_METHOD Result SendScriptToIDE(const PythonElementScriptRef& script) const 515 | { 516 | iferr_scope; 517 | 518 | for (auto const& sockRef : _websockets) 519 | { 520 | if (!sockRef) 521 | continue; 522 | 523 | const NetworkWebSocketConnectionRef sock = sockRef; 524 | 525 | DataDictionary outDict; 526 | outDict.Set(CODEEXCHANGE::ACTION, CODEEXCHANGE::C4D2IDE::SET_SCRIPT_CONTENT) iferr_return; 527 | outDict.Set(CODEEXCHANGE::SCRIPT_PATH, script.GetPath()) iferr_return; 528 | 529 | String code = script.GetScript() iferr_return; 530 | outDict.Set(CODEEXCHANGE::VALUE, code) iferr_return; 531 | 532 | String out = DataDictToJsonString(outDict) iferr_return; 533 | iferr (sock.Send(out)) 534 | continue; 535 | } 536 | return OK; 537 | } 538 | 539 | MAXON_METHOD Result SendConsoleOutput(const String& content) 540 | { 541 | iferr_scope; 542 | 543 | if (!_isRunning) 544 | return OK; 545 | 546 | Bool isConnected = false; 547 | for (auto const& sockWeakRef : _websockets) 548 | { 549 | if (!sockWeakRef) 550 | continue; 551 | 552 | const NetworkWebSocketConnectionRef sockRef = sockWeakRef; 553 | if (sockRef.GetState() == WEBSOCKETSTATE::CONNECTED) 554 | { 555 | isConnected = true; 556 | break; 557 | } 558 | } 559 | 560 | if (isConnected) 561 | { 562 | g_consolOutputBufferLock.Lock(); 563 | g_consolOutputBuffer += content; 564 | g_consolOutputBufferLock.Unlock(); 565 | } 566 | 567 | return OK; 568 | } 569 | 570 | MAXON_METHOD BaseArray>* GetWebSockets() 571 | { 572 | return &_websockets; 573 | } 574 | 575 | MAXON_METHOD Bool IsRunning() 576 | { 577 | return _isRunning; 578 | } 579 | }; 580 | 581 | 582 | MAXON_COMPONENT_CLASS_REGISTER(WebSocketJsonCodeExchangeImpl, CodeExchanges, "net.maxonsdk.class.codeexchange.websocket_json"); 583 | } 584 | 585 | -------------------------------------------------------------------------------- /cinema4d_connector/source/websocket_json_codeexchange.h: -------------------------------------------------------------------------------- 1 | #ifndef WBSOCK_JSON_CE_H__ 2 | #define WBSOCK_JSON_CE_H__ 3 | 4 | #include "maxon/interface.h" 5 | #include "maxon/objectbase.h" 6 | #include "maxon/network_websocket.h" 7 | #include "maxon/fid.h" 8 | #include "maxon/basearray.h" 9 | #include "maxon/weakref.h" 10 | #include "maxon/code_exchange.h" 11 | 12 | namespace maxon 13 | { 14 | 15 | // Key in the JSON communication with VsCode Extension 16 | namespace CODEEXCHANGE 17 | { 18 | static const LiteralId ACTION { "action" }; 19 | static const LiteralId VALUE { "value" }; 20 | static const LiteralId DEBUG { "debug" }; 21 | static const LiteralId SCRIPT_PATH { "script_path" }; 22 | 23 | // ACTION ID for message received from the IDE 24 | namespace IDE2C4D 25 | { 26 | static const LiteralId GET_SCRIPT_CONTENT { "idea2c4d.get_script_content" }; 27 | static const LiteralId SET_SCRIPT_CONTENT { "idea2c4d.set_script_content" }; 28 | static const LiteralId LOAD_IN_SCRIPT_MANAGER { "idea2c4d.load_in_script_manager" }; 29 | static const LiteralId EXECUTE_SCRIPT { "idea2c4d.execute" }; 30 | static const LiteralId GET_PID { "idea2c4d.get_pid" }; 31 | static const LiteralId GET_PATH { "idea2c4d.get_path" }; 32 | } 33 | 34 | // ACTION ID for message send from Cinema 4D 35 | namespace C4D2IDE 36 | { 37 | static const LiteralId SET_SCRIPT_CONTENT { "c4d2ide.set_script_content" }; 38 | static const LiteralId GET_PID { "c4d2ide.get_pid" }; 39 | static const LiteralId GET_PATH { "c4d2ide.get_path" }; 40 | static const LiteralId CONSOLE { "c4d2ide.console" }; 41 | } 42 | } 43 | 44 | //---------------------------------------------------------------------------------------- 45 | /// WebSocket and JSON Python code communication for Cinema 4D and IDE(s). 46 | /// 47 | /// Only 1 implementation of this interface is allowed and it should be singleton. 48 | //---------------------------------------------------------------------------------------- 49 | class WebSocketJsonCodeExchangeInterface : MAXON_INTERFACE_BASES(CodeExchangeInterface) 50 | { 51 | MAXON_INTERFACE(WebSocketJsonCodeExchangeInterface, MAXON_REFERENCE_NORMAL, "net.maxonsdk.interfaces.codeexchange.websocket_json"); 52 | 53 | public: 54 | 55 | //---------------------------------------------------------------------------------------- 56 | /// Retrieves a list of ongoing connections. 57 | /// 58 | /// @return Connection list with Cinema 4D and IDE. 59 | //---------------------------------------------------------------------------------------- 60 | MAXON_METHOD BaseArray>* GetWebSockets(); 61 | 62 | //---------------------------------------------------------------------------------------- 63 | /// Returns the current WebSocket server running state. 64 | /// 65 | /// @return true if the WebSocket server is running otherwise, false. 66 | //---------------------------------------------------------------------------------------- 67 | MAXON_METHOD Bool IsRunning(); 68 | }; 69 | 70 | 71 | #include "websocket_json_codeexchange1.hxx" 72 | 73 | MAXON_DECLARATION(maxon::Class, WebSocketJsonCodeExchange, "net.maxonsdk.class.codeexchange.websocket_json"); 74 | 75 | #include "websocket_json_codeexchange2.hxx" 76 | 77 | } 78 | 79 | #endif // WBSOCK_JSON_CE_H__ -------------------------------------------------------------------------------- /cinema4d_connector/source/websocket_json_preference.cpp: -------------------------------------------------------------------------------- 1 | #include "websocket_json_preference.h" 2 | #include "websocket_json_codeexchange.h" 3 | #include "pref_websocket_json_ce.h" 4 | #include "c4d_symbols.h" 5 | #include "maxon/code_exchange.h" 6 | 7 | //---------------------------------------------------------------------------------------- 8 | /// Retrieves or creates the WebSocket BaseContainer preference from the world container 9 | //---------------------------------------------------------------------------------------- 10 | BaseContainer* GetPreferences() 11 | { 12 | BaseContainer* bc = GetWorldContainerInstance()->GetContainerInstance(WEBSOCKET_JSON_PREFS); 13 | if (!bc) 14 | { 15 | GetWorldContainerInstance()->SetContainer(WEBSOCKET_JSON_PREFS, BaseContainer()); 16 | 17 | bc = GetWorldContainerInstance()->GetContainerInstance(WEBSOCKET_JSON_PREFS); 18 | if (!bc) 19 | return nullptr; 20 | } 21 | 22 | return bc; 23 | } 24 | 25 | //---------------------------------------------------------------------------------------- 26 | /// Retrieve the value of the port from the preference 27 | //---------------------------------------------------------------------------------------- 28 | Int32 GetPortFromPreference() 29 | { 30 | BaseContainer* bc = GetPreferences(); 31 | if (bc == nullptr) 32 | return DEFAULT_PORT_VALUE; 33 | 34 | return bc->GetInt32(PREFS_WEBSOCKET_JSON_CE_PORT, DEFAULT_PORT_VALUE); 35 | } 36 | 37 | Bool WebSocketJsonCodeExchangePreferences::InitValues(const DescID& id, Description* desc) 38 | { 39 | BaseContainer* bc = GetPreferences(); 40 | if (!bc) 41 | return false; 42 | 43 | switch (id[0].id) 44 | { 45 | case PREFS_WEBSOCKET_JSON_CE_PORT: 46 | InitPrefsValue(PREFS_WEBSOCKET_JSON_CE_PORT, GeData(DEFAULT_PORT_VALUE), desc, id, bc); 47 | break; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | Bool WebSocketJsonCodeExchangePreferences::Init(GeListNode* node) 54 | { 55 | // Leave if no CodeExchangeImpl is present 56 | if (!InitValues(PREFS_WEBSOCKET_JSON_CE_PORT)) 57 | return false; 58 | 59 | return true; 60 | } 61 | 62 | Bool WebSocketJsonCodeExchangePreferences::GetDDescription(GeListNode* node, Description* description, DESCFLAGS_DESC& flags) 63 | { 64 | iferr_scope_handler 65 | { 66 | ApplicationOutput(err.GetMessage()); 67 | return false; 68 | }; 69 | 70 | if (!description || !description->LoadDescription("pref_websocket_json_ce"_s)) 71 | return false; 72 | 73 | 74 | if (flags & DESCFLAGS_DESC::NEEDDEFAULTVALUE) 75 | { 76 | if (!InitValues(PREFS_WEBSOCKET_JSON_CE_PORT, description)) 77 | return false; 78 | } 79 | 80 | flags |= DESCFLAGS_DESC::LOADED; 81 | return SUPER::GetDDescription(node, description, flags); 82 | } 83 | 84 | Bool WebSocketJsonCodeExchangePreferences::SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags) 85 | { 86 | BaseContainer* bc = GetPreferences(); 87 | if (!bc) 88 | return SUPER::SetDParameter(node, id, t_data, flags); 89 | 90 | // PREFS_USED_CODE_EXCHANGE Cycle value store the String value in the BaseContainer 91 | // Name of the CodeExchangeImpl is safer than index since the cycle is built from the registry CodeExchanges. 92 | if (id[0].id == PREFS_WEBSOCKET_JSON_CE_PORT) 93 | { 94 | Int32 ceUsed = t_data.GetInt32(); 95 | bc->SetInt32(PREFS_WEBSOCKET_JSON_CE_PORT, ceUsed); 96 | flags |= DESCFLAGS_SET::PARAM_SET; 97 | 98 | // Restart the WebSocket server if the port changed 99 | MAXON_SCOPE 100 | { 101 | iferr_scope_handler 102 | { return true; }; 103 | 104 | maxon::WebSocketJsonCodeExchangeRef ce = maxon::WebSocketJsonCodeExchange().Create() iferr_return; 105 | Bool isRunning = ce.IsRunning(); 106 | 107 | if (isRunning) 108 | { 109 | ce.Stop() iferr_return; 110 | ce.Start() iferr_return; 111 | } 112 | } 113 | 114 | return true; 115 | } 116 | 117 | return SUPER::SetDParameter(node, id, t_data, flags); 118 | } 119 | 120 | Bool WebSocketJsonCodeExchangePreferences::GetDParameter(GeListNode* node, const DescID& id, GeData& t_data, DESCFLAGS_GET& flags) 121 | { 122 | BaseContainer* bc = GetPreferences(); 123 | if (!bc) 124 | return SUPER::GetDParameter(node, id, t_data, flags); 125 | 126 | if (id[0].id == PREFS_WEBSOCKET_JSON_CE_PORT) 127 | { 128 | t_data = bc->GetInt32(PREFS_WEBSOCKET_JSON_CE_PORT, DEFAULT_PORT_VALUE); 129 | flags |= DESCFLAGS_GET::PARAM_GET; 130 | return true; 131 | } 132 | 133 | return SUPER::GetDParameter(node, id, t_data, flags); 134 | } 135 | 136 | Bool RegisterWebSocketJsonCodeExchangePreferences() 137 | { 138 | const auto* websock = maxon::CodeExchanges::Find(maxon::Id("net.maxonsdk.class.codeexchange.websocket_json")); 139 | if (websock == nullptr) 140 | return true; 141 | 142 | if (!PrefsDialogObject::Register(WEBSOCKET_JSON_PREFS, WebSocketJsonCodeExchangePreferences::Alloc, GeLoadString(IDS_WEBSOCKET_JSON_CE), "pref_websocket_json_ce"_s, CODEEXCHANGE_PREFS, 0)) 143 | return false; 144 | 145 | return true; 146 | } -------------------------------------------------------------------------------- /cinema4d_connector/source/websocket_json_preference.h: -------------------------------------------------------------------------------- 1 | #ifndef WEBSOCKET_JSON_CE_PREF_H__ 2 | #define WEBSOCKET_JSON_CE_PREF_H__ 3 | 4 | #include "maxon/code_exchange.h" 5 | #include "c4d.h" 6 | #include "lib_prefs.h" 7 | #include "c4d_basecontainer.h" 8 | 9 | #define CODEEXCHANGE_PREFS 1058660 // Registered by Cinema 4D 10 | #define WEBSOCKET_JSON_PREFS 1058664 11 | #define DEFAULT_PORT_VALUE 7788 12 | 13 | class WebSocketJsonCodeExchangePreferences : public PrefsDialogObject 14 | { 15 | INSTANCEOF(WebSocketJsonCodeExchangePreferences, PrefsDialogObject) 16 | 17 | public: 18 | virtual Bool InitValues(const DescID& id, Description* desc = nullptr); 19 | 20 | virtual Bool Init(GeListNode* node); 21 | virtual Bool GetDParameter(GeListNode* node, const DescID& id, GeData& t_data, DESCFLAGS_GET& flags); 22 | virtual Bool SetDParameter(GeListNode* node, const DescID& id, const GeData& t_data, DESCFLAGS_SET& flags); 23 | virtual Bool GetDDescription(GeListNode* node, Description* description, DESCFLAGS_DESC& flags); 24 | 25 | static NodeData* Alloc() { return NewObjClear(WebSocketJsonCodeExchangePreferences); } 26 | }; 27 | 28 | Bool RegisterWebSocketJsonCodeExchangePreferences(); 29 | BaseContainer* GetPreferences(); 30 | Int32 GetPortFromPreference(); 31 | 32 | 33 | #endif // WEBSOCKET_JSON_CE_PREF_H__ -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | # Cinema 4D Connector - Documentation 2 | 3 | This document contains documentation for the Cinema 4D Connector project. 4 | 5 | ## Installation 6 | 7 | In order to be able to use all the features it is necessary to install the following two extensions: 8 | 9 | - The Cinema 4D plugin, downloadable [here](https://github.com/PluginCafe/Cinema4D_Connector-Cinema4D_Plugin/releases). Once downloaded, extract the archive to the Cinema 4D S26+ plugins folder. You then need to activate the extension in the Cinema 4D preferences in the `Extensions | Code Exchange` menu, activate the WebSocket Json checkbox. 10 | 11 | - The `Cinema 4D Connector` extension for Visual Studio Code, directly accessible in the Visual Studio code marketplace, or download it [here](https://github.com/PluginCafe/Cinema4D_Connector-VisualStudioCode_Extension/releases). 12 | 13 | ## Settings 14 | 15 | In Cinema 4D preference within the `Extensions | Code Exchange` menu. 16 | 17 | **WebSocket Json**: If enabled, the WebSocket server is started. The server will then start automatically during Cinema 4D startup. Once the server started, you can connect an IDE instance to it and shares codes. 18 | 19 | **Port**: The port the current WebSocket server is listening to. 20 | 21 | In Visual Studio Code preference within the `Extensions | Cinema 4D` category. 22 | 23 | **c4d.path**: Path to the Cinema 4D directory used for autocompletion and debugging. If not defined or invalid, it is automatically defined when Visual Studio Code and Cinema 4D first connect. 24 | 25 | **c4d.ip**: IP address used to connect to Cinema 4D. 26 | 27 | **c4d.port**: Port used to connect to Cinema 4D. This should be the same than the one in Cinema 4D preference. 28 | 29 | **c4d.template**: Path to the directory containing the python scripts used as template. 30 | 31 | ## Feature Description 32 | 33 | **Connection states with Cinema 4D**: In the lower left corner of the Visual Studio Code windows, a status bar indicates the current connection status with Cinema 4D. Runing the `Toggle Connection with Cinema 4D` command or clicking on the items in the status bar toggles the current connection status. If the status bar shows "C4D ✓", it means that the connection is active and that the communication is going well. Otherwise, "C4D X" means that the connection is not active. 34 | 35 | **Send script from Cinema 4D to Visual Studio Code**: The command in the "File" menu of the Cinema 4D Script Manager allows you to send the contents of the active script in the Script Manager to all IDEs connected to Cinema 4D. In order to use this feature Cinema 4D and Visual Studio Code must be connected. 36 | 37 | **Load Script in Script Manager**: Load the active script from the Visual Studio Code editor into the Cinema 4D Script Manager. In order to use this feature Cinema 4D and Visual Studio Code must be connected. Lastly, depending on where the script is saved in Visual Studio Code, different behaviour is expected if the file is: 38 | 39 | - Saved from file to disk: Works as expected, saving the file refreshes the script content in both the IDE and Cinema 4D. 40 | - A temporary script from Visual Studio Code: A new temporary file will be created in Cinema 4D. This file will be added to Visual Studio Code and to communicate with Cinema 4D you will need to use this file instead of the previous temporary file. 41 | - A temporary script from Cinema 4D: Works as expected, CTRL+S will send the content to Cinema 4D. Autocomplete does not work. 42 | 43 | **Execute in Cinema 4D as a Script in Script Manager**: Run a script directly in Cinema 4D. In order to use this feature, Cinema 4D and Visual Studio Code must be connected. Lastly, depending on where the script is saved in Visual Studio Code, a different behaviour is expected if the file is: 44 | 45 | - Saved to disk or from a temporary Cinema 4D script: Update the contents of the script in Cinema 4D and run it. 46 | - A temporary script from Visual Studio Code: Create a new temporary python script in Cinema 4D. Run it and delete it from Cinema 4D. 47 | 48 | **Debug in Cinema 4D as a Script in Script Manager**: Start a debugging session for the given script to Cinema 4D. Encrypted Python files (.pypv, .pype) are ignored and can't be debugged. In order to use this feature, Cinema 4D and Visual Studio Code must be connected. Works only for file saved on the disk. 49 | 50 | **Load Cinema 4D Script Template**: Loads a template script, corresponding to a python file from the folder defined by the `c4d.template` option. 51 | 52 | **Python Console output**: Once Visual Studio Code is connected to Cinema 4D, if new content appears in the Python console, it will also be displayed in a Visual Studio Code console called "Cinema 4D". 53 | 54 | **Syntax highliting for \*.res and \*.str files**: The syntax for files with the extension .str and .res has a syntax colouring. For .str files, if the syntax is invalid, the line is coloured red. 55 | 56 | 57 | ## Known Issues 58 | 59 | - Autocompletion does not work for the `maxon` package. 60 | - Autocompletion does not work for temporary scripts from Cinema 4D, those whose path begins with `Root@`, e.g. `Root@12345678/Scripts@12345678/untilted.py.` 61 | - Autocompletion for methods from the `c4d` package will generate incomplete default argument if this argument is part of the `c4d` package, e.g. the automcpletion will output only `BaseContainer` while it should be `c4d.BaseContainer`. 62 | - When the `Load Script in Script Manager` command is used on an untitled file, it creates a new temporary file in Cinema 4D and this is returned to Visual Studio Code. This file should be used to exchange data to/from Cinema 4D. 63 | - The first debugging session will show a message about the deprecated use of `ptvsd`, this is a false positive and can be ignored. 64 | -------------------------------------------------------------------------------- /image/code_exchange_c4d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Maxon-Computer/Cinema-4D-Legacy-Visual-Studio-Code-Bridge-Plugin/977bdfa2a90dca740732023d0b8f85f42952f623/image/code_exchange_c4d.jpg -------------------------------------------------------------------------------- /project/projectdefinition.txt: -------------------------------------------------------------------------------- 1 | Platform=Win64;OSX 2 | Type=Solution 3 | Name=project_cinema4d_connector 4 | AdditionalSolutionFiles=\ 5 | ..\..\frameworks\core.framework\project\typeviewer\msvc\maxon.natvis;\ 6 | ..\..\frameworks\cinema.framework\project\typeviewer\msvc\c4dapi.natvis 7 | Solution=\ 8 | plugins/cinema4d_connector;\ 9 | 10 | --------------------------------------------------------------------------------