How can I control what ToolbarTabs show a particular Toolbar Panel?

How can I control what ToolbarTabs show a particular Toolbar Panel?

zxynine
Enthusiast Enthusiast
1,624 Views
12 Replies
Message 1 of 13

How can I control what ToolbarTabs show a particular Toolbar Panel?

zxynine
Enthusiast
Enthusiast

Hi, i have been working on a pretty massive project and have found myself in a situation where making any changes to the location of a toolbarPanel, results in the old panel sill being left behind when i add the command to the new location. Ive parsed all of the ui after cleaning the first panel, and that clearly says no command definition, command control, nor toolbar panel is left over. However, once i restart the addin that defines a new location, the old panel shows up along with the new one and both reference the same panel index. Ive noticed that many panels are referenced multiple times in different tabs like the "SelectPanel":index 38. I imagine the tabs are tracking panel ids on a higher level not documented in the api. Is there any way to tell a toolbarTab to forget ever having created the panel so i can add it to different locations without restarting f360? 

I could attempt to create a dummy panel with a different id/index and then delete that to break the link, but id really rather have a more clean method.'

Is it as simple as me not deleting everything properly? I have maintained a list of any ui objects i create and call "delete me" on each of them. Any help would be greatly appreciated.

 

Ive included my run function below, its part of a massive project and is found inside of a class which is why there are "self." calls in it.

 

	def run(self, *args):
		"""Function is run when the addin starts. Use the onCreate function instad!
		Important! If overridden ensure to execute with super().on_run()"""
		global customTab, customPannel, createdObjects
		with  ErrorCatcher(True):
			ui = AppObjects().ui

			designWorkspace = ui.workspaces.itemById('FusionSolidEnvironment')

			def NewTab(id, Name = 'No Name Provided'):
				newDesignTab = None
				newDesignTab = designWorkspace.toolbarTabs.itemById(id)
				if newDesignTab == None: 
					newDesignTab = designWorkspace.toolbarTabs.add(id, str(Name))
					createdObjects.append(newDesignTab)
				return newDesignTab
			def NewPanel(tab:core.ToolbarTab, id, Name = 'No Name Provided'):
				allNewDesignTabPanels = tab.toolbarPanels
				brandNewDesignPanel = None
				brandNewDesignPanel = allNewDesignTabPanels.itemById(id)
				if brandNewDesignPanel is None:	
					brandNewDesignPanel = allNewDesignTabPanels.add(id, Name)
					createdObjects.append(brandNewDesignPanel)
				return brandNewDesignPanel
			def NewControl(controls : core.ToolbarControls, id, anotherExtrudeCmd):
				if anotherExtrudeCmd:
					# Go ahead and add the command to the panel:
					extrudeCmdControl =  None
					extrudeCmdControl = controls.itemById(id)
					if extrudeCmdControl == None : 
						extrudeCmdControl = controls.addCommand(anotherExtrudeCmd)
						if extrudeCmdControl:
							extrudeCmdControl.isVisible = True
							extrudeCmdControl.isPromoted = True
							extrudeCmdControl.isPromotedByDefault = True
							if id == 'Extrude': ui.messageBox('Do you see Best Design Panel now?')
						createdObjects.append(extrudeCmdControl)
					return extrudeCmdControl

			def NewDefinition(cmdID, cmdName, cmdDes, cmdIcon):
				commandDefinition = None
				commandDefinition = ui.commandDefinitions.itemById(cmdID)
				if not commandDefinition: 
					commandDefinition = ui.commandDefinitions.addButtonDefinition(cmdID, cmdName, cmdDes, cmdIcon)
					createdObjects.append(commandDefinition)
				return commandDefinition


			newDesignTab = None
			brandNewDesignPanel = None
			extrudeCmdControl = None
			# newDesignTab = NewTab('ToolsTab', 'Tools')
			newDesignTab = NewTab('SolidTab', 'Solid')
			# newDesignTab = NewTab('NewDesignTabHere', 'New Design Tab')
			if newDesignTab != None:
				brandNewDesignPanel = NewPanel(newDesignTab, 'bestDesignPanelEverId', 'Best Design Panel')
				if brandNewDesignPanel != None:
					newDesignTab.activate()
					if newDesignTab.isActive:
						extrudeCmdControl = NewControl(brandNewDesignPanel.controls, 'Extrude', ui.commandDefinitions.itemById('Extrude'))
						

			self.commandDefinition = NewDefinition(self.commandID, self.commandName, self.commandDescription, self.commandIcon)

			cmdTab = NewTab(self.toolbarTabID, self.toolbarTabName)
			cmdPanel:core.ToolbarPanel = NewPanel(cmdTab, self.toolbarPanelID, self.toolbarPanelName)
			self.control = NewControl(cmdPanel.controls, self.commandControlID, self.commandDefinition)

 

