From f3be5d866003566e843854eba9475078a7c60937 Mon Sep 17 00:00:00 2001 From: Ma Nan Date: Wed, 5 Jun 2024 09:11:22 +0800 Subject: [PATCH] feat: support blender 4.2 extension --- .github/workflows/upload-assets.yml | 37 +++++- bioxelnodes/auto_load.py | 2 +- bioxelnodes/io_points.py | 198 +--------------------------- bioxelnodes/misc.py | 2 +- bioxelnodes/nodes.py | 4 +- bioxelnodes/preferences.py | 1 - extension/__init__.py | 22 ++++ extension/blender_manifest.toml | 56 ++++++++ extension/preferences.py | 23 ++++ 9 files changed, 141 insertions(+), 204 deletions(-) create mode 100644 extension/__init__.py create mode 100644 extension/blender_manifest.toml create mode 100644 extension/preferences.py diff --git a/.github/workflows/upload-assets.yml b/.github/workflows/upload-assets.yml index 7c6898b..02db20a 100644 --- a/.github/workflows/upload-assets.yml +++ b/.github/workflows/upload-assets.yml @@ -22,7 +22,7 @@ jobs: # id: draft_release # uses: cardinalby/git-get-release-action@1.2.4 # with: - # releaseName: Draft + # releaseName: Upload # env: # GITHUB_TOKEN: ${{ github.token }} @@ -70,5 +70,38 @@ jobs: with: upload_url: ${{ needs.draft_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps asset_path: ./package.zip - asset_name: BioxelNodes_${{ needs.draft_release.outputs.version }}.zip + asset_name: BioxelNodes_Addon_${{ needs.draft_release.outputs.version }}.zip + asset_content_type: application/zip + + upload_blender_extension: + name: Upload Blender Extension + needs: draft_release + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + lfs: 'true' + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + - name: Zip Extension + run: | + pip download SimpleITK --dest bioxelnodes/wheels --only-binary=:all: --python-version=3.11 --platform=macosx_11_0_arm64 + pip download SimpleITK --dest bioxelnodes/wheels --only-binary=:all: --python-version=3.11 --platform=win_amd64 + rm -r bioxelnodes/externalpackage + cp extension/__init__.py bioxelnodes/__init__.py + cp extension/preferences.py bioxelnodes/preferences.py + cp extension/blender_manifest.toml bioxelnodes/blender_manifest.toml + zip -r package.zip bioxelnodes + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.draft_release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps + asset_path: ./package.zip + asset_name: BioxelNodes_Extension_${{ needs.draft_release.outputs.version }}.zip asset_content_type: application/zip diff --git a/bioxelnodes/auto_load.py b/bioxelnodes/auto_load.py index 6b4eede..145371c 100644 --- a/bioxelnodes/auto_load.py +++ b/bioxelnodes/auto_load.py @@ -57,7 +57,7 @@ def unregister(): ################################################# def get_all_submodules(directory): - return list(iter_submodules(directory, directory.name)) + return list(iter_submodules(directory, __package__)) def iter_submodules(path, package_name): diff --git a/bioxelnodes/io_points.py b/bioxelnodes/io_points.py index 083fdf8..0bd36f7 100644 --- a/bioxelnodes/io_points.py +++ b/bioxelnodes/io_points.py @@ -22,200 +22,4 @@ def create_points_obj(points, name="points"): # make the bmesh the object's mesh bm.to_mesh(mesh) bm.free() # always do this when finished - return obj - - -# class ImportDICOMPointsDialog(bpy.types.Operator): -# bl_idname = "bioxelnodes.import_dicom_points_dialog" -# bl_label = "Volume Data as Bioxels" -# bl_description = "Import Volume Data as Bioxels (VDB)" -# bl_options = {'UNDO'} - -# filepath: bpy.props.StringProperty( -# subtype="FILE_PATH", -# options={'HIDDEN'} -# ) # type: ignore - -# bioxels_shape: bpy.props.IntVectorProperty( -# name="Bioxels Shape (ReadOnly)", -# min=0, -# default=(100, 100, 100) -# ) # type: ignore - -# bioxel_size: bpy.props.FloatProperty( -# name="Bioxel Size", -# soft_min=0.1, soft_max=10.0, -# min=1e-2, max=1e2, -# default=1, -# update=on_bioxel_size_changed -# ) # type: ignore - -# orig_spacing: bpy.props.FloatVectorProperty( -# name="Original Spacing", -# default=(1, 1, 1), -# update=on_orig_spacing_changed -# ) # type: ignore - -# orig_shape: bpy.props.IntVectorProperty( -# name="Original Shape", -# default=(100, 100, 100), -# options={'HIDDEN'} -# ) # type: ignore - -# auto: bpy.props.BoolProperty( -# name="Auto Setting", -# default=False, -# options={'HIDDEN'} -# ) # type: ignore - -# scene_scale: bpy.props.FloatProperty( -# name="Scene Scale", -# soft_min=0.001, soft_max=100.0, -# min=1e-6, max=1e6, -# default=0.01, -# ) # type: ignore - -# do_add_segmentnode: bpy.props.BoolProperty( -# name="Add Segment Node", -# default=True, -# ) # type: ignore - -# do_change_render_setting: bpy.props.BoolProperty( -# name="Change Render Setting", -# default=True, -# ) # type: ignore - -# def execute(self, context): -# files = get_data_files(self.filepath) -# name = Path(self.filepath).parent.name - -# image = sitk.ReadImage(files) - -# bioxel_size = float(self.bioxel_size) -# orig_spacing = tuple(self.orig_spacing) -# image_spacing = image.GetSpacing() -# image_shape = image.GetSize() - -# bioxels_spacing = ( -# image_spacing[0] / orig_spacing[0] * bioxel_size, -# image_spacing[1] / orig_spacing[1] * bioxel_size, -# image_spacing[2] / orig_spacing[2] * bioxel_size -# ) - -# bioxels_shape = ( -# int(image_shape[0] / bioxel_size * orig_spacing[0]), -# int(image_shape[1] / bioxel_size * orig_spacing[1]), -# int(image_shape[2] / bioxel_size * orig_spacing[2]), -# ) - -# print("Resampling...") -# image = sitk.Resample( -# image1=image, -# size=bioxels_shape, -# transform=sitk.Transform(), -# interpolator=sitk.sitkLinear, -# outputOrigin=image.GetOrigin(), -# outputSpacing=bioxels_spacing, -# outputDirection=image.GetDirection(), -# defaultPixelValue=0, -# outputPixelType=image.GetPixelID(), -# ) - -# print("Orienting to RAS...") -# image = sitk.DICOMOrient(image, 'RAS') - -# array = sitk.GetArrayFromImage(image) -# orig_dtype = str(array.dtype) -# print(f"Coverting Dtype from {orig_dtype} to float...") -# array = array.astype(float) - -# # ITK indices, by convention, are [i,j,k] while NumPy indices are [k,j,i] -# # https://www.slicer.org/wiki/Coordinate_systems - -# # ITK Numpy 3D -# # R (ight) i -> k -> x -# # A (nterior) j -> j -> y -# # S (uperior) k -> i -> z - -# array = np.transpose(array) -# bioxels_max = float(np.max(array)) -# bioxels_min = float(np.min(array)) -# bioxels_shape = array.shape - -# print("Bioxel Size:", bioxel_size) -# print("Bioxels Shape:", bioxels_shape) - -# bioxels_offset = 0.0 -# if bioxels_min < 0 and orig_dtype[0] != "u": -# bioxels_offset = -bioxels_min -# array = array + np.full_like(array, bioxels_offset) -# bioxels_max = float(np.max(array)) -# bioxels_min = float(np.min(array)) -# print("Offseted Max:", bioxels_max) -# print("Offseted Min:", bioxels_min) - -# points = [] -# for x in range(array.shape[0]): -# for y in range(array.shape[1]): -# for z in range(array.shape[2]): -# points.append({ -# "pos": (x, y, z), -# "value": array[x, y, z], -# }) - -# # # Build VDB -# # grid = vdb.FloatGrid() -# # grid.copyFromArray(array.copy()) - -# # # After sitk.DICOMOrient(), origin and direction will also orient base on LPS -# # # so we need to convert them into RAS -# # mat_lps2ras = axis_conversion( -# # from_forward='-Z', -# # from_up='-Y', -# # to_forward='-Z', -# # to_up='Y' -# # ).to_4x4() - -# # mat_location = mathutils.Matrix.Translation( -# # mathutils.Vector(image.GetOrigin()) -# # ) - -# # mat_rotation = mathutils.Matrix( -# # np.array(image.GetDirection()).reshape((3, 3)) -# # ).to_4x4() - -# # mat_scale = mathutils.Matrix.Scale( -# # bioxel_size, 4 -# # ) - -# # transfrom = mat_lps2ras @ mat_location @ mat_rotation @ mat_scale - -# obj = create_points_obj(points) - -# # Make transformation -# scene_scale = float(self.scene_scale) -# # (S)uperior -Z -> Y -# # (A)osterior Y -> Z -# mat_ras2blender = axis_conversion( -# from_forward='-Z', -# from_up='Y', -# to_forward='Y', -# to_up='Z' -# ).to_4x4() - -# mat_scene_scale = mathutils.Matrix.Scale( -# scene_scale, 4 -# ) - -# obj.matrix_world = mat_ras2blender @ mat_scene_scale - -# self.report({"INFO"}, "Successfully Imported") - -# return {'FINISHED'} - -# def invoke(self, context, event): -# # print(tuple(self.orig_shape)) -# self.auto = True -# self.bioxel_size = 1 -# context.window_manager.invoke_props_dialog(self) -# return {'RUNNING_MODAL'} + return obj \ No newline at end of file diff --git a/bioxelnodes/misc.py b/bioxelnodes/misc.py index 9d3d1db..bbc8fba 100644 --- a/bioxelnodes/misc.py +++ b/bioxelnodes/misc.py @@ -1,7 +1,7 @@ import bpy from pathlib import Path import shutil -from bioxelnodes.utils import get_all_layers, get_container, get_container_layers +from .utils import get_all_layers, get_container, get_container_layers def save_layer(layer, output_dir): diff --git a/bioxelnodes/nodes.py b/bioxelnodes/nodes.py index 0a95ec0..af6633e 100644 --- a/bioxelnodes/nodes.py +++ b/bioxelnodes/nodes.py @@ -7,7 +7,7 @@ import bpy def add_driver_to_node_factory(source_prop, target_prop): callback_str = f""" import bpy -from bioxelnodes.utils import add_direct_driver, get_bioxels_obj +from .utils import add_direct_driver, get_bioxels_obj bioxels_obj = get_bioxels_obj(bpy.context.active_object) if bioxels_obj: container_obj = bioxels_obj.parent @@ -26,7 +26,7 @@ else: def set_prop_to_node_factory(source_prop, target_prop): callback_str = f""" import bpy -from bioxelnodes.utils import get_bioxels_obj +from .utils import get_bioxels_obj bioxels_obj = get_bioxels_obj(bpy.context.active_object) if bioxels_obj: container_obj = bioxels_obj.parent diff --git a/bioxelnodes/preferences.py b/bioxelnodes/preferences.py index e239a0c..24a44b0 100644 --- a/bioxelnodes/preferences.py +++ b/bioxelnodes/preferences.py @@ -26,5 +26,4 @@ class BioxelNodesPreferences(bpy.types.AddonPreferences, ExternalPackagePreferen layout.label(text="Configuration") layout.prop(self, 'cache_dir') - layout.prop(self, "do_change_render_setting") diff --git a/extension/__init__.py b/extension/__init__.py new file mode 100644 index 0000000..9d122ed --- /dev/null +++ b/extension/__init__.py @@ -0,0 +1,22 @@ +import bpy + +from . import auto_load +from . import menus + + +auto_load.init() + + +def register(): + auto_load.register() + menus.add() + bpy.types.Scene.bioxel_layer_dir = bpy.props.StringProperty( + name="Bioxel Layers Directory", + subtype='DIR_PATH', + default="//" + ) + + +def unregister(): + menus.remove() + auto_load.unregister() diff --git a/extension/blender_manifest.toml b/extension/blender_manifest.toml new file mode 100644 index 0000000..9108043 --- /dev/null +++ b/extension/blender_manifest.toml @@ -0,0 +1,56 @@ +schema_version = "1.0.0" + +# Example of manifest file for a Blender extension +# Change the values according to your extension +id = "bioxelnodes" +version = "0.2.0" +name = "Bioxel Nodes" +tagline = "For scientific volumetric data visualization in Blender" +maintainer = "Ma Nan " +# Supported types: "add-on", "theme" +type = "add-on" + +# Optional: add-ons can list which resources they will require: +# * "files" (for access of any filesystem operations) +# * "network" (for internet access) +# * "clipboard" (to read and/or write the system clipboard) +# * "camera" (to capture photos and videos) +# * "microphone" (to capture audio) +permissions = ["files"] + +# Optional link to documentation, support, source files, etc +website = "https://omoolab.github.io/BioxelNodes/latest" + +# Optional list defined by Blender and server, see: +# https://docs.blender.org/manual/en/dev/extensions/tags.html +tags = ["Geometry Nodes", "Render", "Material"] + +blender_version_min = "4.2.0" +# Optional: maximum supported Blender version +# blender_version_max = "5.1.0" + +# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix) +# https://docs.blender.org/manual/en/dev/extensions/licenses.html +license = ["SPDX:MIT"] +# Optional: required by some licenses. +copyright = ["2024 OmooLab"] + +# Optional list of supported platforms. If omitted, the extension will be available in all operating systems. +platforms = ["windows-amd64", "macos-x86_64"] +# Other supported platforms: "windows-arm64", "macos-x86_64" + +# TODO: externalpackage to wheels +# Optional: bundle 3rd party Python modules. +# https://docs.blender.org/manual/en/dev/extensions/python_wheels.html +wheels = [ + "./wheels/SimpleITK-2.3.1-cp311-cp311-win_amd64.whl", + "./wheels/SimpleITK-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", +] + +# Optional: build setting. +# https://docs.blender.org/manual/en/dev/extensions/command_line_arguments.html#command-line-args-extension-build +# [build] +# paths_exclude_pattern = [ +# "/.git/" +# "__pycache__/" +# ] diff --git a/extension/preferences.py b/extension/preferences.py new file mode 100644 index 0000000..754a64d --- /dev/null +++ b/extension/preferences.py @@ -0,0 +1,23 @@ +import bpy +from pathlib import Path + +class BioxelNodesPreferences(bpy.types.AddonPreferences): + bl_idname = __package__ + + cache_dir: bpy.props.StringProperty( + name="Set Cache Directory", + subtype='DIR_PATH', + default=str(Path(Path.home(), '.bioxelnodes')) + ) # type: ignore + + do_change_render_setting: bpy.props.BoolProperty( + name="Change Render Setting", + default=True, + ) # type: ignore + + def draw(self, context): + layout = self.layout + + layout.label(text="Configuration") + layout.prop(self, 'cache_dir') + layout.prop(self, "do_change_render_setting")