Skip to content

Commit

Permalink
Exporter Packaging and Installer [AARD-1742] (#1073)
Browse files Browse the repository at this point in the history
  • Loading branch information
HunterBarclay authored Aug 21, 2024
2 parents 9510b56 + 1ab3a28 commit 1473f05
Show file tree
Hide file tree
Showing 20 changed files with 319 additions and 34 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.vs/
.vscode/
/build/
build/
dist/
*.log
.DS_Store
*.pkg
*.exe
2 changes: 1 addition & 1 deletion exporter/SynthesisFusionAddin/Synthesis.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"description": {
"": "Synthesis Exporter"
},
"version": "1.0.0",
"version": "2.0.0",
"runOnStartup": true,
"supportedOS": "windows|mac",
"editEnabled": true
Expand Down
23 changes: 13 additions & 10 deletions exporter/SynthesisFusionAddin/src/Logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

import adsk.core

from src import INTERNAL_ID
from src.UI.OsHelper import getOSPath
from src import INTERNAL_ID, IS_RELEASE, SUPPORT_PATH
from src.Util import makeDirectories

MAX_LOG_FILES_TO_KEEP = 10
TIMING_LEVEL = 25
Expand All @@ -30,14 +30,17 @@ def cleanupHandlers(self) -> None:
def setupLogger() -> SynthesisLogger:
now = datetime.now().strftime("%H-%M-%S")
today = date.today()
logFileFolder = getOSPath(f"{pathlib.Path(__file__).parent.parent}", "logs")
logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")]
logFiles.sort()
if len(logFiles) >= MAX_LOG_FILES_TO_KEEP:
for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]:
os.remove(file)

logFileName = f"{logFileFolder}{getOSPath(f'{INTERNAL_ID}-{today}-{now}.log')}"
if IS_RELEASE:
logFileFolder = makeDirectories(os.path.join(SUPPORT_PATH, "Logs"))
else:
logFileFolder = makeDirectories(os.path.join(f"{pathlib.Path(__file__).parent.parent}", "logs"))
logFiles = [os.path.join(logFileFolder, file) for file in os.listdir(logFileFolder) if file.endswith(".log")]
logFiles.sort()
if len(logFiles) >= MAX_LOG_FILES_TO_KEEP:
for file in logFiles[: len(logFiles) - MAX_LOG_FILES_TO_KEEP]:
os.remove(file)

logFileName = os.path.join(logFileFolder, f"{today}-{now}.log")
logHandler = logging.handlers.WatchedFileHandler(logFileName, mode="w")
logHandler.setFormatter(logging.Formatter("%(name)s - %(levelname)s - %(message)s"))

Expand Down
4 changes: 3 additions & 1 deletion exporter/SynthesisFusionAddin/src/Types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import platform
from dataclasses import dataclass, field, fields, is_dataclass
from enum import Enum, EnumType
from typing import Union, get_origin
from typing import Literal, TypeAlias, Union, get_origin

OperatingSystemString: TypeAlias = Literal["Windows", "Darwin", "Linux"]

# Not 100% sure what this is for - Brandon
JointParentType = Enum("JointParentType", ["ROOT", "END"])
Expand Down
9 changes: 6 additions & 3 deletions exporter/SynthesisFusionAddin/src/UI/Camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import adsk.core

from src import SUPPORT_PATH
from src.Logging import logFailure
from src.Types import OString
from src.Util import makeDirectories


@logFailure
Expand All @@ -21,9 +23,10 @@ def captureThumbnail(size=250):
) # remove whitespace from just the filename
)

path = OString.ThumbnailPath(name)
path = makeDirectories(f"{SUPPORT_PATH}/Resources/Icons/")
path += name

saveOptions = adsk.core.SaveImageFileOptions.create(str(path.getPath()))
saveOptions = adsk.core.SaveImageFileOptions.create(path)
saveOptions.height = size
saveOptions.width = size
saveOptions.isAntiAliased = True
Expand All @@ -36,7 +39,7 @@ def captureThumbnail(size=250):
app.activeViewport.saveAsImageFileWithOptions(saveOptions)
app.activeViewport.camera = originalCamera

return str(path.getPath())
return path


def clearIconCache() -> None:
Expand Down
10 changes: 7 additions & 3 deletions exporter/SynthesisFusionAddin/src/UI/ConfigCommand.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,13 @@ def notify(self, args):
# save was canceled
return

updatedPath = pathlib.Path(savepath).parent
if updatedPath != self.current.filePath:
self.current.filePath = str(updatedPath)
# Transition: AARD-1742
# With the addition of a 'release' build the fusion exporter will not have permissions within the sourced
# folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and
# should be removed.
# updatedPath = pathlib.Path(savepath).parent
# if updatedPath != self.current.filePath:
# self.current.filePath = str(updatedPath)
else:
savepath = processedFileName

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ class SerialCommand:
def __init__(self):
self.general = General()
self.advanced = Advanced()
self.filePath = generateFilePath()