Accepted solutions (1)
1,625 Views
12 Replies
Replies (12)
Message 2 of 13

tykapl.breuil
Advocate
Advocate

Hey there !

 

I've tested it myself and it definitely seems bugged where even though the former panel was deleted, the tab still somewhere references that panel.

This is weird because the API doesn't let you have panels with the same id in different tabs, nor does it let you add an existing ToolbarPanel object to a tab.

I think this is a bug of the API but in the mean time you should probably avoid using the same panel ids across different tabs as that causes problem.

Maybe you can try adding the ToolbarTab's id to your panel's id in your new Panel function so that process is automated ?

Message 3 of 13

zxynine
Enthusiast
Enthusiast

Hey, thanks for the reply. I do like your proposed workaround, the only problem with that would have to be the reason for my addin in the first place. My addin is designed to help others make addins so changing the id of their panel if they make one is non ideal as it would not be clear how to access it without explicitly knowing that my addin changes the id of it. There are many ways to address it, sadly i fear none would still allow ease of access for my target audience.

 

Im glad this is a bug in fusion and not my code, i have like 10k lines of code that i spent hours rubberduck debugging last night when i hit the issue and i couldnt think of any reason why they were being so dumb.

 

Im open to more suggestions if you or anyone else has any, as its one of the only problems ive been stuck on for a while now.

0 Likes
Message 4 of 13

tykapl.breuil
Advocate
Advocate

What you could do is detect when your user does something that would cause the aforementioned problem then bring up a warning/an error ?

0 Likes
Message 5 of 13

zxynine
Enthusiast
Enthusiast
Sadly, thats a no go. The problem is, theres no way for me to know without physically logging every panel made in some data file somewhere because the panels can only be found after the command was added back in. Before that point, I haven't been able to find any ruminants of old ui elements. Ive even tried to add it back and delete it immedietly after creation and then add it somewhere else, but the problem still persists.
0 Likes
Message 6 of 13

zxynine
Enthusiast
Enthusiast

Should I make a bug report or something for this? It seems like this is an oversight and we should have some control over what tabs show a panel.

 

My best solution I can come up with right now is to create a separate thread that frequently checks the currently active toolbar tab in the active workspace and then have the addin say what tabs (aside from the one the panel is being made in) should show the panel. If none are provided, anytime the user swaps tabs the addin will check if the tab allows its visibility and will toggle the panel.

 

Currently toolbar panels dont allow toggling, however i can easily work around it by either deleting the panel and making it, or i think changing its active workspaces to an empty list works. I only just got my threading to work so I will report back, but this kinda sucks. 

0 Likes
Message 7 of 13

BrianEkins
Mentor
Mentor

I'm not sure I fully understood what you're trying to do but you should delete all UI elements that your add-in has created. It sounds like you're doing that but it would be good to confirm that.  If you find a simple reproducible case where UI elements are not being deleted after calling deleteMe, that is a bug and it would be good to post here so a bug can be logged and it can be investigated.  Personally, I've not seen this but in my add-ins I haven't been creating tabs. 

 

Panel ID's need to be unique within a workspace, not a tab.  Tab's just provide additional organization but it's the workspace that owns the panels.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 8 of 13

zxynine
Enthusiast
Enthusiast

I have specifically designed functions that append any objects they create into a global list which is used for clean up and I can confirm that they are "being deleted" at least according to the references to them anywhere I look. Im not sure if youll remember me, but I im the same person who redid the "Write ui controls to file" and included enough info to fill 30k lines in a text file and this topic here is actually why I did it haha. I could find not a single trace of my toolbar panels anywhere after calling the "deleteme()" func.

