# Copyright 2015-2022 - RoboDK Inc. - https://robodk.com/
# Attempt to run this file as a single file for debugging purposes
#if __name__== "__main__":
#    # For debugging purposes on Windows
#    import sys
#    import os    
#    #C:\Users\Albert\AppData\Local\Autodesk\webdeploy\production\999917b56d84bad9b7325987880958e07a04d903\Python
#    sys.path.append(os.path.abspath(os.getenv('APPDATA') + '/../Local/Autodesk/webdeploy/production/999917b56d84bad9b7325987880958e07a04d903/Api/Python/packages')) # temporarily add path to POSTS folder
#    #sys.path.append(os.path.abspath(os.getenv('APPDATA') + '/../Local/Autodesk/webdeploy/production/999917b56d84bad9b7325987880958e07a04d903')) # temporarily add path to POSTS folder
#    
#    #C:\Users\Albert\AppData\Local\Autodesk\webdeploy\production\999917b56d84bad9b7325987880958e07a04d903
#    #C:/Users/Albert/AppData/Local/Autodesk/webdeploy/production/999917b56d84bad9b7325987880958e07a04d903/Api/Python/packages
    


import adsk.core
import adsk.fusion
import adsk.cam
from .fission.app import CommandBase
from .fission.app import Settings
from .fission.utils.message_box import message_box
from .fission.drawing.better_types import (Point3D, Matrix, format_point, format_points, ObjectCollectionFromList)
from .fission.utils.timers import RelayStopwatch
from .fission.utils.sketch_recorder import SketchRecorder
from . import tools_robodk


from enum import IntEnum
import time
import math
import os
import random
import tempfile
import traceback
import sys


global last_scaleNeeded
last_scaleNeeded = 1.0

# All suportable formats: IGES, STEP, SAT, SMT, F3D and STL
if sys.platform == "linux" or sys.platform == "linux2":
    # Ubuntu, Linux or Debian
    class ExportFormatEnum(IntEnum):
        Default = 0
        STL = 1
elif sys.platform == "darwin":
    # Mac
    class ExportFormatEnum(IntEnum):
        Default = 0
        STL = 1

else:
    class ExportFormatEnum(IntEnum):
        Default = 0
        STEP = 1
        STL = 2
        IGES = 3    
    
    
class RoboDkSettings(Settings):
    """Class representing the settings that apply to user preferences (regarding the Fusion-RoboDK plugin)"""
    def __init__(self):
        super().__init__('RoboDkSharedSettings')
        #
        # IMPORTANT when adding or removing a field also update on_execute of the settings command
        #
        
        # Field
        self.RDK_COM = -1 # RoboDK port for the API
        self.RDK_ARGS = "" # Additional arguments to pass to RoboDK
        self.ThemeDefaults = True # If True, default Fusion theme will be used for RoboDK
    
        # RoboDK object names
        self.NameObject = ""
        self.ObjectOverride = True
        self.ExportFormat = ExportFormatEnum.Default # Choose from: STL, IGES, STEP, ...
        self.ShowObjectCurves = tools_robodk.CurveDisplayEnum.Default # 0=default, 1=Yes, 2=No
        self.NameRDK = "" # RDK project name
        self.NameRobot = "" # Robot name
        self.NameRef = "" # Reference name
        self.NameTool = ""
        self.NameProgram = ""
        self.PreviewCheck = True
        self.EntityLoadsObject = False#True
    
        # Scale
        self.LoadPathAsPoints = False
        self.CurveZigZag = False
        self.InvertNormals = False
        self.Scale = 1.0
        
        self.UpdateLevel = tools_robodk.UpdateLevelEnum.UpdateAndSimulate
        
        self.ToleranceMM = 0.25
        self.NormalCalcToleranceMM = 0.1
        self.CheckCollisions = False
    
    def getNameObject(self):
        return self.NameObject if self.NameObject else AdskProjectName()
       
        
def AdskProjectName():
    # Safely return a name related to the current project
    name = adsk.core.Application.get().activeDocument.name
    if not name or len(name) <= 0:
        name = "FusionProject"
    return name
    

