mirror of
https://github.com/ApfelTeeSaft/lightspeed64.git
synced 2026-08-26 19:33:24 +00:00
Make update UI more understandable (#446)
* Make update UI more understandable Don't box the ui as the preferences are already part of a box (looks disgusting in 4.2) Update to stable now on the same column as update to main to make them seem both equally useful Change text for main/old to be more obviously important, from "Install main / old version" to "Install specific version (Latest or Older)" Move the restore operator into another row Updated readme with new instructions and screenshots * Remove mat screenshot and link to updater instructions early into the readme * Update redundant text * merge request button * Update README.md Co-authored-by: Dragorn421 <[email protected]> * change to gross github name only used by github and no one else Co-authored-by: Dragorn421 <[email protected]> * use to be added updater in fast64docs * mention ability to test prs --------- Co-authored-by: Dragorn421 <[email protected]>
This commit is contained in:
@@ -8,13 +8,11 @@ Forked from [kurethedead/fast64 on BitBucket](https://bitbucket.org/kurethedead/
|
||||
|
||||
This is a Blender plugin that allows one to export F3D display lists. It also has the ability to export assets for Super Mario 64 and Ocarina of Time decompilation projects. It supports custom color combiners / geometry modes / etc. It is also possible to use exported C code in homebrew applications.
|
||||
|
||||
Make sure to save often, as this plugin is prone to crashing when creating materials / undoing material creation. This is a Blender issue.
|
||||
|
||||
<https://developer.blender.org/T70574>
|
||||
Make sure to save often, as this plugin is prone to crashing when creating materials / undoing material creation. [This is a Blender issue](https://developer.blender.org/T70574).
|
||||
|
||||
### Example models can be found [here](https://github.com/Fast-64/fast64-models)
|
||||
|
||||

|
||||
Fast64 features an updater (which can also be used to try out in testing features and bugfixes), [follow these instructions to use it](https://fast64.readthedocs.io/en/latest/common/updater/updater.html)
|
||||
|
||||
### Credits
|
||||
Thanks to anonymous_moose, Cheezepin, Rovert, and especially InTheBeef for testing.
|
||||
@@ -63,28 +61,6 @@ Selecting F3DEX3 as your microcode unlocks a large number of additional presets
|
||||
|
||||
For cel shading, it is recommended to start with one of the cel shading presets, then modify the settings under the `Use Cel Shading` panel. Hover over each UI control for additional information about how that setting works.
|
||||
|
||||
### Updater
|
||||
|
||||
Fast64 features an updater ([CGCookie/blender-addon-updater](https://github.com/CGCookie/blender-addon-updater)).
|
||||
|
||||
It can be found in the addon preferences:
|
||||
|
||||

|
||||
|
||||
Click the "Check now for fast64 update" button to check for updates.
|
||||
|
||||

|
||||
|
||||
Click "Install main / old version" and choose "Main" if it isn't already selected:
|
||||
|
||||

|
||||
|
||||
Click OK, there should be a message "Addon successfully installed" and prompting you to restart Blender:
|
||||
|
||||

|
||||
|
||||
Clicking the red button will close Blender. After restarting, fast64 will be up-to-date with the latest main revision.
|
||||
|
||||
### Fast64 Development
|
||||
If you'd like to develop in VSCode, follow this tutorial to get proper autocomplete. Skip the linter for now, we'll need to make sure the entire project gets linted before enabling autosave linting because the changes will be massive.
|
||||
https://b3d.interplanety.org/en/using-microsoft-visual-studio-code-as-external-ide-for-writing-blender-scripts-add-ons/
|
||||
|
||||
+88
-3
@@ -68,6 +68,8 @@ class SingletonUpdater:
|
||||
self._latest_release = None
|
||||
self._use_releases = False
|
||||
self._include_branches = False
|
||||
self._include_merge_requests = False
|
||||
self._merge_requests = list()
|
||||
self._include_branch_list = ['master']
|
||||
self._include_branch_auto_check = False
|
||||
self._manual_only = False
|
||||
@@ -304,6 +306,17 @@ class SingletonUpdater:
|
||||
except:
|
||||
raise ValueError("include_branches must be a boolean value")
|
||||
|
||||
@property
|
||||
def include_merge_requests(self):
|
||||
return self._include_merge_requests
|
||||
|
||||
@include_merge_requests.setter
|
||||
def include_merge_requests(self, value):
|
||||
try:
|
||||
self._include_merge_requests = bool(value)
|
||||
except:
|
||||
raise ValueError("include_branches must be a boolean value")
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
if len(self._json) == 0:
|
||||
@@ -415,6 +428,13 @@ class SingletonUpdater:
|
||||
self._subfolder_path = value
|
||||
|
||||
@property
|
||||
def merge_requests(self):
|
||||
if len(self._merge_requests) == 0:
|
||||
return {}
|
||||
return {
|
||||
mr["id"]: mr["title"] for mr in self._merge_requests
|
||||
}
|
||||
@property
|
||||
def tags(self):
|
||||
if len(self._tags) == 0:
|
||||
return list()
|
||||
@@ -590,9 +610,25 @@ class SingletonUpdater:
|
||||
|
||||
def form_tags_url(self):
|
||||
return self._engine.form_tags_url(self)
|
||||
|
||||
def form_mrs_url(self):
|
||||
return self._engine.form_mrs_url(self)
|
||||
|
||||
def form_branch_url(self, branch):
|
||||
return self._engine.form_branch_url(branch, self)
|
||||
|
||||
def form_mr_url(self, mr):
|
||||
return self._engine.form_mr_url(mr, self)
|
||||
|
||||
def get_mrs(self):
|
||||
if self._include_merge_requests is False:
|
||||
return
|
||||
request = self.form_mrs_url()
|
||||
self.print_verbose("Getting merge requests from server")
|
||||
|
||||
# get all merge requests, internet call
|
||||
all_mrs = self._engine.parse_mrs(self.get_api(request), self) or []
|
||||
self._merge_requests = [mr for mr in all_mrs if mr["state"] == "open"]
|
||||
|
||||
def get_tags(self):
|
||||
request = self.form_tags_url()
|
||||
@@ -1274,8 +1310,9 @@ class SingletonUpdater:
|
||||
self._update_version,
|
||||
self._update_link)
|
||||
|
||||
# Primary internet call, sets self._tags and self._tag_latest.
|
||||
# Primary internet call, sets self._tags, self._merge_requests and self._tag_latest.
|
||||
self.get_tags()
|
||||
self.get_mrs()
|
||||
|
||||
self._json["last_check"] = str(datetime.now())
|
||||
self.save_updater_json()
|
||||
@@ -1357,8 +1394,21 @@ class SingletonUpdater:
|
||||
self._update_link = link
|
||||
if not tg:
|
||||
raise ValueError("Version tag not found: " + name)
|
||||
|
||||
def set_mr(self, id: str):
|
||||
"""Assign the merge request name and url to update to"""
|
||||
mr = None
|
||||
for merge_request in self._merge_requests:
|
||||
if id == str(merge_request["id"]):
|
||||
mr = merge_request
|
||||
break
|
||||
if mr:
|
||||
self._update_link = self.form_mr_url(mr)
|
||||
self._update_version = mr["id"]
|
||||
else:
|
||||
raise ValueError("Merge request not found: " + id)
|
||||
|
||||
def run_update(self, force=False, revert_tag=None, clean=False, callback=None):
|
||||
def run_update(self, force=False, revert_tag=None, merge_request=False, clean=False, callback=None):
|
||||
"""Runs an install, update, or reversion of an addon from online source
|
||||
|
||||
Arguments:
|
||||
@@ -1372,7 +1422,10 @@ class SingletonUpdater:
|
||||
self._json["version_text"] = dict()
|
||||
|
||||
if revert_tag is not None:
|
||||
self.set_tag(revert_tag)
|
||||
if merge_request:
|
||||
self.set_mr(revert_tag)
|
||||
else:
|
||||
self.set_tag(revert_tag)
|
||||
self._update_ready = True
|
||||
|
||||
# clear the errors if any
|
||||
@@ -1643,9 +1696,15 @@ class BitbucketEngine:
|
||||
|
||||
def form_tags_url(self, updater):
|
||||
return self.form_repo_url(updater) + "/refs/tags?sort=-name"
|
||||
|
||||
def form_mrs_url(self, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
def form_branch_url(self, branch, updater):
|
||||
return self.get_zip_url(branch, updater)
|
||||
|
||||
def form_mr_url(self, mr, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_zip_url(self, name, updater):
|
||||
return "https://bitbucket.org/{user}/{repo}/get/{name}.zip".format(
|
||||
@@ -1661,6 +1720,9 @@ class BitbucketEngine:
|
||||
"name": tag["name"],
|
||||
"zipball_url": self.get_zip_url(tag["name"], updater)
|
||||
} for tag in response["values"]]
|
||||
|
||||
def parse_mrs(self, response, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GithubEngine:
|
||||
@@ -1680,17 +1742,31 @@ class GithubEngine:
|
||||
return "{}/releases".format(self.form_repo_url(updater))
|
||||
else:
|
||||
return "{}/tags".format(self.form_repo_url(updater))
|
||||
|
||||
def form_mrs_url(self, updater):
|
||||
return "{}/pulls".format(self.form_repo_url(updater))
|
||||
|
||||
def form_branch_list_url(self, updater):
|
||||
return "{}/branches".format(self.form_repo_url(updater))
|
||||
|
||||
def form_branch_url(self, branch, updater):
|
||||
return "{}/zipball/{}".format(self.form_repo_url(updater), branch)
|
||||
|
||||
def form_mr_url(self, mr, updater):
|
||||
head = mr["head"]
|
||||
branch_name = head["ref"]
|
||||
repo = head["repo"]["url"]
|
||||
return "{}/zipball/{}".format(repo, branch_name)
|
||||
|
||||
def parse_tags(self, response, updater):
|
||||
if response is None:
|
||||
return list()
|
||||
return response
|
||||
|
||||
def parse_mrs(self, response, updater):
|
||||
if response is None:
|
||||
return list()
|
||||
return response
|
||||
|
||||
|
||||
class GitlabEngine:
|
||||
@@ -1706,6 +1782,9 @@ class GitlabEngine:
|
||||
|
||||
def form_tags_url(self, updater):
|
||||
return "{}/repository/tags".format(self.form_repo_url(updater))
|
||||
|
||||
def form_mrs_url(self, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
def form_branch_list_url(self, updater):
|
||||
# does not validate branch name.
|
||||
@@ -1717,6 +1796,9 @@ class GitlabEngine:
|
||||
# instead of branch zip to get direct path, would need.
|
||||
return "{}/repository/archive.zip?sha={}".format(
|
||||
self.form_repo_url(updater), branch)
|
||||
|
||||
def form_mr_url(self, mr, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_zip_url(self, sha, updater):
|
||||
return "{base}/repository/archive.zip?sha={sha}".format(
|
||||
@@ -1734,6 +1816,9 @@ class GitlabEngine:
|
||||
"name": tag["name"],
|
||||
"zipball_url": self.get_zip_url(tag["commit"]["id"], updater)
|
||||
} for tag in response]
|
||||
|
||||
def parse_mrs(self, response, updater):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
+105
-37
@@ -58,7 +58,7 @@ except Exception as e:
|
||||
self.error_msg = None
|
||||
self.async_checking = None
|
||||
|
||||
def run_update(self, force, callback, clean):
|
||||
def run_update(self, force, callback, merge_request, clean):
|
||||
pass
|
||||
|
||||
def check_for_update(self, now):
|
||||
@@ -340,14 +340,8 @@ class AddonUpdaterUpdateTarget(bpy.types.Operator):
|
||||
def target_version(self, context):
|
||||
# In case of error importing updater.
|
||||
if updater.invalid_updater:
|
||||
ret = []
|
||||
|
||||
ret = []
|
||||
i = 0
|
||||
for tag in updater.tags:
|
||||
ret.append((tag, tag, "Select to install " + tag))
|
||||
i += 1
|
||||
return ret
|
||||
return []
|
||||
return [(tag, tag, "Select to install " + tag) for tag in updater.tags]
|
||||
|
||||
target = bpy.props.EnumProperty(
|
||||
name="Target version to install",
|
||||
@@ -407,6 +401,78 @@ class AddonUpdaterUpdateTarget(bpy.types.Operator):
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class AddonUpdaterTryMR(bpy.types.Operator):
|
||||
bl_label = updater.addon + " merge requests"
|
||||
bl_idname = updater.addon + ".updater_try_merge_request"
|
||||
bl_description = "Install a merge request of the {x} addon".format(
|
||||
x=updater.addon)
|
||||
bl_options = {'REGISTER', 'INTERNAL'}
|
||||
|
||||
def target_version(self, context):
|
||||
# In case of error importing updater.
|
||||
if updater.invalid_updater:
|
||||
return []
|
||||
|
||||
return [(str(id), title, "Select to install " + title) for id, title in updater.merge_requests.items()]
|
||||
|
||||
target = bpy.props.EnumProperty(
|
||||
name="Target version to install",
|
||||
description="Select the version to install",
|
||||
items=target_version
|
||||
)
|
||||
|
||||
# If true, run clean install - ie remove all files before adding new
|
||||
# equivalent to deleting the addon and reinstalling, except the
|
||||
# updater folder/backup folder remains.
|
||||
clean_install = bpy.props.BoolProperty(
|
||||
name="Clean install",
|
||||
description=("If enabled, completely clear the addon's folder before "
|
||||
"installing new update, creating a fresh install"),
|
||||
default=False,
|
||||
options={'HIDDEN'}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if updater.invalid_updater:
|
||||
return False
|
||||
return updater.update_ready is not None and len(updater.merge_requests) > 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
if updater.invalid_updater:
|
||||
layout.label(text="Updater error")
|
||||
return
|
||||
split = layout_split(layout, factor=0.5)
|
||||
sub_col = split.column()
|
||||
sub_col.label(text="Select install version")
|
||||
sub_col = split.column()
|
||||
sub_col.prop(self, "target", text="")
|
||||
|
||||
def execute(self, context):
|
||||
# In case of error importing updater.
|
||||
if updater.invalid_updater:
|
||||
return {'CANCELLED'}
|
||||
|
||||
res = updater.run_update(
|
||||
force=False,
|
||||
revert_tag=self.target,
|
||||
merge_request=True,
|
||||
callback=post_update_callback,
|
||||
clean=self.clean_install)
|
||||
|
||||
# Should return 0, if not something happened.
|
||||
if res == 0:
|
||||
updater.print_verbose("Updater returned successful")
|
||||
else:
|
||||
updater.print_verbose(
|
||||
"Updater returned {}, , error occurred".format(res))
|
||||
return {'CANCELLED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class AddonUpdaterInstallManually(bpy.types.Operator):
|
||||
"""As a fallback, direct the user to download the addon manually"""
|
||||
@@ -950,21 +1016,20 @@ def update_settings_ui(self, context, element=None):
|
||||
# Element is a UI element, such as layout, a row, column, or box.
|
||||
if element is None:
|
||||
element = self.layout
|
||||
box = element.box()
|
||||
|
||||
# In case of error importing updater.
|
||||
if updater.invalid_updater:
|
||||
box.label(text="Error initializing updater code:")
|
||||
box.label(text=updater.error_msg)
|
||||
element.label(text="Error initializing updater code:")
|
||||
element.label(text=updater.error_msg)
|
||||
return
|
||||
settings = get_user_preferences(context)
|
||||
if not settings:
|
||||
box.label(text="Error getting updater preferences", icon='ERROR')
|
||||
element.label(text="Error getting updater preferences", icon='ERROR')
|
||||
return
|
||||
|
||||
# auto-update settings
|
||||
box.label(text="Updater Settings")
|
||||
row = box.row()
|
||||
element.label(text="Updater Settings")
|
||||
row = element.row()
|
||||
|
||||
# special case to tell user to restart blender, if set that way
|
||||
if not updater.auto_reload_post_update:
|
||||
@@ -976,7 +1041,7 @@ def update_settings_ui(self, context, element=None):
|
||||
icon="ERROR")
|
||||
return
|
||||
|
||||
split = layout_split(row, factor=0.4)
|
||||
split = layout_split(row, factor=0.5)
|
||||
sub_col = split.column()
|
||||
sub_col.prop(settings, "auto_check_update")
|
||||
sub_col = split.column()
|
||||
@@ -998,7 +1063,7 @@ def update_settings_ui(self, context, element=None):
|
||||
# check_col.prop(settings,"updater_interval_minutes")
|
||||
|
||||
# Checking / managing updates.
|
||||
row = box.row()
|
||||
row = element.row()
|
||||
col = row.column()
|
||||
if updater.error is not None:
|
||||
sub_col = col.row(align=True)
|
||||
@@ -1018,7 +1083,7 @@ def update_settings_ui(self, context, element=None):
|
||||
split.operator(AddonUpdaterCheckNow.bl_idname,
|
||||
text="", icon="FILE_REFRESH")
|
||||
|
||||
elif updater.update_ready is None and not updater.async_checking:
|
||||
elif not updater.async_checking:
|
||||
col.scale_y = 2
|
||||
col.operator(AddonUpdaterCheckNow.bl_idname)
|
||||
elif updater.update_ready is None: # async is running
|
||||
@@ -1048,25 +1113,13 @@ def update_settings_ui(self, context, element=None):
|
||||
split.operator(AddonUpdaterCheckNow.bl_idname,
|
||||
text="", icon="FILE_REFRESH")
|
||||
|
||||
elif updater.update_ready and not updater.manual_only:
|
||||
sub_col = col.row(align=True)
|
||||
sub_col.scale_y = 1
|
||||
split = sub_col.split(align=True)
|
||||
split.scale_y = 2
|
||||
split.operator(AddonUpdaterUpdateNow.bl_idname,
|
||||
text="Update now to " + str(updater.update_version))
|
||||
split = sub_col.split(align=True)
|
||||
split.scale_y = 2
|
||||
split.operator(AddonUpdaterCheckNow.bl_idname,
|
||||
text="", icon="FILE_REFRESH")
|
||||
|
||||
elif updater.update_ready and updater.manual_only:
|
||||
col.scale_y = 2
|
||||
dl_now_txt = "Download " + str(updater.update_version)
|
||||
col.operator("wm.url_open",
|
||||
text=dl_now_txt).url = updater.website
|
||||
else: # i.e. that updater.update_ready == False.
|
||||
sub_col = col.row(align=True)
|
||||
else: # i.e. that updater.update_ready == False.
|
||||
sub_col = element.row(align=True)
|
||||
sub_col.scale_y = 1
|
||||
split = sub_col.split(align=True)
|
||||
split.enabled = False
|
||||
@@ -1080,13 +1133,24 @@ def update_settings_ui(self, context, element=None):
|
||||
|
||||
if not updater.manual_only:
|
||||
col = row.column(align=True)
|
||||
if updater.update_ready:
|
||||
sub_col = col.row(align=True)
|
||||
sub_col.scale_y = 1
|
||||
split = sub_col.split(align=True)
|
||||
split.operator(AddonUpdaterUpdateNow.bl_idname,
|
||||
text=f"Update now to {updater.update_version} (Stable)")
|
||||
|
||||
if updater.include_branches and len(updater.include_branch_list) > 0:
|
||||
branch = updater.include_branch_list[0]
|
||||
col.operator(AddonUpdaterUpdateTarget.bl_idname,
|
||||
text="Install {} / old version".format(branch))
|
||||
text="Install specific version (Latest or Older)")
|
||||
else:
|
||||
col.operator(AddonUpdaterUpdateTarget.bl_idname,
|
||||
text="(Re)install addon version")
|
||||
|
||||
if updater.include_merge_requests and len(updater.merge_requests) > 0:
|
||||
col.operator(AddonUpdaterTryMR.bl_idname,
|
||||
text="Try a pull request")
|
||||
|
||||
last_date = "none found"
|
||||
backup_path = os.path.join(updater.stage_path, "backup")
|
||||
if "backup_date" in updater.json and os.path.isdir(backup_path):
|
||||
@@ -1095,9 +1159,9 @@ def update_settings_ui(self, context, element=None):
|
||||
else:
|
||||
last_date = updater.json["backup_date"]
|
||||
backup_text = "Restore addon backup ({})".format(last_date)
|
||||
col.operator(AddonUpdaterRestoreBackup.bl_idname, text=backup_text)
|
||||
element.operator(AddonUpdaterRestoreBackup.bl_idname, text=backup_text)
|
||||
|
||||
row = box.row()
|
||||
row = element.row()
|
||||
row.scale_y = 0.7
|
||||
last_check = updater.json["last_check"]
|
||||
if updater.error is not None and updater.error_msg is not None:
|
||||
@@ -1195,7 +1259,7 @@ def update_settings_ui_condensed(self, context, element=None):
|
||||
split = sub_col.split(align=True)
|
||||
split.scale_y = 2
|
||||
split.operator(AddonUpdaterUpdateNow.bl_idname,
|
||||
text="Update now to " + str(updater.update_version))
|
||||
text=f"Update now to {updater.update_version} (Stable)")
|
||||
split = sub_col.split(align=True)
|
||||
split.scale_y = 2
|
||||
split.operator(AddonUpdaterCheckNow.bl_idname,
|
||||
@@ -1358,6 +1422,7 @@ classes = (
|
||||
AddonUpdaterCheckNow,
|
||||
AddonUpdaterUpdateNow,
|
||||
AddonUpdaterUpdateTarget,
|
||||
AddonUpdaterTryMR,
|
||||
AddonUpdaterInstallManually,
|
||||
AddonUpdaterUpdatedSuccessful,
|
||||
AddonUpdaterRestoreBackup,
|
||||
@@ -1440,6 +1505,9 @@ def register(bl_info):
|
||||
# the "install {branch}/older version" operator.
|
||||
updater.include_branches = True
|
||||
|
||||
# Allows the user to try merge requests as an option to "update" to.
|
||||
updater.include_merge_requests = True
|
||||
|
||||
# (GitHub only) This options allows using "releases" instead of "tags",
|
||||
# which enables pulling down release logs/notes, as well as installs update
|
||||
# from release-attached zips (instead of the auto-packaged code generated
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 86 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 80 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 88 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
Reference in New Issue
Block a user