Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1f835dc77 | |||
| e2172216a5 | |||
| 965c3e9c6e | |||
| 65e4812ba1 | |||
| 87df86f723 | |||
| fd32371be3 | |||
| 19c1334bb3 | |||
| 7a36450143 | |||
| d37fce644b |
@@ -1,29 +1,14 @@
|
||||
import bpy
|
||||
from bpy.types import Context
|
||||
from bpy.types import Event
|
||||
from typing import Set, List, Optional
|
||||
from typing import Set
|
||||
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.models.collections.collection import Collection
|
||||
from specklepy.core.api import operations
|
||||
from specklepy.core.api.client import SpeckleClient
|
||||
from specklepy.transports.server import ServerTransport
|
||||
from specklepy.core.api.inputs.version_inputs import CreateVersionInput
|
||||
from specklepy.core.api.credentials import get_local_accounts
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
from ...converter.to_speckle import convert_to_speckle
|
||||
from ...converter.to_speckle.material_to_speckle import (
|
||||
add_render_material_proxies_to_base,
|
||||
)
|
||||
from ...converter.utils import get_project_workspace_id
|
||||
from specklepy.logging import metrics
|
||||
from ....bpy_speckle import bl_info
|
||||
from ..operations.publish_operation import publish_operation
|
||||
from ..utils.account_manager import get_server_url_by_account_id
|
||||
|
||||
|
||||
class SPECKLE_OT_publish(bpy.types.Operator):
|
||||
bl_idname = "speckle.publish"
|
||||
|
||||
bl_label = "Publish to Speckle"
|
||||
bl_description = "Publish selected objects to Speckle"
|
||||
|
||||
@@ -33,8 +18,12 @@ class SPECKLE_OT_publish(bpy.types.Operator):
|
||||
def execute(self, context: Context) -> Set[str]:
|
||||
wm = context.window_manager
|
||||
|
||||
if not context.selected_objects and not context.active_object:
|
||||
self.report({"ERROR"}, "No objects selected to publish")
|
||||
# check if we have stored objects from selection dialog
|
||||
if not wm.speckle_objects:
|
||||
self.report(
|
||||
{"ERROR"},
|
||||
"No objects selected to publish. Please use 'Select Objects' first.",
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
account_id = getattr(wm, "selected_account_id", "")
|
||||
@@ -53,155 +42,52 @@ class SPECKLE_OT_publish(bpy.types.Operator):
|
||||
self.report({"ERROR"}, "No model selected")
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
account = next(
|
||||
(acc for acc in get_local_accounts() if acc.id == account_id),
|
||||
None,
|
||||
)
|
||||
|
||||
if account is None:
|
||||
self.report({"ERROR"}, "No Speckle account found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
client = SpeckleClient(host=account.serverInfo.url)
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
transport = ServerTransport(stream_id=project_id, client=client)
|
||||
|
||||
# get objects to convert
|
||||
objects_to_convert = context.selected_objects or [context.active_object]
|
||||
speckle_objects = self.convert_selected_objects(context)
|
||||
|
||||
if not speckle_objects:
|
||||
objects_to_convert = []
|
||||
for speckle_obj in wm.speckle_objects:
|
||||
blender_obj = bpy.data.objects.get(speckle_obj.name)
|
||||
if blender_obj:
|
||||
objects_to_convert.append(blender_obj)
|
||||
else:
|
||||
self.report(
|
||||
{"ERROR"}, "No objects could be converted to Speckle format"
|
||||
)
|
||||
return {"CANCELLED"}
|
||||
|
||||
# get the Blender file name to set the name
|
||||
file_name = bpy.path.basename(bpy.data.filepath)
|
||||
collection_name = file_name if file_name else "Untitled.blend"
|
||||
|
||||
# create a collection to hold all objects
|
||||
collection = Collection(name=collection_name)
|
||||
collection.units = Units.m.value
|
||||
collection["version"] = 3
|
||||
|
||||
for obj in speckle_objects:
|
||||
if obj is not None:
|
||||
collection.elements.append(obj)
|
||||
|
||||
add_render_material_proxies_to_base(collection, objects_to_convert)
|
||||
|
||||
obj_id = operations.send(collection, [transport])
|
||||
|
||||
version_input = CreateVersionInput(
|
||||
objectId=obj_id,
|
||||
modelId=model_id,
|
||||
projectId=project_id,
|
||||
message="",
|
||||
sourceApplication="blender",
|
||||
)
|
||||
|
||||
version = client.version.create(version_input)
|
||||
version_id = version.id
|
||||
|
||||
metrics.set_host_app("blender")
|
||||
|
||||
metrics.track(
|
||||
metrics.SEND,
|
||||
account,
|
||||
{
|
||||
"ui": "dui3",
|
||||
"hostAppVersion": ",".join(map(str, bl_info["blender"])),
|
||||
"core_version": ",".join(map(str, bl_info["version"])),
|
||||
"workspace_id": get_project_workspace_id(client, project_id),
|
||||
},
|
||||
)
|
||||
|
||||
# Update model card if needed
|
||||
if hasattr(context.scene, "speckle_state") and hasattr(
|
||||
context.scene.speckle_state, "model_cards"
|
||||
):
|
||||
model_card = context.scene.speckle_state.model_cards.add()
|
||||
model_card.account_id = account_id
|
||||
model_card.server_url = account.serverInfo.url
|
||||
model_card.project_id = project_id
|
||||
model_card.project_name = getattr(wm, "selected_project_name", "")
|
||||
model_card.model_id = model_id
|
||||
model_card.model_name = getattr(wm, "selected_model_name", "")
|
||||
model_card.is_publish = True
|
||||
model_card.load_option = "SPECIFIC" # Published versions are specific
|
||||
model_card.version_id = version_id
|
||||
model_card.collection_name = (
|
||||
f"{getattr(wm, 'selected_model_name', 'Model')} - {version_id[:8]}"
|
||||
{"WARNING"}, f"Object '{speckle_obj.name}' not found, skipping"
|
||||
)
|
||||
|
||||
# Clear selected model details from Window Manager AFTER creating model card
|
||||
wm.selected_account_id = ""
|
||||
wm.selected_project_id = ""
|
||||
wm.selected_project_name = ""
|
||||
wm.selected_model_id = ""
|
||||
wm.selected_model_name = ""
|
||||
wm.selected_version_load_option = ""
|
||||
wm.selected_version_id = ""
|
||||
|
||||
self.report(
|
||||
{"INFO"},
|
||||
f"Successfully published {len(speckle_objects)} objects to Speckle with materials",
|
||||
)
|
||||
return {"FINISHED"}
|
||||
|
||||
except Exception as e:
|
||||
self.report({"ERROR"}, f"Failed to publish: {str(e)}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
if not objects_to_convert:
|
||||
self.report({"ERROR"}, "None of the selected objects could be found")
|
||||
return {"CANCELLED"}
|
||||
|
||||
def convert_selected_objects(self, context: Context) -> List[Optional[Base]]:
|
||||
scene = context.scene
|
||||
unit_settings = scene.unit_settings
|
||||
success, message, version_id = publish_operation(context, objects_to_convert)
|
||||
|
||||
# get units from Blender's unit system
|
||||
if unit_settings.system == "METRIC":
|
||||
if unit_settings.length_unit == "METERS":
|
||||
units = Units.m
|
||||
elif unit_settings.length_unit == "CENTIMETERS":
|
||||
units = Units.cm
|
||||
elif unit_settings.length_unit == "MILLIMETERS":
|
||||
units = Units.mm
|
||||
elif unit_settings.length_unit == "KILOMETERS":
|
||||
units = Units.km
|
||||
else:
|
||||
units = Units.m
|
||||
elif unit_settings.system == "IMPERIAL":
|
||||
if unit_settings.length_unit == "FEET":
|
||||
units = Units.feet
|
||||
elif unit_settings.length_unit == "INCHES":
|
||||
units = Units.inches
|
||||
elif unit_settings.length_unit == "YARDS":
|
||||
units = Units.yards
|
||||
elif unit_settings.length_unit == "MILES":
|
||||
units = Units.miles
|
||||
else:
|
||||
units = Units.feet # default to feet
|
||||
else:
|
||||
units = Units.m # default to meters
|
||||
if not success:
|
||||
self.report({"ERROR"}, message)
|
||||
return {"CANCELLED"}
|
||||
|
||||
scale_factor = unit_settings.scale_length
|
||||
# create model card if operation was successful
|
||||
if hasattr(context.scene, "speckle_state") and hasattr(
|
||||
context.scene.speckle_state, "model_cards"
|
||||
):
|
||||
model_card = context.scene.speckle_state.model_cards.add()
|
||||
model_card.account_id = account_id
|
||||
model_card.server_url = get_server_url_by_account_id(account_id)
|
||||
model_card.project_id = project_id
|
||||
model_card.project_name = getattr(wm, "selected_project_name", "")
|
||||
model_card.model_id = model_id
|
||||
model_card.model_name = getattr(wm, "selected_model_name", "")
|
||||
model_card.is_publish = True
|
||||
model_card.load_option = "SPECIFIC" # published versions are specific
|
||||
model_card.version_id = version_id
|
||||
model_card.collection_name = (
|
||||
f"{getattr(wm, 'selected_model_name', 'Model')} - {version_id[:8]}"
|
||||
)
|
||||
|
||||
# convert each selected object
|
||||
speckle_objects = []
|
||||
objects_to_convert = context.selected_objects or [context.active_object]
|
||||
for obj in objects_to_convert:
|
||||
# Skip objects that are not supported
|
||||
if not obj or obj.type not in ["MESH", "CURVE", "EMPTY"]:
|
||||
continue
|
||||
# clear selected model details from Window Manager
|
||||
wm.selected_account_id = ""
|
||||
wm.selected_project_id = ""
|
||||
wm.selected_project_name = ""
|
||||
wm.selected_model_id = ""
|
||||
wm.selected_model_name = ""
|
||||
wm.selected_version_load_option = ""
|
||||
wm.selected_version_id = ""
|
||||
|
||||
# convert the object
|
||||
speckle_obj = convert_to_speckle(obj, scale_factor, units.value)
|
||||
if speckle_obj:
|
||||
speckle_objects.append(speckle_obj)
|
||||
|
||||
return speckle_objects
|
||||
self.report({"INFO"}, message)
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from ..operations.load_operation import load_operation # noqa: F401
|
||||
from ..operations.publish_operation import publish_operation # noqa: F401
|
||||
|
||||
@@ -19,7 +19,7 @@ from ...converter.to_native import (
|
||||
find_instance_definitions,
|
||||
)
|
||||
from specklepy.logging import metrics
|
||||
from ....bpy_speckle import bl_info
|
||||
from ... import bl_info
|
||||
|
||||
|
||||
def load_operation(context: Context) -> None:
|
||||
@@ -62,8 +62,8 @@ def load_operation(context: Context) -> None:
|
||||
account,
|
||||
{
|
||||
"ui": "dui3",
|
||||
"hostAppVersion": ",".join(map(str, bl_info["blender"])),
|
||||
"core_version": ",".join(map(str, bl_info["version"])),
|
||||
"hostAppVersion": ".".join(map(str, bl_info["blender"])),
|
||||
"core_version": ".".join(map(str, bl_info["version"])),
|
||||
"sourceHostApp": host_applications.get_host_app_from_string(
|
||||
version.source_application
|
||||
).slug,
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
import bpy
|
||||
from bpy.types import Context, Collection as BlenderCollection
|
||||
from typing import List, Optional, Dict, Tuple
|
||||
|
||||
from specklepy.objects import Base
|
||||
from specklepy.objects.models.collections.collection import Collection
|
||||
from specklepy.core.api import operations
|
||||
from specklepy.core.api.client import SpeckleClient
|
||||
from specklepy.transports.server import ServerTransport
|
||||
from specklepy.core.api.inputs.version_inputs import CreateVersionInput
|
||||
from specklepy.core.api.credentials import get_local_accounts
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
from ...converter.to_speckle import convert_to_speckle
|
||||
from ...converter.to_speckle.material_to_speckle import (
|
||||
add_render_material_proxies_to_base,
|
||||
)
|
||||
from ...converter.utils import get_project_workspace_id
|
||||
from specklepy.logging import metrics
|
||||
from ... import bl_info
|
||||
|
||||
|
||||
def publish_operation(
|
||||
context: Context, objects_to_convert: List
|
||||
) -> Tuple[bool, str, Optional[str]]:
|
||||
"""
|
||||
publish objects to speckle
|
||||
"""
|
||||
wm = context.window_manager
|
||||
|
||||
try:
|
||||
# get account and authenticate
|
||||
account = next(
|
||||
(acc for acc in get_local_accounts() if acc.id == wm.selected_account_id),
|
||||
None,
|
||||
)
|
||||
|
||||
if account is None:
|
||||
return False, "No Speckle account found", None
|
||||
|
||||
client = SpeckleClient(host=account.serverInfo.url)
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
transport = ServerTransport(stream_id=wm.selected_project_id, client=client)
|
||||
|
||||
# build collection hierarchy and convert objects
|
||||
root_collection = build_collection_hierarchy(context, objects_to_convert)
|
||||
|
||||
if not root_collection:
|
||||
return False, "No objects could be converted to Speckle format", None
|
||||
|
||||
# add material proxies
|
||||
add_render_material_proxies_to_base(root_collection, objects_to_convert)
|
||||
|
||||
obj_id = operations.send(root_collection, [transport])
|
||||
|
||||
version_input = CreateVersionInput(
|
||||
objectId=obj_id,
|
||||
modelId=wm.selected_model_id,
|
||||
projectId=wm.selected_project_id,
|
||||
message="",
|
||||
sourceApplication="blender",
|
||||
)
|
||||
|
||||
version = client.version.create(version_input)
|
||||
version_id = version.id
|
||||
|
||||
# track metrics
|
||||
metrics.set_host_app("blender")
|
||||
metrics.track(
|
||||
metrics.SEND,
|
||||
account,
|
||||
{
|
||||
"ui": "dui3",
|
||||
"hostAppVersion": ".".join(map(str, bl_info["blender"])),
|
||||
"core_version": ".".join(map(str, bl_info["version"])),
|
||||
"workspace_id": get_project_workspace_id(
|
||||
client, wm.selected_project_id
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
# count total objects for success message
|
||||
total_objects = count_objects_in_collection(root_collection)
|
||||
|
||||
return (
|
||||
True,
|
||||
f"Successfully published {total_objects} objects with hierarchy to Speckle",
|
||||
version_id,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return False, f"Failed to publish: {str(e)}", None
|
||||
|
||||
|
||||
def build_collection_hierarchy(
|
||||
context: Context, objects_to_convert: List
|
||||
) -> Optional[Collection]:
|
||||
"""
|
||||
build a speckle collection hierarchy that mimicks blender's collection structure
|
||||
"""
|
||||
# set name for root collection
|
||||
file_name = bpy.path.basename(bpy.data.filepath)
|
||||
collection_name = file_name if file_name else "Untitled.blend"
|
||||
|
||||
collection_data = analyze_collection_structure(objects_to_convert)
|
||||
|
||||
if not collection_data["objects"] and not collection_data["collections"]:
|
||||
return None
|
||||
|
||||
converted_objects = convert_selected_objects(context, objects_to_convert)
|
||||
if not converted_objects:
|
||||
return None
|
||||
|
||||
# create the root Speckle collection
|
||||
root_collection = Collection(name=collection_name)
|
||||
root_collection.units = get_scene_units(context.scene).value
|
||||
root_collection["version"] = 3
|
||||
|
||||
# maps Blender collection to Speckle collection
|
||||
collection_mapping = {} #
|
||||
|
||||
# create Speckle collections for each blender collection
|
||||
for blender_coll in collection_data["collections"]:
|
||||
speckle_coll = Collection(name=blender_coll.name)
|
||||
speckle_coll.units = root_collection.units
|
||||
collection_mapping[blender_coll] = speckle_coll
|
||||
|
||||
for blender_coll in collection_data["collections"]:
|
||||
speckle_coll = collection_mapping[blender_coll]
|
||||
|
||||
parent_coll = find_parent_collection(
|
||||
blender_coll, collection_data["collections"]
|
||||
)
|
||||
|
||||
if parent_coll and parent_coll in collection_mapping:
|
||||
parent_speckle_coll = collection_mapping[parent_coll]
|
||||
parent_speckle_coll.elements.append(speckle_coll)
|
||||
else:
|
||||
root_collection.elements.append(speckle_coll)
|
||||
|
||||
# assign objects to their collections
|
||||
object_mapping = {}
|
||||
for i, blender_obj in enumerate(objects_to_convert):
|
||||
if i < len(converted_objects) and converted_objects[i] is not None:
|
||||
object_mapping[blender_obj] = converted_objects[i]
|
||||
|
||||
for blender_obj, speckle_obj in object_mapping.items():
|
||||
placed = False
|
||||
|
||||
target_collection = find_target_collection_for_object(
|
||||
blender_obj, collection_data["collections"]
|
||||
)
|
||||
|
||||
if target_collection and target_collection in collection_mapping:
|
||||
collection_mapping[target_collection].elements.append(speckle_obj)
|
||||
placed = True
|
||||
|
||||
# if not placed in any subcollection, add to root
|
||||
if not placed:
|
||||
root_collection.elements.append(speckle_obj)
|
||||
|
||||
return root_collection
|
||||
|
||||
|
||||
def analyze_collection_structure(objects: List) -> Dict:
|
||||
"""
|
||||
analyze the collection structure of the given objects
|
||||
"""
|
||||
collections_set = set()
|
||||
objects_collections = {}
|
||||
|
||||
direct_collections = set()
|
||||
for obj in objects:
|
||||
obj_collections = []
|
||||
for collection in bpy.data.collections:
|
||||
if obj.name in collection.objects:
|
||||
direct_collections.add(collection)
|
||||
obj_collections.append(collection)
|
||||
objects_collections[obj] = obj_collections
|
||||
|
||||
# find all ancestor collections
|
||||
def find_all_ancestors(collection):
|
||||
"""recursively find all ancestor collections"""
|
||||
ancestors = set()
|
||||
|
||||
for potential_parent in bpy.data.collections:
|
||||
if collection.name in potential_parent.children:
|
||||
ancestors.add(potential_parent)
|
||||
# Recursively find ancestors of the parent
|
||||
ancestors.update(find_all_ancestors(potential_parent))
|
||||
|
||||
return ancestors
|
||||
|
||||
for collection in direct_collections:
|
||||
collections_set.add(collection)
|
||||
ancestors = find_all_ancestors(collection)
|
||||
collections_set.update(ancestors)
|
||||
|
||||
collections_list = list(collections_set)
|
||||
collections_list.sort(key=lambda c: get_collection_depth(c))
|
||||
|
||||
return {
|
||||
"collections": collections_list,
|
||||
"objects": objects,
|
||||
"object_collections": objects_collections,
|
||||
}
|
||||
|
||||
|
||||
def get_collection_depth(collection: BlenderCollection) -> int:
|
||||
"""
|
||||
get the depth of a collection in the hierarchy
|
||||
"""
|
||||
depth = 0
|
||||
for scene in bpy.data.scenes:
|
||||
if collection.name in scene.collection.children:
|
||||
return depth
|
||||
|
||||
for parent_coll in bpy.data.collections:
|
||||
if collection.name in parent_coll.children:
|
||||
return get_collection_depth(parent_coll) + 1
|
||||
|
||||
return depth
|
||||
|
||||
|
||||
def find_parent_collection(
|
||||
collection: BlenderCollection, all_collections: List[BlenderCollection]
|
||||
) -> Optional[BlenderCollection]:
|
||||
"""
|
||||
find the parent collection
|
||||
"""
|
||||
for potential_parent in all_collections:
|
||||
if collection.name in potential_parent.children:
|
||||
return potential_parent
|
||||
return None
|
||||
|
||||
|
||||
def find_target_collection_for_object(
|
||||
obj, collections: List[BlenderCollection]
|
||||
) -> Optional[BlenderCollection]:
|
||||
"""
|
||||
find the deepest collection that contains this object
|
||||
"""
|
||||
target_collection = None
|
||||
max_depth = -1
|
||||
|
||||
for collection in collections:
|
||||
if obj.name in collection.objects:
|
||||
depth = get_collection_depth(collection)
|
||||
if depth > max_depth:
|
||||
max_depth = depth
|
||||
target_collection = collection
|
||||
|
||||
return target_collection
|
||||
|
||||
|
||||
def convert_selected_objects(
|
||||
context: Context, objects_to_convert: List
|
||||
) -> List[Optional[Base]]:
|
||||
"""
|
||||
convert selected objects to Speckle format with proper units
|
||||
"""
|
||||
scene = context.scene
|
||||
units = get_scene_units(scene)
|
||||
scale_factor = scene.unit_settings.scale_length
|
||||
|
||||
speckle_objects = []
|
||||
for obj in objects_to_convert:
|
||||
if not obj or obj.type not in ["MESH", "CURVE", "EMPTY"]:
|
||||
speckle_objects.append(None)
|
||||
continue
|
||||
|
||||
speckle_obj = convert_to_speckle(obj, scale_factor, units.value)
|
||||
speckle_objects.append(speckle_obj)
|
||||
|
||||
return speckle_objects
|
||||
|
||||
|
||||
def get_scene_units(scene) -> Units:
|
||||
"""
|
||||
get units from Blender's unit system
|
||||
"""
|
||||
unit_settings = scene.unit_settings
|
||||
|
||||
if unit_settings.system == "METRIC":
|
||||
if unit_settings.length_unit == "METERS":
|
||||
return Units.m
|
||||
elif unit_settings.length_unit == "CENTIMETERS":
|
||||
return Units.cm
|
||||
elif unit_settings.length_unit == "MILLIMETERS":
|
||||
return Units.mm
|
||||
elif unit_settings.length_unit == "KILOMETERS":
|
||||
return Units.km
|
||||
else:
|
||||
return Units.m
|
||||
elif unit_settings.system == "IMPERIAL":
|
||||
if unit_settings.length_unit == "FEET":
|
||||
return Units.feet
|
||||
elif unit_settings.length_unit == "INCHES":
|
||||
return Units.inches
|
||||
elif unit_settings.length_unit == "YARDS":
|
||||
return Units.yards
|
||||
elif unit_settings.length_unit == "MILES":
|
||||
return Units.miles
|
||||
else:
|
||||
return Units.feet
|
||||
else:
|
||||
return Units.m # default to meters
|
||||
|
||||
|
||||
def count_objects_in_collection(collection: Collection) -> int:
|
||||
"""
|
||||
recursively count all objects in a collection and its sub-collections
|
||||
"""
|
||||
count = 0
|
||||
if hasattr(collection, "elements"):
|
||||
for element in collection.elements:
|
||||
if isinstance(element, Collection):
|
||||
count += count_objects_in_collection(element)
|
||||
else:
|
||||
count += 1
|
||||
return count
|
||||
+28
-47
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Provides uniform and consistent path helpers for `specklepy`
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -55,9 +56,7 @@ def user_application_data_path() -> Path:
|
||||
if sys.platform.startswith("win"):
|
||||
app_data_path = os.getenv("APPDATA")
|
||||
if not app_data_path:
|
||||
raise Exception(
|
||||
"Cannot get appdata path from environment."
|
||||
)
|
||||
raise Exception("Cannot get appdata path from environment.")
|
||||
return Path(app_data_path)
|
||||
else:
|
||||
# try getting the standard XDG_DATA_HOME value
|
||||
@@ -68,9 +67,7 @@ def user_application_data_path() -> Path:
|
||||
else:
|
||||
return _ensure_folder_exists(Path.home(), ".config")
|
||||
except Exception as ex:
|
||||
raise Exception(
|
||||
"Failed to initialize user application data path.", ex
|
||||
)
|
||||
raise Exception("Failed to initialize user application data path.", ex)
|
||||
|
||||
|
||||
def user_speckle_folder_path() -> Path:
|
||||
@@ -90,19 +87,16 @@ def user_speckle_connector_installation_path(host_application: str) -> Path:
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
print("Starting module dependency installation")
|
||||
print(sys.executable)
|
||||
|
||||
PYTHON_PATH = sys.executable
|
||||
|
||||
|
||||
|
||||
def connector_installation_path(host_application: str) -> Path:
|
||||
connector_installation_path = user_speckle_connector_installation_path(host_application)
|
||||
connector_installation_path = user_speckle_connector_installation_path(
|
||||
host_application
|
||||
)
|
||||
connector_installation_path.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
# set user modules path at beginning of paths for earlier hit
|
||||
@@ -113,7 +107,6 @@ def connector_installation_path(host_application: str) -> Path:
|
||||
return connector_installation_path
|
||||
|
||||
|
||||
|
||||
def is_pip_available() -> bool:
|
||||
try:
|
||||
import_module("pip") # noqa F401
|
||||
@@ -132,25 +125,9 @@ def ensure_pip() -> None:
|
||||
if completed_process.returncode == 0:
|
||||
print("Successfully installed pip")
|
||||
else:
|
||||
raise Exception(f"Failed to install pip, got {completed_process.returncode} return code")
|
||||
|
||||
|
||||
def is_uv_available() -> bool:
|
||||
try:
|
||||
import_module("uv") # noqa F401
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
|
||||
def ensure_uv() -> None:
|
||||
print("Installing uv... ")
|
||||
from subprocess import run
|
||||
completed_process = run([PYTHON_PATH, "-m", "pip", "install", "uv"])
|
||||
if completed_process.returncode == 0:
|
||||
print("Successfully installed uv")
|
||||
else:
|
||||
raise Exception(f"Failed to install uv, got {completed_process.returncode} return code")
|
||||
raise Exception(
|
||||
f"Failed to install pip, got {completed_process.returncode} return code"
|
||||
)
|
||||
|
||||
|
||||
def get_requirements_path() -> Path:
|
||||
@@ -169,12 +146,12 @@ def install_requirements(host_application: str) -> None:
|
||||
|
||||
def debugger_is_active() -> bool:
|
||||
"""Return if the debugger is currently active"""
|
||||
return hasattr(sys, 'gettrace') and sys.gettrace() is not None
|
||||
|
||||
return hasattr(sys, "gettrace") and sys.gettrace() is not None
|
||||
|
||||
requirements_path = get_requirements_path()
|
||||
|
||||
is_debug = debugger_is_active()
|
||||
|
||||
|
||||
if not is_debug and not requirements_path.exists():
|
||||
print("Skipped installing dependencies")
|
||||
return
|
||||
@@ -184,11 +161,15 @@ def install_requirements(host_application: str) -> None:
|
||||
[
|
||||
PYTHON_PATH,
|
||||
"-m",
|
||||
"uv",
|
||||
"pip",
|
||||
"-q",
|
||||
"--disable-pip-version-check",
|
||||
"install",
|
||||
"--system",
|
||||
"--target",
|
||||
"--prefer-binary",
|
||||
"--ignore-installed",
|
||||
"--no-compile",
|
||||
"--no-deps",
|
||||
"-t",
|
||||
str(path),
|
||||
"-r",
|
||||
str(requirements_path),
|
||||
@@ -198,10 +179,12 @@ def install_requirements(host_application: str) -> None:
|
||||
)
|
||||
|
||||
if completed_process.returncode != 0:
|
||||
m = f"Failed to install dependencies through uv, got {completed_process.returncode} return code"
|
||||
print(completed_process.stdout)
|
||||
print(completed_process.stderr)
|
||||
m = f"Failed to install dependencies through pip, got {completed_process.returncode} return code"
|
||||
print(m)
|
||||
raise Exception(m)
|
||||
|
||||
|
||||
print("Successfully installed dependencies")
|
||||
|
||||
if not is_debug:
|
||||
@@ -211,9 +194,6 @@ def install_requirements(host_application: str) -> None:
|
||||
def install_dependencies(host_application: str) -> None:
|
||||
if not is_pip_available():
|
||||
ensure_pip()
|
||||
|
||||
if not is_uv_available():
|
||||
ensure_uv()
|
||||
|
||||
install_requirements(host_application)
|
||||
|
||||
@@ -223,7 +203,7 @@ def _import_dependencies() -> None:
|
||||
# the code above doesn't work for now, it fails on importing graphql-core
|
||||
# despite that, the connector seams to be working as expected
|
||||
# But it would be nice to make this solution work
|
||||
# it would ensure that all dependencies are fully loaded
|
||||
# it would ensure that all dependencies are fully loaded
|
||||
# requirements = get_requirements_path().read_text()
|
||||
# reqs = [
|
||||
# req.split(" ; ")[0].split("==")[0].split("[")[0].replace("-", "_")
|
||||
@@ -234,6 +214,7 @@ def _import_dependencies() -> None:
|
||||
# print(req)
|
||||
# import_module("specklepy")
|
||||
|
||||
|
||||
def ensure_dependencies(host_application: str) -> None:
|
||||
try:
|
||||
install_dependencies(host_application)
|
||||
@@ -241,6 +222,6 @@ def ensure_dependencies(host_application: str) -> None:
|
||||
_import_dependencies()
|
||||
print("Successfully found dependencies")
|
||||
except ImportError:
|
||||
raise Exception(f"Cannot automatically ensure Speckle dependencies. Please try restarting the host application {host_application}!")
|
||||
|
||||
|
||||
raise Exception(
|
||||
f"Cannot automatically ensure Speckle dependencies. Please try restarting the host application {host_application}!"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user