Last active
July 17, 2026 00:21
-
-
Save stephensmitchell/2458ea2d1bc749cee3e06c39d18f1b18 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # created with Alibre Script Genie by Stephen S. Mitchell, https://github.com/stephensmitchell | |
| import clr | |
| import sys | |
| clr.AddReference("System.Windows.Forms") | |
| clr.AddReference("System.Drawing") | |
| clr.AddReference("System.ComponentModel") | |
| from System.Windows.Forms import * | |
| from System.Drawing import * | |
| from System.ComponentModel import * | |
| from System import * | |
| def printTraceBack(): | |
| import traceback | |
| print("\n--------------\n" + str(traceback.format_exc()) + "--------------\n") | |
| def show_error(message, title="Custom Property Explorer"): | |
| try: | |
| Win = Windows() | |
| Win.ErrorDialog(str(message), str(title)) | |
| except: | |
| print("ERROR: " + str(message)) | |
| def show_info(message, title="Custom Property Explorer"): | |
| try: | |
| Win = Windows() | |
| Win.InfoDialog(str(message), str(title)) | |
| except: | |
| print(str(message)) | |
| def safe_try(action_name, func): | |
| try: | |
| return True, func() | |
| except Exception as ex: | |
| print("Action failed: " + str(action_name)) | |
| print("Reason: " + str(ex)) | |
| printTraceBack() | |
| return False, None | |
| def CreateCustomPropertyExplorer(): | |
| # Resolve current model | |
| MyModel = None | |
| IsAssembly = False | |
| try: | |
| MyModel = CurrentPart() | |
| except: | |
| try: | |
| MyModel = CurrentAssembly() | |
| IsAssembly = True | |
| except: | |
| show_error("No active part or assembly found.\n\nPlease open a part or assembly first.") | |
| return | |
| # Default visible custom properties from the original script | |
| custom_property_names = ["A", "B", "C", "D", "E", "F", "G"] | |
| # DPI scaling | |
| try: | |
| dpi_scale = Graphics.FromHwnd(IntPtr.Zero).DpiX / 96.0 | |
| except: | |
| dpi_scale = 1.0 | |
| def scale(value): | |
| return int(value * dpi_scale) | |
| class Colors: | |
| PRIMARY = Color.FromArgb(45, 45, 48) | |
| SECONDARY = Color.FromArgb(62, 62, 66) | |
| ACCENT = Color.FromArgb(0, 122, 204) | |
| SURFACE = Color.FromArgb(37, 37, 38) | |
| TEXT_PRIMARY = Color.FromArgb(241, 241, 241) | |
| TEXT_SECONDARY = Color.FromArgb(204, 204, 204) | |
| BORDER = Color.FromArgb(63, 63, 70) | |
| SUCCESS = Color.FromArgb(73, 156, 84) | |
| WARNING = Color.FromArgb(252, 196, 25) | |
| ERROR = Color.FromArgb(255, 100, 100) | |
| # ------------------------- | |
| # COMMENTED OUT / REMOVED: | |
| # - real-time Alibre selection tracking | |
| # - geometry tree population | |
| # - feature/sketch/plane/axis/point discovery | |
| # - non-custom property wrappers | |
| # ------------------------- | |
| class CustomPropertyWrapper(object): | |
| def __init__(self, model, prop_names): | |
| self._model = model | |
| self._properties = {} | |
| self._load_custom_properties(prop_names) | |
| def _load_custom_properties(self, prop_names): | |
| for cp_name in prop_names: | |
| try: | |
| val = self._model.GetCustomProperty(cp_name) | |
| if val is None: | |
| self._properties["Custom: " + cp_name] = "" | |
| else: | |
| self._properties["Custom: " + cp_name] = str(val) | |
| except: | |
| self._properties["Custom: " + cp_name] = "(not defined)" | |
| def GetAllProperties(self): | |
| return self._properties | |
| class CustomPropertyDisplayObject(object): | |
| def __init__(self, model, prop_names): | |
| self._model = model | |
| self._prop_names = list(prop_names) | |
| wrapper = CustomPropertyWrapper(model, prop_names) | |
| for prop_name, prop_value in wrapper.GetAllProperties().items(): | |
| clean_prop_name = prop_name.replace(" ", "_").replace("-", "_").replace("(", "").replace(")", "") | |
| setattr(self, clean_prop_name, prop_value) | |
| form = Form() | |
| form.Text = "Custom Property Explorer - " + str(MyModel.Name) | |
| form.Width = scale(1000) | |
| form.Height = scale(700) | |
| form.StartPosition = FormStartPosition.CenterScreen | |
| form.BackColor = Colors.PRIMARY | |
| form.ForeColor = Colors.TEXT_PRIMARY | |
| form.Font = Font("Segoe UI", scale(9), FontStyle.Regular) | |
| try: | |
| form.AutoScaleMode = AutoScaleMode.Dpi | |
| form.AutoScaleDimensions = SizeF(96, 96) | |
| except: | |
| pass | |
| statusStrip = StatusStrip() | |
| statusStrip.BackColor = Colors.SURFACE | |
| statusStrip.ForeColor = Colors.TEXT_SECONDARY | |
| statusStrip.Font = Font("Segoe UI", scale(9)) | |
| statusStrip.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| statusLabel = ToolStripStatusLabel() | |
| statusLabel.Text = "Ready - Model: " + str(MyModel.Name) | |
| statusLabel.Spring = True | |
| statusLabel.TextAlign = ContentAlignment.MiddleLeft | |
| selectionLabel = ToolStripStatusLabel() | |
| selectionLabel.Text = "Custom properties only" | |
| statusStrip.Items.AddRange(Array[ToolStripItem]((statusLabel, selectionLabel))) | |
| form.Controls.Add(statusStrip) | |
| mainSplitter = SplitContainer() | |
| mainSplitter.Dock = DockStyle.Fill | |
| mainSplitter.Orientation = Orientation.Vertical | |
| mainSplitter.SplitterDistance = scale(380) | |
| mainSplitter.BackColor = Colors.BORDER | |
| mainSplitter.SplitterWidth = scale(8) | |
| form.Controls.Add(mainSplitter) | |
| leftPanel = Panel() | |
| leftPanel.Dock = DockStyle.Fill | |
| leftPanel.BackColor = Colors.SECONDARY | |
| leftPanel.Padding = Padding(scale(12)) | |
| mainSplitter.Panel1.Controls.Add(leftPanel) | |
| rightPanel = Panel() | |
| rightPanel.Dock = DockStyle.Fill | |
| rightPanel.BackColor = Colors.SECONDARY | |
| rightPanel.Padding = Padding(scale(12)) | |
| mainSplitter.Panel2.Controls.Add(rightPanel) | |
| treeHeader = Label() | |
| treeHeader.Text = "Custom Properties" | |
| treeHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| treeHeader.ForeColor = Colors.TEXT_PRIMARY | |
| treeHeader.BackColor = Colors.SURFACE | |
| treeHeader.Height = scale(40) | |
| treeHeader.TextAlign = ContentAlignment.MiddleLeft | |
| treeHeader.Padding = Padding(scale(12), 0, 0, 0) | |
| treeHeader.Dock = DockStyle.Top | |
| propHeader = Label() | |
| propHeader.Text = "Property Grid" | |
| propHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| propHeader.ForeColor = Colors.TEXT_PRIMARY | |
| propHeader.BackColor = Colors.SURFACE | |
| propHeader.Height = scale(40) | |
| propHeader.TextAlign = ContentAlignment.MiddleLeft | |
| propHeader.Padding = Padding(scale(12), 0, 0, 0) | |
| propHeader.Dock = DockStyle.Top | |
| toolStrip = ToolStrip() | |
| toolStrip.BackColor = Colors.SURFACE | |
| toolStrip.ForeColor = Colors.TEXT_PRIMARY | |
| toolStrip.Font = Font("Segoe UI", scale(9)) | |
| toolStrip.Renderer = ToolStripProfessionalRenderer() | |
| toolStrip.GripStyle = ToolStripGripStyle.Hidden | |
| toolStrip.Padding = Padding(scale(10), scale(6), scale(10), scale(6)) | |
| toolStrip.AutoSize = False | |
| toolStrip.Height = scale(52) | |
| toolStrip.Dock = DockStyle.Top | |
| customPropButton = ToolStripButton("Lookup Custom Prop") | |
| customPropButton.ForeColor = Colors.WARNING | |
| customPropButton.ToolTipText = "Lookup a custom property by name" | |
| customPropButton.AutoSize = True | |
| customPropButton.Font = Font("Segoe UI", scale(9)) | |
| setPropButton = ToolStripButton("Set Custom Prop") | |
| setPropButton.ForeColor = Colors.WARNING | |
| setPropButton.ToolTipText = "Set a custom property value" | |
| setPropButton.AutoSize = True | |
| setPropButton.Font = Font("Segoe UI", scale(9)) | |
| toolStrip.Items.AddRange(Array[ToolStripItem]((customPropButton, setPropButton))) | |
| treeView = TreeView() | |
| treeView.Dock = DockStyle.Fill | |
| treeView.Font = Font("Segoe UI", scale(9)) | |
| treeView.BackColor = Colors.PRIMARY | |
| treeView.ForeColor = Colors.TEXT_PRIMARY | |
| treeView.LineColor = Colors.BORDER | |
| treeView.HideSelection = False | |
| treeView.FullRowSelect = True | |
| treeView.BorderStyle = BorderStyle.None | |
| treeView.Indent = scale(24) | |
| treeView.ItemHeight = scale(28) | |
| propertyGrid = PropertyGrid() | |
| propertyGrid.Dock = DockStyle.Fill | |
| propertyGrid.BackColor = Colors.PRIMARY | |
| propertyGrid.LineColor = Colors.BORDER | |
| propertyGrid.CategoryForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.CategorySplitterColor = Colors.BORDER | |
| propertyGrid.HelpBackColor = Colors.SURFACE | |
| propertyGrid.HelpForeColor = Colors.TEXT_SECONDARY | |
| propertyGrid.ViewBackColor = Colors.PRIMARY | |
| propertyGrid.ViewForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.ToolbarVisible = True | |
| propertyGrid.Font = Font("Segoe UI", scale(9)) | |
| propertyGrid.PropertySort = PropertySort.Categorized | |
| propertyGrid.HelpVisible = True | |
| leftPanel.Controls.Add(treeView) | |
| leftPanel.Controls.Add(toolStrip) | |
| leftPanel.Controls.Add(treeHeader) | |
| rightPanel.Controls.Add(propertyGrid) | |
| rightPanel.Controls.Add(propHeader) | |
| def UpdateStatusBar(message, selection_text=""): | |
| statusLabel.Text = str(message) | |
| selectionLabel.Text = str(selection_text) | |
| def EnsureCustomPropertyInList(prop_name): | |
| if prop_name is None: | |
| return | |
| prop_name = str(prop_name).strip() | |
| if prop_name == "": | |
| return | |
| if prop_name not in custom_property_names: | |
| custom_property_names.append(prop_name) | |
| def GetDisplayedCustomPropertyPairs(): | |
| pairs = [] | |
| for cp_name in custom_property_names: | |
| try: | |
| val = MyModel.GetCustomProperty(cp_name) | |
| if val is None: | |
| pairs.append((cp_name, "")) | |
| else: | |
| pairs.append((cp_name, str(val))) | |
| except: | |
| pairs.append((cp_name, "(not defined)")) | |
| return pairs | |
| def BuildTreeNodes(): | |
| treeView.BeginUpdate() | |
| treeView.Nodes.Clear() | |
| rootNode = TreeNode(str(MyModel.Name)) | |
| rootNode.Tag = MyModel | |
| treeView.Nodes.Add(rootNode) | |
| pairs = GetDisplayedCustomPropertyPairs() | |
| cpNode = TreeNode("Custom Properties (%d)" % len(pairs)) | |
| cpNode.Tag = MyModel | |
| rootNode.Nodes.Add(cpNode) | |
| for cp_name, cp_val in pairs: | |
| childNode = TreeNode(str(cp_name) + " = " + str(cp_val)) | |
| childNode.Tag = MyModel | |
| cpNode.Nodes.Add(childNode) | |
| rootNode.Expand() | |
| cpNode.Expand() | |
| treeView.SelectedNode = cpNode | |
| treeView.EndUpdate() | |
| propertyGrid.SelectedObject = CustomPropertyDisplayObject(MyModel, custom_property_names) | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Loaded custom properties") | |
| def OnNodeSelect(sender, args): | |
| try: | |
| if treeView.SelectedNode is None: | |
| propertyGrid.SelectedObject = None | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "No selection") | |
| return | |
| propertyGrid.SelectedObject = CustomPropertyDisplayObject(MyModel, custom_property_names) | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Selected: " + str(treeView.SelectedNode.Text)) | |
| except: | |
| propertyGrid.SelectedObject = None | |
| printTraceBack() | |
| def OnCustomPropClick(sender, args): | |
| dlg = Form() | |
| dlg.Text = "Lookup Custom Property" | |
| dlg.Width = scale(400) | |
| dlg.Height = scale(220) | |
| dlg.StartPosition = FormStartPosition.CenterParent | |
| dlg.BackColor = Colors.PRIMARY | |
| dlg.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Font = Font("Segoe UI", scale(9)) | |
| dlg.FormBorderStyle = FormBorderStyle.FixedDialog | |
| dlg.MaximizeBox = False | |
| dlg.MinimizeBox = False | |
| lbl = Label() | |
| lbl.Text = "Property Name:" | |
| lbl.Location = Point(scale(20), scale(20)) | |
| lbl.Size = Size(scale(340), scale(25)) | |
| lbl.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lbl) | |
| txtName = TextBox() | |
| txtName.Location = Point(scale(20), scale(50)) | |
| txtName.Size = Size(scale(340), scale(28)) | |
| txtName.BackColor = Colors.SURFACE | |
| txtName.ForeColor = Colors.TEXT_PRIMARY | |
| txtName.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtName) | |
| lblResult = Label() | |
| lblResult.Text = "" | |
| lblResult.Location = Point(scale(20), scale(90)) | |
| lblResult.Size = Size(scale(340), scale(40)) | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| dlg.Controls.Add(lblResult) | |
| def OnLookup(s, e): | |
| prop_name = txtName.Text.strip() | |
| if prop_name == "": | |
| lblResult.Text = "Please enter a property name." | |
| lblResult.ForeColor = Colors.ERROR | |
| return | |
| try: | |
| val = MyModel.GetCustomProperty(prop_name) | |
| EnsureCustomPropertyInList(prop_name) | |
| if val is not None: | |
| lblResult.Text = prop_name + " = " + str(val) | |
| lblResult.ForeColor = Colors.SUCCESS | |
| else: | |
| lblResult.Text = prop_name + " = (empty)" | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Looked up: " + prop_name) | |
| except Exception as ex: | |
| lblResult.Text = "Not found or error: " + str(ex) | |
| lblResult.ForeColor = Colors.ERROR | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Lookup failed") | |
| btnLookup = Button() | |
| btnLookup.Text = "Lookup" | |
| btnLookup.Location = Point(scale(20), scale(140)) | |
| btnLookup.Size = Size(scale(100), scale(32)) | |
| btnLookup.BackColor = Colors.ACCENT | |
| btnLookup.ForeColor = Colors.TEXT_PRIMARY | |
| btnLookup.FlatStyle = FlatStyle.Flat | |
| btnLookup.Click += OnLookup | |
| dlg.Controls.Add(btnLookup) | |
| dlg.AcceptButton = btnLookup | |
| btnClose = Button() | |
| btnClose.Text = "Close" | |
| btnClose.Location = Point(scale(260), scale(140)) | |
| btnClose.Size = Size(scale(100), scale(32)) | |
| btnClose.BackColor = Colors.SECONDARY | |
| btnClose.ForeColor = Colors.TEXT_PRIMARY | |
| btnClose.FlatStyle = FlatStyle.Flat | |
| btnClose.Click += lambda s, e: dlg.Close() | |
| dlg.Controls.Add(btnClose) | |
| dlg.CancelButton = btnClose | |
| dlg.ShowDialog() | |
| def OnSetPropClick(sender, args): | |
| dlg = Form() | |
| dlg.Text = "Set Custom Property" | |
| dlg.Width = scale(400) | |
| dlg.Height = scale(280) | |
| dlg.StartPosition = FormStartPosition.CenterParent | |
| dlg.BackColor = Colors.PRIMARY | |
| dlg.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Font = Font("Segoe UI", scale(9)) | |
| dlg.FormBorderStyle = FormBorderStyle.FixedDialog | |
| dlg.MaximizeBox = False | |
| dlg.MinimizeBox = False | |
| lblName = Label() | |
| lblName.Text = "Property Name:" | |
| lblName.Location = Point(scale(20), scale(20)) | |
| lblName.Size = Size(scale(340), scale(25)) | |
| lblName.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lblName) | |
| txtName = TextBox() | |
| txtName.Location = Point(scale(20), scale(50)) | |
| txtName.Size = Size(scale(340), scale(28)) | |
| txtName.BackColor = Colors.SURFACE | |
| txtName.ForeColor = Colors.TEXT_PRIMARY | |
| txtName.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtName) | |
| lblVal = Label() | |
| lblVal.Text = "Property Value:" | |
| lblVal.Location = Point(scale(20), scale(90)) | |
| lblVal.Size = Size(scale(340), scale(25)) | |
| lblVal.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lblVal) | |
| txtVal = TextBox() | |
| txtVal.Location = Point(scale(20), scale(120)) | |
| txtVal.Size = Size(scale(340), scale(28)) | |
| txtVal.BackColor = Colors.SURFACE | |
| txtVal.ForeColor = Colors.TEXT_PRIMARY | |
| txtVal.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtVal) | |
| lblResult = Label() | |
| lblResult.Text = "" | |
| lblResult.Location = Point(scale(20), scale(160)) | |
| lblResult.Size = Size(scale(340), scale(40)) | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| dlg.Controls.Add(lblResult) | |
| def OnSet(s, e): | |
| prop_name = txtName.Text.strip() | |
| prop_val = txtVal.Text | |
| if prop_name == "": | |
| lblResult.Text = "Please enter a property name." | |
| lblResult.ForeColor = Colors.ERROR | |
| return | |
| try: | |
| MyModel.SetCustomProperty(prop_name, prop_val) | |
| EnsureCustomPropertyInList(prop_name) | |
| BuildTreeNodes() | |
| lblResult.Text = "Set " + prop_name + " = " + prop_val | |
| lblResult.ForeColor = Colors.SUCCESS | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Set: " + prop_name) | |
| except Exception as ex: | |
| lblResult.Text = "Error: " + str(ex) | |
| lblResult.ForeColor = Colors.ERROR | |
| btnSet = Button() | |
| btnSet.Text = "Set" | |
| btnSet.Location = Point(scale(20), scale(210)) | |
| btnSet.Size = Size(scale(100), scale(32)) | |
| btnSet.BackColor = Colors.ACCENT | |
| btnSet.ForeColor = Colors.TEXT_PRIMARY | |
| btnSet.FlatStyle = FlatStyle.Flat | |
| btnSet.Click += OnSet | |
| dlg.Controls.Add(btnSet) | |
| dlg.AcceptButton = btnSet | |
| btnClose = Button() | |
| btnClose.Text = "Close" | |
| btnClose.Location = Point(scale(260), scale(210)) | |
| btnClose.Size = Size(scale(100), scale(32)) | |
| btnClose.BackColor = Colors.SECONDARY | |
| btnClose.ForeColor = Colors.TEXT_PRIMARY | |
| btnClose.FlatStyle = FlatStyle.Flat | |
| btnClose.Click += lambda s, e: dlg.Close() | |
| dlg.Controls.Add(btnClose) | |
| dlg.CancelButton = btnClose | |
| dlg.ShowDialog() | |
| def OnPropertyValueChanged(sender, args): | |
| try: | |
| label = args.ChangedItem.Label | |
| if label is None: | |
| return | |
| if label.startswith("Custom:_"): | |
| cp_name = label[len("Custom:_"):] | |
| new_val = str(args.ChangedItem.Value) | |
| try: | |
| MyModel.SetCustomProperty(cp_name, new_val) | |
| EnsureCustomPropertyInList(cp_name) | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + str(MyModel.Name), "Updated: " + cp_name + " = " + new_val) | |
| except Exception as ex: | |
| show_error( | |
| "Failed to set custom property '" + str(cp_name) + "':\n" + str(ex) + | |
| "\n\nThe property must already be defined on the part/assembly or on your PC.", | |
| "Custom Property Error" | |
| ) | |
| except: | |
| printTraceBack() | |
| treeView.AfterSelect += OnNodeSelect | |
| customPropButton.Click += OnCustomPropClick | |
| setPropButton.Click += OnSetPropClick | |
| propertyGrid.PropertyValueChanged += OnPropertyValueChanged | |
| BuildTreeNodes() | |
| form.ShowDialog() | |
| safe_try("CreateCustomPropertyExplorer", CreateCustomPropertyExplorer) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # created with Alibre Script Genie by Stephen S. Mitchell, https://github.com/stephensmitchell | |
| import sys | |
| import clr | |
| import math | |
| # Import Windows Forms | |
| clr.AddReference("System.Windows.Forms") | |
| clr.AddReference("System.Drawing") | |
| clr.AddReference("System.ComponentModel") | |
| from System.Windows.Forms import * | |
| from System.Drawing import * | |
| from System.ComponentModel import * | |
| from System import * | |
| def CreatePropertyExplorer(): | |
| # Get the current model (try part first, then assembly) | |
| MyModel = None | |
| IsAssembly = False | |
| try: | |
| #Units.Current = UnitTypes.Centimeters | |
| #Units.Current = UnitTypes.Millimeters | |
| Units.Current = UnitTypes.Inches | |
| MyModel = CurrentPart() | |
| except: | |
| try: | |
| MyModel = CurrentAssembly() | |
| IsAssembly = True | |
| except: | |
| Win = Windows() | |
| Win.ErrorDialog("No active part or assembly found.\n\nPlease open or create a part/assembly first.", "Alibre Property Explorer") | |
| return | |
| # High DPI support - detect current DPI scaling | |
| import System.Windows.Forms.Application as WinApp | |
| try: | |
| # Get current DPI scaling factor | |
| dpi_scale = System.Drawing.Graphics.FromHwnd(System.IntPtr.Zero).DpiX / 96.0 | |
| except: | |
| dpi_scale = 2.0 # Default to 200% for 5K displays | |
| # Scale factors for high DPI | |
| def scale(value): | |
| return int(value * dpi_scale) | |
| # Professional color scheme | |
| class Colors: | |
| PRIMARY = Color.FromArgb(45, 45, 48) # Dark gray | |
| SECONDARY = Color.FromArgb(62, 62, 66) # Medium gray | |
| ACCENT = Color.FromArgb(0, 122, 204) # Blue accent | |
| SURFACE = Color.FromArgb(37, 37, 38) # Darker surface | |
| TEXT_PRIMARY = Color.FromArgb(241, 241, 241) # Light text | |
| TEXT_SECONDARY = Color.FromArgb(204, 204, 204) # Medium text | |
| BORDER = Color.FromArgb(63, 63, 70) # Border color | |
| SUCCESS = Color.FromArgb(73, 156, 84) # Green | |
| WARNING = Color.FromArgb(252, 196, 25) # Orange | |
| # Create the main form with high DPI awareness | |
| form = Form() | |
| form.Text = "Alibre Property Explorer - " + MyModel.Name | |
| form.Width = scale(1600) | |
| form.Height = scale(1000) | |
| form.StartPosition = FormStartPosition.CenterScreen | |
| form.BackColor = Colors.PRIMARY | |
| form.ForeColor = Colors.TEXT_PRIMARY | |
| form.Font = Font("Segoe UI", scale(9), FontStyle.Regular) | |
| form.MinimumSize = Size(scale(1300), scale(800)) | |
| # Enable high DPI support | |
| try: | |
| form.AutoScaleMode = AutoScaleMode.Dpi | |
| form.AutoScaleDimensions = SizeF(scale(96), scale(96)) | |
| except: | |
| pass | |
| # Add professional icon if possible | |
| try: | |
| form.ShowIcon = True | |
| except: | |
| pass | |
| # Create status bar with DPI scaling | |
| statusStrip = StatusStrip() | |
| statusStrip.BackColor = Colors.SURFACE | |
| statusStrip.ForeColor = Colors.TEXT_SECONDARY | |
| statusStrip.Font = Font("Segoe UI", scale(9)) | |
| statusStrip.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| statusLabel = ToolStripStatusLabel() | |
| statusLabel.Text = "Ready - Model: " + MyModel.Name | |
| statusLabel.Spring = True | |
| statusLabel.TextAlign = ContentAlignment.MiddleLeft | |
| statusLabel.Margin = Padding(scale(8), scale(3), scale(8), scale(3)) | |
| selectionLabel = ToolStripStatusLabel() | |
| selectionLabel.Text = "No selection" | |
| selectionLabel.AutoSize = True | |
| selectionLabel.Margin = Padding(scale(8), scale(3), scale(8), scale(3)) | |
| statusStrip.Items.AddRange(Array[ToolStripItem]((statusLabel, selectionLabel))) | |
| form.Controls.Add(statusStrip) | |
| # Main layout container with DPI scaling | |
| mainSplitter = SplitContainer() | |
| mainSplitter.Dock = DockStyle.Fill | |
| mainSplitter.Orientation = Orientation.Vertical | |
| mainSplitter.SplitterDistance = scale(800) | |
| mainSplitter.BackColor = Colors.BORDER | |
| mainSplitter.SplitterWidth = scale(8) | |
| form.Controls.Add(mainSplitter) | |
| # LEFT PANEL for TreeView with DPI scaling | |
| leftPanel = Panel() | |
| leftPanel.Dock = DockStyle.Fill | |
| leftPanel.BackColor = Colors.SECONDARY | |
| leftPanel.Padding = Padding(scale(16), scale(16), scale(12), scale(16)) | |
| mainSplitter.Panel1.Controls.Add(leftPanel) | |
| # RIGHT PANEL for PropertyGrid with DPI scaling | |
| rightPanel = Panel() | |
| rightPanel.Dock = DockStyle.Fill | |
| rightPanel.BackColor = Colors.SECONDARY | |
| rightPanel.Padding = Padding(scale(12), scale(16), scale(16), scale(16)) | |
| mainSplitter.Panel2.Controls.Add(rightPanel) | |
| # Add header labels with high DPI scaling FIRST | |
| treeHeader = Label() | |
| treeHeader.Text = "Model Structure" | |
| treeHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| treeHeader.ForeColor = Colors.TEXT_PRIMARY | |
| treeHeader.BackColor = Colors.SURFACE | |
| treeHeader.Height = scale(50) # Extra height for high DPI | |
| treeHeader.TextAlign = ContentAlignment.MiddleLeft | |
| treeHeader.Padding = Padding(20, 0, 0, 0) | |
| treeHeader.Dock = DockStyle.Top | |
| propHeader = Label() | |
| propHeader.Text = "Properties" | |
| propHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| propHeader.ForeColor = Colors.TEXT_PRIMARY | |
| propHeader.BackColor = Colors.SURFACE | |
| propHeader.Height = scale(50) # Extra height for high DPI | |
| propHeader.TextAlign = ContentAlignment.MiddleLeft | |
| propHeader.Padding = Padding(scale(20), 0, 0, 0) | |
| propHeader.Dock = DockStyle.Top | |
| # Professional Toolbar with high DPI scaling | |
| toolStrip = ToolStrip() | |
| toolStrip.BackColor = Colors.SURFACE | |
| toolStrip.ForeColor = Colors.TEXT_PRIMARY | |
| toolStrip.Font = Font("Segoe UI", scale(9)) | |
| toolStrip.Renderer = ToolStripProfessionalRenderer() | |
| toolStrip.GripStyle = ToolStripGripStyle.Hidden | |
| toolStrip.Padding = Padding(scale(20), scale(8), scale(20), scale(8)) | |
| toolStrip.AutoSize = False | |
| toolStrip.Height = scale(60) # Extra height for high DPI | |
| toolStrip.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow | |
| toolStrip.Dock = DockStyle.Top | |
| # Create toolbar buttons with high DPI scaling | |
| refreshButton = ToolStripButton("🔄 Refresh") | |
| refreshButton.ForeColor = Colors.TEXT_PRIMARY | |
| refreshButton.ToolTipText = "Refresh the model tree" | |
| refreshButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| refreshButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| refreshButton.AutoSize = True | |
| refreshButton.Font = Font("Segoe UI", scale(9)) | |
| expandAllButton = ToolStripButton("⊞ Expand All") | |
| expandAllButton.ForeColor = Colors.TEXT_PRIMARY | |
| expandAllButton.ToolTipText = "Expand all tree nodes" | |
| expandAllButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| expandAllButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| expandAllButton.AutoSize = True | |
| expandAllButton.Font = Font("Segoe UI", scale(9)) | |
| collapseAllButton = ToolStripButton("⊟ Collapse All") | |
| collapseAllButton.ForeColor = Colors.TEXT_PRIMARY | |
| collapseAllButton.ToolTipText = "Collapse all tree nodes" | |
| collapseAllButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| collapseAllButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| collapseAllButton.AutoSize = True | |
| collapseAllButton.Font = Font("Segoe UI", scale(9)) | |
| realTimeButton = ToolStripButton("🔗 Real-time Sync") | |
| realTimeButton.CheckOnClick = True | |
| realTimeButton.Checked = True | |
| realTimeButton.ForeColor = Colors.SUCCESS | |
| realTimeButton.ToolTipText = "Automatically sync with Alibre selection" | |
| realTimeButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| realTimeButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| realTimeButton.AutoSize = True | |
| realTimeButton.Font = Font("Segoe UI", scale(9)) | |
| # Separator styling with high DPI scaling | |
| sep1 = ToolStripSeparator() | |
| sep1.Margin = Padding(scale(16), 0, scale(16), 0) | |
| sep2 = ToolStripSeparator() | |
| sep2.Margin = Padding(scale(16), 0, scale(16), 0) | |
| toolStrip.Items.AddRange(Array[ToolStripItem](( | |
| refreshButton, sep1, expandAllButton, collapseAllButton, sep2, realTimeButton | |
| ))) | |
| # Professional TreeView with high DPI scaling | |
| treeView = TreeView() | |
| treeView.Dock = DockStyle.Fill | |
| treeView.Font = Font("Segoe UI", scale(9)) | |
| treeView.BackColor = Colors.PRIMARY | |
| treeView.ForeColor = Colors.TEXT_PRIMARY | |
| treeView.LineColor = Colors.BORDER | |
| treeView.ShowLines, treeView.ShowPlusMinus, treeView.ShowRootLines = True, True, True | |
| treeView.HideSelection, treeView.FullRowSelect = False, True | |
| treeView.BorderStyle = BorderStyle.None | |
| treeView.Indent = scale(32) | |
| treeView.ItemHeight = scale(32) # Extra height for high DPI | |
| treeView.Margin = Padding(100, scale(8), 0, 0) | |
| # Professional PropertyGrid with high DPI scaling | |
| propertyGrid = PropertyGrid() | |
| propertyGrid.Dock = DockStyle.Fill | |
| propertyGrid.BackColor = Colors.PRIMARY | |
| propertyGrid.LineColor = Colors.BORDER | |
| propertyGrid.CategoryForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.CategorySplitterColor = Colors.BORDER | |
| propertyGrid.HelpBackColor = Colors.SURFACE | |
| propertyGrid.HelpForeColor = Colors.TEXT_SECONDARY | |
| propertyGrid.ViewBackColor = Colors.PRIMARY | |
| propertyGrid.ViewForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.ToolbarVisible = True | |
| propertyGrid.Font = Font("Segoe UI", scale(9)) | |
| propertyGrid.PropertySort = PropertySort.Categorized | |
| propertyGrid.HelpVisible = True | |
| propertyGrid.Margin = Padding(0, scale(8), 0, 0) | |
| # --- CORRECTED CONTROL ADDITION ORDER --- | |
| # To fix the layout, add the control with Dock.Fill first. Then, add the | |
| # controls with Dock.Top. The last 'Top' control added will be at the very top. | |
| # Add controls to the left panel | |
| leftPanel.Controls.Add(treeView) # Add FILL control first | |
| leftPanel.Controls.Add(toolStrip) # This will sit below the header | |
| leftPanel.Controls.Add(treeHeader) # This will be at the very top | |
| # Add controls to the right panel | |
| rightPanel.Controls.Add(propertyGrid) # Add FILL control first | |
| rightPanel.Controls.Add(propHeader) # This will be at the very top | |
| # Custom Property Wrapper Classes (unchanged functionality) | |
| class PropertyWrapper(object): | |
| """Base wrapper to expose all API properties""" | |
| def __init__(self, obj, obj_type): | |
| self._wrapped_object = obj | |
| self._object_type = obj_type | |
| self._properties = {} | |
| self._load_properties() | |
| def _load_properties(self): | |
| """Load all available properties based on object type""" | |
| # This dictionary dispatches to the correct loading function | |
| loader_map = { | |
| "Part": self._load_part_properties, | |
| "Assembly": self._load_assembly_properties, | |
| "AssembledPart": self._load_assembled_part_properties, | |
| "AssembledSubAssembly": self._load_assembled_sub_assembly_properties, | |
| "Face": self._load_face_properties, | |
| "Edge": self._load_edge_properties, | |
| "Vertex": self._load_vertex_properties, | |
| "Sketch": self._load_sketch_properties, | |
| "Sketch3D": self._load_sketch_3d_properties, | |
| "Parameter": self._load_parameter_properties, | |
| "Configuration": self._load_configuration_properties, | |
| "Feature": self._load_feature_properties, | |
| "Plane": self._load_plane_properties, | |
| "Axis": self._load_axis_properties, | |
| "Point": self._load_point_properties, | |
| "Line": self._load_line_properties, | |
| "Line3D": self._load_line3d_properties, | |
| "Circle": self._load_circle_properties, | |
| "CircularArc": self._load_circular_arc_properties, | |
| "CircularArc3D": self._load_circular_arc3d_properties, | |
| "Ellipse": self._load_ellipse_properties, | |
| "EllipticalArc": self._load_elliptical_arc_properties, | |
| "Bspline": self._load_bspline_properties, | |
| "Bspline3D": self._load_bspline3d_properties, | |
| "SketchPoint": self._load_sketchpoint_properties, | |
| "SketchPoint3D": self._load_sketchpoint3d_properties, | |
| } | |
| # Get the loader function from the map or use the generic one | |
| loader_func = loader_map.get(self._object_type, self._load_generic_properties) | |
| loader_func() | |
| def _safe_get_property(self, prop_name, default_value="N/A"): | |
| """Safely get a property value, handling potential errors.""" | |
| try: | |
| if hasattr(self._wrapped_object, prop_name): | |
| value = getattr(self._wrapped_object, prop_name) | |
| if value is None: | |
| return "None" | |
| # Format lists/tuples for better readability | |
| if isinstance(value, (list, tuple, Array)): | |
| return ", ".join(map(str, value)) | |
| return str(value) | |
| return default_value | |
| except Exception as e: | |
| return "Error: " + str(e) | |
| def _format_bbox(self, bbox_method_name): | |
| """Helper to get and format a bounding box.""" | |
| try: | |
| bbox = getattr(self._wrapped_object, bbox_method_name)() | |
| if bbox and len(bbox) == 8: | |
| return "; ".join(["({:.3f}, {:.3f}, {:.3f})".format(p[0], p[1], p[2]) for p in bbox]) | |
| return "Invalid BBox Data" | |
| except: | |
| return "N/A" | |
| def _load_base_file_properties(self): | |
| """Loads common properties for Part and Assembly objects.""" | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "FileName": self._safe_get_property("FileName"), | |
| "Material": self._safe_get_property("Material"), | |
| "Density": self._safe_get_property("Density"), | |
| "Mass": self._safe_get_property("Mass"), | |
| "Description": self._safe_get_property("Description"), | |
| "Number": self._safe_get_property("Number"), | |
| "Comment": self._safe_get_property("Comment"), | |
| "CostCenter": self._safe_get_property("CostCenter"), | |
| "CreatedBy": self._safe_get_property("CreatedBy"), | |
| "CreatedDate": self._safe_get_property("CreatedDate"), | |
| "CreatingApplication": self._safe_get_property("CreatingApplication"), | |
| "DocumentNumber": self._safe_get_property("DocumentNumber"), | |
| "EngineeringApprovalDate": self._safe_get_property("EngineeringApprovalDate"), | |
| "EngineeringApprovedBy": self._safe_get_property("EngineeringApprovedBy"), | |
| "EstimatedCost": self._safe_get_property("EstimatedCost"), | |
| "Keywords": self._safe_get_property("Keywords"), | |
| "LastAuthor": self._safe_get_property("LastAuthor"), | |
| "LastUpdateDate": self._safe_get_property("LastUpdateDate"), | |
| "ExtendedMaterialInformation": self._safe_get_property("ExtendedMaterialInformation"), | |
| "ManufacturingApprovedBy": self._safe_get_property("ManufacturingApprovedBy"), | |
| "ManufacturingApprovedDate": self._safe_get_property("ManufacturingApprovedDate"), | |
| "ModifiedInformation": self._safe_get_property("ModifiedInformation"), | |
| "Product": self._safe_get_property("Product"), | |
| "ReceivedFrom": self._safe_get_property("ReceivedFrom"), | |
| "Revision": self._safe_get_property("Revision"), | |
| "StockSize": self._safe_get_property("StockSize"), | |
| "Supplier": self._safe_get_property("Supplier"), | |
| "Title": self._safe_get_property("Title"), | |
| "Vendor": self._safe_get_property("Vendor"), | |
| "WebLink": self._safe_get_property("WebLink"), | |
| }) | |
| try: | |
| self._properties["DisplayUnits"] = str(self._wrapped_object.DisplayUnits()) | |
| except: | |
| self._properties["DisplayUnits"] = "N/A" | |
| def _load_part_properties(self): | |
| self._load_base_file_properties() | |
| self._properties["BoundingBox"] = self._format_bbox("GetBoundingBox") | |
| def _load_assembly_properties(self): | |
| self._load_base_file_properties() | |
| # Assembly does not have GetBoundingBox, it is calculated from components. | |
| def _load_assembled_part_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| self._properties["BoundingBox (Assembly Coords)"] = self._format_bbox("GetAssemblyBoundingBox") | |
| try: | |
| self._properties["ConfigurationCount"] = str(len(self._wrapped_object.Configurations)) | |
| except: | |
| self._properties["ConfigurationCount"] = "0" | |
| def _load_assembled_sub_assembly_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: | |
| self._properties["ConfigurationCount"] = str(len(self._wrapped_object.Configurations)) | |
| except: | |
| self._properties["ConfigurationCount"] = "0" | |
| def _load_face_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["Area"] = str(self._wrapped_object.GetArea()) | |
| except: self._properties["Area"] = "N/A" | |
| try: self._properties["IsRectangle"] = str(self._wrapped_object.IsRectangle()) | |
| except: self._properties["IsRectangle"] = "N/A" | |
| try: self._properties["EdgeCount"] = str(len(self._wrapped_object.GetEdges())) | |
| except: self._properties["EdgeCount"] = "N/A" | |
| try: self._properties["VertexCount"] = str(len(self._wrapped_object.GetVertices())) | |
| except: self._properties["VertexCount"] = "N/A" | |
| try: self._properties["AdjoiningFaceCount"] = str(len(self._wrapped_object.GetAdjoiningFaces())) | |
| except: self._properties["AdjoiningFaceCount"] = "N/A" | |
| def _load_edge_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["Diameter"] = self._safe_get_property("Diameter") | |
| try: self._properties["VertexCount"] = str(len(self._wrapped_object.GetVertices())) | |
| except: self._properties["VertexCount"] = "N/A" | |
| def _load_vertex_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "X": self._safe_get_property("X"), | |
| "Y": self._safe_get_property("Y"), | |
| "Z": self._safe_get_property("Z") | |
| }) | |
| def _load_sketch_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["FigureCount"] = str(len(self._wrapped_object.Figures)) | |
| except: self._properties["FigureCount"] = "0" | |
| try: self._properties["Surface"] = self._wrapped_object.GetSurface().Name | |
| except: self._properties["Surface"] = "N/A" | |
| def _load_sketch_3d_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["FigureCount"] = str(len(self._wrapped_object.Figures)) | |
| except: self._properties["FigureCount"] = "0" | |
| def _load_parameter_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "Value": self._safe_get_property("Value"), | |
| "Equation": self._safe_get_property("Equation"), | |
| "Comment": self._safe_get_property("Comment"), | |
| "Type": self._safe_get_property("Type"), | |
| "Units": self._safe_get_property("Units"), | |
| "RawValue": self._safe_get_property("RawValue"), | |
| "ExcelWorkbook": self._safe_get_property("ExcelWorkbook"), | |
| "ExcelSheet": self._safe_get_property("ExcelSheet"), | |
| "ExcelCell": self._safe_get_property("ExcelCell"), | |
| }) | |
| def _load_configuration_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "IsActive": self._safe_get_property("IsActive") | |
| }) | |
| def _load_feature_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| # Color is a method SetColor, not a property to get. | |
| def _load_plane_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| def _load_axis_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| def _load_point_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: | |
| coords = self._wrapped_object.GetCoordinates() | |
| self._properties["X"] = str(coords[0]) | |
| self._properties["Y"] = str(coords[1]) | |
| self._properties["Z"] = str(coords[2]) | |
| except: | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["Z"] = self._safe_get_property("Z") | |
| # Sketch Figure Properties | |
| def _load_line_properties(self): | |
| self._properties["Type"] = "Line" | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_line3d_properties(self): | |
| self._properties["Type"] = "3D Line" | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circle_properties(self): | |
| self._properties["Type"] = "Circle" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circular_arc_properties(self): | |
| self._properties["Type"] = "Circular Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Angle"] = self._safe_get_property("Angle") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circular_arc3d_properties(self): | |
| self._properties["Type"] = "3D Circular Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Angle"] = self._safe_get_property("Angle") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_ellipse_properties(self): | |
| self._properties["Type"] = "Ellipse" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["MajorAxisAngle"] = self._safe_get_property("MajorAxisAngle") | |
| self._properties["MinorMajorRatio"] = self._safe_get_property("MinorMajorRatio") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_elliptical_arc_properties(self): | |
| self._properties["Type"] = "Elliptical Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["MajorAxisAngle"] = self._safe_get_property("MajorAxisAngle") | |
| self._properties["MinorMajorRatio"] = self._safe_get_property("MinorMajorRatio") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_bspline_properties(self): | |
| self._properties["Type"] = "B-spline" | |
| self._properties["Order"] = self._safe_get_property("Order") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["ControlPoints"] = self._safe_get_property("ControlPoints") | |
| self._properties["KnotVectors"] = self._safe_get_property("KnotVectors") | |
| self._properties["Weights"] = self._safe_get_property("Weights") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_bspline3d_properties(self): | |
| self._properties["Type"] = "3D B-spline" | |
| self._properties["Order"] = self._safe_get_property("Order") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["ControlPoints"] = self._safe_get_property("ControlPoints") | |
| self._properties["KnotVectors"] = self._safe_get_property("KnotVectors") | |
| self._properties["Weights"] = self._safe_get_property("Weights") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_sketchpoint_properties(self): | |
| self._properties["Type"] = "Sketch Point" | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_sketchpoint3d_properties(self): | |
| self._properties["Type"] = "3D Sketch Point" | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["Z"] = self._safe_get_property("Z") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_generic_properties(self): | |
| """Load generic properties for unknown types""" | |
| self._properties["Name"] = self._safe_get_property("Name", "N/A") | |
| self._properties["ObjectType"] = str(type(self._wrapped_object).__name__) | |
| def GetAllProperties(self): | |
| return self._properties | |
| # Enhanced property display using a custom object | |
| class PropertyDisplayObject(object): | |
| def __init__(self, wrapped_obj): | |
| self._original_object = wrapped_obj | |
| obj_type_name = type(wrapped_obj).__name__ | |
| # Use a dictionary for more robust type mapping | |
| type_map = { | |
| "ADPart": "Part", | |
| "ADAssembly": "Assembly", | |
| "ADFace": "Face", | |
| "ADEdge": "Edge", | |
| "ADVertex": "Vertex", | |
| "ADSketch": "Sketch", | |
| "ADSketch3D": "Sketch3D", | |
| "ADParameter": "Parameter", | |
| "ADConfiguration": "Configuration", | |
| "ADFeature": "Feature", | |
| "ADPlane": "Plane", | |
| "ADAxis": "Axis", | |
| "ADPoint": "Point", | |
| "ADAssembledPart": "AssembledPart", | |
| "ADAssembledSubAssembly": "AssembledSubAssembly", | |
| "ADLine": "Line", | |
| "ADLine3D": "Line3D", | |
| "ADCircle": "Circle", | |
| "ADCircularArc": "CircularArc", | |
| "ADCircularArc3D": "CircularArc3D", | |
| "ADEllipse": "Ellipse", | |
| "ADEllipticalArc": "EllipticalArc", | |
| "ADBspline": "Bspline", | |
| "ADBspline3D": "Bspline3D", | |
| "ADSketchPoint": "SketchPoint", | |
| "ADSketchPoint3D": "SketchPoint3D" | |
| } | |
| obj_type = type_map.get(obj_type_name, obj_type_name) | |
| self._wrapper = PropertyWrapper(wrapped_obj, obj_type) | |
| # Dynamically add properties for the PropertyGrid | |
| for prop_name, prop_value in self._wrapper.GetAllProperties().items(): | |
| clean_prop_name = prop_name.replace(" ", "_").replace("-", "_").replace("(", "").replace(")", "") | |
| setattr(self, clean_prop_name, prop_value) | |
| # --- REAL-TIME SELECTION TRACKING --- | |
| last_alibre_selection = None | |
| manual_selection_active = False | |
| selectionTimer = Timer() | |
| selectionTimer.Interval = 500 # Check for new selections every 500ms | |
| def UpdateStatusBar(message, selection_text=""): | |
| """Update the status bar with current information""" | |
| statusLabel.Text = message | |
| selectionLabel.Text = selection_text | |
| def OnTimerTick(sender, args): | |
| """Checks the active selection in Alibre and updates the PropertyGrid""" | |
| global last_alibre_selection, manual_selection_active | |
| if not realTimeButton.Checked or manual_selection_active: | |
| return | |
| try: | |
| selections = MyModel.Selections | |
| if selections and len(selections) > 0: | |
| current_object = selections[0] | |
| if current_object != last_alibre_selection: | |
| display_object = PropertyDisplayObject(current_object) | |
| propertyGrid.SelectedObject = display_object | |
| last_alibre_selection = current_object | |
| # Update status bar | |
| obj_name = "Unknown" | |
| try: | |
| if hasattr(current_object, 'Name') and current_object.Name: | |
| obj_name = current_object.Name | |
| else: | |
| obj_name = type(current_object).__name__ | |
| except: | |
| obj_name = type(current_object).__name__ | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Selected: " + obj_name) | |
| elif last_alibre_selection is not None: | |
| if not manual_selection_active: | |
| propertyGrid.SelectedObject = None | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "No selection") | |
| last_alibre_selection = None | |
| except: | |
| selectionTimer.Stop() | |
| selectionTimer.Tick += OnTimerTick | |
| selectionTimer.Start() | |
| form.FormClosing += lambda s, e: selectionTimer.Stop() | |
| def AddObjectNode(parentNode, item, name=None): | |
| """Helper to add an object node to the tree - only if object is valid""" | |
| if item is None: | |
| return None | |
| try: | |
| if name: | |
| display_name = name | |
| elif hasattr(item, 'Name') and item.Name: | |
| display_name = item.Name | |
| else: | |
| display_name = str(type(item).__name__) | |
| except: | |
| display_name = str(type(item).__name__) | |
| node = TreeNode(display_name) | |
| node.Tag = item | |
| # Add visual indicators with Unicode symbols | |
| obj_type = type(item).__name__ | |
| if "Assembly" in obj_type: | |
| display_name = "🗂️ " + display_name | |
| elif "Part" in obj_type: | |
| display_name = "🔧 " + display_name | |
| elif "Face" in obj_type: | |
| display_name = "📄 " + display_name | |
| elif "Edge" in obj_type: | |
| display_name = "📏 " + display_name | |
| elif "Vertex" in obj_type: | |
| display_name = "📍 " + display_name | |
| elif "Sketch" in obj_type: | |
| display_name = "✏️ " + display_name | |
| elif "Feature" in obj_type: | |
| display_name = "⚙️ " + display_name | |
| elif "Parameter" in obj_type: | |
| display_name = "🔢 " + display_name | |
| elif "Configuration" in obj_type: | |
| display_name = "⚙️ " + display_name | |
| elif "Plane" in obj_type: | |
| display_name = "🔲 " + display_name | |
| elif "Axis" in obj_type: | |
| display_name = "↔️ " + display_name | |
| elif "Point" in obj_type: | |
| display_name = "🎯 " + display_name | |
| node.Text = display_name | |
| if parentNode: | |
| parentNode.Nodes.Add(node) | |
| else: | |
| treeView.Nodes.Add(node) | |
| return node | |
| def AddCollectionNode(parentNode, collectionName, collection_provider): | |
| """Helper to add a collection node with all its items - only if collection has real items""" | |
| collection = None | |
| try: | |
| collection = collection_provider() if callable(collection_provider) else collection_provider | |
| if collection is None or len(collection) == 0: | |
| return None | |
| except: | |
| return None | |
| collectionNode = TreeNode("📁 %s (%d)" % (collectionName, len(collection))) | |
| collectionNode.Tag = collection | |
| parentNode.Nodes.Add(collectionNode) | |
| for i, item in enumerate(collection): | |
| if item is None: | |
| continue | |
| try: | |
| if hasattr(item, 'Name') and item.Name: | |
| item_name = item.Name | |
| else: | |
| item_name = "%s[%d]" % (type(item).__name__, i) | |
| itemNode = AddObjectNode(collectionNode, item, item_name) | |
| # Recursively add nodes for complex objects like sketches or assembled parts | |
| if itemNode: | |
| obj_type_name = type(item).__name__ | |
| if "Sketch" in obj_type_name and hasattr(item, 'Figures'): | |
| AddCollectionNode(itemNode, "Figures", item.Figures) | |
| elif obj_type_name == "ADAssembledPart": | |
| PopulateAssembledPartNode(itemNode, item) | |
| elif obj_type_name == "ADAssembledSubAssembly": | |
| PopulateSubAssemblyNode(itemNode, item) | |
| except Exception as e: | |
| # Skip invalid items silently | |
| pass | |
| return collectionNode | |
| def PopulateAssembledPartNode(partNode, part): | |
| """Adds geometry collections for an AssembledPart""" | |
| AddCollectionNode(partNode, "Faces", part.GetFaces) | |
| AddCollectionNode(partNode, "Edges", part.GetEdges) | |
| # GetAssemblyVertices returns a list of lists, not vertex objects, so we handle it differently | |
| try: | |
| vertices = part.GetAssemblyVertices() | |
| if vertices and len(vertices) > 0: | |
| v_node = TreeNode("📁 Vertices (%d)" % len(vertices)) | |
| partNode.Nodes.Add(v_node) | |
| except: | |
| pass | |
| def PopulateSubAssemblyNode(assemblyNode, subAssembly): | |
| """Recursively populates a sub-assembly node""" | |
| AddCollectionNode(assemblyNode, "Parts", subAssembly.Parts) | |
| AddCollectionNode(assemblyNode, "SubAssemblies", subAssembly.SubAssemblies) | |
| def BuildTreeNodes(): | |
| """Builds the tree structure using only real model data""" | |
| treeView.BeginUpdate() | |
| treeView.Nodes.Clear() | |
| UpdateStatusBar("Building model tree...", "") | |
| # Root node | |
| rootNode = AddObjectNode(None, MyModel, MyModel.Name) | |
| # Add common collections that are directly accessible | |
| AddCollectionNode(rootNode, "Configurations", MyModel.Configurations) | |
| AddCollectionNode(rootNode, "Parameters", MyModel.Parameters) | |
| if IsAssembly: | |
| AddCollectionNode(rootNode, "Parts", MyModel.Parts) | |
| AddCollectionNode(rootNode, "SubAssemblies", MyModel.SubAssemblies) | |
| else: # It's a Part | |
| AddCollectionNode(rootNode, "Faces", MyModel.GetFaces) | |
| AddCollectionNode(rootNode, "Edges", MyModel.GetEdges) | |
| AddCollectionNode(rootNode, "Vertices", MyModel.GetVertices) | |
| # --- BRUTE-FORCE DISCOVERY --- | |
| # The Alibre API does not provide direct collections for items like Features, Sketches, | |
| # Planes, Axes, or Points. The only way to find them is to try getting them by name. | |
| # The following sections attempt to find these items by iterating through common naming patterns. | |
| def find_named_items(collection_name, get_method, name_patterns, try_3d_sketch=False): | |
| found_items = [] | |
| found_names = set() | |
| for name in name_patterns: | |
| try: | |
| item = get_method(name) | |
| if item and name not in found_names: | |
| found_items.append((name, item)) | |
| found_names.add(name) | |
| except: pass | |
| if try_3d_sketch: | |
| try: | |
| item3d = MyModel.Get3DSketch(name) | |
| if item3d and (name + "_3D") not in found_names: | |
| found_items.append((name + " (3D)", item3d)) | |
| found_names.add(name + "_3D") | |
| except: pass | |
| if found_items: | |
| node = TreeNode("📁 %s (%d)" % (collection_name, len(found_items))) | |
| rootNode.Nodes.Add(node) | |
| for item_name, item_obj in found_items: | |
| itemNode = AddObjectNode(node, item_obj, item_name) | |
| # For sketches, also add their figures | |
| if itemNode and "Sketch" in collection_name and hasattr(item_obj, 'Figures'): | |
| AddCollectionNode(itemNode, "Figures", item_obj.Figures) | |
| # Define name patterns | |
| base_names = { | |
| "Feature": ["Extrude", "Extrusion", "Cut", "Fillet", "Chamfer", "Hole", "Pattern", "Revolve", "Loft", "Sweep", "Shell", "Draft", "Mirror", "Feature", "Boss", "stack"], | |
| "Sketch": ["Sketch", "Profile", "Path", "Section", "2D Sketch", "3D Sketch"], | |
| "Plane": ["Plane"], | |
| "Axis": ["Axis"], | |
| "Point": ["Point", "Origin"] | |
| } | |
| names_to_try = {} | |
| for key, prefixes in base_names.items(): | |
| current_list = [] | |
| for prefix in prefixes: | |
| # Also try the prefix by itself, e.g., for a feature named just "stack" | |
| current_list.append(prefix) | |
| for i in range(1, 51): # Check up to 50 of each type | |
| current_list.extend([ | |
| "%s<%d>" % (prefix, i), # e.g., Sketch<1> | |
| "%s%d" % (prefix, i), # e.g., Sketch1 | |
| "%s <%d>" % (prefix, i), # e.g., Sketch <1> | |
| "%s %d" % (prefix, i) # e.g., Sketch 1 | |
| ]) | |
| names_to_try[key] = current_list | |
| # Add standard planes/axes/origin if they exist | |
| try: | |
| if MyModel.XYPlane: AddObjectNode(rootNode, MyModel.XYPlane, "XY-Plane") | |
| if MyModel.YZPlane: AddObjectNode(rootNode, MyModel.YZPlane, "YZ-Plane") | |
| if MyModel.ZXPlane: AddObjectNode(rootNode, MyModel.ZXPlane, "ZX-Plane") | |
| if MyModel.XAxis: AddObjectNode(rootNode, MyModel.XAxis, "X-Axis") | |
| if MyModel.YAxis: AddObjectNode(rootNode, MyModel.YAxis, "Y-Axis") | |
| if MyModel.ZAxis: AddObjectNode(rootNode, MyModel.ZAxis, "Z-Axis") | |
| if MyModel.Origin: AddObjectNode(rootNode, MyModel.Origin, "Origin") | |
| except: pass | |
| # Find and add items | |
| find_named_items("Features", MyModel.GetFeature, names_to_try["Feature"]) | |
| find_named_items("Sketches", MyModel.GetSketch, names_to_try["Sketch"], try_3d_sketch=True) | |
| find_named_items("Planes", MyModel.GetPlane, names_to_try["Plane"]) | |
| find_named_items("Axes", MyModel.GetAxis, names_to_try["Axis"]) | |
| find_named_items("Points", MyModel.GetPoint, names_to_try["Point"]) | |
| treeView.EndUpdate() | |
| rootNode.Expand() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Tree built successfully") | |
| def OnNodeSelect(sender, args): | |
| """When a node is selected in the tree, show its properties with full API exposure""" | |
| global manual_selection_active | |
| manual_selection_active = True | |
| if treeView.SelectedNode and treeView.SelectedNode.Tag is not None: | |
| selected_object = treeView.SelectedNode.Tag | |
| # Check if tag is a collection, if so, select the parent model | |
| if isinstance(selected_object, (list, tuple, Array)): | |
| if treeView.SelectedNode.Parent and treeView.SelectedNode.Parent.Tag: | |
| selected_object = treeView.SelectedNode.Parent.Tag | |
| else: | |
| selected_object = MyModel | |
| propertyGrid.SelectedObject = PropertyDisplayObject(selected_object) | |
| # Update status bar | |
| try: | |
| obj_name = treeView.SelectedNode.Text | |
| if obj_name.startswith("📁 "): | |
| obj_name = obj_name[3:] # Remove folder emoji and extra space | |
| elif len(obj_name) > 3 and obj_name[2:3] == " ": | |
| obj_name = obj_name[3:] # Remove other emojis and extra space | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Selected: " + obj_name) | |
| except: | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Tree selection") | |
| else: | |
| propertyGrid.SelectedObject = None | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "No selection") | |
| # Reset manual selection flag after a short delay | |
| def ResetManualFlag(s, e): | |
| global manual_selection_active | |
| manual_selection_active = False | |
| s.Stop() | |
| resetTimer = Timer() | |
| resetTimer.Interval = 1000 | |
| resetTimer.Tick += ResetManualFlag | |
| resetTimer.Start() | |
| # Enhanced Event Handlers | |
| def OnRefreshClick(sender, args): | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Model refreshed") | |
| def OnExpandAllClick(sender, args): | |
| treeView.ExpandAll() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "All nodes expanded") | |
| def OnCollapseAllClick(sender, args): | |
| treeView.CollapseAll() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "All nodes collapsed") | |
| def OnRealTimeToggle(sender, args): | |
| if realTimeButton.Checked: | |
| realTimeButton.ForeColor = Colors.SUCCESS | |
| selectionTimer.Start() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Real-time sync enabled") | |
| else: | |
| realTimeButton.ForeColor = Colors.TEXT_PRIMARY | |
| selectionTimer.Stop() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Real-time sync disabled") | |
| # Assign Event Handlers | |
| treeView.AfterSelect += OnNodeSelect | |
| refreshButton.Click += OnRefreshClick | |
| expandAllButton.Click += OnExpandAllClick | |
| collapseAllButton.Click += OnCollapseAllClick | |
| realTimeButton.Click += OnRealTimeToggle | |
| # Build the initial tree and show the form | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Explorer initialized") | |
| form.ShowDialog() | |
| # Run the script | |
| CreatePropertyExplorer() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # created with Alibre Script Genie by Stephen S. Mitchell, https://github.com/stephensmitchell | |
| import sys | |
| import clr | |
| import math | |
| # Import Windows Forms | |
| clr.AddReference("System.Windows.Forms") | |
| clr.AddReference("System.Drawing") | |
| clr.AddReference("System.ComponentModel") | |
| from System.Windows.Forms import * | |
| from System.Drawing import * | |
| from System.ComponentModel import * | |
| from System import * | |
| def CreatePropertyExplorer(): | |
| # Get the current model (try part first, then assembly) | |
| MyModel = None | |
| IsAssembly = False | |
| try: | |
| #Units.Current = UnitTypes.Centimeters | |
| #Units.Current = UnitTypes.Millimeters | |
| Units.Current = UnitTypes.Inches | |
| MyModel = CurrentPart() | |
| except: | |
| try: | |
| MyModel = CurrentAssembly() | |
| IsAssembly = True | |
| except: | |
| Win = Windows() | |
| Win.ErrorDialog("No active part or assembly found.\n\nPlease open or create a part/assembly first.", "Alibre Property Explorer") | |
| return | |
| # High DPI support - detect current DPI scaling | |
| import System.Windows.Forms.Application as WinApp | |
| try: | |
| # Get current DPI scaling factor | |
| dpi_scale = System.Drawing.Graphics.FromHwnd(System.IntPtr.Zero).DpiX / 96.0 | |
| except: | |
| dpi_scale = 2.0 # Default to 200% for 5K displays | |
| # Scale factors for high DPI | |
| def scale(value): | |
| return int(value * dpi_scale) | |
| # Professional color scheme | |
| class Colors: | |
| PRIMARY = Color.FromArgb(45, 45, 48) # Dark gray | |
| SECONDARY = Color.FromArgb(62, 62, 66) # Medium gray | |
| ACCENT = Color.FromArgb(0, 122, 204) # Blue accent | |
| SURFACE = Color.FromArgb(37, 37, 38) # Darker surface | |
| TEXT_PRIMARY = Color.FromArgb(241, 241, 241) # Light text | |
| TEXT_SECONDARY = Color.FromArgb(204, 204, 204) # Medium text | |
| BORDER = Color.FromArgb(63, 63, 70) # Border color | |
| SUCCESS = Color.FromArgb(73, 156, 84) # Green | |
| WARNING = Color.FromArgb(252, 196, 25) # Orange | |
| # Create the main form with high DPI awareness | |
| form = Form() | |
| form.Text = "Alibre Property Explorer - " + MyModel.Name | |
| form.Width = scale(1600) | |
| form.Height = scale(1000) | |
| form.StartPosition = FormStartPosition.CenterScreen | |
| form.BackColor = Colors.PRIMARY | |
| form.ForeColor = Colors.TEXT_PRIMARY | |
| form.Font = Font("Segoe UI", scale(9), FontStyle.Regular) | |
| form.MinimumSize = Size(scale(1300), scale(800)) | |
| # Enable high DPI support | |
| try: | |
| form.AutoScaleMode = AutoScaleMode.Dpi | |
| form.AutoScaleDimensions = SizeF(scale(96), scale(96)) | |
| except: | |
| pass | |
| # Add professional icon if possible | |
| try: | |
| form.ShowIcon = True | |
| except: | |
| pass | |
| # Create status bar with DPI scaling | |
| statusStrip = StatusStrip() | |
| statusStrip.BackColor = Colors.SURFACE | |
| statusStrip.ForeColor = Colors.TEXT_SECONDARY | |
| statusStrip.Font = Font("Segoe UI", scale(9)) | |
| statusStrip.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| statusLabel = ToolStripStatusLabel() | |
| statusLabel.Text = "Ready - Model: " + MyModel.Name | |
| statusLabel.Spring = True | |
| statusLabel.TextAlign = ContentAlignment.MiddleLeft | |
| statusLabel.Margin = Padding(scale(8), scale(3), scale(8), scale(3)) | |
| selectionLabel = ToolStripStatusLabel() | |
| selectionLabel.Text = "No selection" | |
| selectionLabel.AutoSize = True | |
| selectionLabel.Margin = Padding(scale(8), scale(3), scale(8), scale(3)) | |
| statusStrip.Items.AddRange(Array[ToolStripItem]((statusLabel, selectionLabel))) | |
| form.Controls.Add(statusStrip) | |
| # Main layout container with DPI scaling | |
| mainSplitter = SplitContainer() | |
| mainSplitter.Dock = DockStyle.Fill | |
| mainSplitter.Orientation = Orientation.Vertical | |
| mainSplitter.SplitterDistance = scale(800) | |
| mainSplitter.BackColor = Colors.BORDER | |
| mainSplitter.SplitterWidth = scale(8) | |
| form.Controls.Add(mainSplitter) | |
| # LEFT PANEL for TreeView with DPI scaling | |
| leftPanel = Panel() | |
| leftPanel.Dock = DockStyle.Fill | |
| leftPanel.BackColor = Colors.SECONDARY | |
| leftPanel.Padding = Padding(scale(16), scale(16), scale(12), scale(16)) | |
| mainSplitter.Panel1.Controls.Add(leftPanel) | |
| # RIGHT PANEL for PropertyGrid with DPI scaling | |
| rightPanel = Panel() | |
| rightPanel.Dock = DockStyle.Fill | |
| rightPanel.BackColor = Colors.SECONDARY | |
| rightPanel.Padding = Padding(scale(12), scale(16), scale(16), scale(16)) | |
| mainSplitter.Panel2.Controls.Add(rightPanel) | |
| # Add header labels with high DPI scaling FIRST | |
| treeHeader = Label() | |
| treeHeader.Text = "Model Structure" | |
| treeHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| treeHeader.ForeColor = Colors.TEXT_PRIMARY | |
| treeHeader.BackColor = Colors.SURFACE | |
| treeHeader.Height = scale(50) # Extra height for high DPI | |
| treeHeader.TextAlign = ContentAlignment.MiddleLeft | |
| treeHeader.Padding = Padding(20, 0, 0, 0) | |
| treeHeader.Dock = DockStyle.Top | |
| propHeader = Label() | |
| propHeader.Text = "Properties" | |
| propHeader.Font = Font("Segoe UI", scale(10), FontStyle.Bold) | |
| propHeader.ForeColor = Colors.TEXT_PRIMARY | |
| propHeader.BackColor = Colors.SURFACE | |
| propHeader.Height = scale(50) # Extra height for high DPI | |
| propHeader.TextAlign = ContentAlignment.MiddleLeft | |
| propHeader.Padding = Padding(scale(20), 0, 0, 0) | |
| propHeader.Dock = DockStyle.Top | |
| # Professional Toolbar with high DPI scaling | |
| toolStrip = ToolStrip() | |
| toolStrip.BackColor = Colors.SURFACE | |
| toolStrip.ForeColor = Colors.TEXT_PRIMARY | |
| toolStrip.Font = Font("Segoe UI", scale(9)) | |
| toolStrip.Renderer = ToolStripProfessionalRenderer() | |
| toolStrip.GripStyle = ToolStripGripStyle.Hidden | |
| toolStrip.Padding = Padding(scale(20), scale(8), scale(20), scale(8)) | |
| toolStrip.AutoSize = False | |
| toolStrip.Height = scale(60) # Extra height for high DPI | |
| toolStrip.LayoutStyle = ToolStripLayoutStyle.HorizontalStackWithOverflow | |
| toolStrip.Dock = DockStyle.Top | |
| # Create toolbar buttons with high DPI scaling | |
| refreshButton = ToolStripButton("🔄 Refresh") | |
| refreshButton.ForeColor = Colors.TEXT_PRIMARY | |
| refreshButton.ToolTipText = "Refresh the model tree" | |
| refreshButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| refreshButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| refreshButton.AutoSize = True | |
| refreshButton.Font = Font("Segoe UI", scale(9)) | |
| expandAllButton = ToolStripButton("⊞ Expand All") | |
| expandAllButton.ForeColor = Colors.TEXT_PRIMARY | |
| expandAllButton.ToolTipText = "Expand all tree nodes" | |
| expandAllButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| expandAllButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| expandAllButton.AutoSize = True | |
| expandAllButton.Font = Font("Segoe UI", scale(9)) | |
| collapseAllButton = ToolStripButton("⊟ Collapse All") | |
| collapseAllButton.ForeColor = Colors.TEXT_PRIMARY | |
| collapseAllButton.ToolTipText = "Collapse all tree nodes" | |
| collapseAllButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| collapseAllButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| collapseAllButton.AutoSize = True | |
| collapseAllButton.Font = Font("Segoe UI", scale(9)) | |
| realTimeButton = ToolStripButton("🔗 Real-time Sync") | |
| realTimeButton.CheckOnClick = True | |
| realTimeButton.Checked = True | |
| realTimeButton.ForeColor = Colors.SUCCESS | |
| realTimeButton.ToolTipText = "Automatically sync with Alibre selection" | |
| realTimeButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| realTimeButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| realTimeButton.AutoSize = True | |
| realTimeButton.Font = Font("Segoe UI", scale(9)) | |
| customPropButton = ToolStripButton("Custom Props") | |
| customPropButton.ForeColor = Colors.WARNING | |
| customPropButton.ToolTipText = "Lookup a custom property by name" | |
| customPropButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| customPropButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| customPropButton.AutoSize = True | |
| customPropButton.Font = Font("Segoe UI", scale(9)) | |
| setPropButton = ToolStripButton("Set Custom Prop") | |
| setPropButton.ForeColor = Colors.WARNING | |
| setPropButton.ToolTipText = "Set a custom property value" | |
| setPropButton.Margin = Padding(scale(10), scale(5), scale(10), scale(5)) | |
| setPropButton.Padding = Padding(scale(12), scale(6), scale(12), scale(6)) | |
| setPropButton.AutoSize = True | |
| setPropButton.Font = Font("Segoe UI", scale(9)) | |
| # Separator styling with high DPI scaling | |
| sep1 = ToolStripSeparator() | |
| sep1.Margin = Padding(scale(16), 0, scale(16), 0) | |
| sep2 = ToolStripSeparator() | |
| sep2.Margin = Padding(scale(16), 0, scale(16), 0) | |
| sep3 = ToolStripSeparator() | |
| sep3.Margin = Padding(scale(16), 0, scale(16), 0) | |
| toolStrip.Items.AddRange(Array[ToolStripItem](( | |
| refreshButton, sep1, expandAllButton, collapseAllButton, sep2, realTimeButton, sep3, customPropButton, setPropButton | |
| ))) | |
| # Professional TreeView with high DPI scaling | |
| treeView = TreeView() | |
| treeView.Dock = DockStyle.Fill | |
| treeView.Font = Font("Segoe UI", scale(9)) | |
| treeView.BackColor = Colors.PRIMARY | |
| treeView.ForeColor = Colors.TEXT_PRIMARY | |
| treeView.LineColor = Colors.BORDER | |
| treeView.ShowLines, treeView.ShowPlusMinus, treeView.ShowRootLines = True, True, True | |
| treeView.HideSelection, treeView.FullRowSelect = False, True | |
| treeView.BorderStyle = BorderStyle.None | |
| treeView.Indent = scale(32) | |
| treeView.ItemHeight = scale(32) # Extra height for high DPI | |
| treeView.Margin = Padding(100, scale(8), 0, 0) | |
| # Professional PropertyGrid with high DPI scaling | |
| propertyGrid = PropertyGrid() | |
| propertyGrid.Dock = DockStyle.Fill | |
| propertyGrid.BackColor = Colors.PRIMARY | |
| propertyGrid.LineColor = Colors.BORDER | |
| propertyGrid.CategoryForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.CategorySplitterColor = Colors.BORDER | |
| propertyGrid.HelpBackColor = Colors.SURFACE | |
| propertyGrid.HelpForeColor = Colors.TEXT_SECONDARY | |
| propertyGrid.ViewBackColor = Colors.PRIMARY | |
| propertyGrid.ViewForeColor = Colors.TEXT_PRIMARY | |
| propertyGrid.ToolbarVisible = True | |
| propertyGrid.Font = Font("Segoe UI", scale(9)) | |
| propertyGrid.PropertySort = PropertySort.Categorized | |
| propertyGrid.HelpVisible = True | |
| propertyGrid.Margin = Padding(0, scale(8), 0, 0) | |
| # --- CORRECTED CONTROL ADDITION ORDER --- | |
| # To fix the layout, add the control with Dock.Fill first. Then, add the | |
| # controls with Dock.Top. The last 'Top' control added will be at the very top. | |
| # Add controls to the left panel | |
| leftPanel.Controls.Add(treeView) # Add FILL control first | |
| leftPanel.Controls.Add(toolStrip) # This will sit below the header | |
| leftPanel.Controls.Add(treeHeader) # This will be at the very top | |
| # Add controls to the right panel | |
| rightPanel.Controls.Add(propertyGrid) # Add FILL control first | |
| rightPanel.Controls.Add(propHeader) # This will be at the very top | |
| # Custom Property Wrapper Classes (unchanged functionality) | |
| class PropertyWrapper(object): | |
| """Base wrapper to expose all API properties""" | |
| def __init__(self, obj, obj_type): | |
| self._wrapped_object = obj | |
| self._object_type = obj_type | |
| self._properties = {} | |
| self._load_properties() | |
| def _load_properties(self): | |
| """Load all available properties based on object type""" | |
| # This dictionary dispatches to the correct loading function | |
| loader_map = { | |
| "Part": self._load_part_properties, | |
| "Assembly": self._load_assembly_properties, | |
| "AssembledPart": self._load_assembled_part_properties, | |
| "AssembledSubAssembly": self._load_assembled_sub_assembly_properties, | |
| "Face": self._load_face_properties, | |
| "Edge": self._load_edge_properties, | |
| "Vertex": self._load_vertex_properties, | |
| "Sketch": self._load_sketch_properties, | |
| "Sketch3D": self._load_sketch_3d_properties, | |
| "Parameter": self._load_parameter_properties, | |
| "Configuration": self._load_configuration_properties, | |
| "Feature": self._load_feature_properties, | |
| "Plane": self._load_plane_properties, | |
| "Axis": self._load_axis_properties, | |
| "Point": self._load_point_properties, | |
| "Line": self._load_line_properties, | |
| "Line3D": self._load_line3d_properties, | |
| "Circle": self._load_circle_properties, | |
| "CircularArc": self._load_circular_arc_properties, | |
| "CircularArc3D": self._load_circular_arc3d_properties, | |
| "Ellipse": self._load_ellipse_properties, | |
| "EllipticalArc": self._load_elliptical_arc_properties, | |
| "Bspline": self._load_bspline_properties, | |
| "Bspline3D": self._load_bspline3d_properties, | |
| "SketchPoint": self._load_sketchpoint_properties, | |
| "SketchPoint3D": self._load_sketchpoint3d_properties, | |
| } | |
| # Get the loader function from the map or use the generic one | |
| loader_func = loader_map.get(self._object_type, self._load_generic_properties) | |
| loader_func() | |
| def _safe_get_property(self, prop_name, default_value="N/A"): | |
| """Safely get a property value, handling potential errors.""" | |
| try: | |
| if hasattr(self._wrapped_object, prop_name): | |
| value = getattr(self._wrapped_object, prop_name) | |
| if value is None: | |
| return "None" | |
| # Format lists/tuples for better readability | |
| if isinstance(value, (list, tuple, Array)): | |
| return ", ".join(map(str, value)) | |
| return str(value) | |
| return default_value | |
| except Exception as e: | |
| return "Error: " + str(e) | |
| def _format_bbox(self, bbox_method_name): | |
| """Helper to get and format a bounding box.""" | |
| try: | |
| bbox = getattr(self._wrapped_object, bbox_method_name)() | |
| if bbox and len(bbox) == 8: | |
| return "; ".join(["({:.3f}, {:.3f}, {:.3f})".format(p[0], p[1], p[2]) for p in bbox]) | |
| return "Invalid BBox Data" | |
| except: | |
| return "N/A" | |
| def _load_base_file_properties(self): | |
| """Loads common properties for Part and Assembly objects.""" | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "FileName": self._safe_get_property("FileName"), | |
| "Material": self._safe_get_property("Material"), | |
| "Density": self._safe_get_property("Density"), | |
| "Mass": self._safe_get_property("Mass"), | |
| "Description": self._safe_get_property("Description"), | |
| "Number": self._safe_get_property("Number"), | |
| "Comment": self._safe_get_property("Comment"), | |
| "CostCenter": self._safe_get_property("CostCenter"), | |
| "CreatedBy": self._safe_get_property("CreatedBy"), | |
| "CreatedDate": self._safe_get_property("CreatedDate"), | |
| "CreatingApplication": self._safe_get_property("CreatingApplication"), | |
| "DocumentNumber": self._safe_get_property("DocumentNumber"), | |
| "EngineeringApprovalDate": self._safe_get_property("EngineeringApprovalDate"), | |
| "EngineeringApprovedBy": self._safe_get_property("EngineeringApprovedBy"), | |
| "EstimatedCost": self._safe_get_property("EstimatedCost"), | |
| "Keywords": self._safe_get_property("Keywords"), | |
| "LastAuthor": self._safe_get_property("LastAuthor"), | |
| "LastUpdateDate": self._safe_get_property("LastUpdateDate"), | |
| "ExtendedMaterialInformation": self._safe_get_property("ExtendedMaterialInformation"), | |
| "ManufacturingApprovedBy": self._safe_get_property("ManufacturingApprovedBy"), | |
| "ManufacturingApprovedDate": self._safe_get_property("ManufacturingApprovedDate"), | |
| "ModifiedInformation": self._safe_get_property("ModifiedInformation"), | |
| "Product": self._safe_get_property("Product"), | |
| "ReceivedFrom": self._safe_get_property("ReceivedFrom"), | |
| "Revision": self._safe_get_property("Revision"), | |
| "StockSize": self._safe_get_property("StockSize"), | |
| "Supplier": self._safe_get_property("Supplier"), | |
| "Title": self._safe_get_property("Title"), | |
| "Vendor": self._safe_get_property("Vendor"), | |
| "WebLink": self._safe_get_property("WebLink"), | |
| }) | |
| try: | |
| self._properties["DisplayUnits"] = str(self._wrapped_object.DisplayUnits()) | |
| except: | |
| self._properties["DisplayUnits"] = "N/A" | |
| def _load_custom_properties(self): | |
| """Load custom properties using GetCustomProperty API""" | |
| default_custom_names = ["A", "B", "C", "D", "E", "F", "G"] | |
| for cp_name in default_custom_names: | |
| try: | |
| val = self._wrapped_object.GetCustomProperty(cp_name) | |
| if val is not None: | |
| self._properties["Custom: " + cp_name] = str(val) | |
| else: | |
| self._properties["Custom: " + cp_name] = "" | |
| except: | |
| self._properties["Custom: " + cp_name] = "(not defined)" | |
| def _load_part_properties(self): | |
| self._load_base_file_properties() | |
| self._load_custom_properties() | |
| self._properties["BoundingBox"] = self._format_bbox("GetBoundingBox") | |
| def _load_assembly_properties(self): | |
| self._load_base_file_properties() | |
| self._load_custom_properties() | |
| # Assembly does not have GetBoundingBox, it is calculated from components. | |
| def _load_assembled_part_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| self._properties["BoundingBox (Assembly Coords)"] = self._format_bbox("GetAssemblyBoundingBox") | |
| try: | |
| self._properties["ConfigurationCount"] = str(len(self._wrapped_object.Configurations)) | |
| except: | |
| self._properties["ConfigurationCount"] = "0" | |
| def _load_assembled_sub_assembly_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: | |
| self._properties["ConfigurationCount"] = str(len(self._wrapped_object.Configurations)) | |
| except: | |
| self._properties["ConfigurationCount"] = "0" | |
| def _load_face_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["Area"] = str(self._wrapped_object.GetArea()) | |
| except: self._properties["Area"] = "N/A" | |
| try: self._properties["IsRectangle"] = str(self._wrapped_object.IsRectangle()) | |
| except: self._properties["IsRectangle"] = "N/A" | |
| try: self._properties["EdgeCount"] = str(len(self._wrapped_object.GetEdges())) | |
| except: self._properties["EdgeCount"] = "N/A" | |
| try: self._properties["VertexCount"] = str(len(self._wrapped_object.GetVertices())) | |
| except: self._properties["VertexCount"] = "N/A" | |
| try: self._properties["AdjoiningFaceCount"] = str(len(self._wrapped_object.GetAdjoiningFaces())) | |
| except: self._properties["AdjoiningFaceCount"] = "N/A" | |
| def _load_edge_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["Diameter"] = self._safe_get_property("Diameter") | |
| try: self._properties["VertexCount"] = str(len(self._wrapped_object.GetVertices())) | |
| except: self._properties["VertexCount"] = "N/A" | |
| def _load_vertex_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "X": self._safe_get_property("X"), | |
| "Y": self._safe_get_property("Y"), | |
| "Z": self._safe_get_property("Z") | |
| }) | |
| def _load_sketch_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["FigureCount"] = str(len(self._wrapped_object.Figures)) | |
| except: self._properties["FigureCount"] = "0" | |
| try: self._properties["Surface"] = self._wrapped_object.GetSurface().Name | |
| except: self._properties["Surface"] = "N/A" | |
| def _load_sketch_3d_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: self._properties["FigureCount"] = str(len(self._wrapped_object.Figures)) | |
| except: self._properties["FigureCount"] = "0" | |
| def _load_parameter_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "Value": self._safe_get_property("Value"), | |
| "Equation": self._safe_get_property("Equation"), | |
| "Comment": self._safe_get_property("Comment"), | |
| "Type": self._safe_get_property("Type"), | |
| "Units": self._safe_get_property("Units"), | |
| "RawValue": self._safe_get_property("RawValue"), | |
| "ExcelWorkbook": self._safe_get_property("ExcelWorkbook"), | |
| "ExcelSheet": self._safe_get_property("ExcelSheet"), | |
| "ExcelCell": self._safe_get_property("ExcelCell"), | |
| }) | |
| def _load_configuration_properties(self): | |
| self._properties.update({ | |
| "Name": self._safe_get_property("Name"), | |
| "IsActive": self._safe_get_property("IsActive") | |
| }) | |
| def _load_feature_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| # Color is a method SetColor, not a property to get. | |
| def _load_plane_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| def _load_axis_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| def _load_point_properties(self): | |
| self._properties["Name"] = self._safe_get_property("Name") | |
| try: | |
| coords = self._wrapped_object.GetCoordinates() | |
| self._properties["X"] = str(coords[0]) | |
| self._properties["Y"] = str(coords[1]) | |
| self._properties["Z"] = str(coords[2]) | |
| except: | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["Z"] = self._safe_get_property("Z") | |
| # Sketch Figure Properties | |
| def _load_line_properties(self): | |
| self._properties["Type"] = "Line" | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_line3d_properties(self): | |
| self._properties["Type"] = "3D Line" | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circle_properties(self): | |
| self._properties["Type"] = "Circle" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circular_arc_properties(self): | |
| self._properties["Type"] = "Circular Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Angle"] = self._safe_get_property("Angle") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_circular_arc3d_properties(self): | |
| self._properties["Type"] = "3D Circular Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["Angle"] = self._safe_get_property("Angle") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_ellipse_properties(self): | |
| self._properties["Type"] = "Ellipse" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["MajorAxisAngle"] = self._safe_get_property("MajorAxisAngle") | |
| self._properties["MinorMajorRatio"] = self._safe_get_property("MinorMajorRatio") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_elliptical_arc_properties(self): | |
| self._properties["Type"] = "Elliptical Arc" | |
| self._properties["Center"] = self._safe_get_property("Center") | |
| self._properties["StartPoint"] = self._safe_get_property("StartPoint") | |
| self._properties["EndPoint"] = self._safe_get_property("EndPoint") | |
| self._properties["Radius"] = self._safe_get_property("Radius") | |
| self._properties["MajorAxisAngle"] = self._safe_get_property("MajorAxisAngle") | |
| self._properties["MinorMajorRatio"] = self._safe_get_property("MinorMajorRatio") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_bspline_properties(self): | |
| self._properties["Type"] = "B-spline" | |
| self._properties["Order"] = self._safe_get_property("Order") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["ControlPoints"] = self._safe_get_property("ControlPoints") | |
| self._properties["KnotVectors"] = self._safe_get_property("KnotVectors") | |
| self._properties["Weights"] = self._safe_get_property("Weights") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_bspline3d_properties(self): | |
| self._properties["Type"] = "3D B-spline" | |
| self._properties["Order"] = self._safe_get_property("Order") | |
| self._properties["Length"] = self._safe_get_property("Length") | |
| self._properties["ControlPoints"] = self._safe_get_property("ControlPoints") | |
| self._properties["KnotVectors"] = self._safe_get_property("KnotVectors") | |
| self._properties["Weights"] = self._safe_get_property("Weights") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_sketchpoint_properties(self): | |
| self._properties["Type"] = "Sketch Point" | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_sketchpoint3d_properties(self): | |
| self._properties["Type"] = "3D Sketch Point" | |
| self._properties["X"] = self._safe_get_property("X") | |
| self._properties["Y"] = self._safe_get_property("Y") | |
| self._properties["Z"] = self._safe_get_property("Z") | |
| self._properties["IsReference"] = self._safe_get_property("IsReference") | |
| def _load_generic_properties(self): | |
| """Load generic properties for unknown types""" | |
| self._properties["Name"] = self._safe_get_property("Name", "N/A") | |
| self._properties["ObjectType"] = str(type(self._wrapped_object).__name__) | |
| def GetAllProperties(self): | |
| return self._properties | |
| # Enhanced property display using a custom object | |
| class PropertyDisplayObject(object): | |
| def __init__(self, wrapped_obj): | |
| self._original_object = wrapped_obj | |
| obj_type_name = type(wrapped_obj).__name__ | |
| # Use a dictionary for more robust type mapping | |
| type_map = { | |
| "ADPart": "Part", | |
| "ADAssembly": "Assembly", | |
| "ADFace": "Face", | |
| "ADEdge": "Edge", | |
| "ADVertex": "Vertex", | |
| "ADSketch": "Sketch", | |
| "ADSketch3D": "Sketch3D", | |
| "ADParameter": "Parameter", | |
| "ADConfiguration": "Configuration", | |
| "ADFeature": "Feature", | |
| "ADPlane": "Plane", | |
| "ADAxis": "Axis", | |
| "ADPoint": "Point", | |
| "ADAssembledPart": "AssembledPart", | |
| "ADAssembledSubAssembly": "AssembledSubAssembly", | |
| "ADLine": "Line", | |
| "ADLine3D": "Line3D", | |
| "ADCircle": "Circle", | |
| "ADCircularArc": "CircularArc", | |
| "ADCircularArc3D": "CircularArc3D", | |
| "ADEllipse": "Ellipse", | |
| "ADEllipticalArc": "EllipticalArc", | |
| "ADBspline": "Bspline", | |
| "ADBspline3D": "Bspline3D", | |
| "ADSketchPoint": "SketchPoint", | |
| "ADSketchPoint3D": "SketchPoint3D" | |
| } | |
| obj_type = type_map.get(obj_type_name, obj_type_name) | |
| self._wrapper = PropertyWrapper(wrapped_obj, obj_type) | |
| # Dynamically add properties for the PropertyGrid | |
| for prop_name, prop_value in self._wrapper.GetAllProperties().items(): | |
| clean_prop_name = prop_name.replace(" ", "_").replace("-", "_").replace("(", "").replace(")", "") | |
| setattr(self, clean_prop_name, prop_value) | |
| # --- REAL-TIME SELECTION TRACKING --- | |
| last_alibre_selection = None | |
| manual_selection_active = False | |
| selectionTimer = Timer() | |
| selectionTimer.Interval = 500 # Check for new selections every 500ms | |
| def UpdateStatusBar(message, selection_text=""): | |
| """Update the status bar with current information""" | |
| statusLabel.Text = message | |
| selectionLabel.Text = selection_text | |
| def OnTimerTick(sender, args): | |
| """Checks the active selection in Alibre and updates the PropertyGrid""" | |
| global last_alibre_selection, manual_selection_active | |
| if not realTimeButton.Checked or manual_selection_active: | |
| return | |
| try: | |
| selections = MyModel.Selections | |
| if selections and len(selections) > 0: | |
| current_object = selections[0] | |
| if current_object != last_alibre_selection: | |
| display_object = PropertyDisplayObject(current_object) | |
| propertyGrid.SelectedObject = display_object | |
| last_alibre_selection = current_object | |
| # Update status bar | |
| obj_name = "Unknown" | |
| try: | |
| if hasattr(current_object, 'Name') and current_object.Name: | |
| obj_name = current_object.Name | |
| else: | |
| obj_name = type(current_object).__name__ | |
| except: | |
| obj_name = type(current_object).__name__ | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Selected: " + obj_name) | |
| elif last_alibre_selection is not None: | |
| if not manual_selection_active: | |
| propertyGrid.SelectedObject = None | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "No selection") | |
| last_alibre_selection = None | |
| except: | |
| selectionTimer.Stop() | |
| selectionTimer.Tick += OnTimerTick | |
| selectionTimer.Start() | |
| form.FormClosing += lambda s, e: selectionTimer.Stop() | |
| def AddObjectNode(parentNode, item, name=None): | |
| """Helper to add an object node to the tree - only if object is valid""" | |
| if item is None: | |
| return None | |
| try: | |
| if name: | |
| display_name = name | |
| elif hasattr(item, 'Name') and item.Name: | |
| display_name = item.Name | |
| else: | |
| display_name = str(type(item).__name__) | |
| except: | |
| display_name = str(type(item).__name__) | |
| node = TreeNode(display_name) | |
| node.Tag = item | |
| # Add visual indicators with Unicode symbols | |
| obj_type = type(item).__name__ | |
| if "Assembly" in obj_type: | |
| display_name = "🗂️ " + display_name | |
| elif "Part" in obj_type: | |
| display_name = "🔧 " + display_name | |
| elif "Face" in obj_type: | |
| display_name = "📄 " + display_name | |
| elif "Edge" in obj_type: | |
| display_name = "📏 " + display_name | |
| elif "Vertex" in obj_type: | |
| display_name = "📍 " + display_name | |
| elif "Sketch" in obj_type: | |
| display_name = "[S] " + display_name | |
| elif "Feature" in obj_type: | |
| display_name = "⚙️ " + display_name | |
| elif "Parameter" in obj_type: | |
| display_name = "🔢 " + display_name | |
| elif "Configuration" in obj_type: | |
| display_name = "⚙️ " + display_name | |
| elif "Plane" in obj_type: | |
| display_name = "🔲 " + display_name | |
| elif "Axis" in obj_type: | |
| display_name = "↔️ " + display_name | |
| elif "Point" in obj_type: | |
| display_name = "🎯 " + display_name | |
| node.Text = display_name | |
| if parentNode: | |
| parentNode.Nodes.Add(node) | |
| else: | |
| treeView.Nodes.Add(node) | |
| return node | |
| def AddCollectionNode(parentNode, collectionName, collection_provider): | |
| """Helper to add a collection node with all its items - only if collection has real items""" | |
| collection = None | |
| try: | |
| collection = collection_provider() if callable(collection_provider) else collection_provider | |
| if collection is None or len(collection) == 0: | |
| return None | |
| except: | |
| return None | |
| collectionNode = TreeNode("📁 %s (%d)" % (collectionName, len(collection))) | |
| collectionNode.Tag = collection | |
| parentNode.Nodes.Add(collectionNode) | |
| for i, item in enumerate(collection): | |
| if item is None: | |
| continue | |
| try: | |
| if hasattr(item, 'Name') and item.Name: | |
| item_name = item.Name | |
| else: | |
| item_name = "%s[%d]" % (type(item).__name__, i) | |
| itemNode = AddObjectNode(collectionNode, item, item_name) | |
| # Recursively add nodes for complex objects like sketches or assembled parts | |
| if itemNode: | |
| obj_type_name = type(item).__name__ | |
| if "Sketch" in obj_type_name and hasattr(item, 'Figures'): | |
| AddCollectionNode(itemNode, "Figures", item.Figures) | |
| elif obj_type_name == "ADAssembledPart": | |
| PopulateAssembledPartNode(itemNode, item) | |
| elif obj_type_name == "ADAssembledSubAssembly": | |
| PopulateSubAssemblyNode(itemNode, item) | |
| except Exception as e: | |
| # Skip invalid items silently | |
| pass | |
| return collectionNode | |
| def PopulateAssembledPartNode(partNode, part): | |
| """Adds geometry collections for an AssembledPart""" | |
| AddCollectionNode(partNode, "Faces", part.GetFaces) | |
| AddCollectionNode(partNode, "Edges", part.GetEdges) | |
| # GetAssemblyVertices returns a list of lists, not vertex objects, so we handle it differently | |
| try: | |
| vertices = part.GetAssemblyVertices() | |
| if vertices and len(vertices) > 0: | |
| v_node = TreeNode("📁 Vertices (%d)" % len(vertices)) | |
| partNode.Nodes.Add(v_node) | |
| except: | |
| pass | |
| def PopulateSubAssemblyNode(assemblyNode, subAssembly): | |
| """Recursively populates a sub-assembly node""" | |
| AddCollectionNode(assemblyNode, "Parts", subAssembly.Parts) | |
| AddCollectionNode(assemblyNode, "SubAssemblies", subAssembly.SubAssemblies) | |
| def BuildTreeNodes(): | |
| """Builds the tree structure using only real model data""" | |
| treeView.BeginUpdate() | |
| treeView.Nodes.Clear() | |
| UpdateStatusBar("Building model tree...", "") | |
| # Root node | |
| rootNode = AddObjectNode(None, MyModel, MyModel.Name) | |
| # Add common collections that are directly accessible | |
| AddCollectionNode(rootNode, "Configurations", MyModel.Configurations) | |
| AddCollectionNode(rootNode, "Parameters", MyModel.Parameters) | |
| # Add Custom Properties node | |
| default_custom_names = ["A", "B", "C", "D", "E", "F", "G"] | |
| custom_props_found = [] | |
| for cp_name in default_custom_names: | |
| try: | |
| val = MyModel.GetCustomProperty(cp_name) | |
| if val is not None: | |
| custom_props_found.append((cp_name, str(val))) | |
| else: | |
| custom_props_found.append((cp_name, "")) | |
| except: | |
| custom_props_found.append((cp_name, "(not defined)")) | |
| cpNode = TreeNode("Custom Properties (%d)" % len(custom_props_found)) | |
| cpNode.Tag = MyModel | |
| rootNode.Nodes.Add(cpNode) | |
| for cp_name, cp_val in custom_props_found: | |
| display_text = cp_name + " = " + cp_val | |
| childNode = TreeNode(display_text) | |
| childNode.Tag = MyModel | |
| cpNode.Nodes.Add(childNode) | |
| if IsAssembly: | |
| AddCollectionNode(rootNode, "Parts", MyModel.Parts) | |
| AddCollectionNode(rootNode, "SubAssemblies", MyModel.SubAssemblies) | |
| else: # It's a Part | |
| AddCollectionNode(rootNode, "Faces", MyModel.GetFaces) | |
| AddCollectionNode(rootNode, "Edges", MyModel.GetEdges) | |
| AddCollectionNode(rootNode, "Vertices", MyModel.GetVertices) | |
| # --- BRUTE-FORCE DISCOVERY --- | |
| # The Alibre API does not provide direct collections for items like Features, Sketches, | |
| # Planes, Axes, or Points. The only way to find them is to try getting them by name. | |
| # The following sections attempt to find these items by iterating through common naming patterns. | |
| def find_named_items(collection_name, get_method, name_patterns, try_3d_sketch=False): | |
| found_items = [] | |
| found_names = set() | |
| for name in name_patterns: | |
| try: | |
| item = get_method(name) | |
| if item and name not in found_names: | |
| found_items.append((name, item)) | |
| found_names.add(name) | |
| except: pass | |
| if try_3d_sketch: | |
| try: | |
| item3d = MyModel.Get3DSketch(name) | |
| if item3d and (name + "_3D") not in found_names: | |
| found_items.append((name + " (3D)", item3d)) | |
| found_names.add(name + "_3D") | |
| except: pass | |
| if found_items: | |
| node = TreeNode("📁 %s (%d)" % (collection_name, len(found_items))) | |
| rootNode.Nodes.Add(node) | |
| for item_name, item_obj in found_items: | |
| itemNode = AddObjectNode(node, item_obj, item_name) | |
| # For sketches, also add their figures | |
| if itemNode and "Sketch" in collection_name and hasattr(item_obj, 'Figures'): | |
| AddCollectionNode(itemNode, "Figures", item_obj.Figures) | |
| # Define name patterns | |
| base_names = { | |
| "Feature": ["Extrude", "Extrusion", "Cut", "Fillet", "Chamfer", "Hole", "Pattern", "Revolve", "Loft", "Sweep", "Shell", "Draft", "Mirror", "Feature", "Boss", "stack"], | |
| "Sketch": ["Sketch", "Profile", "Path", "Section", "2D Sketch", "3D Sketch"], | |
| "Plane": ["Plane"], | |
| "Axis": ["Axis"], | |
| "Point": ["Point", "Origin"] | |
| } | |
| names_to_try = {} | |
| for key, prefixes in base_names.items(): | |
| current_list = [] | |
| for prefix in prefixes: | |
| # Also try the prefix by itself, e.g., for a feature named just "stack" | |
| current_list.append(prefix) | |
| for i in range(1, 51): # Check up to 50 of each type | |
| current_list.extend([ | |
| "%s<%d>" % (prefix, i), # e.g., Sketch<1> | |
| "%s%d" % (prefix, i), # e.g., Sketch1 | |
| "%s <%d>" % (prefix, i), # e.g., Sketch <1> | |
| "%s %d" % (prefix, i) # e.g., Sketch 1 | |
| ]) | |
| names_to_try[key] = current_list | |
| # Add standard planes/axes/origin if they exist | |
| try: | |
| if MyModel.XYPlane: AddObjectNode(rootNode, MyModel.XYPlane, "XY-Plane") | |
| if MyModel.YZPlane: AddObjectNode(rootNode, MyModel.YZPlane, "YZ-Plane") | |
| if MyModel.ZXPlane: AddObjectNode(rootNode, MyModel.ZXPlane, "ZX-Plane") | |
| if MyModel.XAxis: AddObjectNode(rootNode, MyModel.XAxis, "X-Axis") | |
| if MyModel.YAxis: AddObjectNode(rootNode, MyModel.YAxis, "Y-Axis") | |
| if MyModel.ZAxis: AddObjectNode(rootNode, MyModel.ZAxis, "Z-Axis") | |
| if MyModel.Origin: AddObjectNode(rootNode, MyModel.Origin, "Origin") | |
| except: pass | |
| # Find and add items | |
| find_named_items("Features", MyModel.GetFeature, names_to_try["Feature"]) | |
| find_named_items("Sketches", MyModel.GetSketch, names_to_try["Sketch"], try_3d_sketch=True) | |
| find_named_items("Planes", MyModel.GetPlane, names_to_try["Plane"]) | |
| find_named_items("Axes", MyModel.GetAxis, names_to_try["Axis"]) | |
| find_named_items("Points", MyModel.GetPoint, names_to_try["Point"]) | |
| treeView.EndUpdate() | |
| rootNode.Expand() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Tree built successfully") | |
| def OnNodeSelect(sender, args): | |
| """When a node is selected in the tree, show its properties with full API exposure""" | |
| global manual_selection_active | |
| manual_selection_active = True | |
| if treeView.SelectedNode and treeView.SelectedNode.Tag is not None: | |
| selected_object = treeView.SelectedNode.Tag | |
| # Check if tag is a collection, if so, select the parent model | |
| if isinstance(selected_object, (list, tuple, Array)): | |
| if treeView.SelectedNode.Parent and treeView.SelectedNode.Parent.Tag: | |
| selected_object = treeView.SelectedNode.Parent.Tag | |
| else: | |
| selected_object = MyModel | |
| propertyGrid.SelectedObject = PropertyDisplayObject(selected_object) | |
| # Update status bar | |
| try: | |
| obj_name = treeView.SelectedNode.Text | |
| if obj_name.startswith("📁 "): | |
| obj_name = obj_name[3:] # Remove folder emoji and extra space | |
| elif len(obj_name) > 3 and obj_name[2:3] == " ": | |
| obj_name = obj_name[3:] # Remove other emojis and extra space | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Selected: " + obj_name) | |
| except: | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Tree selection") | |
| else: | |
| propertyGrid.SelectedObject = None | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "No selection") | |
| # Reset manual selection flag after a short delay | |
| def ResetManualFlag(s, e): | |
| global manual_selection_active | |
| manual_selection_active = False | |
| s.Stop() | |
| resetTimer = Timer() | |
| resetTimer.Interval = 1000 | |
| resetTimer.Tick += ResetManualFlag | |
| resetTimer.Start() | |
| # Enhanced Event Handlers | |
| def OnRefreshClick(sender, args): | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Model refreshed") | |
| def OnExpandAllClick(sender, args): | |
| treeView.ExpandAll() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "All nodes expanded") | |
| def OnCollapseAllClick(sender, args): | |
| treeView.CollapseAll() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "All nodes collapsed") | |
| def OnRealTimeToggle(sender, args): | |
| if realTimeButton.Checked: | |
| realTimeButton.ForeColor = Colors.SUCCESS | |
| selectionTimer.Start() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Real-time sync enabled") | |
| else: | |
| realTimeButton.ForeColor = Colors.TEXT_PRIMARY | |
| selectionTimer.Stop() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Real-time sync disabled") | |
| def OnCustomPropClick(sender, args): | |
| """Show a dialog to lookup a custom property by name""" | |
| dlg = Form() | |
| dlg.Text = "Lookup Custom Property" | |
| dlg.Width = scale(400) | |
| dlg.Height = scale(220) | |
| dlg.StartPosition = FormStartPosition.CenterParent | |
| dlg.BackColor = Colors.PRIMARY | |
| dlg.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Font = Font("Segoe UI", scale(9)) | |
| dlg.FormBorderStyle = FormBorderStyle.FixedDialog | |
| dlg.MaximizeBox = False | |
| dlg.MinimizeBox = False | |
| lbl = Label() | |
| lbl.Text = "Property Name:" | |
| lbl.Location = Point(scale(20), scale(20)) | |
| lbl.Size = Size(scale(340), scale(25)) | |
| lbl.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lbl) | |
| txtName = TextBox() | |
| txtName.Location = Point(scale(20), scale(50)) | |
| txtName.Size = Size(scale(340), scale(28)) | |
| txtName.BackColor = Colors.SURFACE | |
| txtName.ForeColor = Colors.TEXT_PRIMARY | |
| txtName.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtName) | |
| lblResult = Label() | |
| lblResult.Text = "" | |
| lblResult.Location = Point(scale(20), scale(90)) | |
| lblResult.Size = Size(scale(340), scale(40)) | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| dlg.Controls.Add(lblResult) | |
| def OnLookup(s, e): | |
| prop_name = txtName.Text.strip() | |
| if prop_name == "": | |
| lblResult.Text = "Please enter a property name." | |
| return | |
| try: | |
| val = MyModel.GetCustomProperty(prop_name) | |
| if val is not None: | |
| lblResult.Text = prop_name + " = " + str(val) | |
| lblResult.ForeColor = Colors.SUCCESS | |
| else: | |
| lblResult.Text = prop_name + " = (empty)" | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| except Exception as ex: | |
| lblResult.Text = "Not found or error: " + str(ex) | |
| lblResult.ForeColor = Color.FromArgb(255, 100, 100) | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Looked up: " + prop_name) | |
| btnLookup = Button() | |
| btnLookup.Text = "Lookup" | |
| btnLookup.Location = Point(scale(20), scale(140)) | |
| btnLookup.Size = Size(scale(100), scale(32)) | |
| btnLookup.BackColor = Colors.ACCENT | |
| btnLookup.ForeColor = Colors.TEXT_PRIMARY | |
| btnLookup.FlatStyle = FlatStyle.Flat | |
| btnLookup.Click += OnLookup | |
| dlg.Controls.Add(btnLookup) | |
| dlg.AcceptButton = btnLookup | |
| btnClose = Button() | |
| btnClose.Text = "Close" | |
| btnClose.Location = Point(scale(260), scale(140)) | |
| btnClose.Size = Size(scale(100), scale(32)) | |
| btnClose.BackColor = Colors.SECONDARY | |
| btnClose.ForeColor = Colors.TEXT_PRIMARY | |
| btnClose.FlatStyle = FlatStyle.Flat | |
| btnClose.Click += lambda s, e: dlg.Close() | |
| dlg.Controls.Add(btnClose) | |
| dlg.CancelButton = btnClose | |
| dlg.ShowDialog() | |
| def OnSetPropClick(sender, args): | |
| """Show a dialog to set a custom property value""" | |
| dlg = Form() | |
| dlg.Text = "Set Custom Property" | |
| dlg.Width = scale(400) | |
| dlg.Height = scale(280) | |
| dlg.StartPosition = FormStartPosition.CenterParent | |
| dlg.BackColor = Colors.PRIMARY | |
| dlg.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Font = Font("Segoe UI", scale(9)) | |
| dlg.FormBorderStyle = FormBorderStyle.FixedDialog | |
| dlg.MaximizeBox = False | |
| dlg.MinimizeBox = False | |
| lblName = Label() | |
| lblName.Text = "Property Name:" | |
| lblName.Location = Point(scale(20), scale(20)) | |
| lblName.Size = Size(scale(340), scale(25)) | |
| lblName.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lblName) | |
| txtName = TextBox() | |
| txtName.Location = Point(scale(20), scale(50)) | |
| txtName.Size = Size(scale(340), scale(28)) | |
| txtName.BackColor = Colors.SURFACE | |
| txtName.ForeColor = Colors.TEXT_PRIMARY | |
| txtName.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtName) | |
| lblVal = Label() | |
| lblVal.Text = "Property Value:" | |
| lblVal.Location = Point(scale(20), scale(90)) | |
| lblVal.Size = Size(scale(340), scale(25)) | |
| lblVal.ForeColor = Colors.TEXT_PRIMARY | |
| dlg.Controls.Add(lblVal) | |
| txtVal = TextBox() | |
| txtVal.Location = Point(scale(20), scale(120)) | |
| txtVal.Size = Size(scale(340), scale(28)) | |
| txtVal.BackColor = Colors.SURFACE | |
| txtVal.ForeColor = Colors.TEXT_PRIMARY | |
| txtVal.Font = Font("Segoe UI", scale(9)) | |
| dlg.Controls.Add(txtVal) | |
| lblResult = Label() | |
| lblResult.Text = "" | |
| lblResult.Location = Point(scale(20), scale(160)) | |
| lblResult.Size = Size(scale(340), scale(40)) | |
| lblResult.ForeColor = Colors.TEXT_SECONDARY | |
| dlg.Controls.Add(lblResult) | |
| def OnSet(s, e): | |
| prop_name = txtName.Text.strip() | |
| prop_val = txtVal.Text | |
| if prop_name == "": | |
| lblResult.Text = "Please enter a property name." | |
| return | |
| try: | |
| MyModel.SetCustomProperty(prop_name, prop_val) | |
| lblResult.Text = "Set " + prop_name + " = " + prop_val | |
| lblResult.ForeColor = Colors.SUCCESS | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Set: " + prop_name) | |
| # Refresh tree to show updated values | |
| BuildTreeNodes() | |
| except Exception as ex: | |
| lblResult.Text = "Error: " + str(ex) | |
| lblResult.ForeColor = Color.FromArgb(255, 100, 100) | |
| btnSet = Button() | |
| btnSet.Text = "Set" | |
| btnSet.Location = Point(scale(20), scale(210)) | |
| btnSet.Size = Size(scale(100), scale(32)) | |
| btnSet.BackColor = Colors.ACCENT | |
| btnSet.ForeColor = Colors.TEXT_PRIMARY | |
| btnSet.FlatStyle = FlatStyle.Flat | |
| btnSet.Click += OnSet | |
| dlg.Controls.Add(btnSet) | |
| dlg.AcceptButton = btnSet | |
| btnClose = Button() | |
| btnClose.Text = "Close" | |
| btnClose.Location = Point(scale(260), scale(210)) | |
| btnClose.Size = Size(scale(100), scale(32)) | |
| btnClose.BackColor = Colors.SECONDARY | |
| btnClose.ForeColor = Colors.TEXT_PRIMARY | |
| btnClose.FlatStyle = FlatStyle.Flat | |
| btnClose.Click += lambda s, e: dlg.Close() | |
| dlg.Controls.Add(btnClose) | |
| dlg.CancelButton = btnClose | |
| dlg.ShowDialog() | |
| def OnPropertyValueChanged(sender, args): | |
| """Write custom property changes back to the model""" | |
| try: | |
| label = args.ChangedItem.Label | |
| if label is not None and label.startswith("Custom:_"): | |
| cp_name = label[len("Custom:_"):] | |
| new_val = str(args.ChangedItem.Value) | |
| try: | |
| MyModel.SetCustomProperty(cp_name, new_val) | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Updated: " + cp_name + " = " + new_val) | |
| BuildTreeNodes() | |
| except Exception as ex: | |
| Win = Windows() | |
| Win.ErrorDialog("Failed to set custom property '" + cp_name + "':\n" + str(ex) + "\n\nThe property must already be defined on the part/assembly or on your PC.", "Custom Property Error") | |
| except: | |
| pass | |
| # Assign Event Handlers | |
| treeView.AfterSelect += OnNodeSelect | |
| refreshButton.Click += OnRefreshClick | |
| expandAllButton.Click += OnExpandAllClick | |
| collapseAllButton.Click += OnCollapseAllClick | |
| realTimeButton.Click += OnRealTimeToggle | |
| customPropButton.Click += OnCustomPropClick | |
| setPropButton.Click += OnSetPropClick | |
| propertyGrid.PropertyValueChanged += OnPropertyValueChanged | |
| # Build the initial tree and show the form | |
| BuildTreeNodes() | |
| UpdateStatusBar("Ready - Model: " + MyModel.Name, "Explorer initialized") | |
| form.ShowDialog() | |
| # Run the script | |
| CreatePropertyExplorer() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

Uh oh!
There was an error while loading. Please reload this page.