settings = RoboDkSettings()
settings.load()

      
class RoboDk(CommandBase):
    """RoboDK Add-In Settings - General settings related to the RoboDK Add-In for Fusion 360.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/robodk')
        self._render_counts = 0
        self._initialized = False

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'RoboDK Add-In'

    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/settings')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def add_button(self):
        self.remove_button()
        
        button = super().add_button()
        panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        panel.controls.addCommand(button)
        
        panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        panel.controls.addCommand(button)
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        panel.controls.addCommand(button)
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        panel.controls.addCommand(button)
        
        panel = self.ui.allToolbarPanels.itemById('CAMAdditiveActionPanel')
        panel.controls.addCommand(button)
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        if button: button.deleteMe()
        
        panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        button_control = panel.controls.itemById(self.command_id)
        if button_control: button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        button_control = panel.controls.itemById(self.command_id)
        if button_control: button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
        
        panel = self.ui.allToolbarPanels.itemById('CAMAdditiveActionPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
        
            
        #cam_panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        #button_CAM = cam_panel.controls.itemById(self.command_id)
        #if button_CAM:
        #    button_CAM.deleteMe()

    def initialize_inputs(self, factory):
        # Does not work if we open/close
        #if self._initialized:
        #    return
        #self._initialized = True
        
        global settings
        factory.begin_tab('demo_tab', 'Select')
        # Available selection filters
        # http://help.autodesk.com/view/fusion360/ENU/?guid=GUID-03033DE6-AD8E-46B3-B4E6-DADA8D389E4E
        self.sel_points = factory.addSelectionInput(
            'sel_points',
            'Points',
            'Select Point(s)',
            filter=[adsk.core.SelectionCommandInput.Vertices,
                    adsk.core.SelectionCommandInput.SketchPoints,
                    adsk.core.SelectionCommandInput.ConstructionPoints,
                    adsk.core.SelectionCommandInput.Faces])
        self.sel_points.setSelectionLimits(0, 0)  #not required, unlimited
        self.sel_curves = factory.addSelectionInput(
            'sel_curves',
            'Curves',
            'Select Curve(s)',
            filter=[adsk.core.SelectionCommandInput.Edges,
                    adsk.core.SelectionCommandInput.SketchCurves,
                    adsk.core.SelectionCommandInput.Faces])
        self.sel_curves.setSelectionLimits(0, 0)  #not required, unlimited
        self.sel_faces = factory.addSelectionInput(
            'sel_faces',
            'Faces',
            'Select the face(s) to use for normals calculation.',
            filter=[adsk.core.SelectionCommandInput.Faces])
        self.sel_faces.setSelectionLimits(0, 0)  #not required, unlimited
        
        #self.sel_bodies = factory.addSelectionInput(
        #    'sel_bodies',
        #    'Bodies',
        #    'Select Bodies.',
        #    filter=[adsk.core.SelectionCommandInput.SolidBodies])
        #self.sel_bodies.setSelectionLimits(0, 1)  #not required, at most one
        
        #def create_button_row(self, id, name, items=[], default=None, values=[], tooltip='', description='', help_image=None, on_change=None, on_validate=None, persist=True, full_width=False, multiselect=False)
        #factory.create_button_row('Load2RoboDK','Load in RoboDK')#,items=['value1','value2'],values=[1,2],persist=False,full_width=True)
        
        
        # --------------------------------------------
        factory.begin_tab('basic_settings_tab', 'Basic Settings')
        
        factory.begin_group(
            'theme_groupm',
            'General Settings',
            expanded=True,
            show_enable_checkbox=False)
            
        self.ThemeDefaults = factory.create_checkbox(
            'ThemeDefaults',
            'Use Fusion Theme',
            initial_value=settings.ThemeDefaults,
            persist=False)
            
                        
        self.EntityLoadsObject = factory.create_checkbox(
            'EntityLoadsObject',
            'Always Update Part',
            initial_value=settings.EntityLoadsObject,
            persist=False)
                        
        self.PreviewCheck = factory.create_checkbox(
            'PreviewCheck',
            'Preview Selection',
            initial_value=settings.PreviewCheck,
            persist=False)  
            
        factory.close_group()
        
        factory.begin_group(
            'names_group',
            'Item Names in RoboDK',
            expanded=True,
            show_enable_checkbox=False)
        self.NameObject = factory.addStringInput(
            'NameObject', 
            'Object Name',
            initial_value=settings.NameObject,
            persist=False)
        self.ObjectOverride = factory.create_checkbox(
            'ObjectOverride', 
            'Override Object',
            initial_value=settings.ObjectOverride,
            persist=False)
        self.NameRDK = factory.addStringInput(
            'NameRDK', 
            'Project Name',
            initial_value=settings.NameRDK,
            persist=False)
        self.NameRobot = factory.addStringInput(
            'NameRobot', 
            'Robot Name',
            initial_value=settings.NameRobot,
            persist=False)
        self.NameRef = factory.addStringInput(
            'NameRef', 
            'Reference Name',
            initial_value=settings.NameRef,
            persist=False)
        self.NameTool = factory.addStringInput(
            'NameTool', 
            'Tool Name',
            initial_value=settings.NameTool,
            persist=False)
        self.NameProgram = factory.addStringInput(
            'NameProgram', 
            'Program Name',
            initial_value=settings.NameProgram,
            persist=False)
            
        factory.close_group()
        
        factory.begin_group(
            'curve_points_group',
            'Cuve/Points Settings',
            expanded=False,
            show_enable_checkbox=False)

        self.LoadPathAsPoints = factory.create_checkbox(
            'LoadPathAsPoints',
            'Load Path as Points',
            initial_value=settings.LoadPathAsPoints,
            persist=False)
        self.CurveZigZag = factory.create_checkbox(
            'CurveZigZag',
            'Alternate Curve Directions',
            initial_value=settings.CurveZigZag,
            persist=False)
        self.InvertNormals = factory.create_checkbox(
            'InvertNormals',
            'Invert Normals',
            initial_value=settings.InvertNormals,
            persist=False)
        self.Scale = factory.create_float_input(
            'Scale',
            'Scale Path/Points',
            initial_value=1,
            min_value=1e-12,
            default=settings.Scale,
            persist=False)
        factory.close_group()
        
        factory.begin_group(
            'program_update_level_group',
            'Program Update Level',
            expanded=True,
            show_enable_checkbox=False)
        self.UpdateLevel = factory.create_text_drop_down(
            'UpdateLevel',
            'Program Update Level',
            items=[e.name for e in tools_robodk.UpdateLevelEnum],
            values=[e for e in tools_robodk.UpdateLevelEnum],
            default=settings.UpdateLevel.name,
            persist=False)
        factory.close_group()
        
        # --------------------------------------------
        factory.begin_tab('advanced_settings_tab', 'Advanced Settings')
        factory.begin_group(
            'api_settings_group',
            'RoboDK Commands',
            expanded=True,
            show_enable_checkbox=False)
        
        self.RDK_COM = factory.create_int_spinner(
            'RDK_COM',
            'Communication Port',
            min_value=-1,
            max_value=2**16-1,
            initial_value=settings.RDK_COM,
            persist=False
        )
        self.RDK_ARGS = factory.addStringInput('RDK_ARGS', 'Startup Options')
        factory.close_group()
        
        factory.begin_group(
            'polyline_curve_group',
            'Polyline Curve Settings',
            expanded=True,
            show_enable_checkbox=False)
            
        self.ToleranceMM = factory.addValueInput(
            'ToleranceMM',
            'Linear Tolerance',
            settings.ToleranceMM,
            'mm',
            on_validate=lambda i: i.eval() > 0,
            eval_scale=10,
            persist=False)
        self.NormalCalcToleranceMM = factory.addValueInput(
            'NormalCalcToleranceMM',
            'Mix Normals Tolerance',
            settings.NormalCalcToleranceMM,
            'mm',
            on_validate=lambda i: i.eval() >= -1,
            eval_scale=10,
            persist=False)
        factory.close_group()
            
        factory.begin_group(
            'export_options_group',
            'Object Export Options',
            expanded=True,
            show_enable_checkbox=False)
            
        self.ExportFormat = factory.create_text_drop_down(
            'ExportFormat',
            'Export Format',
            items=[e.name for e in ExportFormatEnum],
            values=[e for e in ExportFormatEnum],
            default=settings.ExportFormat.name,
            persist=False)
            
        self.ShowObjectCurves = factory.create_text_drop_down(
            'ShowObjectCurves',
            'Display all Curves',
            items=[e.name for e in tools_robodk.CurveDisplayEnum],
            values=[e for e in tools_robodk.CurveDisplayEnum],
            default=settings.ShowObjectCurves.name,
            persist=False)
        
        self.CheckCollisions = factory.create_checkbox('CheckCollisions', 'Check Collisions')
        self.CheckCollisions.isVisible = False
        factory.close_group()
        
#    def on_preview(self, args) -> 'preview':
#        pass
            
    def on_execute(self, args) -> 'execute':
        # Fusion bug - need to blur these inputs or they don't represent the correct state.
        self.sel_points.hasFocus = False
        self.sel_curves.hasFocus = False
        self.sel_faces.hasFocus = False
        #self.sel_bodies.hasFocus = False
        self.preserve_inputs()
        # message_box(repr(sys.version_info)) # 3.5.3
        global settings
        settings.RDK_COM = self.RDK_COM.eval()
        settings.RDK_ARGS = self.RDK_ARGS.eval()
        settings.ThemeDefaults = self.ThemeDefaults.eval()
        settings.NameObject = self.NameObject.eval()
        settings.ObjectOverride = self.ObjectOverride.eval()
        settings.ExportFormat = self.ExportFormat.eval()
        settings.ShowObjectCurves = self.ShowObjectCurves.eval()
        settings.NameRDK = self.NameRDK.eval()
        settings.NameRobot = self.NameRobot.eval()
        settings.NameRef = self.NameRef.eval()
        settings.NameTool = self.NameTool.eval()
        settings.NameProgram = self.NameProgram.eval()
        settings.PreviewCheck = self.PreviewCheck.eval()
        settings.EntityLoadsObject = self.EntityLoadsObject.eval()
        settings.LoadPathAsPoints = self.LoadPathAsPoints.eval()
        settings.CurveZigZag = self.CurveZigZag.eval()
        settings.InvertNormals = self.InvertNormals.eval()
        settings.Scale = self.Scale.eval()
        settings.UpdateLevel = self.UpdateLevel.eval()
        settings.ToleranceMM = self.ToleranceMM.eval()
        settings.NormalCalcToleranceMM = self.NormalCalcToleranceMM.eval()
        settings.CheckCollisions = self.CheckCollisions.eval()
        settings.save()
        
        #if self.sel_curves.selectionCount:
        #    curves = self.interpolate_curves()
        #    faces = self.normalize_selected_faces()
        #    list_mat = tools_robodk.Curves_2_Mat(settings, curves, faces)
        #    # Send the Mat to RoboDK
        #    item_object, status, message = tools_robodk.Mat_2_RoboDK(settings, list_mat, False)
        #    if status != tools_robodk.STATUS_OK:
        #        message_box(message)
        #            
        #if self.sel_points.selectionCount:
        #    points = self.normalize_selected_points()
        #    faces = self.normalize_selected_faces()
        #    list_mat = tools_robodk.Points_2_Mat(settings, points, faces)
        #    # Send the Mat to RoboDK
        #    item_object, status, message = tools_robodk.Mat_2_RoboDK(settings, list_mat, True)
        #    if status != tools_robodk.STATUS_OK:
        #        message_box(message)
        #    #message_box(repr(points))
        #    #message_box(format_points(points, sep=',\n', include_z_zero=True))
        #        
        #if self.sel_bodies.selectionCount:
        #    selected_body = self.sel_bodies.selection(0).entity
        #    # message_box(str(selected_body))
        #    if selected_body:
        #        item_object, message = AdskPart2RoboDK(self, selected_body)
        #        if not item_object:
        #            message_box(message)
        #         
        
        entity_list = []
        for i in range(0,self.sel_curves.selectionCount):
            entity_list.append(self.sel_curves.selection(i).entity)
            
        for i in range(0,self.sel_points.selectionCount):
            entity_list.append(self.sel_points.selection(i).entity)
            
        for i in range(0,self.sel_faces.selectionCount):
            entity_list.append(self.sel_faces.selection(i).entity)
        
        EntitySelection_2_RoboDK(entity_list)

        
def EntitySelection_2_RoboDK(entity_list, autosetup=False):
    global settings
    item_object = None
    something_loaded = False
    
    # Note: If we load the part first, the part and curves are merged as one single object and RoboDK hides curves by default
    # Using RDK.Command("DisplayCurves", "1") will force curves to display even if there is geometry
    if settings.EntityLoadsObject: # or autosetup
        item_object, message = AdskPart2RoboDK()
        if not item_object:
            ShowMessage(message)          
            
    curves = GetEntities_Curves(entity_list)
    points = GetEntities_Points(entity_list)
    faces = GetEntities_Faces(entity_list)
    if len(curves) > 0:
        list_mat = tools_robodk.Curves_2_Mat(settings, curves, faces)
        # Send the Mat to RoboDK
        item_object, status, message = tools_robodk.Mat_2_RoboDK(settings, list_mat, False, item_object)
        if status != tools_robodk.STATUS_OK:
            ShowMessage(message)
    
        something_loaded = True
                
    if len(points) > 0:
        list_mat = tools_robodk.Points_2_Mat(settings, points, faces)
        # Send the Mat to RoboDK
        item_object, status, message = tools_robodk.Mat_2_RoboDK(settings, list_mat, True, item_object)
        if status != tools_robodk.STATUS_OK:
            ShowMessage(message)
    
        something_loaded = True
        #message_box(repr(points))
        #message_box(format_points(points, sep=',\n', include_z_zero=True))
        
    #if autosetup or settings.EntityLoadsObject:
    #    item_object, message = AdskPart2RoboDK()
    #    if not item_object:
    #        ShowMessage(message)      
    return something_loaded
                            

#https://forums.autodesk.com/t5/fusion-360-api-and-scripts/component-s-origin-point/td-p/5805846
def GetMatrix(occurrence):            # method to get resulting matrix for the whole chain of occurrences
    if not occurrence:
        return adsk.core.Matrix3D.create()
        
    ui = None
    try:
        app = adsk.core.Application.get()
        ui = app.userInterface
        matrix = occurrence.transform
        k = 0
        while occurrence.assemblyContext and k < 100:
            k += 1
            matrix.transformBy(occurrence.assemblyContext.transform)
            occurrence = occurrence.assemblyContext
        
        return matrix                                # returns resulting matrix
        
    except:
        if ui:
            ui.messageBox('Get Matrix failed:\n{}'.format(traceback.format_exc()))  

   
def GetEntities_Curves(entity_list):
    curves = []
    #for i in range(0, sel.selectionCount):#self.sel_curves.selectionCount):
    #    c = sel.selection(i).entity
    for entity in entity_list:
        if isinstance(entity, adsk.fusion.BRepEdge):
            brepedge = adsk.fusion.BRepEdge.cast(entity)
            success, start, end = brepedge.evaluator.getParameterExtents()
            if not success:
                # TODO: is recovery possible?
                message_box("Error: Curve may be unbounded - or otherwise failed to calculate extents!")
                tools_robodk.printLog("ERROR! Curve may be unbounded?")
                continue
            
            # IMPORTANT! This is already in Absolute coordinates! No need to transform
            success, points = brepedge.evaluator.getStrokes(start, end, settings.ToleranceMM / 10)
            if success:
                #matrix = GetMatrix(entity.assemblyContext)
                #for i in range(len(points)):
                #    points[i].transformBy(matrix)
                    
                curves.append(points)
                tools_robodk.printLog("Adding BRepEdge with %i points" % (len(points)))
                # message_box(format_points(points, sep=',\n', include_z_zero=True))
                
            else:
                # TODO: is recovery possible?
                tools_robodk.printLog("Error: Failed to interpolate curve!")
            
        elif isinstance(entity, adsk.fusion.SketchCurve):
            #tools_robodk.printLog("Sketch curves are not taken into account")
            # Inspired from:
            #
            points = []
                        
            # old solution (finds the parent sketch and all its entities (profileLoops))
            #sketchCurve = entity
            #sketch = sketchCurve.parentSketch
            #profiles = sketch.profiles
            #for profile in profiles:
            #    loops = profile.profileLoops
            #    for loop in loops:
            #        profileCurves = loop.profileCurves
            #        for profileCurve in profileCurves:
            #            sketchEntity = profileCurve.sketchEntity
            sketchEntity = adsk.fusion.SketchEntity.cast(entity)
            try:
                
                tools_robodk.printLog("Adding Sketch Entity: " + str(sketchEntity.objectType))                                                    
                
                #sEllipse = adsk.fusion.SketchEllipse.cast(sketchEntity)
                curveEval3D = sketchEntity.geometry.evaluator
                success, startparam, endparam = curveEval3D.getParameterExtents()
                if success:
                    success, pts = curveEval3D.getStrokes(startparam, endparam, settings.ToleranceMM / 10)
                    if success:  
                        for pt in pts:
                            points.append(pt)
                            
                        tools_robodk.printLog("Adding sketch curve with %i points" % (len(points)))                                
                    else:
                        tools_robodk.printLog("Error: Failed to interpolate sketch component! " + str(sketchEntity.objectType)) 
                else:
                    tools_robodk.printLog("Unable to retrieve curve endpoints")
                    
            except Exception as e:
                tools_robodk.printLog("Error: " + str(e))                    
            
            # Apply point transformation
            tools_robodk.printLog("Translating sketch with %i points" % len(points))
            if len(points) > 0:
                #matrix = GetMatrix(entity.assemblyContext) # not good
                points_transformed = []
                for i in range(len(points)):
                    pt_tr = entity.parentSketch.sketchToModelSpace(points[i])
                    points_transformed.append(pt_tr)
                    #points[i].transformBy(matrix)
                    
                curves.append(points_transformed)
                
            else:
                tools_robodk.printLog("No points found on the sketch!")
            
        else:
            tools_robodk.printLog("Unknown curve type: " + str(entity.objectType))
            pass
            
    return curves
    
def GetEntities_Points(entity_list):
    """Returned points will be in drawing units (cm)."""
    points = []    
    #for i in range(0, sel.selectionCount):#self.sel_points.selectionCount):
    #    entity = sel.selection(i).entity
    for entity in entity_list:
        #https://forums.autodesk.com/t5/fusion-360-api-and-scripts/how-to-find-the-world-coordinants-of-a-point/td-p/7327444
        #p = None
        p = adsk.core.Point3D.cast(None)
        if isinstance(entity, adsk.fusion.SketchPoint):
            p = entity.worldGeometry
            tools_robodk.printLog("SketchPoint(:,end+1)=[%.3f, %.3f, %.3f]'; " % (10*p.x,10*p.y,10*p.z))
            points.append(p)
            
        elif isinstance(entity, adsk.fusion.BRepVertex):
            p = entity.geometry
            tools_robodk.printLog("BRepVertex(:,end+1)=[%.3f, %.3f, %.3f]'; " % (10*p.x,10*p.y,10*p.z))
            points.append(p)
            
        elif isinstance(entity, adsk.fusion.ConstructionPoint):
            # TODO: this point is in local coords and needs to be transformed into world - this logic is NOT correct (except in simple cases)
            p = entity.geometry
            ac = entity.assemblyContext
            k = 0
            while ac and k < 1000:
                k += 1
                p.transformBy(ac.transform)
                ac = ac.assemblyContext
            
            tools_robodk.printLog("ConstructionPoint(:,end+1)=[%.3f, %.3f, %.3f]'; " % (10*p.x,10*p.y,10*p.z))
            points.append(p)
                
        else:
            tools_robodk.printLog("Unknown point type: " + str(entity.objectType))
        
    return points

def GetEntities_Faces(entity_list):
    faces = []
    #for i in range(0, sel.selectionCount):
        #entity = sel.selection(i).entity
    for entity in entity_list:
        if isinstance(entity, adsk.fusion.BRepFace):
            facematrix = GetMatrix(entity.assemblyContext)
            facematrix_inv = facematrix.copy()
            facematrix_inv.invert()
            #entity.transformBy(matrix) # not allowed
            faces.append([entity, facematrix, facematrix_inv])
         
    tools_robodk.printLog("Number of faces selected: %i\n" % (len(faces)))
    return faces

        

#        m = robolib.eye()
#        message_box(repr(m))


def units2scale(units):
    #ShowMessage(units)
    if units == "mm":
        return 1.0
    if units == "in":
        return 25.4
    if units == "cm":
        return 10.0
    if units == "m":
        return 1000.0
    return 1.0

        
def AdskSaveBody(command, body, file_path, file_format):
    global last_scaleNeeded
    #exporter = command.design.design.exportManager
    exporter = command.design.design.exportManager
    last_scaleNeeded = 1.0
    
    try:
        if "win" in sys.platform.lower():
            #if file_format == ExportFormatEnum.STL:
            # Only for STL, other formats scale automatically
            units = command.design.design.unitsManager.defaultLengthUnits
            last_scaleNeeded = units2scale(units)
            
            opts = {
                ExportFormatEnum.STEP: lambda: exporter.createSTEPExportOptions(file_path, body.parentComponent),
                ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(body, file_path),
                ExportFormatEnum.IGES: lambda: exporter.createIGESExportOptions(file_path, body.parentComponent)                
            }[file_format]()
        else:
            # Only for STL, other formats scale automatically
            units = command.design.design.unitsManager.defaultLengthUnits
            last_scaleNeeded = units2scale(units)
            
            opts = exporter.createSTLExportOptions(rootComp, file_path)
            #opts = {
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path),
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path),
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path)
            #}[file_format]()
    except Exception as e:
        with open(file_path,'w') as fid:
            # Create empty file
            pass
        
        #ShowMessage(str(e))
        ShowMessage("Open or create a design first")
        
        return True
        
    return exporter.execute(opts)
    
def AdskSaveAll(file_path, file_format):
    global last_scaleNeeded
    #exporter = command.design.design.exportManager
    app = adsk.core.Application.get()
    design = adsk.fusion.Design.cast(app.activeProduct)
    #exporter = design.exportManager
    exporter = adsk.fusion.ExportManager.cast(design.exportManager)
    last_scaleNeeded = 1.0
    rootComp = design.rootComponent
    
    try:
    #if True:
        if "win" in sys.platform.lower():
            #if file_format == ExportFormatEnum.STL:
            # Only for STL, other formats scale automatically
            units = design.unitsManager.defaultLengthUnits
            last_scaleNeeded = units2scale(units)
                
            opts = {
                ExportFormatEnum.STEP: lambda: exporter.createSTEPExportOptions(file_path, rootComp),
                ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path),
                ExportFormatEnum.IGES: lambda: exporter.createIGESExportOptions(file_path, rootComp) 
            }[file_format]()
        else:
            # Only for STL, other formats scale automatically
            units = design.unitsManager.defaultLengthUnits
            last_scaleNeeded = units2scale(units)
                
            opts = exporter.createSTLExportOptions(rootComp, file_path)
            #opts = {
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path),
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path),
            #    ExportFormatEnum.STL: lambda: exporter.createSTLExportOptions(rootComp, file_path)
            #}[file_format]()
    
    except Exception as e:
        
        with open(file_path,'w') as fid:
            # Create empty file
            pass

        #ShowMessage(str(e))
        ShowMessage("Open or create a design first")
        
        return True
    
    # Taken from:
    # https://forums.autodesk.com/t5/fusion-360-api-and-scripts/script-api-to-change-parameter-and-then-export-as-stl/td-p/7038013
    # Save the file as STL.
    #exportMgr = adsk.fusion.ExportManager.cast(command.design.exportManager)
    #stlOptions = exportMgr.createSTLExportOptions(rootComp)
    #stlOptions.meshRefinement = adsk.fusion.MeshRefinementSettings.MeshRefinementMedium
    #stlOptions.filename = filename
    #exportMgr.execute(stlOptions)    
    status = exporter.execute(opts)
    #ShowMessage("Done!")
    return status
        
def AdskPart2RoboDK(command=None, body=None):
    """Export the current part/assembly and load it to RoboDK, returns the object as RoboDK.Item. Returns None and a message if failed"""
    file_format = settings.ExportFormat
    #ShowMessage(str(settings.ExportFormat) + " - " + str(ExportFormatEnum.Default))
    if file_format != ExportFormatEnum.Default:
        file_extension = file_format.name.lower()
    else:
        # ShowMessage(sys.platform.lower())
        if "win" in sys.platform.lower():
            file_format = ExportFormatEnum.STEP
            file_extension = 'step'
        else:
            file_format = ExportFormatEnum.STL
            file_extension = 'stl'
          
    if False: #body:
        # Save only selected body
        # Important: in all cases, RoboDK should export the complete project
        file_path = tempfile.gettempdir() + "/" + tools_robodk.strip_unsafe_chars_for_filename(body.name) + "." + file_extension
        if not AdskSaveBody(command, body, file_path, file_format):
            message = "Unable to export 3D model to " + file_path
            return None, message
    else:
        # Export everything
        file_path = tempfile.gettempdir() + "/" + tools_robodk.strip_unsafe_chars_for_filename(AdskProjectName()) + "." + file_extension
        if not AdskSaveAll(file_path, file_format):
            message = "Unable to export 3D model to " + file_path
            return None, message
    
    item_object, message = tools_robodk.Part_2_RoboDK(settings, file_path, last_scaleNeeded)
    return item_object, message
#        m = robolib.eye()
#        message_box(repr(m))







#-----------------------------------------------------------------------
#---------------- LOAD CURVES COMMAND -------------------------------------
def CmdRun_AutoSetup(command, args):
    ui = getUI()
    if ui:
        sel_list = ui.activeSelections
        entity_list = []
        for sel in sel_list:
            entity_list.append(sel.entity)
            
        if not EntitySelection_2_RoboDK(entity_list, True):
            # trigger the settings window
            #global RoboDKMainWindow
            #RoboDKMainWindow.on_execute(args)
            #RoboDKMainWindow.run()
            # General settings:
            global RoboDKMainWindow
            RoboDKMainWindow.show()
            return
        

class Cmd_AutoSetup(CommandBase):
    """RoboDK - Auto Setup: 
    Load selected curves and/or points to RoboDK as a curve follow project. Optionally select one or more surfaces to extract the toolpath orientation.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Auto Setup - RoboDK'

    @property
    def command_id_for_settings(self):
        return 'RoboDk'
        
    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/robodk')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_marking_menu(self, _args) -> 'marking_menu_displaying':
        try:
            # "cast" to get type helping from spyder
            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-1CFFE536-D5E0-449B-8D2B-8A010990AE59
            args = adsk.core.MarkingMenuEventArgs.cast(_args)
            
            # Menu shown when right clicking.
            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-626F9254-9F24-4F14-A431-EBCB2DFA95CA
            linearMenu = args.linearMarkingMenu
            
            # Radial menu is shown when drag-right-clicking (the linear menu is also shown under the radial in right-click-drag actions)
            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-1A6EC269-B573-475B-89B5-EDCA14E4B9EB
            radialMenu = args.radialMarkingMenu

    #        exportable_body_selected = False
    #        if args.selectedEntities:
    #            types = (adsk.fusion.BRepBody, adsk.fusion.BRepFace, adsk.fusion.Component)
    #            for e in args.selectedEntities:
    #                if type(e) in types:
    #                    exportable_body_selected = True
    #                    break;
    #
    #        if exportable_body_selected:

            # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-df24e158-40e0-4aed-9990-97c470935dbb
            linearMenu.controls.addSeparator()
            linearMenu.controls.addCommand(self._command_definition)
        except:
            print("Error... Did you unload the plugin? Probably linearMenu is None/invalid")
            
    def on_execute(self, args) -> 'execute':
        CmdRun_AutoSetup(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        panel.controls.addCommand(button)
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        bc = panel.controls.addCommand(button)
        # Surface this button in the ribbon (make this a top level icon).
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        bc = panel.controls.addCommand(button)
        # Surface this button in the ribbon (make this a top level icon).
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        #panel = self.ui.allToolbarPanels.itemById('RoboDKCAMAdditiveActionPanel')
        #bc = panel.controls.addCommand(button)
        ## Surface this button in the ribbon (make this a top level icon).
        #bc.isPromotedByDefault = True
        #bc.isPromoted = True
        
        # This shows the button in the dropdown
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        if button: button.deleteMe()
        
        panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        button_control = panel.controls.itemById(self.command_id)
        if button_control: button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
        
        #panel = self.ui.allToolbarPanels.itemById('RoboDKCAMAdditiveActionPanel')
        #if panel:
        #    button_control = panel.controls.itemById(self.command_id)
        #    if button_control: button_control.deleteMe()
        
        
      
#-----------------------------------------------------------------------
#---------------- LOAD PART COMMAND -------------------------------------
def CmdRun_LoadPart(command, args):
    # Load part
    item_object, message = AdskPart2RoboDK()
    if not item_object:
        ShowMessage(message)      


class Cmd_LoadPart(CommandBase):
    """RoboDK - Load all your 3D models in RoboDK as objects
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Load Model - RoboDK'
        
    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/loadpart')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''
        
    def on_execute(self, args) -> 'execute':
        CmdRun_LoadPart(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        create_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        create_panel.controls.addCommand(button)

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        bc = panel.controls.addCommand(button)   
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        create_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        button_control = create_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        button_control = panel.controls.itemById(self.command_id)
        if button_control: button_control.deleteMe()

#-----------------------------------------------------------------------
#---------------- LOAD CURVES COMMAND -------------------------------------
def CmdRun_LoadCurves(command, args):
    ui = getUI()
    if ui:
        sel_list = ui.activeSelections
        entity_list = []
        for sel in sel_list:
            entity_list.append(sel.entity)
            
        if not EntitySelection_2_RoboDK(entity_list):
            # trigger the settings window
            #global RoboDKMainWindow
            #RoboDKMainWindow.on_execute(args)
            #RoboDKMainWindow.run()
            # General settings:
            global RoboDKMainWindow
            RoboDKMainWindow.show()
            return
            #pass
        

class Cmd_LoadCurves(CommandBase):
    """RoboDK - Load selected curves to RoboDK as a curve follow project. Selected surfaces are also used to extract toolpath orientation.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Load Curve(s) - RoboDK'

    @property
    def command_id_for_settings(self):
        return 'RoboDk'
        
    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/loadcurves')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_execute(self, args) -> 'execute':
        CmdRun_LoadCurves(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        cam_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        cam_panel.controls.addCommand(button)

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        panel.controls.addCommand(button)

        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        cam_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        button_control = cam_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
            

            
            
#-----------------------------------------------------------------------
#---------------- LOAD POINTS COMMAND -------------------------------------
def CmdRun_LoadPoints(command, args):
    CmdRun_LoadCurves(command, args)
    #ui = getUI()
    #if ui:
    #    sel = ui.activeSelections
    #    EntitySelection_2_RoboDK(sel)
        

class Cmd_LoadPoints(CommandBase):
    """RoboDK - Load selected points to RoboDK as point-follow project. Selected surfaces are also used to extract point orientations.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Load Point(s) - RoboDK'

    @property
    def command_id_for_settings(self):
        return 'RoboDk'
        
    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/loadpoints')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_execute(self, args) -> 'execute':
        CmdRun_LoadPoints(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        
        cam_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        cam_panel.controls.addCommand(button)

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        panel.controls.addCommand(button)     
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        cam_panel = self.ui.allToolbarPanels.itemById('SolidCreatePanel')
        button_control = cam_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
            


def getCAM():
    # Get the application.
    app = adsk.core.Application.get()
    
    # Get the active document.
    doc = app.activeDocument
    
    # Get the products collection on the active document.
    products = doc.products
    
    # Get the CAM product.
    product = products.itemByProductType('CAMProductType')
    
    # Check if the document has a CAMProductType. It will not if there are no CAM operations in it.
    if product == None:
        ShowMessage('There are no CAM operations in the active document')
        return None
    
    # Cast the CAM product to a CAM object (a subtype of product).
    cam = adsk.cam.CAM.cast(product)
    return cam




def wait_written(filename):
    for i in range(100):
        try:
            with open(filename, 'rb') as _:
                return True
                
            
        except IOError:
            time.sleep(0.2)
            
    return False
            
#-----------------------------------------------------------------------
#---------------- POST PROCESS/SIMULATE CAM COMMAND -------------------------------------
def CmdRun_LoadCAM(command, args):
    #https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-7F3F9D48-ED88-451A-907C-82EAE67DEA93
    RDK, message = tools_robodk.GetRoboDK(settings)
    if RDK is None:
        ShowMessage(message)
        return
        
        
    cam = getCAM()
    if not cam:
        return
    
    ## Mykal's code:    
    setups = cam.setups
    for setup in setups:
        operations = setup.operations
        for operation in operations:
            programName = operation.name

            if len(programName) < 1:
                programName = AdskProjectName()
            #programName = command.design.active_document.name  
            
            if not programName:
                programName = "Machining"
                
            programName.replace(' ', '')
            
            # Specify a destination folder.
            outputFolder = tempfile.gettempdir()
            ncfile_good_name = outputFolder + '/' + programName + '.apt'
            
            if os.path.isfile(ncfile_good_name):
                os.remove(ncfile_good_name)
            
            # Specify a post configuration to use.
            #postConfig = cam.genericPostFolder + '/' + 'camplete.cps'
            #postConfig = cam.genericPostFolder + '/' + 'apt.cps'    
            
            postConfig = os.path.dirname(os.path.abspath(__file__)) + '/apt_RoboDK.cps'
            
            
            # Specify the NC file output units.
            units = adsk.cam.PostOutputUnitOptions.DocumentUnitsOutput
            
            # Create the postInput object.
            postInput = adsk.cam.PostProcessInput.create(programName, postConfig, outputFolder, units)
            
            # Open the resulting NC file in the editor for viewing
            postInput.isOpenInEditor = False
            
            # Post TOOLPATH
            if operation.hasToolpath == True:
                cam.postProcess(operation, postInput)

                if not wait_written(ncfile_good_name):
                    ShowMessage("No file written or failed to post data. See log for details.")
                    return

                # IMPORTANT: IRIDIUM CNC Tests required the following:
                # Updating post to apt only (not CAMPlete)
                # This post uses ARCS ALWAYS, however, the radius provided is NOT ACCURATE!!!
                # we must force arcs to be ARCS ant not helix by providing a large helix tolerance (default tolerance is 0.002)
                # IRIDIUM CNC required at least 0.05 inch
                RDK.Command("HelixTol", 5)
                RDK.AddFile(ncfile_good_name)

    # RoboDK original code
    # programName = settings.NameProgram

    # if len(programName) < 1:
    #     programName = AdskProjectName()
    # #programName = command.design.active_document.name  
    
    # if not programName:
    #     programName = "Machining"
        
    # programName.replace(' ', '')
    
    # # Specify a destination folder.
    # outputFolder = tempfile.gettempdir()
    # ncfile_good_name = outputFolder + '/' + programName + '.apt'
    
    # if os.path.isfile(ncfile_good_name):
    #     os.remove(ncfile_good_name)
    
    # # Specify a post configuration to use.
    # #postConfig = cam.genericPostFolder + '/' + 'camplete.cps'
    # #postConfig = cam.genericPostFolder + '/' + 'apt.cps'    
    
    # postConfig = os.path.dirname(os.path.abspath(__file__)) + '/apt_RoboDK.cps'
    
    
    # # Specify the NC file output units.
    # units = adsk.cam.PostOutputUnitOptions.DocumentUnitsOutput
    
    # # Create the postInput object.
    # postInput = adsk.cam.PostProcessInput.create(programName, postConfig, outputFolder, units)
    
    # # Open the resulting NC file in the editor for viewing
    # postInput.isOpenInEditor = False
    
    # # Post all toolpaths in the document
    # result = cam.postProcessAll(postInput)   
    
    
    # #if False:
    #     #itei_object = LoadModelToRoboDK(OPTS, i_inventorDocument);

    # if not wait_written(ncfile_good_name):
    #     ShowMessage("No file written or failed to post data. See log for details.")
    #     return

    # # IMPORTANT: IRIDIUM CNC Tests required the following:
    # # Updating post to apt only (not CAMPlete)
    # # This post uses ARCS ALWAYS, however, the radius provided is NOT ACCURATE!!!
    # # we must force arcs to be ARCS ant not helix by providing a large helix tolerance (default tolerance is 0.002)
    # # IRIDIUM CNC required at least 0.05 inch
    # RDK.Command("HelixTol", 5)
    # RDK.AddFile(ncfile_good_name)
    


class Cmd_LoadCAM(CommandBase):
    """RoboDK - Load your CAM project in RoboDK to setup a robot machining project.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Load CAM project in RoboDK'

    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/loadcam')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_execute(self, args) -> 'execute':
        CmdRun_LoadCAM(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        create_panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        bc = create_panel.controls.addCommand(button)
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        panel.controls.addCommand(button)     
                
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        bc = panel.controls.addCommand(button)      
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        create_panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        button_control = create_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        button_control = panel.controls.itemById(self.command_id)
        if button_control: button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()

     
     
#-----------------------------------------------------------------------
#---------------- POST PROCESS/SIMULATE CAM COMMAND -------------------------------------
def CmdRun_LoadAdditive(command, args):
    #https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-7F3F9D48-ED88-451A-907C-82EAE67DEA93
    RDK, message = tools_robodk.GetRoboDK(settings)
    if RDK is None:
        ShowMessage(message)
        return
        
        
    cam = getCAM()
    if not cam:
        return

    programName = settings.NameProgram
    
    if len(programName) < 1:
        programName = AdskProjectName()
    #programName = command.design.active_document.name  
    
    if not programName:
        programName = "Additive"
        
    programName.replace(' ', '')
    
    # Specify a destination folder.
    outputFolder = tempfile.gettempdir()
    ncfile_good_name = outputFolder + '/' + programName + '.gcode'
    print(ncfile_good_name)
    
    if os.path.isfile(ncfile_good_name):
        os.remove(ncfile_good_name)
    
    # Specify a post configuration to use.
    postConfig = cam.genericPostFolder + '/' + 'generic fff machine.cps'
    
    # Specify the NC file output units.
    units = adsk.cam.PostOutputUnitOptions.DocumentUnitsOutput
    
    # Create the postInput object.
    postInput = adsk.cam.PostProcessInput.create(programName, postConfig, outputFolder, units)
    
    # Open the resulting NC file in the editor for viewing
    postInput.isOpenInEditor = False
    
    # Post all toolpaths in the document
    result = cam.postProcessAll(postInput)   
    
    
    #if False:
        #itei_object = LoadModelToRoboDK(OPTS, i_inventorDocument);

    if not wait_written(ncfile_good_name):
        ShowMessage("No file written or failed to post data. See log for details.")
        return


    RDK.AddFile(ncfile_good_name)
     
     
     
class Cmd_LoadAdditive(CommandBase):
    """RoboDK - Load your Additive Manufacturing project in RoboDK to setup a 3D printing robot project.
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Load Additive Manufacturing project in RoboDK'

    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/loadadditive')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_execute(self, args) -> 'execute':
        CmdRun_LoadAdditive(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        create_panel = self.ui.allToolbarPanels.itemById('CAMAdditiveActionPanel')
        bc = create_panel.controls.addCommand(button)
        bc.isPromotedByDefault = True
        bc.isPromoted = True
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        panel.controls.addCommand(button)     
                
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')#'RoboDKCAMAdditiveActionPanel')
        bc = panel.controls.addCommand(button)      
        #bc.isPromotedByDefault = True
        #bc.isPromoted = True
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        create_panel = self.ui.allToolbarPanels.itemById('CAMAdditiveActionPanel')
        button_control = create_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()

        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')#('RoboDKCAMAdditiveActionPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()


#-----------------------------------------------------------------------
#---------------- GENERATE PROGRAMS COMMAND -------------------------------------
def CmdRun_GenerateProg(command, args):
    RDK, message = tools_robodk.GetRoboDK(settings)
    if RDK is None:
        ShowMessage(message)
        return
    
    # lazy way to generate programs in RoboDK: TODO: Update CAM files and then generate programs
    RDK.Command("MakeProgs", "") 


class Cmd_GenerateProg(CommandBase):
    """RoboDK - Generate all Programs available in your RoboDK station
    """
    def __init__(self):
        super().__init__()
        #self._resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '/resources/post')
        self._render_counts = 0

    @property
    def is_repeatable(self):
        return True

    @property
    def command_name(self):
        return 'Generate Robot Programs - RoboDK'
        
    @property
    def resource_dir(self):
        try:
            resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources/post')
            return resource_dir if os.path.isdir(resource_dir) else ''
        except:
            return ''

    def on_execute(self, args) -> 'execute':
        CmdRun_GenerateProg(self, args)
    
    def add_button(self):
        self.remove_button()
        button = super().add_button()
        cam_panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        cam_panel.controls.addCommand(button)

        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        bc = panel.controls.addCommand(button)            

        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        bc = panel.controls.addCommand(button)     

        #panel = self.ui.allToolbarPanels.itemById('RoboDKCAMAdditiveActionPanel')
        #bc = panel.controls.addCommand(button)             
        
        button.isPromotedByDefault = True
        button.isPromoted = True
        return button

    def remove_button(self):
        button = self.ui.commandDefinitions.itemById(self.command_id)
        cam_panel = self.ui.allToolbarPanels.itemById('CAMActionPanel')
        button_control = cam_panel.controls.itemById(self.command_id)
        if button:
            button.deleteMe()
        if button_control:
            button_control.deleteMe()
            
        panel = self.ui.allToolbarPanels.itemById('RoboDKSolidPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()            
        
        panel = self.ui.allToolbarPanels.itemById('RoboDKCAMPanel')
        if panel:
            button_control = panel.controls.itemById(self.command_id)
            if button_control: button_control.deleteMe()
        
        #panel = self.ui.allToolbarPanels.itemById('RoboDKCAMAdditiveActionPanel')
        #if panel:
        #    button_control = panel.controls.itemById(self.command_id)
        #    if button_control: button_control.deleteMe()


def getUI():
    return adsk.core.Application.get().userInterface


def ShowMessage(message):
    tools_robodk.printLog("--> ShowMessage : " + str(message))
    ui = getUI()
    if ui:
        ui.messageBox(message)
    else:
        print("UI not available to display message: " + str(message))

# Available Fusion panels
#https://forums.autodesk.com/t5/fusion-360-api-and-scripts/how-do-i-get-panel-name-strings/td-p/7536055
#CAMJobPanel CAM2DPanel CAM3DPanel CAMDrillingPanel CAMMultiAxisPanel CAMTurningPanel CAMWLPCPanel CAMActionPanel  CAMInspectPanel CAMManagePanel VisualizationPanel CAMScriptsAddinsPanel SelectPanel 3DStoryboardPanel
#3DComponentPanel AnnotationPanel PublisherViewPanel PublishVideoPanel DebugDialog ForDeletionPanel SketchPanel SolidCreatePanel  SolidModifyPanel AssembleJointsPanel ConstructionPanel InspectPanel InsertPanel
#SolidMakePanel SolidScriptsAddinsPanel SelectPanel FusionDocumentationEnvironment ViewsPanel ModifyPanel GeometryPanel DimensionsPanel  TextPanel SymbolsPanel BillOfMaterialsPanel OutputPanel StudyPanel
#MaterialsPanel ConstraintsPanel LoadsPanel ContactsPanel ViewsPanel SolvePanel ManagePanel  ResultsPanel InspectPanel SynchronizePanel ResultsAnimatePanel ResultsOptionsPanel PostMeshPanel
#CompareLayoutsPanel SelectPanel DebugPanel
def showToolbarPanels():
    ui = getUI()
    for i in range(ui.allToolbarPanels.count):
        if ui.allToolbarPanels.item(i).isVisible == True:
            print(ui.allToolbarPanels.item(i).id)

__all_command_refs = []
def _register_command(instance):
    global __all_command_refs
    __all_command_refs.append(instance)
    instance.run()

  
def run_old(context):
    ui = getUI()
    try:
        # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-6b387530-93c1-4238-aefb-eca54b03852d
    
        # WORKSPACES:
        # FusionSolidEnvironment
        # CAMEnvironment
        # 
        # TSplineEnvironment
        # GenWorkingModelEnv
        # FusionSurfaceEnvironment
        # FusionSheetMetalEnvironment
        # PCBEnvironment
        # MeshEnvironment
        # FusionRenderEnvironment
        # Publisher3DEnvironment
        # WorkingModelEnv
        # FusionModelingEnvironment
        # FusionAssembleEnvironment
        # FusionVisualizationEnvironment
        # FusionDocumentationEnvironment
        # FusionDrawingsBlockEditorEnv
        # CAMModelingEnvironment

        
        #   FusionSolidEnvironment (20)____
        #      SketchPanel
        #      SolidCreatePanel
        #      SolidModifyPanel
        #      AssembleJointsPanel
        #      ConstructionPanel
        #      GeneratePanel
        #      InspectPanel
        #      InsertPanel
        #      SolidMakePanel
        #      SolidScriptsAddinsPanel
        #      SelectPanel
        #      StopSketchPanel
        #      StopBaseFeaturePanel
        #      StopTSplineBaseFeaturePanel
        #      StopMeshBaseFeaturePanel
        #      StopSnapshotEditPanel
        #      SnapshotPanel
        #      SheetmetalRefoldPanel
        #      SheetmetalFlatPatternExitPanel
        #      ReturnPanel
        ui.workspaces.itemById('FusionSolidEnvironment').toolbarPanels.add('RoboDKSolidPanel', 'RoboDK', 'AssembleJointsPanel')
        
        #   CAMEnvironment (16)____
        #      CAMJobPanel
        #      CAMPositioningPanel
        #      CAMOrientationPanel
        #      CAM2DPanel
        #      CAM3DPanel
        #      CAMDrillingPanel
        #      CAMMultiAxisPanel
        #      CAMTurningPanel
        #      CAMWLPCPanel
        #      CAMAdditivePanel
        #      CAMProbingPanel
        #      CAMActionPanel
        #      CAMInspectPanel
        #      CAMManagePanel
        #      CAMScriptsAddinsPanel
        #      SelectPanel
        ui.workspaces.itemById('CAMEnvironment').toolbarPanels.add('RoboDKCAMPanel', 'RoboDK', 'CAMActionPanel')
        
        # Auto setup command
        _register_command(Cmd_AutoSetup())
    
        # Command to load the 3D model
        _register_command(Cmd_LoadPart())

        # Command to load curves
        _register_command(Cmd_LoadCurves())

        # Command to load points
        _register_command(Cmd_LoadPoints())
        
        # Command to load CAM file (NC)
        _register_command(Cmd_LoadCAM())
        
        # Command to post all programs
        _register_command(Cmd_GenerateProg())
        
        # General settings:
        global RoboDKMainWindow
        RoboDKMainWindow = RoboDk()
        _register_command(RoboDKMainWindow)     
 
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
    

# import adsk.core, adsk.fusion, adsk.
# cam, traceback

def run_show(context):
    ui = None
    try:

        app = adsk.core.Application.get()
        ui  = app.userInterface

        msg = ""
        for i in range(ui.allToolbarPanels.count):
            if ui.allToolbarPanels.item(i).isVisible == True:
                msgi = ui.allToolbarPanels.item(i).id
                msg += msgi
                msg += '\n'
                print(msgi)
        
        #raise Exception(msg)
        
        
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))    
   #CAMJobPanel
#CAMAdditiveJobPanel
#CAM2DPanel
#CAM3DPanel
#CAMEditPanel
#CAMDrillingPanel
#CAMMultiAxisPanel
#CAMTurningPanel
#CAMWLPCPanel
#CAMProbingPanel
#CAMPartAlignmentEditPanel
#CAMPartAlignmentInspectPanel
#CAMActionPanel
#CAMProbingActionPanel
#CAMAdditiveActionPanel
#CAMInspectPanel
#CAMProbingInspectPanel
#CAMManagePanel
#CAMAdditiveManagePanel
#CAMScriptsAddinsPanel
#SelectPanel
#3DStoryboardPanel
#3DComponentPanel
#AnnotationPanel
#PublisherViewPanel
#PublishVideoPanel
#FilePanel
#DiagnosticsPanel
#UIDemo
#SketchCreatePanel
#PCB3DSketchCreatePanel
#SketchModifyPanel
#SketchConstraintsPanel
#SolidCreatePanel
#SolidModifyPanel
#SheetMetalCreatePanel
#SheetMetalModifyPanel
#AssemblePanel
#AssembleModifyPanel
#AssembleUtilityPanel
#SurfaceCreatePanel
#SurfaceModifyPanel
#TSplinePrimitivePanel
#TSplineModifyPanel
#TSplineSymmetryPanel
#TSplineUtilitiesPanel
#MeshPrimitivePanel
#MeshModifyPanel
#PCBCreatePanel
#PCB3DPanel
#ConstructionPanel
#InspectPanel
#InspectMeshPanel
#ToolsInspectPanel
#InsertPanel
#PackagePanel
#Package3DPanel
#MakePanel
#SolidScriptsAddinsPanel
#UtilityPanel
#SelectPanel
#MeshSelectPanel
#SnapshotSolidModifyPanel
#RenderSetupPanel
#InCanvasRenderPanel
#RenderPanel
#ToolsPanel
#FusionDocumentationEnvironment
#ViewsPanel
#DrawingPanel
#ModifyPanel
#GeometryPanel
#DimensionsPanel
#TextPanel
#InspectPanel
#SymbolsPanel
#InsertPanel
#BillOfMaterialsPanel
#BlockPanel
#StopBlockEditPanel
#StopBorderEditPanel
#StopSketchEditPanel
#OutputPanel
            
def run(context):
    #run_show(context)
    ui = getUI()
    try: 
        # https://help.autodesk.com/view/fusion360/ENU/?guid=GUID-6b387530-93c1-4238-aefb-eca54b03852d
        
        
        
        # WORKSPACES:
        # FusionSolidEnvironment
        # CAMEnvironment
        # 
        # TSplineEnvironment
        # GenWorkingModelEnv
        # FusionSurfaceEnvironment
        # FusionSheetMetalEnvironment
        # PCBEnvironment
        # MeshEnvironment
        # FusionRenderEnvironment
        # Publisher3DEnvironment
        # WorkingModelEnv
        # FusionModelingEnvironment
        # FusionAssembleEnvironment
        # FusionVisualizationEnvironment
        # FusionDocumentationEnvironment
        # FusionDrawingsBlockEditorEnv
        # CAMModelingEnvironment

        
        #   FusionSolidEnvironment (20)____
        #      SketchPanel
        #      SolidCreatePanel
        #      SolidModifyPanel
        #      AssembleJointsPanel
        #      ConstructionPanel
        #      GeneratePanel
        #      InspectPanel
        #      InsertPanel
        #      SolidMakePanel
        #      SolidScriptsAddinsPanel
        #      SelectPanel
        #      StopSketchPanel
        #      StopBaseFeaturePanel
        #      StopTSplineBaseFeaturePanel
        #      StopMeshBaseFeaturePanel
        #      StopSnapshotEditPanel
        #      SnapshotPanel
        #      SheetmetalRefoldPanel
        #      SheetmetalFlatPatternExitPanel
        #      ReturnPanel
        ui.workspaces.itemById('FusionSolidEnvironment').toolbarPanels.add('RoboDKSolidPanel', 'RoboDK', 'AssembleJointsPanel')
        
        #   CAMEnvironment (16)____
        #      CAMJobPanel
        #      CAMPositioningPanel
        #      CAMOrientationPanel
        #      CAM2DPanel
        #      CAM3DPanel
        #      CAMDrillingPanel
        #      CAMMultiAxisPanel
        #      CAMTurningPanel
        #      CAMWLPCPanel
        #      CAMAdditivePanel
        #      CAMProbingPanel
        #      CAMActionPanel
        #      CAMInspectPanel
        #      CAMManagePanel
        #      CAMScriptsAddinsPanel
        #      SelectPanel
        ui.workspaces.itemById('CAMEnvironment').toolbarPanels.add('RoboDKCAMPanel', 'RoboDK', 'CAMActionPanel')
        #ui.workspaces.itemById('CAMEnvironment').toolbarPanels.add('RoboDKCAMAdditiveActionPanel', 'RoboDK', 'CAMAdditiveActionPanel')

        
        # Auto setup command
        _register_command(Cmd_AutoSetup())
    
        # Command to load the 3D model
        _register_command(Cmd_LoadPart())

        # Command to load curves
        _register_command(Cmd_LoadCurves())

        # Command to load points
        _register_command(Cmd_LoadPoints())
        
        # Command to load CAM file (NC)
        _register_command(Cmd_LoadCAM())
        
        # Command to load 3D printing
        _register_command(Cmd_LoadAdditive())        
        
        # Command to post all programs
        _register_command(Cmd_GenerateProg())
        

        
        

        
        # General settings:
        global RoboDKMainWindow
        RoboDKMainWindow = RoboDk()
        _register_command(RoboDKMainWindow)     
 
    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))
    
    
def stop(context):
    ui = getUI()
    global __all_command_refs
    for cmd in __all_command_refs:
        cmd.stop(context)
        
    panel = ui.allToolbarPanels.itemById('RoboDKSolidPanel')
    if panel: 
        panel.deleteMe()
    
    panel = ui.allToolbarPanels.itemById('RoboDKCAMPanel')
    if panel: 
        panel.deleteMe()
    
    #panel = ui.allToolbarPanels.itemById('RoboDKCAMAdditiveActionPanel')
    #if panel: 
    #    panel.deleteMe()
    
    __all_command_refs.clear()

if __name__== "__main__":
    run()    
    