Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b05447dc30 | |||
| 7a36f9ec08 | |||
| 80e3971706 | |||
| dc770b7a79 | |||
| f8e7d391be | |||
| 3092ba3056 | |||
| 9d10006116 | |||
| 95f4d051d6 | |||
| c79ad8e87d | |||
| 9797dfbfc0 | |||
| 63b00a6257 | |||
| 36091845a6 | |||
| 89e1855e2c | |||
| b7f5725282 | |||
| dc8c8cedf4 | |||
| 31e8b838dd | |||
| baf7f32c2a | |||
| ad1d58bd4c | |||
| ec86688750 | |||
| 84098f4c42 | |||
| 77f9d73698 | |||
| 812e8dd2f3 |
@@ -7,7 +7,7 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
|
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
|
||||||
|
|
||||||
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://docs.speckle.systems/dev/"><img src="https://img.shields.io/badge/docs-docs.speckle.systems-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
||||||
<p align="center"><a href="https://github.com/specklesystems/speckle-blender/"><img src="https://circleci.com/gh/specklesystems/speckle-blender.svg?style=svg&circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a> </p>
|
<p align="center"><a href="https://github.com/specklesystems/speckle-blender/"><img src="https://circleci.com/gh/specklesystems/speckle-blender.svg?style=svg&circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a> </p>
|
||||||
|
|
||||||
# About Speckle
|
# About Speckle
|
||||||
@@ -25,20 +25,19 @@ What is Speckle? Check our ](https://speckle.xyz) ⇒ creating an account at our public server
|
- [](https://app.speckle.systems) ⇒ creating an account at our public server
|
||||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
- [](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
|
- [](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
|
||||||
- [](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
|
- [](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
|
||||||
- [](https://speckle.guide/user/blender.html) reference on almost any end-user and developer functionality
|
- [](https://docs.speckle.systems/connectors/blender) reference on almost any end-user and developer functionality
|
||||||
|
|
||||||
|
|
||||||
# Blender Connector
|
# Blender Connector
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ bl_info = {
|
|||||||
|
|
||||||
# UI
|
# UI
|
||||||
from .connector.ui.main_panel import SPECKLE_PT_main_panel
|
from .connector.ui.main_panel import SPECKLE_PT_main_panel
|
||||||
|
from .connector.ui.update_panel import SPECKLE_PT_update_panel
|
||||||
|
from .connector.ui.model_cards_panel import SPECKLE_PT_model_cards_panel
|
||||||
from .connector.utils.account_manager import speckle_workspace
|
from .connector.utils.account_manager import speckle_workspace
|
||||||
from .connector.ui.project_selection_dialog import (
|
from .connector.ui.project_selection_dialog import (
|
||||||
SPECKLE_OT_project_selection_dialog,
|
SPECKLE_OT_project_selection_dialog,
|
||||||
@@ -80,6 +82,8 @@ from .connector.blender_operators.add_project_by_url import (
|
|||||||
|
|
||||||
from .connector.blender_operators.create_project import SPECKLE_OT_create_project
|
from .connector.blender_operators.create_project import SPECKLE_OT_create_project
|
||||||
from .connector.blender_operators.create_model import SPECKLE_OT_create_model
|
from .connector.blender_operators.create_model import SPECKLE_OT_create_model
|
||||||
|
from .connector.blender_operators.version_check import SPECKLE_OT_version_check
|
||||||
|
from .connector.blender_operators.update_button import SPECKLE_OT_update_button
|
||||||
from .connector.utils.account_manager import (
|
from .connector.utils.account_manager import (
|
||||||
speckle_account,
|
speckle_account,
|
||||||
get_default_account_id,
|
get_default_account_id,
|
||||||
@@ -105,6 +109,14 @@ from .connector.ui.account_selection_dialog import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def delayed_version_check():
|
||||||
|
"""Timer function to check for updates after addon startup"""
|
||||||
|
try:
|
||||||
|
bpy.ops.speckle.version_check()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[Speckle] Failed to check for updates: {e}")
|
||||||
|
|
||||||
|
|
||||||
def invoke_window_manager_properties():
|
def invoke_window_manager_properties():
|
||||||
# Accounts
|
# Accounts
|
||||||
WindowManager.speckle_accounts = bpy.props.CollectionProperty(type=speckle_account)
|
WindowManager.speckle_accounts = bpy.props.CollectionProperty(type=speckle_account)
|
||||||
@@ -139,11 +151,17 @@ def invoke_window_manager_properties():
|
|||||||
)
|
)
|
||||||
# Objects
|
# Objects
|
||||||
WindowManager.speckle_objects = bpy.props.CollectionProperty(type=speckle_object)
|
WindowManager.speckle_objects = bpy.props.CollectionProperty(type=speckle_object)
|
||||||
|
# Update checking
|
||||||
|
WindowManager.update_available = bpy.props.BoolProperty(default=False)
|
||||||
|
WindowManager.latest_version = bpy.props.StringProperty(default="")
|
||||||
|
WindowManager.update_url = bpy.props.StringProperty(default="")
|
||||||
|
|
||||||
|
|
||||||
# Classes to load
|
# Classes to load
|
||||||
classes = (
|
classes = (
|
||||||
|
SPECKLE_PT_update_panel,
|
||||||
SPECKLE_PT_main_panel,
|
SPECKLE_PT_main_panel,
|
||||||
|
SPECKLE_PT_model_cards_panel,
|
||||||
SPECKLE_OT_publish,
|
SPECKLE_OT_publish,
|
||||||
SPECKLE_OT_load,
|
SPECKLE_OT_load,
|
||||||
SPECKLE_OT_project_selection_dialog,
|
SPECKLE_OT_project_selection_dialog,
|
||||||
@@ -171,6 +189,8 @@ classes = (
|
|||||||
SPECKLE_OT_add_project_by_url,
|
SPECKLE_OT_add_project_by_url,
|
||||||
SPECKLE_OT_create_project,
|
SPECKLE_OT_create_project,
|
||||||
SPECKLE_OT_create_model,
|
SPECKLE_OT_create_model,
|
||||||
|
SPECKLE_OT_version_check,
|
||||||
|
SPECKLE_OT_update_button,
|
||||||
speckle_account,
|
speckle_account,
|
||||||
SPECKLE_UL_workspaces_list,
|
SPECKLE_UL_workspaces_list,
|
||||||
SPECKLE_OT_workspace_selection_dialog,
|
SPECKLE_OT_workspace_selection_dialog,
|
||||||
@@ -203,8 +223,15 @@ def register():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Speckle] Failed to pre-warm client: {e}")
|
print(f"[Speckle] Failed to pre-warm client: {e}")
|
||||||
|
|
||||||
|
# Use a timer to delay the version check
|
||||||
|
bpy.app.timers.register(delayed_version_check, first_interval=2.0)
|
||||||
|
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
|
# Clear any pending timers to prevent duplicate calls
|
||||||
|
if bpy.app.timers.is_registered(delayed_version_check):
|
||||||
|
bpy.app.timers.unregister(delayed_version_check)
|
||||||
|
|
||||||
icons.unload_icons()
|
icons.unload_icons()
|
||||||
unregister_speckle_state() # Unregister SpeckleState
|
unregister_speckle_state() # Unregister SpeckleState
|
||||||
_client_cache.clear()
|
_client_cache.clear()
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import bpy
|
||||||
|
import webbrowser
|
||||||
|
from bpy.types import Context
|
||||||
|
|
||||||
|
|
||||||
|
class SPECKLE_OT_update_button(bpy.types.Operator):
|
||||||
|
"""Operator for opening the download URL for the latest Speckle Blender connector"""
|
||||||
|
|
||||||
|
bl_idname = "speckle.update_button"
|
||||||
|
bl_label = "Update"
|
||||||
|
bl_description = "Download the latest version of the Speckle Blender connector"
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> set[str]:
|
||||||
|
wm = context.window_manager
|
||||||
|
|
||||||
|
if not wm.update_url:
|
||||||
|
self.report({"ERROR"}, "No update URL available")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
try:
|
||||||
|
webbrowser.open(wm.update_url)
|
||||||
|
self.report({"INFO"}, f"Opening download page for v{wm.latest_version}")
|
||||||
|
except Exception as e:
|
||||||
|
self.report({"ERROR"}, f"Failed to open download page: {str(e)}")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import Context
|
||||||
|
from specklepy.core.api.connector_versions import get_latest_version
|
||||||
|
|
||||||
|
# Get current version from bl_info
|
||||||
|
from ... import bl_info
|
||||||
|
|
||||||
|
|
||||||
|
class SPECKLE_OT_version_check(bpy.types.Operator):
|
||||||
|
"""Operator for checking if a newer version of the Speckle Blender connector is available"""
|
||||||
|
|
||||||
|
bl_idname = "speckle.version_check"
|
||||||
|
bl_label = "Check for Updates"
|
||||||
|
bl_description = (
|
||||||
|
"Check if a newer version of the Speckle Blender connector is available"
|
||||||
|
)
|
||||||
|
|
||||||
|
def execute(self, context: Context) -> set[str]:
|
||||||
|
wm = context.window_manager
|
||||||
|
|
||||||
|
# Reset previous state
|
||||||
|
wm.update_available = False
|
||||||
|
wm.latest_version = ""
|
||||||
|
wm.update_url = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
current_version = bl_info["version"]
|
||||||
|
current_version_str = (
|
||||||
|
f"{current_version[0]}.{current_version[1]}.{current_version[2]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get latest version info
|
||||||
|
latest_version_info = get_latest_version("blender", False)
|
||||||
|
latest_version_str = latest_version_info.number # semantic version string
|
||||||
|
|
||||||
|
# Compare versions - if they're different, show update
|
||||||
|
if latest_version_str != current_version_str:
|
||||||
|
wm.update_available = True
|
||||||
|
wm.latest_version = latest_version_str
|
||||||
|
wm.update_url = str(
|
||||||
|
latest_version_info.url
|
||||||
|
) # Convert HttpUrl to string
|
||||||
|
self.report({"INFO"}, f"Update available: v{latest_version_str}")
|
||||||
|
else:
|
||||||
|
self.report({"INFO"}, "You have the latest version")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Failed to check for updates: {str(e)}"
|
||||||
|
self.report({"ERROR"}, error_msg)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
@@ -10,7 +10,11 @@ from specklepy.core.api import host_applications
|
|||||||
|
|
||||||
from ..utils.get_ascendants import get_ascendants
|
from ..utils.get_ascendants import get_ascendants
|
||||||
from ..utils.account_manager import _client_cache
|
from ..utils.account_manager import _client_cache
|
||||||
from ...converter.utils import find_object_by_id, get_project_workspace_id
|
from ...converter.utils import (
|
||||||
|
find_object_by_id,
|
||||||
|
get_project_workspace_id,
|
||||||
|
build_object_id_map,
|
||||||
|
)
|
||||||
from ...converter.to_native import (
|
from ...converter.to_native import (
|
||||||
convert_to_native,
|
convert_to_native,
|
||||||
render_material_proxy_to_native,
|
render_material_proxy_to_native,
|
||||||
@@ -78,11 +82,17 @@ def load_operation(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Build object ID map once
|
||||||
|
object_id_map = build_object_id_map(version_data)
|
||||||
|
|
||||||
# Create material mapping first
|
# Create material mapping first
|
||||||
material_mapping = render_material_proxy_to_native(version_data)
|
material_mapping = render_material_proxy_to_native(version_data)
|
||||||
|
|
||||||
definition_collections, definition_objects = instance_definition_proxy_to_native(
|
definition_collections, definition_objects = instance_definition_proxy_to_native(
|
||||||
version_data, material_mapping, instance_loading_mode=instance_loading_mode
|
version_data,
|
||||||
|
material_mapping,
|
||||||
|
instance_loading_mode=instance_loading_mode,
|
||||||
|
object_id_map=object_id_map,
|
||||||
)
|
)
|
||||||
|
|
||||||
definitions_root_collection = None
|
definitions_root_collection = None
|
||||||
@@ -96,7 +106,8 @@ def load_operation(
|
|||||||
for definition in find_instance_definitions(version_data).values():
|
for definition in find_instance_definitions(version_data).values():
|
||||||
definition_object_ids.update(definition.objects)
|
definition_object_ids.update(definition.objects)
|
||||||
for obj_id in definition.objects:
|
for obj_id in definition.objects:
|
||||||
found_obj = find_object_by_id(version_data, obj_id)
|
# Use ID map
|
||||||
|
found_obj = object_id_map.get(obj_id)
|
||||||
if found_obj:
|
if found_obj:
|
||||||
if hasattr(found_obj, "id"):
|
if hasattr(found_obj, "id"):
|
||||||
definition_object_ids.add(found_obj.id)
|
definition_object_ids.add(found_obj.id)
|
||||||
|
|||||||
@@ -123,7 +123,11 @@ def update_workspaces_list(context: Context) -> None:
|
|||||||
workspace: speckle_workspace = wm.speckle_workspaces.add()
|
workspace: speckle_workspace = wm.speckle_workspaces.add()
|
||||||
workspace.id = id
|
workspace.id = id
|
||||||
workspace.name = name
|
workspace.name = name
|
||||||
wm.selected_workspace.id = get_active_workspace(wm.selected_account_id)["id"]
|
active_workspace = get_active_workspace(wm.selected_account_id)
|
||||||
|
if active_workspace:
|
||||||
|
wm.selected_workspace.id = active_workspace["id"]
|
||||||
|
else:
|
||||||
|
wm.selected_workspace.id = "personal"
|
||||||
print("Updated Workspaces List!")
|
print("Updated Workspaces List!")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -104,70 +104,3 @@ class SPECKLE_PT_main_panel(bpy.types.Panel):
|
|||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.enabled = project_selected and model_selected and version_selected
|
row.enabled = project_selected and model_selected and version_selected
|
||||||
row.operator("speckle.load", text="Load Model", icon="IMPORT")
|
row.operator("speckle.load", text="Load Model", icon="IMPORT")
|
||||||
|
|
||||||
layout.separator()
|
|
||||||
|
|
||||||
# group model cards by project name
|
|
||||||
project_groups = {}
|
|
||||||
for model_card in context.scene.speckle_state.model_cards:
|
|
||||||
project_name = (
|
|
||||||
model_card.project_name if model_card.project_name else "No Project"
|
|
||||||
)
|
|
||||||
if project_name not in project_groups:
|
|
||||||
project_groups[project_name] = []
|
|
||||||
project_groups[project_name].append(model_card)
|
|
||||||
|
|
||||||
for project_name, model_cards in project_groups.items():
|
|
||||||
project_box = layout.box()
|
|
||||||
project_row = project_box.row()
|
|
||||||
project_row.label(text=f"Project: {project_name}", icon="TRIA_RIGHT")
|
|
||||||
|
|
||||||
for model_card in model_cards:
|
|
||||||
box: UILayout = project_box.box()
|
|
||||||
row_1: UILayout = box.row()
|
|
||||||
row_2: UILayout = box.row()
|
|
||||||
|
|
||||||
if model_card.is_publish:
|
|
||||||
# Publish button in the model card
|
|
||||||
row_1.operator(
|
|
||||||
"speckle.model_card_publish", text="", icon="EXPORT"
|
|
||||||
).model_card_id = model_card.get_model_card_id()
|
|
||||||
# Selection filter button in the model card
|
|
||||||
row_2.operator(
|
|
||||||
"speckle.selection_filter_dialog",
|
|
||||||
text=f"Selection: {len(model_card.objects)} objects",
|
|
||||||
).model_card_id = model_card.get_model_card_id()
|
|
||||||
elif not model_card.is_publish:
|
|
||||||
# Load button in the model card
|
|
||||||
row_1.operator(
|
|
||||||
"speckle.model_card_load", text="", icon="IMPORT"
|
|
||||||
).model_card_id = model_card.get_model_card_id()
|
|
||||||
version_button_text = (
|
|
||||||
f"Latest: {model_card.version_id}"
|
|
||||||
if model_card.load_option == "LATEST"
|
|
||||||
else f"{model_card.version_id}"
|
|
||||||
)
|
|
||||||
row_2.operator(
|
|
||||||
"speckle.version_selection_dialog",
|
|
||||||
text=version_button_text,
|
|
||||||
).model_card_id = model_card.get_model_card_id()
|
|
||||||
# TODO: Get last updated time
|
|
||||||
|
|
||||||
else:
|
|
||||||
print({"ERROR"}, "Model card state unknown")
|
|
||||||
return
|
|
||||||
|
|
||||||
row_1.label(text=f"{model_card.model_name}")
|
|
||||||
|
|
||||||
# Select button in the model card
|
|
||||||
select_op = row_1.operator(
|
|
||||||
"speckle.select_objects",
|
|
||||||
text="",
|
|
||||||
icon_value=get_icon("object_highlight"),
|
|
||||||
)
|
|
||||||
select_op.model_card_id = model_card.get_model_card_id()
|
|
||||||
|
|
||||||
# Settings button in the model card
|
|
||||||
row_1.operator(
|
|
||||||
"speckle.model_card_settings", text="", icon="COLLAPSEMENU"
|
|
||||||
).model_card_id = model_card.get_model_card_id()
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import UILayout, Context
|
||||||
|
from .icons import get_icon
|
||||||
|
|
||||||
|
|
||||||
|
class SPECKLE_PT_model_cards_panel(bpy.types.Panel):
|
||||||
|
"""
|
||||||
|
Panel for displaying Speckle model cards.
|
||||||
|
"""
|
||||||
|
|
||||||
|
bl_label = "Model Cards"
|
||||||
|
bl_idname = "SPECKLE_PT_model_cards_panel"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Speckle"
|
||||||
|
bl_order = 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context) -> bool:
|
||||||
|
"""Only show panel when model cards exist"""
|
||||||
|
return bool(context.scene.speckle_state.model_cards)
|
||||||
|
|
||||||
|
def draw(self, context: Context) -> None:
|
||||||
|
layout: UILayout = self.layout
|
||||||
|
|
||||||
|
# group model cards by project name
|
||||||
|
project_groups = {}
|
||||||
|
for model_card in context.scene.speckle_state.model_cards:
|
||||||
|
project_name = (
|
||||||
|
model_card.project_name if model_card.project_name else "No Project"
|
||||||
|
)
|
||||||
|
if project_name not in project_groups:
|
||||||
|
project_groups[project_name] = []
|
||||||
|
project_groups[project_name].append(model_card)
|
||||||
|
|
||||||
|
for project_name, model_cards in project_groups.items():
|
||||||
|
project_box = layout.box()
|
||||||
|
project_row = project_box.row()
|
||||||
|
project_row.label(text=f"Project: {project_name}", icon="TRIA_RIGHT")
|
||||||
|
|
||||||
|
for model_card in model_cards:
|
||||||
|
box: UILayout = project_box.box()
|
||||||
|
row_1: UILayout = box.row()
|
||||||
|
row_2: UILayout = box.row()
|
||||||
|
|
||||||
|
if model_card.is_publish:
|
||||||
|
# Publish button in the model card
|
||||||
|
row_1.operator(
|
||||||
|
"speckle.model_card_publish", text="", icon="EXPORT"
|
||||||
|
).model_card_id = model_card.get_model_card_id()
|
||||||
|
# Selection filter button in the model card
|
||||||
|
row_2.operator(
|
||||||
|
"speckle.selection_filter_dialog",
|
||||||
|
text=f"Selection: {len(model_card.objects)} objects",
|
||||||
|
).model_card_id = model_card.get_model_card_id()
|
||||||
|
elif not model_card.is_publish:
|
||||||
|
# Load button in the model card
|
||||||
|
row_1.operator(
|
||||||
|
"speckle.model_card_load", text="", icon="IMPORT"
|
||||||
|
).model_card_id = model_card.get_model_card_id()
|
||||||
|
version_button_text = (
|
||||||
|
f"Latest: {model_card.version_id}"
|
||||||
|
if model_card.load_option == "LATEST"
|
||||||
|
else f"{model_card.version_id}"
|
||||||
|
)
|
||||||
|
row_2.operator(
|
||||||
|
"speckle.version_selection_dialog",
|
||||||
|
text=version_button_text,
|
||||||
|
).model_card_id = model_card.get_model_card_id()
|
||||||
|
# TODO: Get last updated time
|
||||||
|
|
||||||
|
else:
|
||||||
|
print({"ERROR"}, "Model card state unknown")
|
||||||
|
return
|
||||||
|
|
||||||
|
row_1.label(text=f"{model_card.model_name}")
|
||||||
|
|
||||||
|
# Select button in the model card
|
||||||
|
select_op = row_1.operator(
|
||||||
|
"speckle.select_objects",
|
||||||
|
text="",
|
||||||
|
icon_value=get_icon("object_highlight"),
|
||||||
|
)
|
||||||
|
select_op.model_card_id = model_card.get_model_card_id()
|
||||||
|
|
||||||
|
# Settings button in the model card
|
||||||
|
row_1.operator(
|
||||||
|
"speckle.model_card_settings", text="", icon="COLLAPSEMENU"
|
||||||
|
).model_card_id = model_card.get_model_card_id()
|
||||||
@@ -120,10 +120,13 @@ class SPECKLE_OT_project_selection_dialog(bpy.types.Operator):
|
|||||||
if wm.selected_account_id == "":
|
if wm.selected_account_id == "":
|
||||||
wm.selected_account_id = get_default_account_id()
|
wm.selected_account_id = get_default_account_id()
|
||||||
|
|
||||||
wm.selected_workspace.id = get_active_workspace(wm.selected_account_id)["id"]
|
active_workspace = get_active_workspace(wm.selected_account_id)
|
||||||
wm.selected_workspace.name = get_active_workspace(wm.selected_account_id)[
|
if active_workspace:
|
||||||
"name"
|
wm.selected_workspace.id = active_workspace["id"]
|
||||||
]
|
wm.selected_workspace.name = active_workspace["name"]
|
||||||
|
else:
|
||||||
|
wm.selected_workspace.id = "personal"
|
||||||
|
wm.selected_workspace.name = "Personal Projects"
|
||||||
|
|
||||||
# Fetch projects from server
|
# Fetch projects from server
|
||||||
projects: List[Tuple[str, str, str, str, bool]] = get_projects_for_account(
|
projects: List[Tuple[str, str, str, str, bool]] = get_projects_for_account(
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import bpy
|
||||||
|
from bpy.types import UILayout, Context
|
||||||
|
|
||||||
|
|
||||||
|
class SPECKLE_PT_update_panel(bpy.types.Panel):
|
||||||
|
"""Panel for displaying connector update notifications"""
|
||||||
|
|
||||||
|
bl_label = "Update Speckle"
|
||||||
|
bl_idname = "SPECKLE_PT_update_panel"
|
||||||
|
bl_space_type = "VIEW_3D"
|
||||||
|
bl_region_type = "UI"
|
||||||
|
bl_category = "Speckle"
|
||||||
|
bl_order = 0 # This ensures it appears above the main panel
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context: Context) -> bool:
|
||||||
|
"""Only show this panel when an update is available"""
|
||||||
|
wm = context.window_manager
|
||||||
|
return getattr(wm, "update_available", False)
|
||||||
|
|
||||||
|
def draw(self, context: Context) -> None:
|
||||||
|
layout: UILayout = self.layout
|
||||||
|
wm = context.window_manager
|
||||||
|
|
||||||
|
# Get current version from bl_info
|
||||||
|
from ... import bl_info
|
||||||
|
|
||||||
|
current_version = bl_info["version"]
|
||||||
|
current_version_str = (
|
||||||
|
f"{current_version[0]}.{current_version[1]}.{current_version[2]}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update notification
|
||||||
|
box = layout.box()
|
||||||
|
box.alert = True # Makes the box stand out with alert styling
|
||||||
|
|
||||||
|
col = box.column()
|
||||||
|
col.label(text="New version available!", icon="INFO")
|
||||||
|
|
||||||
|
row = col.row()
|
||||||
|
row.label(text=f"Current: v{current_version_str}")
|
||||||
|
|
||||||
|
row = col.row()
|
||||||
|
row.label(text=f"Latest: v{wm.latest_version}")
|
||||||
|
|
||||||
|
# Update button
|
||||||
|
row = col.row()
|
||||||
|
row.operator("speckle.update_button", text="Download Update", icon="LINKED")
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from specklepy.core.api.credentials import get_local_accounts
|
from specklepy.core.api.credentials import get_local_accounts
|
||||||
from typing import List, Tuple, Optional, Dict
|
from typing import List, Tuple, Optional, Dict
|
||||||
|
from urllib.parse import urlparse
|
||||||
from specklepy.core.api.credentials import Account
|
from specklepy.core.api.credentials import Account
|
||||||
from specklepy.core.api.client import SpeckleClient
|
from specklepy.core.api.client import SpeckleClient
|
||||||
from specklepy.core.api.wrapper import StreamWrapper
|
from specklepy.core.api.wrapper import StreamWrapper
|
||||||
@@ -23,7 +24,9 @@ class SpeckleClientCache:
|
|||||||
if not account:
|
if not account:
|
||||||
raise ValueError(f"No account found for ID: {account_id}")
|
raise ValueError(f"No account found for ID: {account_id}")
|
||||||
|
|
||||||
client = SpeckleClient(host=account.serverInfo.url)
|
url = account.serverInfo.url
|
||||||
|
use_ssl = urlparse(url).scheme.lower() != "http"
|
||||||
|
client = SpeckleClient(host=url, use_ssl=use_ssl)
|
||||||
client.authenticate_with_account(account)
|
client.authenticate_with_account(account)
|
||||||
self._clients[account_id] = client
|
self._clients[account_id] = client
|
||||||
return client
|
return client
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Context
|
from bpy.types import Context
|
||||||
from typing import Dict, Any, Optional
|
|
||||||
from ..utils.property_groups import speckle_model_card
|
from ..utils.property_groups import speckle_model_card
|
||||||
|
|
||||||
|
|
||||||
@@ -316,11 +318,17 @@ def update_model_card_objects(
|
|||||||
if isinstance(converted_objects, list):
|
if isinstance(converted_objects, list):
|
||||||
converted_objects = {obj.name: obj for obj in converted_objects}
|
converted_objects = {obj.name: obj for obj in converted_objects}
|
||||||
|
|
||||||
|
# Using a set keeps lookup O(1)
|
||||||
|
object_names = set()
|
||||||
|
collection_names = set()
|
||||||
|
|
||||||
for obj in converted_objects.values():
|
for obj in converted_objects.values():
|
||||||
# Handle collections
|
# Handle collections
|
||||||
if isinstance(obj, bpy.types.Collection):
|
if isinstance(obj, bpy.types.Collection):
|
||||||
if obj.name in (o.name for o in model_card.collections):
|
if obj.name in collection_names:
|
||||||
continue
|
continue
|
||||||
|
collection_names.add(obj.name)
|
||||||
|
|
||||||
s_col = model_card.collections.add()
|
s_col = model_card.collections.add()
|
||||||
s_col.name = obj.name
|
s_col.name = obj.name
|
||||||
s_col.applicationId = obj.get("applicationId", "")
|
s_col.applicationId = obj.get("applicationId", "")
|
||||||
@@ -334,8 +342,10 @@ def update_model_card_objects(
|
|||||||
|
|
||||||
# Handle objects
|
# Handle objects
|
||||||
elif isinstance(obj, bpy.types.Object):
|
elif isinstance(obj, bpy.types.Object):
|
||||||
if obj.name in (o.name for o in model_card.objects):
|
if obj.name in object_names:
|
||||||
continue
|
continue
|
||||||
|
object_names.add(obj.name)
|
||||||
|
|
||||||
s_obj = model_card.objects.add()
|
s_obj = model_card.objects.add()
|
||||||
s_obj.name = obj.name
|
s_obj.name = obj.name
|
||||||
s_obj.applicationId = obj.get("applicationId", "")
|
s_obj.applicationId = obj.get("applicationId", "")
|
||||||
|
|||||||
@@ -159,7 +159,14 @@ def convert_to_native(
|
|||||||
else:
|
else:
|
||||||
# Fallback to display value if direct conversion not supported
|
# Fallback to display value if direct conversion not supported
|
||||||
mesh, children = display_value_to_native(
|
mesh, children = display_value_to_native(
|
||||||
speckle_object, object_name, data_block_name, scale, material_mapping
|
speckle_object,
|
||||||
|
object_name,
|
||||||
|
data_block_name,
|
||||||
|
scale,
|
||||||
|
material_mapping,
|
||||||
|
definition_collections,
|
||||||
|
root_collection,
|
||||||
|
instance_loading_mode,
|
||||||
)
|
)
|
||||||
if mesh:
|
if mesh:
|
||||||
# Create a mesh object with the object_name (simple name) and mesh data
|
# Create a mesh object with the object_name (simple name) and mesh data
|
||||||
@@ -176,7 +183,11 @@ def convert_to_native(
|
|||||||
# Ensure the converted object has the correct name (especially for DataObjects)
|
# Ensure the converted object has the correct name (especially for DataObjects)
|
||||||
if isinstance(speckle_object, DataObject):
|
if isinstance(speckle_object, DataObject):
|
||||||
converted_object.name = object_name
|
converted_object.name = object_name
|
||||||
data_block_name = converted_object.data.name
|
if (
|
||||||
|
hasattr(converted_object, "data")
|
||||||
|
and converted_object.data is not None
|
||||||
|
):
|
||||||
|
data_block_name = converted_object.data.name
|
||||||
|
|
||||||
# If there are multiple objects, parent remaining ones to the first
|
# If there are multiple objects, parent remaining ones to the first
|
||||||
for child in children[1:]:
|
for child in children[1:]:
|
||||||
@@ -197,6 +208,9 @@ def display_value_to_native(
|
|||||||
data_block_name: str,
|
data_block_name: str,
|
||||||
scale: float,
|
scale: float,
|
||||||
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
||||||
|
definition_collections: Optional[Dict[str, bpy.types.Collection]] = None,
|
||||||
|
root_collection: Optional[bpy.types.Collection] = None,
|
||||||
|
instance_loading_mode: str = "INSTANCE_PROXIES",
|
||||||
) -> Tuple[Optional[bpy.types.Mesh], List[Object]]:
|
) -> Tuple[Optional[bpy.types.Mesh], List[Object]]:
|
||||||
"""
|
"""
|
||||||
fallback conversion mechanism using displayValue if present
|
fallback conversion mechanism using displayValue if present
|
||||||
@@ -215,6 +229,9 @@ def display_value_to_native(
|
|||||||
DISPLAY_VALUE_PROPERTY_ALIASES,
|
DISPLAY_VALUE_PROPERTY_ALIASES,
|
||||||
True,
|
True,
|
||||||
material_mapping,
|
material_mapping,
|
||||||
|
definition_collections,
|
||||||
|
root_collection,
|
||||||
|
instance_loading_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the parent had an applicationId and we created a mesh, apply the material
|
# If the parent had an applicationId and we created a mesh, apply the material
|
||||||
@@ -247,6 +264,9 @@ def elements_to_native(
|
|||||||
data_block_name: str,
|
data_block_name: str,
|
||||||
scale: float,
|
scale: float,
|
||||||
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
||||||
|
definition_collections: Optional[Dict[str, bpy.types.Collection]] = None,
|
||||||
|
root_collection: Optional[bpy.types.Collection] = None,
|
||||||
|
instance_loading_mode: str = "INSTANCE_PROXIES",
|
||||||
) -> List[Object]:
|
) -> List[Object]:
|
||||||
"""
|
"""
|
||||||
convert elements collection of a speckle object
|
convert elements collection of a speckle object
|
||||||
@@ -259,6 +279,9 @@ def elements_to_native(
|
|||||||
ELEMENTS_PROPERTY_ALIASES,
|
ELEMENTS_PROPERTY_ALIASES,
|
||||||
False,
|
False,
|
||||||
material_mapping,
|
material_mapping,
|
||||||
|
definition_collections,
|
||||||
|
root_collection,
|
||||||
|
instance_loading_mode,
|
||||||
)
|
)
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
@@ -271,12 +294,16 @@ def _members_to_native(
|
|||||||
members: Iterable[str],
|
members: Iterable[str],
|
||||||
combineMeshes: bool,
|
combineMeshes: bool,
|
||||||
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
material_mapping: Optional[Dict[str, bpy.types.Material]] = None,
|
||||||
|
definition_collections: Optional[Dict[str, bpy.types.Collection]] = None,
|
||||||
|
root_collection: Optional[bpy.types.Collection] = None,
|
||||||
|
instance_loading_mode: str = "INSTANCE_PROXIES",
|
||||||
) -> Tuple[Optional[bpy.types.Mesh], List[Object]]:
|
) -> Tuple[Optional[bpy.types.Mesh], List[Object]]:
|
||||||
"""
|
"""
|
||||||
converts a given speckle_object by converting specified members
|
converts a given speckle_object by converting specified members
|
||||||
"""
|
"""
|
||||||
meshes: List[Mesh] = []
|
meshes: List[Mesh] = []
|
||||||
others: List[Base] = []
|
others: List[Base] = []
|
||||||
|
instance_proxies: List[InstanceProxy] = []
|
||||||
|
|
||||||
for alias in members:
|
for alias in members:
|
||||||
display = getattr(speckle_object, alias, None)
|
display = getattr(speckle_object, alias, None)
|
||||||
@@ -285,10 +312,13 @@ def _members_to_native(
|
|||||||
MAX_DEPTH = 255 # some large value, to prevent infinite recursion
|
MAX_DEPTH = 255 # some large value, to prevent infinite recursion
|
||||||
|
|
||||||
def separate(value: Any) -> bool:
|
def separate(value: Any) -> bool:
|
||||||
nonlocal meshes, others, count, MAX_DEPTH
|
nonlocal meshes, others, instance_proxies, count, MAX_DEPTH
|
||||||
|
|
||||||
if combineMeshes and isinstance(value, Mesh):
|
if combineMeshes and isinstance(value, Mesh):
|
||||||
meshes.append(value)
|
meshes.append(value)
|
||||||
|
elif isinstance(value, InstanceProxy):
|
||||||
|
# Handle InstanceProxy objects separately - they need definition_collections
|
||||||
|
instance_proxies.append(value)
|
||||||
elif isinstance(value, Base):
|
elif isinstance(value, Base):
|
||||||
others.append(value)
|
others.append(value)
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
@@ -318,10 +348,28 @@ def _members_to_native(
|
|||||||
# Check if the original object is a DataObject
|
# Check if the original object is a DataObject
|
||||||
is_data_object = isinstance(speckle_object, DataObject)
|
is_data_object = isinstance(speckle_object, DataObject)
|
||||||
|
|
||||||
|
# Process InstanceProxy objects - do not add to children list as they are already
|
||||||
|
for item in instance_proxies:
|
||||||
|
try:
|
||||||
|
convert_to_native(
|
||||||
|
item,
|
||||||
|
material_mapping,
|
||||||
|
definition_collections=definition_collections,
|
||||||
|
root_collection=root_collection,
|
||||||
|
instance_loading_mode="LINKED_DUPLICATES", # always use Linked Duplicates for displayValue proxies
|
||||||
|
)
|
||||||
|
except Exception as ex:
|
||||||
|
print(f"Failed to convert instance proxy in display value {item}: {ex}")
|
||||||
|
|
||||||
|
# Process other objects
|
||||||
for item in others:
|
for item in others:
|
||||||
try:
|
try:
|
||||||
blender_object = convert_to_native(
|
blender_object = convert_to_native(
|
||||||
item, material_mapping, instance_loading_mode="INSTANCE_PROXIES"
|
item,
|
||||||
|
material_mapping,
|
||||||
|
definition_collections=definition_collections,
|
||||||
|
root_collection=root_collection,
|
||||||
|
instance_loading_mode=instance_loading_mode,
|
||||||
)
|
)
|
||||||
if blender_object:
|
if blender_object:
|
||||||
# If the parent is a DataObject, override the name of the converted child
|
# If the parent is a DataObject, override the name of the converted child
|
||||||
@@ -647,7 +695,7 @@ def render_material_proxy_to_native(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
render_material = proxy.value
|
render_material = proxy.value
|
||||||
material_name = getattr(render_material, "name", "Material")
|
material_name = getattr(render_material, "name", None) or "Material"
|
||||||
|
|
||||||
# create or get existing material
|
# create or get existing material
|
||||||
blender_material = create_material_from_proxy(render_material, material_name)
|
blender_material = create_material_from_proxy(render_material, material_name)
|
||||||
@@ -987,7 +1035,14 @@ def curve_to_native(
|
|||||||
):
|
):
|
||||||
print("curve_to_native: degree 2 curve, falling back to displayValue")
|
print("curve_to_native: degree 2 curve, falling back to displayValue")
|
||||||
mesh, children = display_value_to_native(
|
mesh, children = display_value_to_native(
|
||||||
speckle_curve, object_name, data_block_name, scale
|
speckle_curve,
|
||||||
|
object_name,
|
||||||
|
data_block_name,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"INSTANCE_PROXIES",
|
||||||
)
|
)
|
||||||
if mesh:
|
if mesh:
|
||||||
curve_obj = bpy.data.objects.new(object_name, mesh)
|
curve_obj = bpy.data.objects.new(object_name, mesh)
|
||||||
@@ -1059,7 +1114,14 @@ def polycurve_to_native(
|
|||||||
and speckle_polycurve.displayValue
|
and speckle_polycurve.displayValue
|
||||||
):
|
):
|
||||||
mesh, children = display_value_to_native(
|
mesh, children = display_value_to_native(
|
||||||
speckle_polycurve, object_name, data_block_name, scale
|
speckle_polycurve,
|
||||||
|
object_name,
|
||||||
|
data_block_name,
|
||||||
|
scale,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
"INSTANCE_PROXIES",
|
||||||
)
|
)
|
||||||
if mesh:
|
if mesh:
|
||||||
curve_obj = bpy.data.objects.new(object_name, mesh)
|
curve_obj = bpy.data.objects.new(object_name, mesh)
|
||||||
@@ -1211,6 +1273,7 @@ def instance_definition_proxy_to_native(
|
|||||||
material_mapping: Dict[str, Any],
|
material_mapping: Dict[str, Any],
|
||||||
processed_definitions: Dict[str, Any] = None,
|
processed_definitions: Dict[str, Any] = None,
|
||||||
instance_loading_mode: str = "INSTANCE_PROXIES",
|
instance_loading_mode: str = "INSTANCE_PROXIES",
|
||||||
|
object_id_map: Optional[Dict[str, Base]] = None,
|
||||||
) -> Tuple[Dict[str, bpy.types.Collection], Dict[str, Any]]:
|
) -> Tuple[Dict[str, bpy.types.Collection], Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
converts instance definition proxies to Blender collections recursively
|
converts instance definition proxies to Blender collections recursively
|
||||||
@@ -1262,7 +1325,8 @@ def instance_definition_proxy_to_native(
|
|||||||
# Process objects, including nested instances
|
# Process objects, including nested instances
|
||||||
if hasattr(definition, "objects") and isinstance(definition.objects, list):
|
if hasattr(definition, "objects") and isinstance(definition.objects, list):
|
||||||
for obj_id in definition.objects:
|
for obj_id in definition.objects:
|
||||||
found_obj = find_object_by_id(root_object, obj_id)
|
# Use the ID map for lookup
|
||||||
|
found_obj = object_id_map.get(obj_id) if object_id_map else None
|
||||||
|
|
||||||
if found_obj:
|
if found_obj:
|
||||||
try:
|
try:
|
||||||
@@ -1362,7 +1426,8 @@ def instance_proxy_to_linked_duplicates(
|
|||||||
print(f"Definition collection not found for instance {speckle_instance.id}")
|
print(f"Definition collection not found for instance {speckle_instance.id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
unit_scale = proxy_scale(speckle_instance)
|
# Use the scale from the parent context
|
||||||
|
unit_scale = scale
|
||||||
|
|
||||||
# convert transformation matrix
|
# convert transformation matrix
|
||||||
matrix = mathutils.Matrix(
|
matrix = mathutils.Matrix(
|
||||||
@@ -1397,7 +1462,6 @@ def instance_proxy_to_linked_duplicates(
|
|||||||
location, rotation, scale_vector = matrix.decompose()
|
location, rotation, scale_vector = matrix.decompose()
|
||||||
location = location * unit_scale
|
location = location * unit_scale
|
||||||
|
|
||||||
# create transformation matrix
|
|
||||||
final_matrix = (
|
final_matrix = (
|
||||||
mathutils.Matrix.Translation(location)
|
mathutils.Matrix.Translation(location)
|
||||||
@ rotation.to_matrix().to_4x4()
|
@ rotation.to_matrix().to_4x4()
|
||||||
@@ -1409,10 +1473,8 @@ def instance_proxy_to_linked_duplicates(
|
|||||||
parent_empty.empty_display_type = "PLAIN_AXES"
|
parent_empty.empty_display_type = "PLAIN_AXES"
|
||||||
parent_empty.empty_display_size = 0.1
|
parent_empty.empty_display_size = 0.1
|
||||||
|
|
||||||
parent_empty.matrix_world = final_matrix
|
|
||||||
|
|
||||||
# link parent to root collection
|
|
||||||
root_collection.objects.link(parent_empty)
|
root_collection.objects.link(parent_empty)
|
||||||
|
parent_empty.matrix_world = final_matrix
|
||||||
|
|
||||||
parent_empty["speckle_id"] = speckle_instance.id
|
parent_empty["speckle_id"] = speckle_instance.id
|
||||||
parent_empty["speckle_type"] = speckle_instance.speckle_type
|
parent_empty["speckle_type"] = speckle_instance.speckle_type
|
||||||
@@ -1422,15 +1484,14 @@ def instance_proxy_to_linked_duplicates(
|
|||||||
|
|
||||||
duplicated_objects = []
|
duplicated_objects = []
|
||||||
for obj in definition_collection.objects:
|
for obj in definition_collection.objects:
|
||||||
# create a copy of the object with linked data
|
|
||||||
duplicate_obj = obj.copy()
|
duplicate_obj = obj.copy()
|
||||||
|
|
||||||
duplicate_obj.name = f"{obj.name}_{speckle_instance.id[:8]}"
|
duplicate_obj.name = f"{obj.name}_{speckle_instance.id[:8]}"
|
||||||
|
|
||||||
root_collection.objects.link(duplicate_obj)
|
root_collection.objects.link(duplicate_obj)
|
||||||
|
|
||||||
# apply the instance transformation directly to each object
|
duplicate_obj.parent = parent_empty
|
||||||
duplicate_obj.matrix_world = final_matrix @ obj.matrix_world
|
duplicate_obj.matrix_parent_inverse.identity()
|
||||||
|
duplicate_obj.matrix_basis = obj.matrix_world
|
||||||
|
|
||||||
duplicated_objects.append(duplicate_obj)
|
duplicated_objects.append(duplicate_obj)
|
||||||
|
|
||||||
@@ -1450,7 +1511,8 @@ def instance_proxy_to_native(
|
|||||||
print(f"Definition collection not found for instance {speckle_instance.id}")
|
print(f"Definition collection not found for instance {speckle_instance.id}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
unit_scale = proxy_scale(speckle_instance)
|
# Use the scale from the parent context
|
||||||
|
unit_scale = scale
|
||||||
|
|
||||||
# convert transformation matrix
|
# convert transformation matrix
|
||||||
matrix = mathutils.Matrix(
|
matrix = mathutils.Matrix(
|
||||||
@@ -1483,35 +1545,24 @@ def instance_proxy_to_native(
|
|||||||
)
|
)
|
||||||
|
|
||||||
location, rotation, scale_vector = matrix.decompose()
|
location, rotation, scale_vector = matrix.decompose()
|
||||||
|
|
||||||
location = location * unit_scale
|
location = location * unit_scale
|
||||||
|
instance_name = f"Instance_{speckle_instance.id}"
|
||||||
bpy.ops.object.collection_instance_add(
|
instance_obj = bpy.data.objects.new(instance_name, None)
|
||||||
collection=definition_collection.name,
|
instance_obj.instance_type = "COLLECTION"
|
||||||
align="WORLD",
|
instance_obj.instance_collection = definition_collection
|
||||||
location=(0, 0, 0),
|
|
||||||
rotation=(0, 0, 0),
|
|
||||||
scale=(1, 1, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
instance_obj = bpy.context.active_object
|
|
||||||
|
|
||||||
instance_obj.empty_display_size = 0
|
instance_obj.empty_display_size = 0
|
||||||
|
|
||||||
instance_name = f"Instance_{speckle_instance.id}"
|
# Link to root collection
|
||||||
instance_obj.name = instance_name
|
root_collection.objects.link(instance_obj)
|
||||||
|
|
||||||
if instance_obj.name not in root_collection.objects:
|
|
||||||
for coll in instance_obj.users_collection:
|
|
||||||
coll.objects.unlink(instance_obj)
|
|
||||||
root_collection.objects.link(instance_obj)
|
|
||||||
|
|
||||||
|
# Store metadata
|
||||||
instance_obj["speckle_id"] = speckle_instance.id
|
instance_obj["speckle_id"] = speckle_instance.id
|
||||||
instance_obj["speckle_type"] = speckle_instance.speckle_type
|
instance_obj["speckle_type"] = speckle_instance.speckle_type
|
||||||
instance_obj["definition_id"] = speckle_instance.definitionId
|
instance_obj["definition_id"] = speckle_instance.definitionId
|
||||||
if hasattr(speckle_instance, "maxDepth"):
|
if hasattr(speckle_instance, "maxDepth"):
|
||||||
instance_obj["max_depth"] = speckle_instance.maxDepth
|
instance_obj["max_depth"] = speckle_instance.maxDepth
|
||||||
|
|
||||||
|
# Apply transformation
|
||||||
final_matrix = (
|
final_matrix = (
|
||||||
mathutils.Matrix.Translation(location)
|
mathutils.Matrix.Translation(location)
|
||||||
@ rotation.to_matrix().to_4x4()
|
@ rotation.to_matrix().to_4x4()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Tuple, List, Optional
|
from typing import Tuple, List, Optional, Dict
|
||||||
import bpy
|
import bpy
|
||||||
import mathutils
|
import mathutils
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
@@ -118,6 +118,25 @@ def transform_matrix(transform: List[float]) -> mathutils.Matrix:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_object_id_map(root_object: Base) -> Dict[str, Base]:
|
||||||
|
"""
|
||||||
|
Builds a dictionary mapping object IDs (both id and applicationId) to objects.
|
||||||
|
"""
|
||||||
|
id_map = {}
|
||||||
|
traversal_function = create_default_traversal_function()
|
||||||
|
|
||||||
|
for traversal_item in traversal_function.traverse(root_object):
|
||||||
|
obj = traversal_item.current
|
||||||
|
|
||||||
|
if hasattr(obj, "id") and obj.id:
|
||||||
|
id_map[obj.id] = obj
|
||||||
|
|
||||||
|
if hasattr(obj, "applicationId") and obj.applicationId:
|
||||||
|
id_map[obj.applicationId] = obj
|
||||||
|
|
||||||
|
return id_map
|
||||||
|
|
||||||
|
|
||||||
def find_object_by_id(root_object: Base, target_id: str) -> Optional[Base]:
|
def find_object_by_id(root_object: Base, target_id: str) -> Optional[Base]:
|
||||||
"""
|
"""
|
||||||
finds an object using traversal, checking both id and applicationId
|
finds an object using traversal, checking both id and applicationId
|
||||||
|
|||||||
+2
-6
@@ -5,12 +5,8 @@ description = "Next-Gen Speckle connector for Blender!"
|
|||||||
requires-python = ">=3.11.9, <4.0.0"
|
requires-python = ">=3.11.9, <4.0.0"
|
||||||
license = "Apache-2.0"
|
license = "Apache-2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"specklepy>=3.0.3",
|
"specklepy>=3.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = ["fake-bpy-module-latest>=20240524,<20240525", "ruff>=0.4.4,<0.5"]
|
||||||
"fake-bpy-module-latest>=20240524,<20240525",
|
|
||||||
"ruff>=0.4.4,<0.5",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user