The problem is specific to toolbar panels and their references to their tabs. I was creating an add-in that would allow me to ease the workflow of ui panel/tab/commandcontrol/toolbarcontrol et cetera. I was working on organising my addins i created for testing when i noticed that swapping the location of the panel to anywhere not in the same tab would result in two controls appearing and no matter what i tried there was no way i could remove that reference from fusion (the object was deleted, but some hidden data must still remain). when I did a UI to file write and checked, the two panels were the exact same and any attempt at removing one would remove them both. After removal, I did another Ui write and There was not a single trace at all in any location in the ui that i had access to. The worst part is, even if i removed both and tried to place the panel somewhere different, three would then show up. It takes a restart of fusion to clean the ui. I have a strong feeling that fusions toolbar tabs are keeping a reference or id of the panel maybe for display indexing.

With that said, I also have to admit I am pushing fusion to the limits with my addins a tad so it could be far more complex than that and down to user error, but I have tried every thing i could even down to trying to go into fusions source code so i could stop loosing my mind lol. I have almost never use this forum for anything other than problem solving so Im not entirely sure where I should go to submit a bug report. Or what else you could find useful (aside from a simple command that reproduces the error which I will work on when i have some free time). Is there anything I could elaborate on to help in the meantime?

0 Likes
Message 9 of 13

zxynine
Enthusiast
Enthusiast
I should clarify, when i say "swapping" tool bar panels, I mean I delete them, stop my add-in, change where it gets created, then reloading my addin
0 Likes
Message 10 of 13

BrianEkins
Mentor
Mentor

I think the best thing to do is to try and create a small sample that demonstrates the problem.  By simplifying it down the bare minimum you may find a mistake on your side or if it's a problem in Fusion it makes it much easier for the development team to find the cause and fix it.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
Message 11 of 13

zxynine
Enthusiast
Enthusiast

Hey, I had a go at creating a small command that reproduces the errors. I made sure to turn off a majority of my add-ins when I ran it too so this should be reproducible. If the text and stuff are too big to fit on one line, im sorry I work in a wide environment so I can typically see everything im working on

 

#Author-
#Description-

import adsk.core, adsk.fusion, adsk.cam, traceback
#List for every object
createdObjects= []

#Context manager
class errorPrint:
	def __init__(self,errorReport): self.errorrep = errorReport
	def __enter__(self,*args): return self
	def __exit__(self,excType,excVal,excTB):
		if excType is not None: 
			self.errorrep.append(excType.__name__ + ": \t"+ str(excVal))
		return True

#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
#Generic function that both gets ant creates new objects depending on if they already exist  and whether the command calling it wants to create it
#If an object is ever made, it gets appended to the global list of objects for proper deletion at the end of the commands life
def addGeneric(baseObj, cmdID, cmdFunc, *addArgs, TryAdd=True):
	global createdObjects
	generic = baseObj.itemById(cmdID)
	if generic == None and TryAdd: 
		generic = cmdFunc(*addArgs)
		createdObjects.append(generic)
	return generic	

#These funcs are focused on toolbar panels and the command buttons in them
#These will either create the obj if it does not exist, or it will return the object if already defined
def AddPanel(panels:adsk.core.ToolbarPanels, id='SolidScriptsAddinsPanel', name = 'Add-Ins', positionID='', isBefore=False):
	return addGeneric(panels, id, panels.add, id, name,positionID,isBefore)
def NewCommand(cmdDefs:adsk.core.CommandDefinitions, cmdID, cmdName, cmdToolTip='', cmdIcon=''):
	return addGeneric(cmdDefs,cmdID, cmdDefs.addButtonDefinition, cmdID,cmdName, cmdToolTip, cmdIcon)
def AddCommand(toolbarControls:adsk.core.ToolbarControls, commandDef:adsk.core.CommandDefinition, positionID='', isBefore=False):
	return addGeneric(toolbarControls, commandDef.id, toolbarControls.addCommand, commandDef, positionID, isBefore)

#These are just util funcs that get me the controls i want
def GetWorkspace(workspaces:adsk.core.Workspaces, id='FusionSolidEnvironment', name='Design', icon=''):
	return addGeneric(workspaces, id, workspaces.add, id, name,icon, TryAdd=False)