# Transition: AARD-1742
# With the addition of a 'release' build the fusion exporter will not have permissions within the sourced
# folder. Because of this we cannot use this kind of tmp path anymore. This code was already unused and
# should be removed.
# self.filePath = generateFilePath()

def toJSON(self) -> str:
"""Converts this class into a json object that can be written to the object data
Expand Down
7 changes: 7 additions & 0 deletions exporter/SynthesisFusionAddin/src/Util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import os


def makeDirectories(directory: str | os.PathLike[str]) -> str | os.PathLike[str]:
"""Ensures than an input directory exists and attempts to create it if it doesn't."""
os.makedirs(directory, exist_ok=True)
return directory
24 changes: 22 additions & 2 deletions exporter/SynthesisFusionAddin/src/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
import os
import platform
from pathlib import Path

from src.GlobalManager import GlobalManager
from src.Types import OperatingSystemString
from src.Util import makeDirectories

APP_NAME = "Synthesis"
APP_TITLE = "Synthesis Robot Exporter"
DESCRIPTION = "Exports files from Fusion into the Synthesis Format"
INTERNAL_ID = "Synthesis"
ADDIN_PATH = os.path.dirname(os.path.realpath(__file__))
IS_RELEASE = str(Path(os.path.abspath(__file__)).parent.parent.parent.parent).split(os.sep)[-1] == "ApplicationPlugins"

SYSTEM = platform.system()
SYSTEM: OperatingSystemString = platform.system()
assert SYSTEM != "Linux"

if SYSTEM == "Windows":
SUPPORT_PATH = makeDirectories(os.path.expandvars(r"%appdata%\Autodesk\Synthesis"))
else:
assert SYSTEM == "Darwin"
SUPPORT_PATH = makeDirectories(f"{os.path.expanduser('~')}/.config/Autodesk/Synthesis")

gm = GlobalManager()

__all__ = ["APP_NAME", "APP_TITLE", "DESCRIPTION", "INTERNAL_ID", "ADDIN_PATH", "SYSTEM", "gm"]
__all__ = [
"APP_NAME",
"APP_TITLE",
"DESCRIPTION",
"INTERNAL_ID",
"ADDIN_PATH",
"IS_RELEASE",
"SYSTEM",
"SUPPORT_PATH",
"gm",
]
22 changes: 16 additions & 6 deletions installer/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
# Installing the Synthesis Fusion Exporter
# Installers

## Manual Install
We recently transitioned to a platform independent, web-based application. As such, we no longer maintain a installer for the core simulator. We do, however, still have one for our Fusion Exporter.

## Installing the Synthesis Fusion Exporter

### Using an Installer

