Open file using relative paths

Open file using relative paths

Anonymous
Not applicable
2,602 Views
7 Replies
Message 1 of 8

Open file using relative paths

Anonymous
Not applicable

I am unable to open a json file (or any file, for that matter) only specifying a relative path location. Here's my example addin code, where test.json is a file within the root directory of the add-in. This throws a FileNotFoundError. open() is unable to find the file on the path. However, in python running on my terminal, the file is found without any issue. Any ideas? Code: 

#Author-
#Description-

import adsk.core, adsk.fusion, adsk.cam, traceback
import json

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        hi = json.load(open("test.json"))
        ui.messageBox(str(hi)+ "I wish this would work...")

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

def stop(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        ui.messageBox('Stop addin')

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

I need relative imports to work for the tool I am building! Alternatively, I could just prepend __file__ to "fix" this issue (absolute paths seem to resolve) but I'd like to know why this relative path doesn't resolve?

0 Likes
2,603 Views
7 Replies
Replies (7)
Message 2 of 8

marshaltu
Autodesk
Autodesk

Hello,

 

The root cause could be the current working directory was different when you run the codes in Fusion 360 and Python terminal. You can use "os.getcwd()" to check what's current working directory in the two scenarios. 

 

import adsk.core, traceback
import os

def run(context):
    ui = None
    try:
        app = adsk.core.Application.get()
        ui  = app.userInterface
        
        ui.messageBox('Current working directory: \"{}\"'.format(os.getcwd()))

    except:
        if ui:
            ui.messageBox('Failed:\n{}'.format(traceback.format_exc()))

Thanks,

Marshal



Marshal Tu
Fusion Developer
>
0 Likes
Message 3 of 8

Anonymous
Not applicable

Absolute paths are the way to go - Here's what I've done for resolving my resource directory.

 

  @property
  def resource_dir(self):
    try:
      _resource_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'resources')
      return _resource_dir if os.path.isdir(_resource_dir) else ''
    except:
      return ''
0 Likes
Message 4 of 8

Anonymous
Not applicable

If you are intending to store dialog values to persist between invocations consider using the attributes collection. This can be used to store values within a design - so design A has a set of remembered input values and design B has an independent set (and new design's start with some default value). If this is your intended use-case I have a handy wrapper class I can share that makes life much easier than dealing with the raw attributes collection.

 

This is the mess to get the collection in a way that Spider will help you with code suggestions:

adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct).attributes

 

0 Likes
Message 5 of 8

Anonymous
Not applicable

When I run the code to find the PWD for python, Fusion reports "/". For whatever reason, Fusion pretends the relative path starts at my system's root. Indeed, I confirmed this is correct by putting the test.json referenced in my original post into the root of my system, and sure enough python found the json. This is strange behavior. Would any of the API developers elaborate on why this happens?

0 Likes
Message 6 of 8

Anonymous
Not applicable

This is helpful! I didn't know about this feature. Thanks Ross. Also, I'd very much like to see your wrapper. 

0 Likes
Message 7 of 8

KrisKaplan
Autodesk
Autodesk

I do not think you will find the current working directory to be useful.  os.getcwd() returns the current working directory value for the current process.  When you launch a script in the terminal, you will normally see the cwd for the pyhon shell process as the same folder as the script.  But this depends on exactly how you start the python process, so it will not always be the script folder.

 

In the case of a python script running in Fusion's embedded python runtime, the current process is the Fusion process.  So getting the cwd in a script in Fusion will just be getting the cwd for the Fusion process, and this depends on exactly how the Fusion process was started.  On Windows, this would normally be the folder containing the fusion360.exe (in the webdeploy folder).  On a Mac, this is often the root folder.  But in either case, this path has nothing to do with the location of your specific script.

 

The best value to use is the '__file__' value that is set on the module's dictionary that is hosting your script.

 

Kris



Kris Kaplan
0 Likes
Message 8 of 8

Anonymous
Not applicable

@Anonymous wrote:

This is helpful! I didn't know about this feature. Thanks Ross. Also, I'd very much like to see your wrapper. 


 

Sure, first the code - then some info on how to use it 😉

 

 

 