def GetTab(toolbarTabs:adsk.core.ToolbarTabs,id='ToolsTab', name = 'Tools'):
	return addGeneric(toolbarTabs, id, toolbarTabs.add, id, name, TryAdd=False)
#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||


def run(context):
	ui = None
	try:
		#Setting up some objs
		app:adsk.core.Application= adsk.core.Application.get()
		ui  = app.userInterface
		ui.messageBox('Hello addin')
		FusionSolid = GetWorkspace(ui.workspaces)
		SolidTab = GetTab(FusionSolid.toolbarTabs, 'SolidTab')
		ToolsTab = GetTab(FusionSolid.toolbarTabs, 'ToolsTab')
		#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		#Creating the toolbar panel within the solids tab
		ExampleCommand:adsk.core.CommandDefinition = NewCommand(ui.commandDefinitions, '__A_Completly_Unique_ID__', 'PanelBugCommand', '', '/resources')
		PanelInSolidTab:adsk.core.ToolbarPanel = AddPanel(SolidTab.toolbarPanels, 'SolidPanelTest', 'Im a solid panel')
		SolidPanelCommand = AddCommand(PanelInSolidTab.controls, ExampleCommand)
		SolidTab.activate()
		ui.messageBox('The Toolbar panel should be visible')
		#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

		#Asserting that fusion says the obects have been deleted
		assert ui.commandDefinitions.itemById('__A_Completly_Unique_ID__').deleteMe()
		assert ui.allToolbarPanels.itemById('SolidPanelTest').deleteMe()

		DelConfirm = []	#Needed a mutable to recieve info back easily
		#Trying to get fusion to complain about the objs being deleted
		with errorPrint(DelConfirm): SolidPanelCommand.id
		with errorPrint(DelConfirm): PanelInSolidTab.id
		DelConfirm.append('Deleting the variables then accessing errors:')
		#This i dont think matters since the objects are stored globally anyhow but whatever
		del PanelInSolidTab
		del SolidPanelCommand
		with errorPrint(DelConfirm): SolidPanelCommand.id
		with errorPrint(DelConfirm): PanelInSolidTab.id
		ui.messageBox('The Toolbar panel and command should be be gone \n\n Errors when trying to access those variables as proof:\n'+ str('\n'.join(DelConfirm)))


		#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		ExampleCommand:adsk.core.CommandDefinition = NewCommand(ui.commandDefinitions, '__A_Completly_Unique_ID__', 'PanelBugCommand', '', '/resources')
		PanelInToolsTab:adsk.core.ToolbarPanel = AddPanel(ToolsTab.toolbarPanels, 'SolidPanelTest', 'Im a solid panel')
		ToolsPanelCommand = AddCommand(PanelInToolsTab.controls, ExampleCommand)
		ToolsTab.activate()
		ui.messageBox('The Tools tab ToolbarPanel should be visible')
		#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
		SolidTab.activate()
		ui.messageBox('The Toolbar panel should not be in the Solids Tab')
		assert ui.commandDefinitions.itemById('__A_Completly_Unique_ID__').deleteMe()
		assert ui.allToolbarPanels.itemById('SolidPanelTest').deleteMe()
		ui.messageBox('Panel and command removed')
	except:TraceError(ui)



def stop(context):
	app:adsk.core.Application= adsk.core.Application.get()
	ui  = app.userInterface
	try: clearCreated(ui)
	except:TraceError(ui)


#||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||

def TraceError(ui): ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
def clearCreated(ui):
	global createdObjects
	try:
		# ui.messageBox(str(len(createdObjects)))
		for obj in createdObjects:
			try:
				obj.deleteMe()
				ui.messageBox('Item')
			except:pass
		# one double check to make sure, more than likley fails
		assert ui.allToolbarPanels.itemById('SolidPanelTest').deleteMe()
		assert ui.commandDefinitions.itemById('__A_Completly_Unique_ID__').deleteMe()
	except: pass
	#Wipes all references to them.
	createdObjects.clear()

 

0 Likes
Message 12 of 13

zxynine
Enthusiast
Enthusiast
Should I put this in its own thread and label it as a bug? I dont think i can change my original title
0 Likes
Message 13 of 13

zxynine
Enthusiast
Enthusiast
Accepted solution

Since this appears to be a bug and this thread is already quite full, a proper report is made in this post

0 Likes