- Visit [synthesis.autodesk.com/download](https://synthesis.autodesk.com/download.html) and select the installer for your operating system.
- Note that there is no installer for Linux since Fusion is only supported on Windows and Mac.
- Once you have downloaded the installer for your operating system (`.exe` for Windows and `.pkg` for Mac) go ahead and run the executable.
- Since we do not code sign our installers (as interns of Autodesk we have very little control over this) you may get a warning from your operating system.
- For Mac to get around this see [this](https://support.apple.com/en-tm/guide/mac-help/mh40616/mac) guide for more information.
- If you are at all concerned that we are doing something nefarious please feel free to [install the exporter manually.](#manual-install)
- Alternatively, you can even inspect how we build our installers [here](./exporter/) and build them yourself.

### <a name="manual-install"></a> Manual Install

- Navigate to [`synthesis.autodesk.com/download`](https://synthesis.autodesk.com/download.html).
- Find the Exporter source code zip download.
Expand All @@ -25,7 +39,3 @@ under the `Utilities` tab.
![image_caption](../tutorials/img/fusion/fusion-utilities-with-synthesis.png)

Thanks for installing the Synthesis Fusion Exporter! For any additional help visit our [Synthesis Community Discord Server](https://www.discord.gg/hHcF9AVgZA) where you can talk directly to our developers.

## Using an Installer

Our automatic installer is still in development, visit the [Synthesis Discord Server](https://www.discord.gg/hHcF9AVgZA) for updates and any manual installing help.
7 changes: 7 additions & 0 deletions installer/exporter/OSX/Scripts/preinstall
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/ApplicationPlugins/

if [ ! -d "$FUSION_ADDIN_LOCATION" ]; then
mkdir -p "$FUSION_ADDIN_LOCATION"
fi
15 changes: 15 additions & 0 deletions installer/exporter/OSX/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

FUSION_ADDIN_LOCATION=~/Library/Application\ Support/Autodesk/ApplicationPlugins/
EXPORTER_SOURCE_DIR=../../../exporter/SynthesisFusionAddin/

mkdir -p tmp/
cp -r synthesis.bundle tmp/
rsync -av ../synthesis.bundle tmp/
cp -r "$EXPORTER_SOURCE_DIR"/* tmp/synthesis.bundle/Contents/

pkgbuild --root tmp/ --identifier com.Autodesk.Synthesis --scripts Scripts/ --version 2.0.0 --install-location "$FUSION_ADDIN_LOCATION" SynthesisExporter.pkg
productbuild --distribution distribution.xml --package-path . SynthesisExporterInstaller.pkg

rm SynthesisExporter.pkg
rm -r tmp/
14 changes: 14 additions & 0 deletions installer/exporter/OSX/distribution.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<installer-gui-script minSpecVersion="1">
<title>Synthesis Exporter Installer</title>
<choices-outline>
<line choice="default">
<line choice="install"/>
</line>
</choices-outline>
<choice id="default"/>
<choice id="install" title="Install MyApp" description="Installs MyApp">
<pkg-ref id="com.Autodesk.Synthesis"/>
</choice>
<pkg-ref id="com.Autodesk.Synthesis" version="2.0.0" auth="root">#SynthesisExporter.pkg</pkg-ref>
</installer-gui-script>
46 changes: 46 additions & 0 deletions installer/exporter/OSX/synthesis.bundle/Contents/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleGetInfoString</key>
<string>Synthesis addin for Fusion.</string>
<key>CFBundleIdentifier</key>
<string>Synthesis Exporter for Autodesk Fusion 360</string>
<key>CFBundleShortVersionString</key>
<string>2.0.0</string>
<key>IFMajorVersion</key>
<integer>1</integer>
<key>IFMinorVersion</key>
<integer>0</integer>
<key>IFPkgFlagAllowBackRev</key>
<false/>
<key>IFPkgFlagAuthorizationAction</key>
<string>AdminAuthorization</string>
<key>IFPkgFlagBackgroundAlignment</key>
<string>topleft</string>
<key>IFPkgFlagBackgroundScaling</key>
<string>none</string>
<key>IFPkgFlagDefaultLocation</key>
<string>/</string>
<key>IFPkgFlagFollowLinks</key>
<true/>
<key>IFPkgFlagInstallFat</key>
<false/>
<key>IFPkgFlagIsRequired</key>
<false/>
<key>IFPkgFlagOverwritePermissions</key>
<false/>
<key>IFPkgFlagRelocatable</key>
<false/>
<key>IFPkgFlagRestartAction</key>
<string>NoRestart</string>
<key>IFPkgFlagRootVolumeOnly</key>
<false/>
<key>IFPkgFlagUpdateInstalledLanguages</key>
<false/>
<key>IFPkgFormatVersion</key>
<real>0.1000000014901161</real>
</dict>
</plist>
29 changes: 29 additions & 0 deletions installer/exporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Synthesis Exporter Installers

This `readme` is for developers of Synthesis or those looking to build the installers themselves, if you are just looking for how to install our Fusion Exporter please navigate to [`/installer/`](../).

## Windows

The windows installer has the following prerequisites:
- Python `3.9` or newer and pip.
- And that's it!

### To Build:

Once you have verified that python and pip are installed on your computer:
- Open a powershell window and navigate to [`/installer/exporter/Windows/`]
- Run `./build.bat` in powershell.
- After some time you should see `installer.exe` in your current directory.
- And that's it! You have now built the Synthesis Exporter Installer for Windows!
- You can then run the `.exe` from file explorer or alternatively, for debugging purposes, run `./installer.exe` from the terminal.

## MacOS

The Mac installer has zero prerequisites. Hooray!

### To Build:

- Navigate to [`/installer/exporter/OSX/`](./OSX/).
- Run `./build.sh` in your terminal.
- You should then find `SynthesisExporterInstaller.pkg` in your current directory.
- And that's it! You now have built the Synthesis Exporter Installer for MacOS!
37 changes: 37 additions & 0 deletions installer/exporter/Windows/build.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@echo off
setlocal enabledelayedexpansion
setlocal

python -m pip install -r requirements.txt --user

set "EXPORTER_SOURCE_DIR=..\..\..\exporter\SynthesisFusionAddin\"

mkdir tmp\synthesis.bundle\Contents\
xcopy ..\synthesis.bundle .\tmp\synthesis.bundle
xcopy /e /i "%EXPORTER_SOURCE_DIR%" tmp\synthesis.bundle\Contents
tar -a -c -f SynthesisExporter.zip -C tmp synthesis.bundle\*

@REM Find and run pyinstaller, this is a workaround that allows you to call pip packages as scripts without
@REM them being added to the system PATH.
for /f "delims=" %%i in ('pip show pyinstaller') do (
echo %%i | findstr /b /c:"Location:" >nul
if not errorlevel 1 (
set "location_line=%%i"
)
)

set "executable=!location_line:Location: =!"
for %%a in ("%executable%") do set "executable=%%~dpa"
set "executable=%executable%Scripts\pyinstaller.exe "
set executable=%executable:~0,-1%

%executable% --onefile --add-data "SynthesisExporter.zip;." installer.py

move .\dist\installer.exe .
rmdir /s /q tmp
rmdir /s /q build
rmdir /s /q dist
del SynthesisExporter.zip
del installer.spec

endlocal
Loading

0 comments on commit 1473f05

Please sign in to comment.