class Settings(object):
  def __init__(self, setting_group_name):
    self.__group_name = setting_group_name
    self.__tracked_inputs = dict()

  @property
  def group_name(self):
    assert self.__group_name, 'Settings must have a group name.'
    return self.__group_name

  @property
  def _attributes_collection(self):
    return adsk.fusion.Design.cast(adsk.core.Application.get().activeProduct).attributes

  def _set_fqn(self, input):
    ipt = input
    fqn = ipt.id
    while ipt.parentCommandInput:
      ipt = ipt.parentCommandInput
      fqn = ipt.id + '^' + fqn
    input.fqn = fqn
    return fqn

  def save(self):
    catch_me = dict()
    for k, v in self.__dict__.items():
      if not re.match( r'^_Settings__.+', k ):
        catch_me[k] = v
    json_value = json.dumps(catch_me, sort_keys=True)
    # adsk.core.Application.get().userInterface.messageBox('Preserving stored values\n' + json_value)
    self._attributes_collection.add(self.group_name, 'settings', json_value)

  def load(self):
    settings_attr = self._attributes_collection.itemByName(self.group_name, 'settings')
    if settings_attr:
      json_value = settings_attr.value
      # adsk.core.Application.get().userInterface.messageBox('Loaded stored values\n' + json_value)
      loaded = json.loads( json_value )
      for k, v in loaded.items():
        self.__dict__[k] = v

  def track(self, input):
    self._set_fqn(input)
    assert input.fqn not in self.__tracked_inputs, 'Duplicate input ID: ' + input.fqn
    self.__tracked_inputs[input.fqn] = input
    if input.fqn in self.__dict__:
      self._restore(input)
    else:
      self._capture(input)
    return input

  def _restore(self, input):
    class_type = input.classType()
    if hasattr(input, 'expression'):
      input.expression = self.__dict__[input.fqn]
    elif hasattr(input, 'value'):
      input.value = self.__dict__[input.fqn]
    elif hasattr(input, 'listItems'):
      selected = self.__dict__[input.fqn]
      for _item in input.listItems:
        item = adsk.core.ListItem.cast(_item)
        item.isSelected = item.name in selected
    elif hasattr(input, 'formattedText'):
      input.formattedText = self.__dict__[input.fqn]
    elif class_type == 'adsk::core::GroupCommandInput':
      d = self.__dict__[input.fqn]
      input.isExpanded = d['is_expanded']
      input.isEnabledCheckBoxDisplayed = d['is_enabled_checkbox_displayed']
      input.isEnabled = d['is_enabled']
    elif class_type == 'adsk::core::TabCommandInput':
      d = self.__dict__[input.fqn]
      input.isVisable = d['is_visable']
      input.isEnabled = d['is_enabled']
      if d['is_active']:
        input.activate()
    else:
      assert False, 'I dont know how to restore ' + input.fqn + ' it\'s a ' + input.classType()
    return input

  def _capture(self, input):
    class_type = input.classType()
    if hasattr(input, 'expression'):
      self.__dict__[input.fqn] = input.expression
    elif hasattr(input, 'value'):
      self.__dict__[input.fqn] = input.value
    elif hasattr(input, 'listItems'):
      selected = []
      for _item in input.listItems:
        item = adsk.core.ListItem.cast(_item)
        if item.isSelected:
          selected.append(item.name)
      self.__dict__[input.fqn] = selected
    elif hasattr(input, 'formattedText'):
      self.__dict__[input.fqn] = input.formattedText
    elif class_type == 'adsk::core::GroupCommandInput':
      self.__dict__[input.fqn] = {
        'is_expanded': input.isExpanded,
        'is_enabled_checkbox_displayed': input.isEnabledCheckBoxDisplayed,
        'is_enabled': input.isEnabled
      }
    elif class_type == 'adsk::core::TabCommandInput':
      self.__dict__[input.fqn] = {
        'is_active': input.isActive,
        'is_visable': input.isVisable,
        'is_enabled': input.isEnabled
      }
    else:
      assert False, 'I dont know how to capture ' + input.fqn + ' it\'s a ' + input.classType()

    try:
      json.dumps( self.__dict__[input.fqn] )
    except TypeError:
      err = input.fqn + ' is not being properly captured'
      if hasattr(input, 'classType'):
        err += '\n  Class Type: ' + input.classType()
      else:
        err += '\n  Type: ' + type(input).__name__

      if hasattr(input, 'expression'):
        err += '\n  expression: ' + input.expression
      if hasattr(input, 'value'):
        err += '\n  value: ' + str(input.value)
      if hasattr(input, 'listItems'):
        err += '\n  listItems: ' + str(input.listItems.count)
      if hasattr(input, 'formattedText'):
        err += '\n  formattedText: ' + input.formattedText

      adsk.core.Application.get().userInterface.messageBox(err)
    return input

  def restore_all(self):
    """Restores tracked input field values from the values held by this settings object."""
    for _, i in self.__tracked_inputs.items():
      self._restore(i)

  def capture_all(self):
    """Captures all tracked input field values into this settings object."""
    for _, i in self.__tracked_inputs.items():
      self._capture(i)

 

First, make an instance of this class and ensure it is appropriately scoped (e.g. make it global like your handlers)

 

 

# The argument can be any string, but it should be unique to your add-in
settings = Settings('my_command_id')
# load any saved settings
settings.load()

# next make your inputs however you usually do input = commandInputs.addStringValueInput(...) # tell settings to track this input settings.track(input) # repeat as necessary # Finally in your execute (or possibly preview) callbacks settings.capture_all() settings.save()

 

Additionally you could use settings.restore_all() to restore the input values to the last saved state - perhaps via some "reset" button.

 

This Settings class does some magical stuff - for example the act of "tracking" an input will restore any saved value magically.

 

Let me know if you are able to make use of this class - or if it's "too much" 😉

 

 

 

0 Likes