├── .gitattributes ├── .gitignore ├── MarkDown ├── Logo.png ├── Gif │ ├── Create.gif │ ├── AddInstance.gif │ └── MarkProject.gif └── Img │ ├── Install.png │ └── AddonFolder.png ├── src └── main │ ├── resources │ ├── services │ │ └── dic │ │ │ └── blender.dic │ ├── Python │ │ ├── Templates │ │ │ ├── new_operator.py │ │ │ ├── new_panel.py │ │ │ └── new_addon.py │ │ ├── Python.iml │ │ └── pycharm_connector.py │ ├── inspectionDescriptions │ │ ├── OperatorErrId.html │ │ └── PanelWarnId.html │ ├── locale │ │ └── BlendCharm.properties │ ├── icons │ │ ├── blender_logo_gray.svg │ │ ├── blender_logo_gray_big.svg │ │ ├── blender_logo.svg │ │ ├── addon_folder.svg │ │ └── addon_src_folder.svg │ └── META-INF │ │ ├── plugin.xml │ │ └── pluginIcon.svg │ └── java │ ├── plugin_settings │ └── PluginSettings.java │ ├── services │ ├── BlenderDic.java │ ├── BlendCharmPersistentData.java │ ├── BlendCharmRepositoryIconManager.java │ ├── inspections │ │ ├── OperatorErrId.java │ │ ├── base │ │ │ └── OperatorIdInspection.java │ │ └── PanelWarnId.java │ └── BlenderConsoleFilter.java │ ├── util │ ├── MyInputStreamHelper.java │ ├── MyVirtualFileHelper.java │ ├── core │ │ ├── MyFileUtils.java │ │ └── socket │ │ │ ├── server │ │ │ └── MyServerSocket.java │ │ │ └── MySocketConnection.java │ ├── MySwingUtil.java │ └── MyProjectHolder.java │ ├── ui │ ├── dialogs │ │ ├── debug_popup │ │ │ ├── DebugPopup.java │ │ │ ├── DebugPopupWrapper.java │ │ │ └── DebugPopup.form │ │ ├── remove_blender_instance │ │ │ ├── RemoveBlenderInstance.java │ │ │ ├── RemoveBlenderInstanceWrapper.java │ │ │ └── RemoveBlenderInstance.form │ │ ├── new_blender_panel │ │ │ ├── NewBlenderPanelWrapper.java │ │ │ ├── NewBlenderPanel.java │ │ │ └── NewBlenderPanel.form │ │ ├── new_blender_addon │ │ │ ├── NewBlenderAddonWrapper.java │ │ │ ├── NewBlenderAddon.java │ │ │ └── NewBlenderAddon.form │ │ ├── new_blender_operator │ │ │ ├── NewBlenderOperatorWrapper.java │ │ │ ├── NewBlenderOperator.java │ │ │ └── NewBlenderOperator.form │ │ └── add_blender_instance │ │ │ ├── AddBlenderInstanceWrapper.java │ │ │ ├── AddBlenderInstance.java │ │ │ └── AddBlenderInstance.form │ └── tool_window │ │ ├── BlenderToolWindowFactory.java │ │ ├── BlenderToolWindow.form │ │ └── BlenderToolWindow.java │ ├── data │ ├── BlenderToolWindowUtils.java │ ├── BlenderInstance.java │ ├── RunningBlenderProcessRenderer.java │ ├── CommunicationData.java │ ├── BlenderExeFileChooserDescriptor.java │ ├── RunningBlenderProcess.java │ └── VirtualBlenderFile.java │ ├── settings │ ├── BlenderSettingsData.java │ ├── BlenderSettingsRaw.java │ ├── Config.java │ └── BlenderSettings.java │ ├── icons │ └── BlendCharmIcons.java │ └── actions │ ├── MarkAsAddonProjectAction.java │ ├── UnmarkAsAddonProjectAction.java │ ├── MarkAsAddonAction.java │ ├── UnmarkAsAddonAction.java │ ├── CreateNewBlenderOperatorAction.java │ ├── CreateNewBlenderPanelAction.java │ ├── DynamicActionGroup.java │ └── CreateNewBlenderAddonAction.java ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle.kts ├── gradle.properties ├── changelog.md ├── LICENSE ├── update-urls ├── community-edition │ └── updatePlugins.xml └── professional-edition │ └── updatePlugins.xml ├── gradlew.bat ├── README.md └── gradlew /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data/* 2 | build/* 3 | .idea/* 4 | .gradle/* 5 | MarkDown/.src/* 6 | .intellijPlatform 7 | -------------------------------------------------------------------------------- /MarkDown/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Logo.png -------------------------------------------------------------------------------- /src/main/resources/services/dic/blender.dic: -------------------------------------------------------------------------------- 1 | # Blender Common Keywords 2 | idname 3 | 4 | # Names 5 | BlackStartx -------------------------------------------------------------------------------- /MarkDown/Gif/Create.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Gif/Create.gif -------------------------------------------------------------------------------- /MarkDown/Img/Install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Img/Install.png -------------------------------------------------------------------------------- /MarkDown/Gif/AddInstance.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Gif/AddInstance.gif -------------------------------------------------------------------------------- /MarkDown/Gif/MarkProject.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Gif/MarkProject.gif -------------------------------------------------------------------------------- /MarkDown/Img/AddonFolder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/MarkDown/Img/AddonFolder.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BlackStartx/PyCharm-Blender-Plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | } 7 | 8 | rootProject.name = "Blend-Charm" -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /src/main/resources/Python/Templates/new_operator.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class OPERATOR_CLASS_NAME(bpy.types.Operator): 5 | bl_idname = "$ID_NAME$" 6 | bl_label = "$LABEL$" 7 | 8 | def execute(self, context): 9 | return {'FINISHED'} 10 | -------------------------------------------------------------------------------- /src/main/java/plugin_settings/PluginSettings.java: -------------------------------------------------------------------------------- 1 | package plugin_settings; 2 | 3 | public class PluginSettings { 4 | public static final boolean isCommunity = false; 5 | 6 | public static String getStripeVersion() { 7 | return isCommunity ? "Community Edition" : "Professional Edition"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/services/BlenderDic.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import com.intellij.spellchecker.BundledDictionaryProvider; 4 | 5 | public class BlenderDic implements BundledDictionaryProvider { 6 | @Override 7 | public String[] getBundledDictionaries() { 8 | return new String[]{"dic/blender.dic"}; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/Python/Templates/new_panel.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | 4 | class PANEL_CLASS_NAME(bpy.types.Panel): 5 | bl_idname = "$ID_NAME$" 6 | bl_label = "$LABEL$" 7 | bl_category = "$CATEGORY$" 8 | bl_space_type = "$SPACE_TYPE$" 9 | bl_region_type = "$REGION_TYPE$" 10 | 11 | def draw(self, context): 12 | pass 13 | -------------------------------------------------------------------------------- /src/main/java/util/MyInputStreamHelper.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import java.io.InputStream; 4 | import java.util.Scanner; 5 | 6 | public class MyInputStreamHelper { 7 | 8 | public static String readString(InputStream stream) { 9 | Scanner s = new Scanner(stream).useDelimiter("\\A"); 10 | return s.hasNext() ? s.next() : ""; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/debug_popup/DebugPopup.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.debug_popup; 2 | 3 | import javax.swing.*; 4 | 5 | public class DebugPopup extends JDialog { 6 | 7 | private JPanel contentPane; 8 | 9 | public DebugPopup() { 10 | setContentPane(contentPane); 11 | } 12 | 13 | JComponent getJComponent() { 14 | return contentPane; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Opt-out flag for bundling Kotlin standard library -> https://jb.gg/intellij-platform-kotlin-stdlib 2 | kotlin.stdlib.default.dependency=false 3 | # Enable Gradle Configuration Cache -> https://docs.gradle.org/current/userguide/configuration_cache.html 4 | org.gradle.configuration-cache=true 5 | # Enable Gradle Build Cache -> https://docs.gradle.org/current/userguide/build_cache.html 6 | org.gradle.caching=true -------------------------------------------------------------------------------- /src/main/java/data/BlenderToolWindowUtils.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import com.intellij.openapi.application.PathManager; 4 | 5 | import java.io.File; 6 | 7 | public class BlenderToolWindowUtils { 8 | public static File getEggFile() { 9 | String ide = PathManager.getHomePath(); 10 | return new File(ide + File.separator + "debug-eggs" + File.separator + "pydevd-pycharm.egg"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/Python/Templates/new_addon.py: -------------------------------------------------------------------------------- 1 | import bpy 2 | 3 | bl_info = { 4 | "name": "$ADDON_NAME$", 5 | "author": "$ADDON_AUTHOR$", 6 | "description": "$ADDON_DESCRIPTION$", 7 | "blender": (2, 80, 0), 8 | "location": "View3D", 9 | "warning": "", 10 | "category": "Generic" 11 | } 12 | 13 | classes = ( 14 | ) 15 | 16 | register, unregister = bpy.utils.register_classes_factory(classes) 17 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/remove_blender_instance/RemoveBlenderInstance.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.remove_blender_instance; 2 | 3 | import javax.swing.*; 4 | 5 | public class RemoveBlenderInstance extends JDialog { 6 | 7 | private JPanel contentPane; 8 | 9 | public RemoveBlenderInstance() { 10 | setContentPane(contentPane); 11 | } 12 | 13 | JComponent getJComponent() { 14 | return contentPane; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/OperatorErrId.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Addons operator IDs must be lowercase.
5 | The following error rise otherwise:

6 | 7 | Error: Registering operator class: '{CLASS}', invalid bl_idname '{ID}'.
8 |


9 | 10 |

Powered by: Blend-Charm plugin
11 | 12 | -------------------------------------------------------------------------------- /src/main/java/settings/BlenderSettingsData.java: -------------------------------------------------------------------------------- 1 | package settings; 2 | 3 | import data.BlenderInstance; 4 | 5 | import javax.xml.bind.annotation.XmlElement; 6 | import java.util.ArrayList; 7 | 8 | public class BlenderSettingsData { 9 | @XmlElement(name = "showVerbose") 10 | public boolean showVerbose; 11 | 12 | @XmlElement(name = "blenderPath") 13 | public ArrayList blenderPaths; 14 | 15 | @XmlElement(name = "blenderAddonNames") 16 | public ArrayList blenderAddOnNames; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/util/MyVirtualFileHelper.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import com.intellij.openapi.vfs.VirtualFile; 4 | 5 | import java.util.Objects; 6 | 7 | public class MyVirtualFileHelper { 8 | public static VirtualFile getProjectFirstVirtualFile(MyProjectHolder project, VirtualFile virtualFile) { 9 | try { 10 | return project.projectVirtualFile.findChild(virtualFile.getPath().substring(Objects.requireNonNull(project.getBasePath()).length()).split("/", 1)[0].split("/", 3)[1]); 11 | } catch (Exception e) { 12 | return null; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/icons/BlendCharmIcons.java: -------------------------------------------------------------------------------- 1 | package icons; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.*; 6 | 7 | public interface BlendCharmIcons { 8 | Icon BLENDER_LOGO = IconLoader.findIcon("/icons/blender_logo_gray.svg", BlendCharmIcons.class); 9 | Icon BLENDER_LOGO_BIG = IconLoader.findIcon("/icons/blender_logo_gray_big.svg", BlendCharmIcons.class); 10 | Icon BLENDER_FOLDER_ICON = IconLoader.findIcon("/icons/addon_folder.svg", BlendCharmIcons.class); 11 | Icon BLENDER_SRC_FOLDER_ICON = IconLoader.findIcon("/icons/addon_src_folder.svg", BlendCharmIcons.class); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/resources/Python/Python.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/remove_blender_instance/RemoveBlenderInstanceWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.remove_blender_instance; 2 | 3 | import com.intellij.openapi.ui.DialogWrapper; 4 | import org.jetbrains.annotations.Nullable; 5 | 6 | import javax.swing.*; 7 | 8 | public class RemoveBlenderInstanceWrapper extends DialogWrapper { 9 | 10 | public RemoveBlenderInstance form; 11 | 12 | public RemoveBlenderInstanceWrapper() { 13 | super(true); 14 | init(); 15 | setTitle("Remove Blender Instance"); 16 | } 17 | 18 | @Override 19 | protected @Nullable JComponent createCenterPanel() { 20 | form = new RemoveBlenderInstance(); 21 | return form.getJComponent(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/debug_popup/DebugPopupWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.debug_popup; 2 | 3 | import com.intellij.openapi.ui.DialogWrapper; 4 | import org.jetbrains.annotations.NotNull; 5 | import org.jetbrains.annotations.Nullable; 6 | 7 | import javax.swing.*; 8 | 9 | public class DebugPopupWrapper extends DialogWrapper { 10 | 11 | public DebugPopup form; 12 | 13 | public DebugPopupWrapper() { 14 | super(true); 15 | setTitle("New Debug Information"); 16 | init(); 17 | } 18 | 19 | @Override 20 | protected Action @NotNull [] createActions() { 21 | return new Action[]{this.getOKAction()}; 22 | } 23 | 24 | @Override 25 | protected @Nullable JComponent createCenterPanel() { 26 | form = new DebugPopup(); 27 | return form.getJComponent(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 |

Changelog

2 |

v2023.2

3 | 8 |

v2023.1

9 | 15 |

v2022.3

16 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_panel/NewBlenderPanelWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_panel; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | 10 | public class NewBlenderPanelWrapper extends DialogWrapper { 11 | 12 | private final Project project; 13 | public NewBlenderPanel form; 14 | 15 | public NewBlenderPanelWrapper(@NotNull Project project) { 16 | super(true); 17 | this.project = project; 18 | init(); 19 | setTitle("Create New Blender Panel"); 20 | } 21 | 22 | @Override 23 | protected @Nullable JComponent createCenterPanel() { 24 | form = new NewBlenderPanel(project); 25 | return form.getJComponent(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_addon/NewBlenderAddonWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_addon; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | 10 | public class NewBlenderAddonWrapper extends DialogWrapper { 11 | 12 | private final Project project; 13 | public NewBlenderAddon form; 14 | 15 | public NewBlenderAddonWrapper(@NotNull Project project) { 16 | super(true); 17 | this.project = project; 18 | init(); 19 | setTitle("Create New Blender Addon"); 20 | } 21 | 22 | @Override 23 | protected @Nullable JComponent createCenterPanel() { 24 | form = new NewBlenderAddon(project, myOKAction); 25 | return form.getJComponent(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_operator/NewBlenderOperatorWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_operator; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.swing.*; 9 | 10 | public class NewBlenderOperatorWrapper extends DialogWrapper { 11 | 12 | private final Project project; 13 | public NewBlenderOperator form; 14 | 15 | public NewBlenderOperatorWrapper(@NotNull Project project) { 16 | super(true); 17 | this.project = project; 18 | init(); 19 | setTitle("Create New Blender Operator"); 20 | } 21 | 22 | @Override 23 | protected @Nullable JComponent createCenterPanel() { 24 | form = new NewBlenderOperator(project); 25 | return form.getJComponent(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/resources/locale/BlendCharm.properties: -------------------------------------------------------------------------------- 1 | addBlenderInstance.extraSettings=Extra Settings 2 | addBlenderInstance.environmentVariables=Environment Variables 3 | addBlenderInstance.name=Name 4 | addBlenderInstance.path=Path 5 | newBlenderAddon.description=Description: 6 | newBlenderAddon.author=Author: 7 | newBlenderAddon.name=Name: 8 | newBlenderOperator.label=Label: 9 | newBlenderOperator.idName=ID Name 10 | newBlenderPanel.label=Label: 11 | newBlenderPanel.category=Category: 12 | newBlenderPanel.region=&Region: 13 | newBlenderPanel.space=&Space: 14 | removeBlenderInstance.removeBlenderInstance=Remove Blender instance? 15 | blenderToolWindow.blenderInstances=&Blender Instances: 16 | blenderToolWindow.noBlenderInstanceRunning=No Blender Instance Running... 17 | debugPopup.title=The debug session has been terminated due to newer information. 18 | debugPopup.description=This is totally normal on first executions, please restart the debug mode. -------------------------------------------------------------------------------- /src/main/java/data/BlenderInstance.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import javax.xml.bind.annotation.XmlElement; 4 | import java.util.Map; 5 | 6 | public class BlenderInstance { 7 | @XmlElement(name = "path") 8 | public String path; 9 | @XmlElement(name = "name") 10 | public String name; 11 | @XmlElement(name = "addonPath") 12 | public String addonPath; 13 | @XmlElement(name = "environment") 14 | public Map environment; 15 | 16 | @SuppressWarnings("unused") 17 | public BlenderInstance() { 18 | } 19 | 20 | public BlenderInstance(String path, String name, Map environment) { 21 | this.path = path; 22 | this.name = name; 23 | this.environment = environment; 24 | } 25 | 26 | public String getAddonPath() { 27 | return addonPath == null ? null : addonPath.toLowerCase(); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/data/RunningBlenderProcessRenderer.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import com.intellij.icons.AllIcons; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | 8 | public class RunningBlenderProcessRenderer extends DefaultListCellRenderer implements ListCellRenderer { 9 | 10 | @Override 11 | public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 12 | RunningBlenderProcess blenderProcess = (RunningBlenderProcess) value; 13 | setText(blenderProcess.getProcessName()); 14 | setIcon(blenderProcess.isDebug() ? AllIcons.Actions.StartDebugger : AllIcons.Actions.Execute); 15 | 16 | setBackground(isSelected ? list.getSelectionBackground() : list.getBackground()); 17 | setForeground(isSelected ? list.getSelectionForeground() : list.getForeground()); 18 | 19 | setEnabled(true); 20 | setFont(list.getFont()); 21 | 22 | return this; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/util/core/MyFileUtils.java: -------------------------------------------------------------------------------- 1 | package util.core; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | /** 9 | * Just some file function... the original class is way more complete... 10 | */ 11 | public class MyFileUtils { 12 | 13 | public static boolean cantCreateDirectory(String path) { 14 | File file = new File(path); 15 | return file.exists() ? !file.isDirectory() : !file.mkdirs(); 16 | } 17 | 18 | public static String readString(Path path) { 19 | try { 20 | return Files.readString(path); 21 | } catch (IOException e) { 22 | e.printStackTrace(); 23 | } 24 | return null; 25 | } 26 | 27 | public static void write(Path path, String text) { 28 | try { 29 | Files.write(path, text.getBytes()); 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/data/CommunicationData.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | public class CommunicationData { 4 | 5 | public static final String REQUEST = "request"; 6 | public static final int REQUEST_PLUGIN_FOLDER = 0; 7 | public static final String REQUEST_PLUGIN_FOLDER_PROJECT_FOLDER = "project"; 8 | public static final String REQUEST_PLUGIN_FOLDER_ADDON_NAMES = "addon_names"; 9 | 10 | public static final int REQUEST_PLUGIN_REFRESH = 1; 11 | public static final String REQUEST_PLUGIN_REFRESH_NAME_LIST = "name_list"; 12 | 13 | public static final String RESPONSE = "response"; 14 | public static final int RESPONSE_PLUGIN_FOLDER = REQUEST_PLUGIN_FOLDER; 15 | public static final String RESPONSE_PLUGIN_FOLDER_PLUGIN_PATH = "plugin_path"; 16 | public static final int RESPONSE_PLUGIN_REFRESH = REQUEST_PLUGIN_REFRESH; 17 | public static final String RESPONSE_PLUGIN_REFRESH_STATUS = "status"; 18 | public static final String RESPONSE_PLUGIN_REFRESH_NAME_LIST = "name_list"; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/inspectionDescriptions/PanelWarnId.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Highlight for the two warnings for Panels:

5 | 6 | Warning: 'NAME' does not contain '_PT_' with prefix and suffix.
7 | Warning: 'NAME' doesn't have upper case alphanumeric prefix.

8 | 9 | Following the naming convention from the official API. 10 |

11 | 12 |

This aim to help the user find the cause of two possible warning that may be encountered when installing the addon.


13 | 14 | Prefix and Suffix: posted in Blender.StackExchange.
15 | Alpha-Numeric Prefix: posted in Blender.StackExchange.

16 | 17 |

Powered by: Blend-Charm plugin
18 | 19 | -------------------------------------------------------------------------------- /src/main/java/ui/tool_window/BlenderToolWindowFactory.java: -------------------------------------------------------------------------------- 1 | package ui.tool_window; 2 | 3 | import plugin_settings.PluginSettings; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.openapi.wm.ToolWindow; 6 | import com.intellij.openapi.wm.ToolWindowFactory; 7 | import com.intellij.ui.content.Content; 8 | import com.intellij.ui.content.ContentFactory; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | public class BlenderToolWindowFactory implements ToolWindowFactory { 12 | 13 | @Override 14 | public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) { 15 | BlenderToolWindow myToolWindow = new BlenderToolWindow(project); 16 | ContentFactory contentFactory = ContentFactory.getInstance(); 17 | Content content = contentFactory.createContent(myToolWindow.getContent(), "", false); 18 | toolWindow.getContentManager().addContent(content); 19 | toolWindow.setStripeTitle(toolWindow.getStripeTitle() + " [" + PluginSettings.getStripeVersion() + "]"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/add_blender_instance/AddBlenderInstanceWrapper.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.add_blender_instance; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.ui.DialogWrapper; 5 | import data.BlenderInstance; 6 | import org.jetbrains.annotations.NotNull; 7 | import org.jetbrains.annotations.Nullable; 8 | 9 | import javax.swing.*; 10 | 11 | public class AddBlenderInstanceWrapper extends DialogWrapper { 12 | 13 | private final Project project; 14 | public final BlenderInstance from; 15 | public AddBlenderInstance form; 16 | 17 | public AddBlenderInstanceWrapper(@NotNull Project project, BlenderInstance from) { 18 | super(true); 19 | this.project = project; 20 | this.from = from; 21 | init(); 22 | setTitle((from == null ? "Add" : "Update") + " Blender Instance"); 23 | } 24 | 25 | @Override 26 | protected @Nullable JComponent createCenterPanel() { 27 | form = new AddBlenderInstance(project, from); 28 | return form.getJComponent(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright © 2021-2023 BlackStartx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/actions/MarkAsAddonProjectAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import data.VirtualBlenderFile; 8 | import icons.BlendCharmIcons; 9 | import org.jetbrains.annotations.NotNull; 10 | import util.MyProjectHolder; 11 | 12 | public class MarkAsAddonProjectAction extends AnAction { 13 | 14 | private final MyProjectHolder project; 15 | 16 | MarkAsAddonProjectAction(MyProjectHolder project) { 17 | super("Mark as Addon Project", "Mark the project as a Plugin Folder", BlendCharmIcons.BLENDER_FOLDER_ICON); 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 23 | VirtualFile[] virtualFiles = anActionEvent.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); 24 | 25 | if (virtualFiles == null || virtualFiles.length == 0) return; 26 | new VirtualBlenderFile(project, virtualFiles[0]).markAsAddonProject(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/services/BlendCharmPersistentData.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import settings.BlenderSettingsRaw; 4 | import com.intellij.openapi.components.PersistentStateComponent; 5 | import com.intellij.openapi.components.State; 6 | import com.intellij.openapi.components.Storage; 7 | import com.intellij.openapi.project.Project; 8 | import com.sun.istack.Nullable; 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | @State(name = "BlendCharmPersistentData", storages = {@Storage("BlendCharmSettings.xml")}) 12 | public class BlendCharmPersistentData implements PersistentStateComponent { 13 | 14 | @Nullable 15 | public static BlendCharmPersistentData getInstance(Project project) { 16 | return project.getService(BlendCharmPersistentData.class); 17 | } 18 | 19 | private BlenderSettingsRaw blenderSettingsRaw = new BlenderSettingsRaw(); 20 | 21 | @Override 22 | public @NotNull BlenderSettingsRaw getState() { 23 | return blenderSettingsRaw; 24 | } 25 | 26 | @Override 27 | public void loadState(@NotNull BlenderSettingsRaw blendCharmPersistentData) { 28 | blenderSettingsRaw = blendCharmPersistentData; 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/actions/UnmarkAsAddonProjectAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import data.VirtualBlenderFile; 8 | import icons.BlendCharmIcons; 9 | import org.jetbrains.annotations.NotNull; 10 | import util.MyProjectHolder; 11 | 12 | public class UnmarkAsAddonProjectAction extends AnAction { 13 | 14 | private final MyProjectHolder project; 15 | 16 | UnmarkAsAddonProjectAction(MyProjectHolder project) { 17 | super("Unmark as Addon Project", "Unmark the project as a Plugin Folder", BlendCharmIcons.BLENDER_FOLDER_ICON); 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 23 | VirtualFile[] virtualFiles = anActionEvent.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); 24 | 25 | if (virtualFiles == null || virtualFiles.length == 0) return; 26 | new VirtualBlenderFile(project, virtualFiles[0]).unmarkAsAddonProject(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/util/core/socket/server/MyServerSocket.java: -------------------------------------------------------------------------------- 1 | package util.core.socket.server; 2 | 3 | import util.core.socket.MySocketConnection; 4 | 5 | import java.io.IOException; 6 | import java.net.InetAddress; 7 | import java.net.ServerSocket; 8 | 9 | public class MyServerSocket { 10 | private final String ip; 11 | private final int port; 12 | private ServerSocket serverSocket; 13 | 14 | public MyServerSocket(String ip, int port) { 15 | this.ip = ip; 16 | this.port = port; 17 | } 18 | 19 | public boolean open() { 20 | if (this.serverSocket != null) return true; 21 | 22 | try { 23 | this.serverSocket = new ServerSocket(port, 50, InetAddress.getByName(ip)); 24 | return true; 25 | } catch (Exception e) { 26 | return false; 27 | } 28 | } 29 | 30 | public void asyncWaitClient(MySocketConnection.MySocketConnectionInterface socketConnectionInterface) { 31 | new Thread(() -> { 32 | try { 33 | new MySocketConnection(this.serverSocket.accept(), socketConnectionInterface); 34 | } catch (IOException ignored) { 35 | } 36 | }).start(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/data/BlenderExeFileChooserDescriptor.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 4 | import com.intellij.openapi.fileChooser.FileElement; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | 7 | import java.util.Set; 8 | 9 | public class BlenderExeFileChooserDescriptor extends FileChooserDescriptor { 10 | private static final Set extensions = Set.of("exe", "app"); 11 | 12 | public BlenderExeFileChooserDescriptor() { 13 | super(true, false, false, false, false, false); 14 | } 15 | 16 | @Override 17 | public boolean isFileSelectable(VirtualFile file) { 18 | if (file == null) return false; 19 | String extension = file.getExtension(); 20 | return extension != null && extensions.contains(extension.toLowerCase()); 21 | } 22 | 23 | @Override 24 | public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) { 25 | if (FileElement.isFileHidden(file) && !showHiddenFiles) return false; 26 | if (file.isDirectory()) return true; 27 | 28 | String extension = file.getExtension(); 29 | return extension != null && extensions.contains(extension.toLowerCase()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/util/MySwingUtil.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import com.intellij.ui.components.JBLabel; 4 | import com.intellij.ui.components.JBTextField; 5 | 6 | import javax.swing.event.DocumentEvent; 7 | import javax.swing.event.DocumentListener; 8 | import java.awt.event.MouseAdapter; 9 | import java.awt.event.MouseEvent; 10 | 11 | public class MySwingUtil { 12 | 13 | public static void setLabelOnClickListener(JBLabel to, Runnable method) { 14 | to.addMouseListener(new MouseAdapter() { 15 | @Override 16 | public void mouseClicked(MouseEvent e) { 17 | method.run(); 18 | } 19 | }); 20 | } 21 | 22 | public static void setFieldTextChangeListener(JBTextField addon_name, Runnable onNameChange) { 23 | addon_name.getDocument().addDocumentListener(new DocumentListener() { 24 | public void changedUpdate(DocumentEvent e) { 25 | onNameChange.run(); 26 | } 27 | 28 | public void removeUpdate(DocumentEvent e) { 29 | onNameChange.run(); 30 | } 31 | 32 | public void insertUpdate(DocumentEvent e) { 33 | onNameChange.run(); 34 | } 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/actions/MarkAsAddonAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import icons.BlendCharmIcons; 4 | import data.VirtualBlenderFile; 5 | import util.MyProjectHolder; 6 | import com.intellij.openapi.actionSystem.AnAction; 7 | import com.intellij.openapi.actionSystem.AnActionEvent; 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class MarkAsAddonAction extends AnAction { 13 | 14 | private final MyProjectHolder project; 15 | 16 | MarkAsAddonAction(MyProjectHolder project) { 17 | super("Mark as Addon Folder", "Mark the selected folder as a Plugin Folder", BlendCharmIcons.BLENDER_FOLDER_ICON); 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 23 | VirtualFile[] virtualFiles = anActionEvent.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); 24 | 25 | if (virtualFiles == null) return; 26 | 27 | for (VirtualFile file : virtualFiles) { 28 | VirtualBlenderFile virtualBlenderFile = new VirtualBlenderFile(project, file); 29 | virtualBlenderFile.markAsAddonFolder(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/services/BlendCharmRepositoryIconManager.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import icons.BlendCharmIcons; 4 | import data.VirtualBlenderFile; 5 | import util.MyProjectHolder; 6 | import com.intellij.ide.IconProvider; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.impl.file.PsiDirectoryImpl; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import javax.swing.*; 14 | 15 | public class BlendCharmRepositoryIconManager extends IconProvider { 16 | @Nullable 17 | @Override 18 | public Icon getIcon(@NotNull PsiElement psiElement, int i) { 19 | if (!(psiElement instanceof PsiDirectoryImpl c)) return null; 20 | 21 | VirtualFile virtualFile = c.getVirtualFile(); 22 | MyProjectHolder project = new MyProjectHolder(psiElement.getProject()); 23 | 24 | VirtualBlenderFile virtualBlenderFile = new VirtualBlenderFile(project, virtualFile); 25 | boolean addon = virtualBlenderFile.isSubRootAndBlenderAddon() || virtualBlenderFile.isRootAndBlenderProject(); 26 | 27 | return !addon ? null : virtualBlenderFile.isSource() ? BlendCharmIcons.BLENDER_SRC_FOLDER_ICON : BlendCharmIcons.BLENDER_FOLDER_ICON; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_operator/NewBlenderOperator.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_operator; 2 | 3 | import com.google.common.base.CaseFormat; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.ui.components.JBTextField; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.swing.*; 9 | 10 | public class NewBlenderOperator extends JDialog { 11 | 12 | private JPanel contentPane; 13 | private JBTextField label; 14 | private JBTextField idName; 15 | 16 | public NewBlenderOperator(@NotNull Project project) { 17 | setContentPane(contentPane); 18 | } 19 | 20 | private String getLabelForCode() { 21 | return label.getText().replaceAll("[^A-Za-z]+", ""); 22 | } 23 | 24 | public String getOperatorClassName() { 25 | return getLabelForCode() + "Operator"; 26 | } 27 | 28 | public String getLabel() { 29 | return label.getText(); 30 | } 31 | 32 | public String getIdName() { 33 | return idName.getText(); 34 | } 35 | 36 | public String getOperatorFileName() { 37 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, getLabelForCode()) + ".py"; 38 | } 39 | 40 | JComponent getJComponent() { 41 | return contentPane; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/actions/UnmarkAsAddonAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import icons.BlendCharmIcons; 4 | import data.VirtualBlenderFile; 5 | import util.MyProjectHolder; 6 | import com.intellij.openapi.actionSystem.AnAction; 7 | import com.intellij.openapi.actionSystem.AnActionEvent; 8 | import com.intellij.openapi.actionSystem.PlatformDataKeys; 9 | import com.intellij.openapi.vfs.VirtualFile; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class UnmarkAsAddonAction extends AnAction { 13 | 14 | private final MyProjectHolder project; 15 | 16 | UnmarkAsAddonAction(MyProjectHolder project) { 17 | super("Unmark as Addon Folder", "Unmark the selected folder as a Plugin Folder", BlendCharmIcons.BLENDER_FOLDER_ICON); 18 | this.project = project; 19 | } 20 | 21 | @Override 22 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 23 | VirtualFile[] virtualFiles = anActionEvent.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); 24 | 25 | if (project == null || virtualFiles == null) return; 26 | 27 | for (VirtualFile file : virtualFiles) { 28 | VirtualBlenderFile virtualBlenderFile = new VirtualBlenderFile(project, file); 29 | virtualBlenderFile.unmarkAsAddonFolder(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /update-urls/community-edition/updatePlugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | BlackStartx 11 | Blend-Charm 12 | 13 | 16 |
17 | Available for both Community or Professional editions of PyCharm. 18 | ]]> 19 |
20 | 21 | 26 | 27 |
28 |
-------------------------------------------------------------------------------- /update-urls/professional-edition/updatePlugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | BlackStartx 11 | Blend-Charm 12 | 13 | 16 |
17 | Available for both Community or Professional editions of PyCharm. 18 | ]]> 19 |
20 | 21 | 26 | 27 |
28 |
-------------------------------------------------------------------------------- /src/main/java/settings/BlenderSettingsRaw.java: -------------------------------------------------------------------------------- 1 | package settings; 2 | 3 | import com.intellij.util.xmlb.Converter; 4 | import com.intellij.util.xmlb.annotations.OptionTag; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.Nullable; 7 | 8 | import javax.xml.bind.JAXB; 9 | import java.io.StringReader; 10 | import java.io.StringWriter; 11 | 12 | public class BlenderSettingsRaw { 13 | @OptionTag(converter = BlenderSettingsRaw.BlenderSettingsConverter.class) 14 | private BlenderSettingsData blenderSettings; 15 | 16 | public BlenderSettingsData getBlenderSettings() { 17 | if (blenderSettings == null) blenderSettings = new BlenderSettingsData(); 18 | return blenderSettings; 19 | } 20 | 21 | public static class BlenderSettingsConverter extends Converter { 22 | 23 | @Override 24 | public @Nullable BlenderSettingsData fromString(@NotNull String value) { 25 | StringReader reader = new StringReader(value); 26 | return JAXB.unmarshal(reader, BlenderSettingsData.class); 27 | } 28 | 29 | @Override 30 | public @Nullable String toString(@NotNull BlenderSettingsData value) { 31 | StringWriter sw = new StringWriter(); 32 | JAXB.marshal(value, sw); 33 | return sw.toString(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/util/MyProjectHolder.java: -------------------------------------------------------------------------------- 1 | package util; 2 | 3 | import com.intellij.openapi.project.Project; 4 | import com.intellij.openapi.vfs.VfsUtil; 5 | import com.intellij.openapi.vfs.VirtualFile; 6 | import settings.BlenderSettings; 7 | 8 | import java.net.MalformedURLException; 9 | import java.net.URL; 10 | 11 | public class MyProjectHolder { 12 | 13 | public final VirtualFile projectVirtualFile; 14 | private final Project project; 15 | 16 | public MyProjectHolder(Project project) { 17 | this.project = project; 18 | this.projectVirtualFile = getProjectVirtualFile(project); 19 | } 20 | 21 | private static VirtualFile getProjectVirtualFile(Project project) { 22 | try { 23 | return VfsUtil.findFileByURL(new URL("file:\\" + project.getBasePath())); 24 | } catch (MalformedURLException e) { 25 | return null; 26 | } 27 | } 28 | 29 | public String addonContainerPath() { 30 | return settings().isBlenderProject() ? projectVirtualFile.getParent().getPath() : getBasePath(); 31 | } 32 | 33 | public String getBasePath() { 34 | return project.getBasePath(); 35 | } 36 | 37 | public Project getProject() { 38 | return project; 39 | } 40 | 41 | public BlenderSettings settings() { 42 | return BlenderSettings.getBlenderSettings(this); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/remove_blender_instance/RemoveBlenderInstance.form: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | -------------------------------------------------------------------------------- /src/main/java/settings/Config.java: -------------------------------------------------------------------------------- 1 | package settings; 2 | 3 | import com.intellij.openapi.options.Configurable; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.ui.components.JBCheckBox; 6 | import com.intellij.util.ui.FormBuilder; 7 | 8 | import javax.swing.*; 9 | 10 | public class Config implements Configurable { 11 | 12 | private final Project project; 13 | private final BlenderSettings settings; 14 | private final JBCheckBox checkBox; 15 | 16 | public Config(Project project) { 17 | this.project = project; 18 | this.settings = BlenderSettings.getBlenderSettings(project); 19 | this.checkBox = new JBCheckBox("Enable verbose.", settings.data().showVerbose); 20 | } 21 | 22 | @Override 23 | public String getDisplayName() { 24 | return "Blend-Charm Settings"; 25 | } 26 | 27 | @Override 28 | public JComponent createComponent() { 29 | return FormBuilder.createFormBuilder() 30 | .addComponent(checkBox, 1) 31 | .addComponentFillVertically(new JPanel(), 0) 32 | .getPanel(); 33 | } 34 | 35 | @Override 36 | public boolean isModified() { 37 | return settings.data().showVerbose != checkBox.isSelected(); 38 | } 39 | 40 | @Override 41 | public void reset() { 42 | checkBox.setSelected(settings.data().showVerbose); 43 | } 44 | 45 | @Override 46 | public void apply() { 47 | settings.data().showVerbose = checkBox.isSelected(); 48 | project.save(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/data/RunningBlenderProcess.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import com.intellij.execution.ui.ConsoleView; 4 | import util.core.socket.MySocketConnection; 5 | import com.intellij.execution.process.OSProcessHandler; 6 | 7 | import javax.swing.*; 8 | 9 | public class RunningBlenderProcess { 10 | 11 | private final BlenderInstance instance; 12 | private final OSProcessHandler processHandler; 13 | private final boolean debugMode; 14 | 15 | private MySocketConnection socket; 16 | private JComponent component; 17 | private ConsoleView console; 18 | 19 | public RunningBlenderProcess(BlenderInstance instance, OSProcessHandler processHandler, boolean debugMode) { 20 | this.instance = instance; 21 | this.processHandler = processHandler; 22 | this.debugMode = debugMode; 23 | this.component = new JPanel(); 24 | } 25 | 26 | public String getProcessName() { 27 | return instance.name; 28 | } 29 | 30 | public boolean isDebug() { 31 | return debugMode; 32 | } 33 | 34 | public OSProcessHandler getProcess() { 35 | return processHandler; 36 | } 37 | 38 | public BlenderInstance getInstance() { 39 | return instance; 40 | } 41 | 42 | public void assignSocket(MySocketConnection socket) { 43 | this.socket = socket; 44 | } 45 | 46 | public MySocketConnection getSocket() { 47 | return socket; 48 | } 49 | 50 | public JComponent getComponent() { 51 | return component; 52 | } 53 | 54 | public ConsoleView getConsole() { 55 | return console; 56 | } 57 | 58 | public void setComponent(JComponent component) { 59 | this.component = component; 60 | } 61 | 62 | public void setConsole(ConsoleView console) { 63 | this.console = console; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_addon/NewBlenderAddon.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_addon; 2 | 3 | import com.intellij.ui.components.JBTextField; 4 | import util.MySwingUtil; 5 | import com.intellij.openapi.project.Project; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.swing.*; 9 | import java.nio.file.InvalidPathException; 10 | import java.nio.file.Paths; 11 | 12 | public class NewBlenderAddon extends JDialog { 13 | 14 | private final Action okAction; 15 | 16 | private JPanel contentPane; 17 | private JBTextField addon_name; 18 | private JBTextField addon_author; 19 | private JBTextField addon_description; 20 | 21 | public NewBlenderAddon(@NotNull Project project, @NotNull Action okAction) { 22 | this.okAction = okAction; 23 | this.okAction.setEnabled(false); 24 | 25 | MySwingUtil.setFieldTextChangeListener(addon_name, this::onNameChange); 26 | 27 | setContentPane(contentPane); 28 | } 29 | 30 | private void onNameChange() { 31 | this.okAction.setEnabled(isNameValid()); 32 | } 33 | 34 | public String getBlenderAddonFolderName() { 35 | return "Addon - " + addon_name.getText(); 36 | } 37 | 38 | public String getBlenderAddonName() { 39 | return addon_name.getText(); 40 | } 41 | 42 | public String getBlenderAddonAuthor() { 43 | return addon_author.getText(); 44 | } 45 | 46 | public String getBlenderAddonDescription() { 47 | return addon_description.getText(); 48 | } 49 | 50 | JComponent getJComponent() { 51 | return contentPane; 52 | } 53 | 54 | public boolean isNameValid() { 55 | if (addon_name.getText().isEmpty()) return false; 56 | 57 | try { 58 | Paths.get(addon_name.getText()); 59 | } catch (InvalidPathException ex) { 60 | return false; 61 | } 62 | return true; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/actions/CreateNewBlenderOperatorAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl; 6 | import data.VirtualBlenderFile; 7 | import org.jetbrains.annotations.NotNull; 8 | import ui.dialogs.new_blender_operator.NewBlenderOperatorWrapper; 9 | import util.MyInputStreamHelper; 10 | import util.MyProjectHolder; 11 | import util.core.MyFileUtils; 12 | 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | public class CreateNewBlenderOperatorAction extends AnAction { 17 | 18 | private final MyProjectHolder project; 19 | private final VirtualBlenderFile virtualBlenderFile; 20 | 21 | CreateNewBlenderOperatorAction(MyProjectHolder project, VirtualBlenderFile virtualBlenderFile) { 22 | super("Create Operator"); 23 | this.virtualBlenderFile = virtualBlenderFile; 24 | this.project = project; 25 | } 26 | 27 | @Override 28 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 29 | NewBlenderOperatorWrapper dialog = new NewBlenderOperatorWrapper(project.getProject()); 30 | if (dialog.showAndGet()) { 31 | String panelsPath = Paths.get(virtualBlenderFile.getSubRootVirtualFile().getPath(), "operators").toString(); 32 | 33 | String stream = MyInputStreamHelper.readString(this.getClass().getClassLoader().getResourceAsStream("Python/Templates/new_operator.py")); 34 | stream = stream.replace("OPERATOR_CLASS_NAME", dialog.form.getOperatorClassName()); 35 | stream = stream.replace("$ID_NAME$", dialog.form.getIdName()); 36 | stream = stream.replace("$LABEL$", dialog.form.getLabel()); 37 | 38 | if (MyFileUtils.cantCreateDirectory(panelsPath)) return; 39 | MyFileUtils.write(Path.of(Paths.get(panelsPath, dialog.form.getOperatorFileName()).toString()), stream); 40 | LocalFileSystemImpl.getInstance().refresh(true); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/icons/blender_logo_gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/actions/CreateNewBlenderPanelAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl; 6 | import data.VirtualBlenderFile; 7 | import org.jetbrains.annotations.NotNull; 8 | import ui.dialogs.new_blender_panel.NewBlenderPanelWrapper; 9 | import util.MyInputStreamHelper; 10 | import util.MyProjectHolder; 11 | import util.core.MyFileUtils; 12 | 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | 16 | public class CreateNewBlenderPanelAction extends AnAction { 17 | 18 | private final MyProjectHolder project; 19 | private final VirtualBlenderFile virtualBlenderFile; 20 | 21 | CreateNewBlenderPanelAction(MyProjectHolder project, VirtualBlenderFile virtualBlenderFile) { 22 | super("Create Panel"); 23 | this.virtualBlenderFile = virtualBlenderFile; 24 | this.project = project; 25 | } 26 | 27 | @Override 28 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 29 | NewBlenderPanelWrapper dialog = new NewBlenderPanelWrapper(project.getProject()); 30 | if (dialog.showAndGet()) { 31 | String panelsPath = Paths.get(virtualBlenderFile.getSubRootVirtualFile().getPath(), "panels").toString(); 32 | 33 | String stream = MyInputStreamHelper.readString(this.getClass().getClassLoader().getResourceAsStream("Python/Templates/new_panel.py")); 34 | stream = stream.replace("PANEL_CLASS_NAME", dialog.form.getPanelClassName()); 35 | stream = stream.replace("$ID_NAME$", dialog.form.getPanelIdName()); 36 | stream = stream.replace("$LABEL$", dialog.form.getLabel()); 37 | stream = stream.replace("$CATEGORY$", dialog.form.getCategory()); 38 | stream = stream.replace("$SPACE_TYPE$", dialog.form.getSpaceType()); 39 | stream = stream.replace("$REGION_TYPE$", dialog.form.getRegionType()); 40 | 41 | if (MyFileUtils.cantCreateDirectory(panelsPath)) return; 42 | MyFileUtils.write(Path.of(Paths.get(panelsPath, dialog.form.getPanelFileName()).toString()), stream); 43 | LocalFileSystemImpl.getInstance().refresh(true); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/icons/blender_logo_gray_big.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/services/inspections/OperatorErrId.java: -------------------------------------------------------------------------------- 1 | package services.inspections; 2 | 3 | import com.intellij.codeInspection.LocalQuickFix; 4 | import com.intellij.codeInspection.ProblemDescriptor; 5 | import com.intellij.codeInspection.ProblemsHolder; 6 | import com.intellij.codeInspection.util.IntentionFamilyName; 7 | import com.intellij.codeInspection.util.IntentionName; 8 | import com.intellij.openapi.project.Project; 9 | import com.jetbrains.python.psi.*; 10 | import org.jetbrains.annotations.NotNull; 11 | import services.inspections.base.OperatorIdInspection; 12 | 13 | public class OperatorErrId extends OperatorIdInspection { 14 | private static final String description = "Blender: Id should be lowercase in order to be registered in to Blender."; 15 | 16 | @Override 17 | public String correctClass() { 18 | return "bpy.types.Operator"; 19 | } 20 | 21 | @Override 22 | protected void visitor(@NotNull ProblemsHolder holder, @NotNull PyAssignmentStatement node) { 23 | for (PyExpression expression : node.getTargets()) { 24 | PyExpression expressionValue = getPyExpression(node, expression); 25 | if (expressionValue == null) continue; 26 | 27 | String text = expressionValue.getText(); 28 | if (text.equals(text.toLowerCase())) continue; 29 | 30 | holder.registerProblem(expressionValue, description, new OperatorErrId.BlIdLowercaseFix()); 31 | } 32 | } 33 | 34 | private static class BlIdLowercaseFix implements LocalQuickFix { 35 | 36 | @Override 37 | public @IntentionName @NotNull String getName() { 38 | return "Convert to lowercase"; 39 | } 40 | 41 | @Override 42 | public @IntentionFamilyName @NotNull String getFamilyName() { 43 | return getName(); 44 | } 45 | 46 | @Override 47 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { 48 | PyExpression binaryExpression = (PyExpression) descriptor.getPsiElement(); 49 | 50 | PyExpression expression = PyElementGenerator.getInstance(project).createExpressionFromText(LanguageLevel.getDefault(), binaryExpression.getText().toLowerCase()); 51 | binaryExpression.replace(expression); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | org.blackstartx.blend-charm 3 | Blend-Charm 4 | BlackStartx 5 | 2024.2 6 | 7 | 10 |
11 | Available for both Community or Professional editions of PyCharm. 12 | ]]>
13 | 14 | com.intellij.modules.lang 15 | com.intellij.modules.python 16 | com.intellij.modules.xdebugger 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
-------------------------------------------------------------------------------- /src/main/java/actions/DynamicActionGroup.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import data.VirtualBlenderFile; 4 | import util.MyProjectHolder; 5 | import com.intellij.openapi.actionSystem.*; 6 | import com.intellij.openapi.vfs.VirtualFile; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | import java.util.ArrayList; 11 | 12 | public class DynamicActionGroup extends ActionGroup { 13 | 14 | @Override 15 | public AnAction @NotNull [] getChildren(@Nullable AnActionEvent anActionEvent) { 16 | if (anActionEvent == null) return new AnAction[0]; 17 | 18 | MyProjectHolder project = new MyProjectHolder(anActionEvent.getProject()); 19 | VirtualFile[] virtualFiles = anActionEvent.getDataContext().getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY); 20 | 21 | if (virtualFiles == null || virtualFiles.length == 0) return new AnAction[0]; 22 | boolean root = true; 23 | boolean addon = true; 24 | boolean subRoot = true; 25 | 26 | for (VirtualFile file : virtualFiles) { 27 | VirtualBlenderFile blenderFile = new VirtualBlenderFile(project, file); 28 | if (!blenderFile.isRoot) root = false; 29 | if (!blenderFile.isSubRoot) subRoot = false; 30 | if (!blenderFile.isBlenderAddon()) addon = false; 31 | } 32 | 33 | ArrayList actions = new ArrayList<>(); 34 | if (addon && virtualFiles.length == 1) { 35 | VirtualBlenderFile virtualBlenderFile = new VirtualBlenderFile(project, virtualFiles[0]); 36 | actions.add(new CreateNewBlenderPanelAction(project, virtualBlenderFile)); 37 | actions.add(new CreateNewBlenderOperatorAction(project, virtualBlenderFile)); 38 | actions.add(new Separator()); 39 | if (subRoot) actions.add(new UnmarkAsAddonAction(project)); 40 | if (root) actions.add(new UnmarkAsAddonProjectAction(project)); 41 | } else { 42 | actions.add(new CreateNewBlenderAddonAction(project)); 43 | if (subRoot && !project.settings().isBlenderProject()) { 44 | actions.add(new Separator()); 45 | actions.add(new MarkAsAddonAction(project)); 46 | } 47 | if (root) { 48 | actions.add(new Separator()); 49 | actions.add(new MarkAsAddonProjectAction(project)); 50 | } 51 | } 52 | return actions.toArray(new AnAction[0]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/resources/icons/blender_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_operator/NewBlenderOperator.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 |
51 | -------------------------------------------------------------------------------- /src/main/java/services/inspections/base/OperatorIdInspection.java: -------------------------------------------------------------------------------- 1 | package services.inspections.base; 2 | 3 | import com.intellij.codeInspection.LocalInspectionTool; 4 | import com.intellij.codeInspection.LocalInspectionToolSession; 5 | import com.intellij.codeInspection.ProblemsHolder; 6 | import com.intellij.psi.PsiElementVisitor; 7 | import com.jetbrains.python.psi.PyAssignmentStatement; 8 | import com.jetbrains.python.psi.PyClass; 9 | import com.jetbrains.python.psi.PyElementVisitor; 10 | import com.jetbrains.python.psi.PyExpression; 11 | import com.jetbrains.python.psi.impl.PyReferenceExpressionImpl; 12 | import com.jetbrains.python.psi.impl.PyTargetExpressionImpl; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.Arrays; 17 | import java.util.Objects; 18 | 19 | public abstract class OperatorIdInspection extends LocalInspectionTool { 20 | protected static final String value = "bl_idname"; 21 | protected static final String separator = "."; 22 | 23 | @NotNull 24 | @Override 25 | public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { 26 | return new PyElementVisitor() { 27 | @Override 28 | public void visitPyAssignmentStatement(@NotNull PyAssignmentStatement node) { 29 | visitor(holder, node); 30 | } 31 | }; 32 | } 33 | 34 | @Nullable 35 | protected PyExpression getPyExpression(@NotNull PyAssignmentStatement node, PyExpression expression) { 36 | if (!Objects.equals(expression.getName(), value)) return null; 37 | 38 | PyExpression expressionValue = node.getAssignedValue(); 39 | if (expressionValue == null) return null; 40 | 41 | PyTargetExpressionImpl targetExpression = (PyTargetExpressionImpl) expression; 42 | PyClass targetClass = targetExpression.getContainingClass(); 43 | if (targetClass == null) return null; 44 | 45 | PyExpression[] superClasses = targetClass.getSuperClassExpressions(); 46 | if (Arrays.stream(superClasses).noneMatch(this::isClass)) return null; 47 | return expressionValue; 48 | } 49 | 50 | protected boolean isClass(PyExpression pyExpression) { 51 | if (pyExpression == null) return false; 52 | PyReferenceExpressionImpl expression = (PyReferenceExpressionImpl) pyExpression; 53 | 54 | var qualifiedName = expression.asQualifiedName(); 55 | if (qualifiedName == null) return false; 56 | 57 | var name = qualifiedName.join(separator); 58 | return name.equals(correctClass()); 59 | } 60 | 61 | public abstract String correctClass(); 62 | 63 | protected abstract void visitor(@NotNull ProblemsHolder holder, @NotNull PyAssignmentStatement node); 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/debug_popup/DebugPopup.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | -------------------------------------------------------------------------------- /src/main/java/services/BlenderConsoleFilter.java: -------------------------------------------------------------------------------- 1 | package services; 2 | 3 | import data.BlenderInstance; 4 | import settings.BlenderSettings; 5 | import com.intellij.execution.filters.*; 6 | import com.intellij.openapi.project.Project; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | public class BlenderConsoleFilter implements ConsoleFilterProvider { 11 | @Override 12 | public Filter @NotNull [] getDefaultFilters(@NotNull Project project) { 13 | return new Filter[]{new BlenderLinkFilter(project)}; 14 | } 15 | 16 | public static class BlenderLinkFilter implements Filter { 17 | 18 | private final Project project; 19 | private final BlenderSettings blenderSettings; 20 | 21 | public BlenderLinkFilter(Project project) { 22 | this.project = project; 23 | this.blenderSettings = BlenderSettings.getBlenderSettings(project); 24 | } 25 | 26 | @Nullable 27 | @Override 28 | public Result applyFilter(@NotNull String s, int end) { 29 | String projectPath = project.getBasePath(); 30 | if (projectPath == null) return null; 31 | 32 | int start = end - s.length(); 33 | for (BlenderInstance instance : blenderSettings.getBlenderInstances()) { 34 | if (instance.addonPath == null || instance.addonPath.isEmpty()) continue; 35 | int index = s.indexOf(instance.addonPath); 36 | if (index != -1) { 37 | String afterPathUnknown = s.substring(index + instance.addonPath.length()); 38 | String afterPath = afterPathUnknown.split("[\":]")[0]; 39 | String blenderFile = instance.addonPath + afterPath; 40 | String localFile = blenderFile.replace(instance.addonPath, projectPath).replace("\\", "/"); 41 | 42 | int fileStart = start + index; 43 | int line = tryGetLine(s); 44 | LazyFileHyperlinkInfo hyperlinkInfo = new LazyFileHyperlinkInfo(project, localFile, line, 0); 45 | return new Result(fileStart, fileStart + blenderFile.length(), hyperlinkInfo); 46 | } 47 | } 48 | return null; 49 | } 50 | 51 | /** 52 | * An awful way to obtain the line from two different type of strings. 53 | * 54 | * @param msg the message from the console. 55 | * @return the line the msg is referring to. 56 | */ 57 | private int tryGetLine(String msg) { 58 | try { 59 | return Integer.parseInt(msg.split("line ")[1].split(", ")[0]) - 1; 60 | } catch (Exception ignored) { 61 | } 62 | try { 63 | return Integer.parseInt(msg.split(":")[2].trim()) - 1; 64 | } catch (Exception ignored) { 65 | } 66 | return 0; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/settings/BlenderSettings.java: -------------------------------------------------------------------------------- 1 | package settings; 2 | 3 | import data.BlenderInstance; 4 | import util.MyProjectHolder; 5 | import com.intellij.openapi.project.Project; 6 | import services.BlendCharmPersistentData; 7 | 8 | import java.util.ArrayList; 9 | import java.util.stream.Collectors; 10 | 11 | public record BlenderSettings(BlenderSettingsData data) { 12 | 13 | public static BlenderSettings getBlenderSettings(MyProjectHolder project) { 14 | return getBlenderSettings(project.getProject()); 15 | } 16 | 17 | public static BlenderSettings getBlenderSettings(Project project) { 18 | return new BlenderSettings(BlendCharmPersistentData.getInstance(project).getState().getBlenderSettings()); 19 | } 20 | 21 | public boolean isBlenderProject() { 22 | return data.blenderAddOnNames != null && data.blenderAddOnNames.size() == 1 && data.blenderAddOnNames.get(0).equals("."); 23 | } 24 | 25 | public void addBlenderInstance(BlenderInstance instance) { 26 | if (data.blenderPaths == null) data.blenderPaths = new ArrayList<>(); 27 | data.blenderPaths.add(instance); 28 | } 29 | 30 | public ArrayList getBlenderInstances() { 31 | if (data.blenderPaths == null) return new ArrayList<>(); 32 | return data.blenderPaths; 33 | } 34 | 35 | public void removeBlenderInstances(BlenderInstance blenderInstance) { 36 | if (data.blenderPaths == null) return; 37 | data.blenderPaths.remove(blenderInstance); 38 | } 39 | 40 | public void addBlenderAddon(String name) { 41 | if (data.blenderAddOnNames == null) data.blenderAddOnNames = new ArrayList<>(); 42 | data.blenderAddOnNames.add(name); 43 | } 44 | 45 | public boolean isBlenderAddon(String name) { 46 | if (data.blenderAddOnNames == null) return false; 47 | return data.blenderAddOnNames.contains(name); 48 | } 49 | 50 | public void removeBlenderAddon(String name) { 51 | if (data.blenderAddOnNames == null) return; 52 | data.blenderAddOnNames.remove(name); 53 | } 54 | 55 | public void markAsAddonProject() { 56 | data.blenderAddOnNames = new ArrayList<>(); 57 | data.blenderAddOnNames.add("."); 58 | } 59 | 60 | public void unmarkAsAddonProject() { 61 | data.blenderAddOnNames = new ArrayList<>(); 62 | } 63 | 64 | public void removeDeletedAddon(MyProjectHolder project) { 65 | if (data.blenderAddOnNames == null || project.projectVirtualFile == null || isBlenderProject()) return; 66 | 67 | data.blenderAddOnNames = data.blenderAddOnNames.stream() 68 | .filter(element -> project.projectVirtualFile.findChild(element) != null) 69 | .collect(Collectors.toCollection(ArrayList::new)); 70 | } 71 | 72 | public ArrayList getBlenderAddons() { 73 | if (data.blenderAddOnNames == null) return new ArrayList<>(); 74 | return data.blenderAddOnNames; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_panel/NewBlenderPanel.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.new_blender_panel; 2 | 3 | import com.google.common.base.CaseFormat; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.ui.components.JBTextField; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import javax.swing.*; 9 | 10 | public class NewBlenderPanel extends JDialog { 11 | 12 | private JPanel contentPane; 13 | private JBTextField label; 14 | private JBTextField category; 15 | private JComboBox regions; 16 | private JComboBox spaces; 17 | 18 | public NewBlenderPanel(@NotNull Project project) { 19 | setContentPane(contentPane); 20 | 21 | setRegions(); 22 | setSpaces(); 23 | } 24 | 25 | private String getLabelForCode() { 26 | return label.getText().replaceAll("[^A-Za-z]+", ""); 27 | } 28 | 29 | public String getPanelClassName() { 30 | return getLabelForCode() + "Panel"; 31 | } 32 | 33 | public String getPanelIdName() { 34 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, getLabelForCode()) + "_PT_Panel"; 35 | } 36 | 37 | public String getPanelFileName() { 38 | return CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, getLabelForCode()) + ".py"; 39 | } 40 | 41 | public String getLabel() { 42 | return label.getText(); 43 | } 44 | 45 | public String getCategory() { 46 | return category.getText(); 47 | } 48 | 49 | public String getSpaceType() { 50 | return (String) spaces.getSelectedItem(); 51 | } 52 | 53 | public String getRegionType() { 54 | return (String) regions.getSelectedItem(); 55 | } 56 | 57 | private void setSpaces() { 58 | spaces.addItem("EMPTY"); 59 | spaces.addItem("VIEW_3D"); 60 | spaces.addItem("IMAGE_EDITOR"); 61 | spaces.addItem("NODE_EDITOR"); 62 | spaces.addItem("SEQUENCE_EDITOR"); 63 | spaces.addItem("CLIP_EDITOR"); 64 | spaces.addItem("DOPESHEET_EDITOR"); 65 | spaces.addItem("GRAPH_EDITOR"); 66 | spaces.addItem("NLA_EDITOR"); 67 | spaces.addItem("TEXT_EDITOR"); 68 | spaces.addItem("CONSOLE"); 69 | spaces.addItem("INFO"); 70 | spaces.addItem("TOPBAR"); 71 | spaces.addItem("STATUSBAR"); 72 | spaces.addItem("OUTLINER"); 73 | spaces.addItem("PROPERTIES"); 74 | spaces.addItem("FILE_BROWSER"); 75 | spaces.addItem("PREFERENCES"); 76 | } 77 | 78 | private void setRegions() { 79 | regions.addItem("WINDOW"); 80 | regions.addItem("HEADER"); 81 | regions.addItem("CHANNELS"); 82 | regions.addItem("TEMPORARY"); 83 | regions.addItem("UI"); 84 | regions.addItem("TOOLS"); 85 | regions.addItem("TOOL_PROPS"); 86 | regions.addItem("PREVIEW"); 87 | regions.addItem("HUD"); 88 | regions.addItem("NAVIGATION_BAR"); 89 | regions.addItem("EXECUTE"); 90 | regions.addItem("FOOTER"); 91 | regions.addItem("TOOL_HEADER"); 92 | } 93 | 94 | JComponent getJComponent() { 95 | return contentPane; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/data/VirtualBlenderFile.java: -------------------------------------------------------------------------------- 1 | package data; 2 | 3 | import settings.BlenderSettings; 4 | import util.MyProjectHolder; 5 | import util.MyVirtualFileHelper; 6 | import com.intellij.openapi.roots.ProjectRootManager; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | 9 | public class VirtualBlenderFile { 10 | 11 | public final MyProjectHolder project; 12 | private final BlenderSettings settings; 13 | 14 | private final VirtualFile selectedVirtualFile; 15 | private final VirtualFile subRootVirtualFile; 16 | private final VirtualFile rootVirtualFile; 17 | 18 | public final boolean isRoot; 19 | public final boolean isSubRoot; 20 | 21 | public VirtualBlenderFile(MyProjectHolder project, VirtualFile virtualFile) { 22 | this.project = project; 23 | this.selectedVirtualFile = virtualFile; 24 | 25 | VirtualFile parent = virtualFile.getParent(); 26 | 27 | this.isRoot = parent != null && virtualFile.getPath().equals(project.getBasePath()); 28 | this.isSubRoot = parent != null && parent.getPath().equals(project.getBasePath()); 29 | this.subRootVirtualFile = this.isSubRoot ? virtualFile : MyVirtualFileHelper.getProjectFirstVirtualFile(project, virtualFile); 30 | this.rootVirtualFile = project.projectVirtualFile; 31 | this.settings = BlenderSettings.getBlenderSettings(project); 32 | } 33 | 34 | /* 35 | * Data 36 | */ 37 | 38 | public boolean isRootAndBlenderProject() { 39 | return isRoot && settings.isBlenderProject(); 40 | } 41 | 42 | public boolean isSubRootAndBlenderAddon() { 43 | return isSubRoot && settings.isBlenderAddon(subRootVirtualFile.getName()); 44 | } 45 | 46 | public boolean isBlenderAddon() { 47 | return isRootAndBlenderProject() || isSubRootAndBlenderAddon(); 48 | } 49 | 50 | public String getRelativeAddonName() { 51 | return settings.isBlenderProject() ? rootVirtualFile.getName() : settings.isBlenderAddon(subRootVirtualFile.getName()) ? subRootVirtualFile.getName() : null; 52 | } 53 | 54 | public void markAsAddonFolder() { 55 | if (isSubRootAndBlenderAddon()) return; 56 | 57 | settings.addBlenderAddon(subRootVirtualFile.getName()); 58 | sync(); 59 | } 60 | 61 | public void markAsAddonProject() { 62 | settings.markAsAddonProject(); 63 | sync(); 64 | } 65 | 66 | public void unmarkAsAddonFolder() { 67 | if (!isSubRootAndBlenderAddon()) return; 68 | 69 | settings.removeBlenderAddon(subRootVirtualFile.getName()); 70 | sync(); 71 | } 72 | 73 | public void unmarkAsAddonProject() { 74 | settings.unmarkAsAddonProject(); 75 | sync(); 76 | } 77 | 78 | private void sync() { 79 | project.getProject().save(); 80 | } 81 | 82 | public VirtualFile getSubRootVirtualFile() { 83 | return subRootVirtualFile; 84 | } 85 | 86 | public boolean isSource() { 87 | for (VirtualFile f : ProjectRootManager.getInstance(project.getProject()).getContentSourceRoots()) 88 | if (f.equals(selectedVirtualFile)) return true; 89 | return false; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/actions/CreateNewBlenderAddonAction.java: -------------------------------------------------------------------------------- 1 | package actions; 2 | 3 | import com.intellij.openapi.actionSystem.AnAction; 4 | import com.intellij.openapi.actionSystem.AnActionEvent; 5 | import com.intellij.openapi.module.Module; 6 | import com.intellij.openapi.module.ModuleUtil; 7 | import com.intellij.openapi.roots.ModifiableRootModel; 8 | import com.intellij.openapi.roots.ModuleRootManager; 9 | import com.intellij.openapi.vfs.VfsUtil; 10 | import com.intellij.openapi.vfs.VirtualFile; 11 | import com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl; 12 | import data.VirtualBlenderFile; 13 | import icons.BlendCharmIcons; 14 | import org.jetbrains.annotations.NotNull; 15 | import ui.dialogs.new_blender_addon.NewBlenderAddonWrapper; 16 | import util.MyInputStreamHelper; 17 | import util.MyProjectHolder; 18 | import util.core.MyFileUtils; 19 | 20 | import java.io.IOException; 21 | import java.nio.file.Path; 22 | import java.nio.file.Paths; 23 | 24 | public class CreateNewBlenderAddonAction extends AnAction { 25 | 26 | private final MyProjectHolder project; 27 | 28 | CreateNewBlenderAddonAction(MyProjectHolder project) { 29 | super("Create New Blender Addon", "Creates a new Blender Addon", BlendCharmIcons.BLENDER_LOGO); 30 | this.project = project; 31 | } 32 | 33 | @Override 34 | public void actionPerformed(@NotNull AnActionEvent anActionEvent) { 35 | NewBlenderAddonWrapper dialog = new NewBlenderAddonWrapper(project.getProject()); 36 | if (dialog.showAndGet()) { 37 | String projectPath = project.getBasePath(); 38 | if (projectPath == null) return; 39 | 40 | try { 41 | if (!dialog.form.isNameValid()) return; 42 | 43 | String directoryPath = Paths.get(projectPath, dialog.form.getBlenderAddonFolderName()).toString(); 44 | VirtualFile directory = VfsUtil.createDirectories(directoryPath); 45 | 46 | Module module = ModuleUtil.findModuleForFile(directory, project.getProject()); 47 | if (module == null) return; 48 | 49 | ModifiableRootModel rootModel = ModuleRootManager.getInstance(module).getModifiableModel(); 50 | rootModel.getContentEntries()[0].addSourceFolder(directory, false); 51 | 52 | VirtualBlenderFile virtualBlenderFile = new VirtualBlenderFile(project, directory); 53 | virtualBlenderFile.markAsAddonFolder(); 54 | 55 | String stream = MyInputStreamHelper.readString(this.getClass().getClassLoader().getResourceAsStream("Python/Templates/new_addon.py")); 56 | stream = stream.replace("$ADDON_NAME$", dialog.form.getBlenderAddonName()); 57 | stream = stream.replace("$ADDON_AUTHOR$", dialog.form.getBlenderAddonAuthor()); 58 | stream = stream.replace("$ADDON_DESCRIPTION$", dialog.form.getBlenderAddonDescription()); 59 | 60 | MyFileUtils.write(Path.of(Paths.get(directoryPath, "__init__.py").toString()), stream); 61 | LocalFileSystemImpl.getInstance().refresh(true); 62 | } catch (IOException e) { 63 | e.printStackTrace(); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/resources/icons/addon_folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/icons/addon_src_folder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_addon/NewBlenderAddon.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 | -------------------------------------------------------------------------------- /src/main/java/services/inspections/PanelWarnId.java: -------------------------------------------------------------------------------- 1 | package services.inspections; 2 | 3 | import com.intellij.codeInspection.LocalQuickFix; 4 | import com.intellij.codeInspection.ProblemDescriptor; 5 | import com.intellij.codeInspection.ProblemsHolder; 6 | import com.intellij.codeInspection.util.IntentionFamilyName; 7 | import com.intellij.codeInspection.util.IntentionName; 8 | import com.intellij.openapi.project.Project; 9 | import com.jetbrains.python.psi.*; 10 | import org.jetbrains.annotations.NotNull; 11 | import services.inspections.base.OperatorIdInspection; 12 | 13 | import java.util.regex.Pattern; 14 | 15 | public class PanelWarnId extends OperatorIdInspection { 16 | private static final int limit = 2; 17 | private static final String div = "_PT_"; 18 | private static final String regex = "(?i)" + div; 19 | private static final Pattern pattern = Pattern.compile(regex); 20 | private static final String description = "Blender: Id should be lowercase in order to be registered in to Blender."; 21 | 22 | @Override 23 | public String correctClass() { 24 | return "bpy.types.Panel"; 25 | } 26 | 27 | @Override 28 | protected void visitor(@NotNull ProblemsHolder holder, @NotNull PyAssignmentStatement node) { 29 | for (PyExpression expression : node.getTargets()) { 30 | PyExpression expressionValue = getPyExpression(node, expression); 31 | if (expressionValue == null) continue; 32 | 33 | String text = expressionValue.getText(); 34 | String[] divided = pattern.split(text, limit); 35 | String prefix = divided.length < 1 ? null : divided[0]; 36 | String suffix = divided.length < 2 ? null : divided[1]; 37 | if (finalCheck(text, prefix, suffix)) continue; 38 | 39 | holder.registerProblem(expressionValue, description, new BlIdPrefixSuFix(prefix, suffix)); 40 | } 41 | } 42 | 43 | private boolean finalCheck(String text, String prefix, String suffix) { 44 | if (prefix == null || suffix == null) return false; 45 | if (prefix.equals("\"") || suffix.equals("\"")) return false; 46 | return text.equals(prefix.toUpperCase() + div + suffix); 47 | } 48 | 49 | private static class BlIdPrefixSuFix implements LocalQuickFix { 50 | private final String s = "\""; 51 | private final String prefix; 52 | private final String suffix; 53 | private final boolean prefixNullOrEmpty; 54 | private final boolean suffixNullOrEmpty; 55 | 56 | public BlIdPrefixSuFix(String prefix, String suffix) { 57 | this.prefix = clean(prefix); 58 | this.suffix = clean(suffix); 59 | this.prefixNullOrEmpty = this.prefix == null || this.prefix.isEmpty(); 60 | this.suffixNullOrEmpty = this.suffix == null || this.suffix.isEmpty(); 61 | } 62 | 63 | protected String clean(String code) { 64 | return code == null ? null : code.equals(s) ? "" : code.substring(code.startsWith(s) ? 1 : 0, code.endsWith(s) ? code.length() - 1 : code.length()); 65 | } 66 | 67 | @Override 68 | public @IntentionName @NotNull String getName() { 69 | return suffix == null ? ("Create '" + div + "' suffix") : "Fix prefix and suffix case"; 70 | } 71 | 72 | @Override 73 | public @IntentionFamilyName @NotNull String getFamilyName() { 74 | return getName(); 75 | } 76 | 77 | @Override 78 | public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { 79 | PyExpression binaryExpression = (PyExpression) descriptor.getPsiElement(); 80 | String newValue = (prefixNullOrEmpty ? "prefix" : prefix).toUpperCase() + div + (suffixNullOrEmpty ? "suffix" : suffix); 81 | newValue = s + newValue + s; 82 | PyExpression expression = PyElementGenerator.getInstance(project).createExpressionFromText(LanguageLevel.getDefault(), newValue); 83 | binaryExpression.replace(expression); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/util/core/socket/MySocketConnection.java: -------------------------------------------------------------------------------- 1 | package util.core.socket; 2 | 3 | import org.json.JSONObject; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | import java.net.Socket; 9 | import java.nio.ByteBuffer; 10 | 11 | public class MySocketConnection { 12 | public final Socket socket; 13 | 14 | private final MySocketConnectionInterface connectionInterface; 15 | private final OutputStream outputStream; 16 | private Thread listeningThread; 17 | 18 | public MySocketConnection(Socket socket, MySocketConnectionInterface connectionInterface) { 19 | this.socket = socket; 20 | this.connectionInterface = connectionInterface; 21 | this.outputStream = getOutputStream(); 22 | 23 | startListeningThread(); 24 | } 25 | 26 | private static Data readData(InputStream inp) throws IOException { 27 | byte[] id = new byte[1]; 28 | if (!read(inp, id)) return null; 29 | byte[] len = new byte[4]; 30 | if (!read(inp, len)) return null; 31 | byte[] packet = new byte[ByteBuffer.wrap(len).getInt()]; 32 | return read(inp, packet) ? new Data(id[0], packet) : null; 33 | } 34 | 35 | private static boolean read(InputStream inp, byte[] array) throws IOException { 36 | return array.length == inp.read(array); 37 | } 38 | 39 | private OutputStream getOutputStream() { 40 | try { 41 | return socket.getOutputStream(); 42 | } catch (IOException e) { 43 | return null; 44 | } 45 | } 46 | 47 | public void sendJsonData(JSONObject jsonObject) { 48 | sendData((byte) 4, jsonObject.toString().getBytes()); 49 | } 50 | 51 | public void close(boolean sendData) { 52 | if (sendData) sendData((byte) 0, new byte[]{0}); 53 | connectionInterface.onEnd(this); 54 | listeningThread.interrupt(); 55 | } 56 | 57 | public void sendData(byte id, byte[] array) { 58 | new Thread(() -> { 59 | byte[] newArray = Data.intToByteArray(array.length); 60 | try { 61 | outputStream.write(id); 62 | outputStream.write(newArray); 63 | outputStream.write(array); 64 | outputStream.flush(); 65 | } catch (IOException e) { 66 | // The connection has been shut-down. 67 | } 68 | }).start(); 69 | } 70 | 71 | private void startListeningThread() { 72 | listeningThread = new Thread(() -> { 73 | try { 74 | connectionInterface.onConnectionStart(this); 75 | InputStream inputStream = socket.getInputStream(); 76 | while (!socket.isClosed()) { 77 | Data data = readData(inputStream); 78 | if (data == null) continue; 79 | if (data.getId() == 0) break; 80 | connectionInterface.onMessage(this, data); 81 | } 82 | close(true); 83 | } catch (IOException e) { 84 | close(false); 85 | } 86 | }); 87 | listeningThread.start(); 88 | } 89 | 90 | public interface MySocketConnectionInterface { 91 | void onConnectionStart(MySocketConnection socket); 92 | 93 | void onMessage(MySocketConnection socket, Data message); 94 | 95 | void onEnd(MySocketConnection socket); 96 | } 97 | 98 | public static class Data { 99 | private final byte id; 100 | private final byte[] data; 101 | 102 | Data(byte id, byte[] data) { 103 | this.id = id; 104 | this.data = data; 105 | } 106 | 107 | public static byte[] intToByteArray(int a) { 108 | return new byte[]{(byte) ((a >> 24) & 0xFF), (byte) ((a >> 16) & 0xFF), (byte) ((a >> 8) & 0xFF), (byte) (a & 0xFF)}; 109 | } 110 | 111 | public byte getId() { 112 | return id; 113 | } 114 | 115 | public byte[] getData() { 116 | return data; 117 | } 118 | 119 | public String getStringData() { 120 | return new String(data); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/new_blender_panel/NewBlenderPanel.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 |
81 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/add_blender_instance/AddBlenderInstance.java: -------------------------------------------------------------------------------- 1 | package ui.dialogs.add_blender_instance; 2 | 3 | import com.intellij.icons.AllIcons; 4 | import com.intellij.openapi.fileChooser.FileChooser; 5 | import com.intellij.openapi.fileChooser.FileChooserDescriptor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.vfs.VirtualFile; 8 | import com.intellij.ui.components.JBLabel; 9 | import com.intellij.ui.components.JBTextField; 10 | import com.intellij.ui.table.JBTable; 11 | import data.BlenderExeFileChooserDescriptor; 12 | import data.BlenderInstance; 13 | import org.jetbrains.annotations.NotNull; 14 | import util.MySwingUtil; 15 | 16 | import javax.swing.*; 17 | import javax.swing.table.AbstractTableModel; 18 | import java.util.HashMap; 19 | 20 | public class AddBlenderInstance extends JDialog { 21 | private final HashMap env = new HashMap<>(); 22 | private final EnvModel model; 23 | private JPanel contentPane; 24 | private JPanel extraPanel; 25 | private JBTextField path; 26 | private JBTextField name; 27 | private JBLabel explore; 28 | private JBTable environment; 29 | private JBLabel add; 30 | private JBLabel arrow; 31 | private boolean extraSettings = false; 32 | 33 | AddBlenderInstance(@NotNull Project project, BlenderInstance from) { 34 | setContentPane(contentPane); 35 | initIcons(); 36 | MySwingUtil.setLabelOnClickListener(explore, () -> onExploreClick(project)); 37 | MySwingUtil.setLabelOnClickListener(arrow, this::onExtraSettings); 38 | MySwingUtil.setLabelOnClickListener(add, this::addNewEnv); 39 | environment.setModel(model = new EnvModel()); 40 | if (from != null) init(from); 41 | } 42 | 43 | private void initIcons() { 44 | this.explore.setIcon(AllIcons.Nodes.Folder); 45 | this.arrow.setIcon(AllIcons.General.ArrowRight); 46 | this.add.setIcon(AllIcons.General.Add); 47 | } 48 | 49 | private void init(BlenderInstance from) { 50 | name.setText(from.name); 51 | path.setText(from.path); 52 | if (from.environment != null) env.putAll(from.environment); 53 | } 54 | 55 | private void onExploreClick(@NotNull Project project) { 56 | FileChooserDescriptor descriptor = new BlenderExeFileChooserDescriptor(); 57 | descriptor.setTitle("Blender Runnable."); 58 | descriptor.setDescription("Select Blender runnable file."); 59 | VirtualFile[] files = FileChooser.chooseFiles(descriptor, project, null); 60 | if (files.length == 0) return; 61 | 62 | path.setText(files[0].getPath()); 63 | } 64 | 65 | JComponent getJComponent() { 66 | return contentPane; 67 | } 68 | 69 | public BlenderInstance getNewConfiguration() { 70 | return new BlenderInstance(path.getText(), name.getText(), env); 71 | } 72 | 73 | private void addNewEnv() { 74 | env.put("", ""); 75 | model.fireTableDataChanged(); 76 | } 77 | 78 | private void onExtraSettings() { 79 | extraSettings = !extraSettings; 80 | this.arrow.setIcon(extraSettings ? AllIcons.General.ArrowDown : AllIcons.General.ArrowRight); 81 | this.extraPanel.setVisible(extraSettings); 82 | } 83 | 84 | public void updateConfiguration(BlenderInstance update) { 85 | update.path = path.getText(); 86 | update.name = name.getText(); 87 | update.environment = env; 88 | } 89 | 90 | private class EnvModel extends AbstractTableModel { 91 | @Override 92 | public int getRowCount() { 93 | return env.size(); 94 | } 95 | 96 | @Override 97 | public int getColumnCount() { 98 | return 2; 99 | } 100 | 101 | @Override 102 | public Object getValueAt(int rowIndex, int columnIndex) { 103 | return columnIndex == 0 ? env.keySet().toArray()[rowIndex] : env.values().toArray()[rowIndex]; 104 | } 105 | 106 | @Override 107 | public void setValueAt(Object value, int rowIndex, int columnIndex) { 108 | String key = (String) env.keySet().toArray()[rowIndex]; 109 | String val = (String) env.values().toArray()[rowIndex]; 110 | 111 | if (columnIndex == 0) { 112 | env.remove(key); 113 | key = (String) value; 114 | if (!key.isEmpty()) env.put(key, val); 115 | } else env.put(key, (String) value); 116 | 117 | fireTableDataChanged(); 118 | } 119 | 120 | @Override 121 | public boolean isCellEditable(int rowIndex, int columnIndex) { 122 | return true; 123 | } 124 | 125 | @Override 126 | public String getColumnName(int column) { 127 | return column == 0 ? "Key" : "Value"; 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ![](MarkDown/Logo.png) 4 | 5 | A PyCharm Plugin that simplify the development of Blender Add-On inside 6 | the PyCharm IDE. 7 | 8 | Available for both **Community** or **Professional** editions of PyCharm! 9 | 10 | ## How to Install 11 | 12 | ### Manual Installation 13 | 14 | Simply choose from the release tab the version that suits your IDE Edition. 15 | 16 | Once downloaded you can go to the Editor Settings (Ctrl + Alt + S) and select 17 | the tab "Plugins". 18 | 19 | From there you can click the gear icon and: "Install Plugin from Disk..." 20 | 21 | Select the downloaded file and PyCharm will install the Plugin! 22 | 23 | ![](MarkDown/Img/Install.png) 24 | 25 | ### IntelliJ Marketplace Installation 26 | 27 | > **_Right now, this plugin is not published on the IntelliJ Marketplace._** 28 | 29 | The reason is that I do not think this plugin is good enough to fit in the 30 | Marketplace. 31 | 32 | Actually, I do not think it's ready for be public on GitHub too, but maybe 33 | even if it's not perfect, it could be useful for someone. 34 | 35 | Maybe one day it will, and it will be the right way to install the plugin. 36 | 37 | ## Setting Up 38 | 39 | ### Configure Blender Executable 40 | 41 | Once the plugin has been installed, you will find a new tab in the bottom of your 42 | IDE called "Blend-Charm". 43 | 44 | This tab will help you to launch Blender from your IDE, 45 | keep tracking of console output and use all the feature of this plugin. 46 | 47 | One of the important things you need to do once the plugin has started 48 | is setting-up your Blender Executable path! 49 | 50 | You can do it by clicking on the '+' sign in the tab as shown in the gif 51 | bellow. 52 | 53 | ![](MarkDown/Gif/AddInstance.gif) 54 | 55 | Once you have configured the Blender Executable, you can start 56 | creating your Blender Addon. 57 | 58 | ### Create your Blender Addon 59 | 60 | You can create a brand-new Blender addon by Right-Clicking your 61 | Project root and go: 62 | 63 | - `New -> Blend-Charm -> Create New Blender Addon` 64 | 65 | This will create all the minimum code required by a Blender Addon. 66 | 67 | ![](MarkDown/Gif/Create.gif) 68 | 69 | ### Understanding the project window. 70 | 71 | All the addon in the project will have an icon of a folder and a small 72 | blender logo. ![](MarkDown/Img/AddonFolder.png) 73 | 74 | **Those folders will automatically be installed as addons whenever 75 | you launch Blender from PyCharm.** 76 | 77 | If for any reason your Addon Folder doesn't have that icon, it means it's 78 | not marked as a Blender Addon Folder. 79 | You can fix it by right-clicking it and go: 80 | 81 | - `New -> Blend-Charm -> Mark as Addon Folder` 82 | 83 | You can unmark a folder too, in the same way. 84 | 85 | > Any addon must be the project **Root Folder** or a **Child** of it!

86 | > No other directory will be allowed to be marked as Blender Addons. 87 | 88 | ### Marking the project root as a Blender Addon. 89 | 90 | Are you opening an existing add-on or do you just want to mark the whole project as a 91 | Blender Addon? 92 | 93 | You can do it by right-clicking the project root folder and go: 94 | 95 | - `New -> Blend-Charm -> Mark as Addon Project` 96 | 97 | ![](MarkDown/Gif/MarkProject.gif) 98 | 99 | This way you cannot have other Blender Addons other than the project itself. 100 | 101 | ## Features: 102 | 103 | **
  • [Auto-Install]
  • ** 104 | 105 | Blender will automatically install all the plugins in the current workspace. 106 | 107 | **
  • [Update On Save]
  • ** 108 | 109 | The blender addon will reload with the new changes whenever 110 | you save it! 111 | 112 | **
  • [Creation Templates]
  • ** 113 | 114 | Create a new Blender Addon, Panel or Operator easily by using the right click 115 | menu. 116 | 117 | **
  • [Debugging (Professional Edition-Only)]
  • ** 118 | 119 | Even if is still a lot buggy and crashy... you can run Blender in Debug-Mode 120 | and debug your code! 121 | 122 | # Community or Professional? 123 | 124 | This tool comes with two version: 125 | 126 | - Community Edition 127 | - Professional Edition 128 | 129 | **Why so?** 130 | 131 | PyCharm Professional Edition comes with a handy configuration 132 | called Remote Debugging. 133 | 134 | This configuration is missing in the Community release. 135 | 136 | As you guys can imagine that is a key piece for the Debug feature of this tool, 137 | so, sadly, that feature can't work on Community Edition. 138 | 139 | # Knew Issues 140 | 141 | ### Run configuration console not printing. 142 | 143 | By running Blender normally through the plugin, the console is a little shy to print output buffers. 144 | 145 | ###### This issue is not present in Debug mode. 146 | 147 | ### Debug configuration crash. 148 | 149 | If you are running in debug mode, and an exception is thrown by the code, 150 | Blender will crash. 151 | 152 | # Credits 153 | 154 | This plugin is mainly inspired by 155 | "**[Blender Development](https://marketplace.visualstudio.com/items?itemName=JacquesLucke.blender-development)**" 156 | of **Jacques Lucke**. 157 | 158 | I used his great plugin until I started missing the PyCharm IDE I used to code 159 | in Python, so I decided to make my simple own version of it for PyCharm. 160 | 161 | I never thought I would release it public. -------------------------------------------------------------------------------- /src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/main/resources/Python/pycharm_connector.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import queue 4 | import socket 5 | import struct 6 | import subprocess 7 | import sys 8 | import threading 9 | import traceback 10 | import logging 11 | import pathlib 12 | 13 | # noinspection PyUnresolvedReferences 14 | import bpy 15 | 16 | argv = {} 17 | if "--" in sys.argv: 18 | args_list = sys.argv[sys.argv.index("--") + 1:] 19 | argv = {args_list[i - 1]: args_list[i] for i in range(1, len(args_list), 2)} 20 | 21 | # # # # # # # # # 22 | # Main Thread # 23 | # # # # # # # # # 24 | 25 | main_thread_queue = queue.Queue() 26 | 27 | 28 | def run_in_main_thread(lambda_f): 29 | main_thread_queue.put(lambda_f) 30 | 31 | 32 | # noinspection PyBroadException 33 | def run_thread_queue(): 34 | while not main_thread_queue.empty(): 35 | lambda_f = main_thread_queue.get() 36 | try: 37 | lambda_f() 38 | except Exception: 39 | traceback.print_exc() 40 | return 0.1 41 | 42 | 43 | bpy.app.timers.register(run_thread_queue, persistent=True) 44 | 45 | # # # # # # # # # 46 | # Communication # 47 | # # # # # # # # # 48 | 49 | request = "request" 50 | request_plugin_folder = 0 51 | request_plugin_folder_project_folder = "project" 52 | request_plugin_folder_addon_names = "addon_names" 53 | 54 | request_plugin_refresh = 1 55 | request_plugin_refresh_name_list = "name_list" 56 | 57 | response = "response" 58 | response_plugin_folder = request_plugin_folder 59 | response_plugin_folder_plugin_path = "plugin_path" 60 | response_plugin_folder_failed_addons = "failed_addons" 61 | 62 | response_plugin_refresh = request_plugin_refresh 63 | response_plugin_refresh_status = "status" 64 | response_plugin_refresh_name_list = request_plugin_refresh_name_list 65 | 66 | # # # # # # # # 67 | # Params: # 68 | # # # # # # # # 69 | 70 | print_on = False 71 | debug_mode = None 72 | debug_port = None 73 | debug_egg = None 74 | 75 | # # # # # # # # 76 | # Cmd Params: # 77 | # # # # # # # # 78 | 79 | if "debug_mode" in argv: 80 | debug_mode = True 81 | if "debug_port" in argv: 82 | debug_port = int(argv["debug_port"]) 83 | if "debug_egg" in argv: 84 | debug_egg = argv["debug_egg"] 85 | if "print_on" in argv: 86 | print_on = True 87 | 88 | 89 | def send_json_string(client, string): 90 | client.send(struct.pack('>b', 4) + struct.pack('>I', len(string)) + str.encode(string)) 91 | 92 | 93 | def log(text): 94 | if print_on: 95 | run_in_main_thread(lambda: logging.info(text)) 96 | 97 | 98 | # noinspection PyUnresolvedReferences,PyBroadException 99 | def on_data(client: socket, data: bytes): 100 | log("[Blend-Charm] -- Data Received --") 101 | json_data = json.loads(data) 102 | request_id = json_data[request] 103 | if request_id == request_plugin_folder: 104 | log("[Blend-Charm] ---- Request Plugin Folder ----") 105 | project = json_data[request_plugin_folder_project_folder] 106 | project_name = os.path.split(project)[1] 107 | 108 | script_folder = bpy.utils.user_resource('SCRIPTS', path="addons") 109 | for addon in json_data[request_plugin_folder_addon_names]: 110 | if addon == ".": 111 | src = project 112 | addon = project_name 113 | else: 114 | src = os.path.join(project, addon) 115 | 116 | dst = os.path.join(script_folder, addon) 117 | if not os.path.exists(dst): 118 | sym_link(src, dst) 119 | try: 120 | bpy.ops.preferences.addon_enable(module=addon) 121 | log("[Blend-Charm] ------ Enabled Addon: " + addon + " ------") 122 | except Exception: 123 | traceback.print_exc() 124 | log("[Blend-Charm] ------ Failed Enabling Addon: " + addon + " ------") 125 | 126 | send_json_string(client, json.dumps({ 127 | response: response_plugin_folder, 128 | response_plugin_folder_plugin_path: script_folder 129 | })) 130 | if request_id == request_plugin_refresh: 131 | log("[Blend-Charm] ---- Data: Refresh ----") 132 | run_in_main_thread(lambda: reload_addons(json_data[request_plugin_refresh_name_list])) 133 | 134 | 135 | def sym_link(src, dst): 136 | path = pathlib.Path(dst) 137 | parent = path.parent 138 | if not parent.exists(): 139 | os.makedirs(parent) 140 | if sys.platform == "win32": 141 | subprocess.check_call('mklink /J "' + dst + '" "' + src + '"', shell=True) 142 | else: 143 | os.symlink(str(src), str(dst), target_is_directory=True) 144 | 145 | 146 | def start_client(): 147 | try: 148 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: 149 | s.connect(('localhost', 8525)) 150 | while True: 151 | log("[Blend-Charm] Waiting For Data...") 152 | data_id_value = struct.unpack('>b', s.recv(1))[0] 153 | if data_id_value == 0: 154 | log("[Blend-Charm] -- Closed Connection --") 155 | break 156 | on_data(s, s.recv(struct.unpack('>i', s.recv(4))[0])) 157 | except socket.error: 158 | pass 159 | bpy.ops.wm.quit_blender() 160 | 161 | 162 | def connect_to_pycharm_debugger(egg_path: str, server_port: int): 163 | if egg_path not in sys.path: 164 | sys.path.append(egg_path) 165 | # noinspection PyUnresolvedReferences 166 | import pydevd_pycharm 167 | pydevd_pycharm.settrace('localhost', port=server_port, stdoutToServer=True, stderrToServer=True, suspend=False) 168 | 169 | 170 | def reload_addons(module_list): 171 | for module in module_list: 172 | log("[Blend-Charm] ------ Reloading: " + module + " ------") 173 | reload_add_on(module) 174 | 175 | 176 | # noinspection PyBroadException 177 | def reload_add_on(module_name): 178 | try: 179 | bpy.ops.preferences.addon_disable(module=module_name) 180 | except Exception: 181 | traceback.print_exc() 182 | return {'CANCELLED'} 183 | 184 | for name in list(sys.modules.keys()): 185 | if name.startswith(module_name): 186 | del sys.modules[name] 187 | 188 | try: 189 | bpy.ops.preferences.addon_enable(module=module_name) 190 | except Exception: 191 | traceback.print_exc() 192 | return {'CANCELLED'} 193 | 194 | for window in bpy.context.window_manager.windows: 195 | for area in window.screen.areas: 196 | area.tag_redraw() 197 | 198 | run_in_main_thread(lambda: logging.info(f"[Blend-Charm] Reloaded add-on: {module_name}")) 199 | return {'FINISHED'} 200 | 201 | 202 | x = threading.Thread(target=start_client, args=()) 203 | x.daemon = True 204 | x.start() 205 | if debug_mode: 206 | connect_to_pycharm_debugger(debug_egg, debug_port) 207 | -------------------------------------------------------------------------------- /src/main/java/ui/dialogs/add_blender_instance/AddBlenderInstance.form: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |
    129 | -------------------------------------------------------------------------------- /src/main/java/ui/tool_window/BlenderToolWindow.form: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 |
    135 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 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 | # https://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 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Use "xargs" to parse quoted args. 209 | # 210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 211 | # 212 | # In Bash we could simply go: 213 | # 214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 215 | # set -- "${ARGS[@]}" "$@" 216 | # 217 | # but POSIX shell has neither arrays nor command substitution, so instead we 218 | # post-process each arg (as a line of input to sed) to backslash-escape any 219 | # character that might be a shell metacharacter, then use eval to reverse 220 | # that process (while maintaining the separation between arguments), and wrap 221 | # the whole thing up as a single "set" statement. 222 | # 223 | # This will of course break if any of these variables contains a newline or 224 | # an unmatched quote. 225 | # 226 | 227 | eval "set -- $( 228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 229 | xargs -n1 | 230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 231 | tr '\n' ' ' 232 | )" '"$@"' 233 | 234 | exec "$JAVACMD" "$@" 235 | -------------------------------------------------------------------------------- /src/main/java/ui/tool_window/BlenderToolWindow.java: -------------------------------------------------------------------------------- 1 | package ui.tool_window; 2 | 3 | import com.intellij.execution.*; 4 | import com.intellij.execution.configurations.GeneralCommandLine; 5 | import com.intellij.execution.executors.DefaultDebugExecutor; 6 | import com.intellij.execution.filters.TextConsoleBuilderFactory; 7 | import com.intellij.execution.process.OSProcessHandler; 8 | import com.intellij.execution.runners.ExecutionEnvironment; 9 | import com.intellij.execution.runners.ExecutionEnvironmentBuilder; 10 | import com.intellij.execution.ui.ConsoleView; 11 | import com.intellij.execution.ui.ConsoleViewContentType; 12 | import com.intellij.execution.ui.RunContentDescriptor; 13 | import com.intellij.icons.AllIcons; 14 | import com.intellij.openapi.application.ApplicationManager; 15 | import com.intellij.openapi.project.Project; 16 | import com.intellij.openapi.vfs.VirtualFile; 17 | import com.intellij.openapi.vfs.VirtualFileManager; 18 | import com.intellij.openapi.vfs.newvfs.BulkFileListener; 19 | import com.intellij.openapi.vfs.newvfs.events.VFileEvent; 20 | import com.intellij.ui.components.JBLabel; 21 | import com.intellij.ui.components.JBList; 22 | import com.intellij.util.PathMappingSettings; 23 | import com.intellij.util.messages.MessageBusConnection; 24 | import com.jetbrains.python.console.PythonDebugLanguageConsoleView; 25 | import com.intellij.python.pro.debugger.remote.PyRemoteDebugConfiguration; 26 | import com.intellij.python.pro.debugger.remote.PyRemoteDebugConfigurationFactory; 27 | import com.intellij.python.pro.debugger.remote.PyRemoteDebugConfigurationType; 28 | import data.*; 29 | import icons.BlendCharmIcons; 30 | import org.jetbrains.annotations.NotNull; 31 | import org.json.JSONArray; 32 | import org.json.JSONObject; 33 | import org.json.JSONTokener; 34 | import plugin_settings.PluginSettings; 35 | import settings.BlenderSettings; 36 | import ui.dialogs.add_blender_instance.AddBlenderInstanceWrapper; 37 | import ui.dialogs.debug_popup.DebugPopupWrapper; 38 | import ui.dialogs.remove_blender_instance.RemoveBlenderInstanceWrapper; 39 | import util.MyInputStreamHelper; 40 | import util.MyProjectHolder; 41 | import util.MySwingUtil; 42 | import util.core.MyFileUtils; 43 | import util.core.socket.MySocketConnection; 44 | import util.core.socket.server.MyServerSocket; 45 | 46 | import javax.swing.*; 47 | import java.awt.*; 48 | import java.awt.event.ItemEvent; 49 | import java.io.File; 50 | import java.io.InputStream; 51 | import java.nio.charset.StandardCharsets; 52 | import java.util.ArrayList; 53 | import java.util.List; 54 | 55 | public class BlenderToolWindow { 56 | 57 | private static final String blenderRunFileName = "pycharm_connector.py"; 58 | private static final File runningFile = new File(System.getProperty("java.io.tmpdir") + File.separator + "BlendCharm" + File.separator + blenderRunFileName); 59 | private static final File egg = BlenderToolWindowUtils.getEggFile(); 60 | private static final int debugPort = 8132; 61 | private static final int socketPort = 8525; 62 | private static DefaultListModel runningInstancesAdapter; 63 | private final BlenderSettings blenderSettings; 64 | private final MyProjectHolder project; 65 | private final MyServerSocket currentSocket = new MyServerSocket("localhost", socketPort); 66 | private JPanel myToolWindowContent; 67 | private JComboBox blenderInstances; 68 | private JBLabel button_add; 69 | private JBLabel button_remove; 70 | private JBLabel start; 71 | private JBLabel debug; 72 | private JBList runningInstances; 73 | private JPanel consolePanel; 74 | private JPanel nullPanel; 75 | private JBLabel button_settings; 76 | private JBLabel blenderLogo; 77 | 78 | BlenderToolWindow(@NotNull Project project) { 79 | this.project = new MyProjectHolder(project); 80 | this.blenderSettings = BlenderSettings.getBlenderSettings(this.project); 81 | initIcons(); 82 | init(); 83 | 84 | for (BlenderInstance savedBlenderInstance : blenderSettings.getBlenderInstances()) 85 | blenderInstances.addItem(savedBlenderInstance); 86 | 87 | blenderInstances.addItemListener(this::onBlenderInstanceChange); 88 | MySwingUtil.setLabelOnClickListener(button_add, this::onAddClick); 89 | MySwingUtil.setLabelOnClickListener(button_remove, this::onRemoveClick); 90 | MySwingUtil.setLabelOnClickListener(button_settings, this::onSettingsClick); 91 | 92 | MySwingUtil.setLabelOnClickListener(start, this::onStartClick); 93 | MySwingUtil.setLabelOnClickListener(debug, this::onDebugClick); 94 | 95 | validateFile(true); 96 | updateButtons(); 97 | 98 | runningInstancesAdapter = new DefaultListModel<>(); 99 | runningInstances.setModel(runningInstancesAdapter); 100 | runningInstances.setCellRenderer(new RunningBlenderProcessRenderer()); 101 | runningInstances.addListSelectionListener(e -> setConsoleView(getSelectedRunningProcess())); 102 | } 103 | 104 | private void initIcons() { 105 | button_settings.setIcon(AllIcons.General.Gear); 106 | start.setIcon(AllIcons.Actions.Execute); 107 | button_add.setIcon(AllIcons.General.Add); 108 | debug.setIcon(AllIcons.Actions.StartDebugger); 109 | button_remove.setIcon(AllIcons.General.Remove); 110 | blenderLogo.setIcon(BlendCharmIcons.BLENDER_LOGO_BIG); 111 | } 112 | 113 | private void init() { 114 | MessageBusConnection connection = project.getProject().getMessageBus().connect(); 115 | connection.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() { 116 | @Override 117 | public void after(@NotNull List events) { 118 | for (VFileEvent event : events) if (event.isFromSave()) onSave(event.getFile()); 119 | } 120 | }); 121 | } 122 | 123 | private RunningBlenderProcess getSelectedRunningProcess() { 124 | return runningInstances.getSelectedIndex() == -1 ? null : runningInstancesAdapter.get(runningInstances.getSelectedIndex()); 125 | } 126 | 127 | private boolean isSelectedInstanceValid() { 128 | return blenderInstances.getModel().getSize() != 0; 129 | } 130 | 131 | private void onSave(VirtualFile virtualFile) { 132 | for (int i = 0; i < runningInstancesAdapter.size(); i++) 133 | intellijConsoleInfoPrintLn(runningInstancesAdapter.get(i).getConsole(), "[On Save: VFS_CHANGE]"); 134 | String ofAddon = new VirtualBlenderFile(project, virtualFile).getRelativeAddonName(); 135 | if (ofAddon != null) reloadAddons(new String[]{ofAddon}); 136 | } 137 | 138 | private void reloadAddons(String[] strings) { 139 | for (int i = 0; i < runningInstancesAdapter.size(); i++) { 140 | RunningBlenderProcess instance = runningInstancesAdapter.get(i); 141 | MySocketConnection socket = instance.getSocket(); 142 | if (socket == null) { 143 | intellijConsoleInfoPrintLn(instance.getConsole(), "[On Save: ERROR - Socket connection lost]"); 144 | continue; 145 | } 146 | socket.sendJsonData(new JSONObject() 147 | .put(CommunicationData.REQUEST, CommunicationData.REQUEST_PLUGIN_REFRESH) 148 | .put(CommunicationData.REQUEST_PLUGIN_REFRESH_NAME_LIST, new JSONArray().putAll(strings)) 149 | ); 150 | intellijConsoleInfoPrintLn(instance.getConsole(), "[On Save: Plugin reload request sent for:"); 151 | for (String s : strings) intellijConsoleInfoPrintLn(instance.getConsole(), " - " + s); 152 | intellijConsoleInfoPrintLn(instance.getConsole(), "]"); 153 | } 154 | } 155 | 156 | private BlenderInstance getSelectedBlenderInstance() { 157 | return (BlenderInstance) blenderInstances.getSelectedItem(); 158 | } 159 | 160 | private void updateButtons() { 161 | start.setEnabled(isSelectedInstanceValid()); 162 | debug.setEnabled(isSelectedInstanceValid() && !PluginSettings.isCommunity); 163 | 164 | button_remove.setEnabled(getSelectedBlenderInstance() != null); 165 | button_settings.setEnabled(getSelectedBlenderInstance() != null); 166 | } 167 | 168 | private void addConfiguration(BlenderInstance configuration) { 169 | blenderSettings.addBlenderInstance(configuration); 170 | blenderInstances.addItem(configuration); 171 | } 172 | 173 | private void onBlenderInstanceChange(ItemEvent itemEvent) { 174 | updateButtons(); 175 | } 176 | 177 | JPanel getContent() { 178 | return myToolWindowContent; 179 | } 180 | 181 | /** 182 | * Validate that the runningFile variable could be used. 183 | * 184 | * @return true if the file exist or has been successfully created. 185 | */ 186 | private boolean validateFile(boolean checkContent) { 187 | if (runningFile.exists() && !checkContent) return true; 188 | 189 | InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("Python/pycharm_connector.py"); 190 | if (resourceAsStream == null) return false; 191 | String realContent = MyInputStreamHelper.readString(resourceAsStream); 192 | 193 | if (runningFile.exists()) { 194 | if (realContent.equals(MyFileUtils.readString(runningFile.toPath()))) return true; 195 | if (!runningFile.delete()) return false; 196 | } 197 | File dirs = runningFile.getParentFile(); 198 | 199 | if (dirs.exists() || dirs.mkdirs()) MyFileUtils.write(runningFile.toPath(), realContent); 200 | return runningFile.exists(); 201 | } 202 | 203 | /* 204 | * Socket methods 205 | */ 206 | 207 | private void onInstanceConnectionStart(MySocketConnection socket, RunningBlenderProcess runningBlenderProcess) { 208 | runningBlenderProcess.assignSocket(socket); 209 | blenderSettings.removeDeletedAddon(project); 210 | socket.sendJsonData(new JSONObject() 211 | .put(CommunicationData.REQUEST, CommunicationData.REQUEST_PLUGIN_FOLDER) 212 | .put(CommunicationData.REQUEST_PLUGIN_FOLDER_PROJECT_FOLDER, project.getBasePath()) 213 | .put(CommunicationData.REQUEST_PLUGIN_FOLDER_ADDON_NAMES, new JSONArray().putAll(blenderSettings.getBlenderAddons())) 214 | ); 215 | } 216 | 217 | private void onInstanceMessage(MySocketConnection.Data message, RunningBlenderProcess runningBlenderProcess) { 218 | JSONObject root = new JSONObject(new JSONTokener(message.getStringData())); 219 | switch (root.getInt(CommunicationData.RESPONSE)) { 220 | case CommunicationData.RESPONSE_PLUGIN_FOLDER -> { 221 | String addonPath = root.getString(CommunicationData.RESPONSE_PLUGIN_FOLDER_PLUGIN_PATH); 222 | String currentPath = runningBlenderProcess.getInstance().addonPath; 223 | if (currentPath == null || !currentPath.equals(addonPath)) { 224 | runningBlenderProcess.getInstance().addonPath = addonPath; 225 | if (runningBlenderProcess.isDebug()) intentionalDebugRestart(runningBlenderProcess); 226 | } 227 | } 228 | case CommunicationData.RESPONSE_PLUGIN_REFRESH -> root.getString(CommunicationData.RESPONSE_PLUGIN_REFRESH_STATUS); 229 | } 230 | } 231 | 232 | private void intentionalDebugRestart(RunningBlenderProcess runningBlenderProcess) { 233 | runningBlenderProcess.getProcess().destroyProcess(); 234 | ApplicationManager.getApplication().invokeLater(this::onNewDebugInformationPopup); 235 | } 236 | 237 | private void onNewDebugInformationPopup() { 238 | new DebugPopupWrapper().show(); 239 | } 240 | 241 | private void onInstanceConnectionEnd(RunningBlenderProcess runningBlenderProcess) { 242 | runningBlenderProcess.getProcess().destroyProcess(); 243 | ApplicationManager.getApplication().invokeLater(() -> runningInstancesAdapter.removeElement(runningBlenderProcess)); 244 | } 245 | 246 | /* 247 | * Process Starting 248 | */ 249 | 250 | @NotNull 251 | private RunningBlenderProcess startBlenderProcess() throws ExecutionException { 252 | return startBlenderProcess(false, () -> { 253 | }); 254 | } 255 | 256 | @NotNull 257 | private RunningBlenderProcess startBlenderProcess(boolean debugMode, Runnable onEnd) throws ExecutionException { 258 | if (currentSocket.open()) { 259 | BlenderInstance instance = getSelectedBlenderInstance(); 260 | OSProcessHandler processHandler = createBlenderProcessHandler(instance, debugMode, blenderSettings.data().showVerbose); 261 | RunningBlenderProcess runningBlenderProcess = new RunningBlenderProcess(instance, processHandler, debugMode); 262 | 263 | currentSocket.asyncWaitClient(new MySocketConnection.MySocketConnectionInterface() { 264 | @Override 265 | public void onConnectionStart(MySocketConnection socket) { 266 | onInstanceConnectionStart(socket, runningBlenderProcess); 267 | } 268 | 269 | @Override 270 | public void onMessage(MySocketConnection socket, MySocketConnection.Data message) { 271 | onInstanceMessage(message, runningBlenderProcess); 272 | } 273 | 274 | @Override 275 | public void onEnd(MySocketConnection socket) { 276 | onInstanceConnectionEnd(runningBlenderProcess); 277 | onEnd.run(); 278 | } 279 | }); 280 | 281 | runningInstancesAdapter.addElement(runningBlenderProcess); 282 | runningInstances.setSelectedIndex(runningInstancesAdapter.size() - 1); 283 | return runningBlenderProcess; 284 | } 285 | throw new ExecutionException("Socket not open!"); 286 | } 287 | 288 | private OSProcessHandler createBlenderProcessHandler(BlenderInstance instance, boolean debugMode, boolean print) throws ExecutionException { 289 | ArrayList command = new ArrayList<>(); 290 | 291 | command.add(instance.path); 292 | command.add("--python"); 293 | command.add(runningFile.getPath()); 294 | command.add("--"); 295 | if (print) { 296 | command.add("print_on"); 297 | command.add("."); 298 | } 299 | if (debugMode && egg.exists()) { 300 | command.add("debug_mode"); 301 | command.add("."); 302 | command.add("debug_port"); 303 | command.add(String.valueOf(debugPort)); 304 | command.add("debug_egg"); 305 | command.add(egg.getPath()); 306 | } 307 | 308 | GeneralCommandLine generalCommandLine = new GeneralCommandLine(command); 309 | generalCommandLine.withEnvironment(instance.environment); 310 | generalCommandLine.setCharset(StandardCharsets.UTF_8); 311 | generalCommandLine.setWorkDirectory(project.getBasePath()); 312 | return new OSProcessHandler(generalCommandLine); 313 | } 314 | 315 | /* 316 | * Label Listener 317 | */ 318 | 319 | private void onAddClick() { 320 | AddBlenderInstanceWrapper dialog = new AddBlenderInstanceWrapper(project.getProject(), null); 321 | if (dialog.showAndGet()) addConfiguration(dialog.form.getNewConfiguration()); 322 | } 323 | 324 | private void onRemoveClick() { 325 | if (getSelectedBlenderInstance() == null) return; 326 | 327 | RemoveBlenderInstanceWrapper dialog = new RemoveBlenderInstanceWrapper(); 328 | if (dialog.showAndGet()) { 329 | blenderSettings.removeBlenderInstances(getSelectedBlenderInstance()); 330 | blenderInstances.removeItem(getSelectedBlenderInstance()); 331 | updateButtons(); 332 | } 333 | } 334 | 335 | private void onSettingsClick() { 336 | BlenderInstance blenderInstance = getSelectedBlenderInstance(); 337 | if (blenderInstance == null) return; 338 | 339 | AddBlenderInstanceWrapper dialog = new AddBlenderInstanceWrapper(project.getProject(), blenderInstance); 340 | if (dialog.showAndGet()) dialog.form.updateConfiguration(blenderInstance); 341 | } 342 | 343 | private void setConsoleView(RunningBlenderProcess runningBlenderProcess) { 344 | consolePanel.removeAll(); 345 | consolePanel.revalidate(); 346 | consolePanel.repaint(); 347 | consolePanel.add(runningBlenderProcess != null ? runningBlenderProcess.getComponent() : nullPanel, BorderLayout.CENTER); 348 | } 349 | 350 | private void destroyDebugInstance(RunContentDescriptor runContentDescriptor) { 351 | if (runContentDescriptor.getProcessHandler() == null) return; 352 | runContentDescriptor.getProcessHandler().destroyProcess(); 353 | } 354 | 355 | private void intellijConsoleInfoPrintLn(ConsoleView console, String message) { 356 | if (!blenderSettings.data().showVerbose) return; 357 | console.print(message + "\n", ConsoleViewContentType.LOG_DEBUG_OUTPUT); 358 | } 359 | 360 | private void onStartClick() { 361 | if (validateFile(false)) { 362 | try { 363 | ConsoleView console = TextConsoleBuilderFactory.getInstance().createBuilder(project.getProject()).getConsole(); 364 | 365 | RunningBlenderProcess runningBlenderProcess = startBlenderProcess(); 366 | runningBlenderProcess.setComponent(console.getComponent()); 367 | 368 | setConsoleView(runningBlenderProcess); 369 | 370 | runningBlenderProcess.setConsole(console); 371 | console.attachToProcess(runningBlenderProcess.getProcess()); 372 | runningBlenderProcess.getProcess().startNotify(); 373 | } catch (ExecutionException e) { 374 | e.printStackTrace(); 375 | } 376 | } 377 | } 378 | 379 | private void onDebugClick() { 380 | if (PluginSettings.isCommunity) return; 381 | 382 | if (validateFile(false)) { 383 | try { 384 | PyRemoteDebugConfigurationType remoteConfigurationType = PyRemoteDebugConfigurationType.getInstance(); 385 | 386 | PyRemoteDebugConfigurationFactory factory = (PyRemoteDebugConfigurationFactory) remoteConfigurationType.getConfigurationFactories()[0]; 387 | 388 | RunnerAndConfigurationSettings runSettings = RunManager.getInstance(project.getProject()).createConfiguration("Blender Debug", factory); 389 | PyRemoteDebugConfiguration configuration = (PyRemoteDebugConfiguration) runSettings.getConfiguration(); 390 | 391 | configuration.setHost("localhost"); 392 | configuration.setPort(debugPort); 393 | configuration.setSuspendAfterConnect(false); 394 | 395 | configuration.setMappingSettings(new PathMappingSettings() {{ 396 | add(new PathMapping(project.addonContainerPath().toLowerCase(), getSelectedBlenderInstance().getAddonPath())); 397 | }}); 398 | 399 | Executor debugExecutorInstance = DefaultDebugExecutor.getDebugExecutorInstance(); 400 | ExecutionEnvironment executionEnvironment = ExecutionEnvironmentBuilder.create(project.getProject(), debugExecutorInstance, configuration).build(); 401 | ProgramRunnerUtil.executeConfigurationAsync(executionEnvironment, false, false, runContentDescriptor -> { 402 | try { 403 | RunningBlenderProcess runningBlenderProcess = startBlenderProcess(true, () -> destroyDebugInstance(runContentDescriptor)); 404 | PythonDebugLanguageConsoleView console = ((PythonDebugLanguageConsoleView) runContentDescriptor.getExecutionConsole()); 405 | runningBlenderProcess.setConsole(console); 406 | console.attachToProcess(runningBlenderProcess.getProcess()); 407 | runningBlenderProcess.getProcess().startNotify(); 408 | } catch (ExecutionException e) { 409 | e.printStackTrace(); 410 | } 411 | }); 412 | } catch (ExecutionException e) { 413 | e.printStackTrace(); 414 | } 415 | } 416 | } 417 | } 418 | --------------------------------------------------------------------------------