jesterKing / import_3dm

Blender importer script for Rhinoceros 3D files
MIT License
302 stars 38 forks source link

Update materials option + automatically install rhino3dm #29

Closed tsvilans closed 5 years ago

tsvilans commented 5 years ago

Update materials option

Adds option to update previously imported materials.

detailed explanation

Automatically install dependencies

Tries to import rhino3dm and installs it using pip if it fails.

detailed explanation

tsvilans commented 5 years ago

Hi @jesterKing , tried to make a PR for the dependencies stuff and realized an older one was still open. Take a look and comment when you have a free moment...

jesterKing commented 5 years ago

Thanks, I'll check them when at my machine later today.

jesterKing commented 5 years ago

Oh! This one slipped my attention. As said, I'll check it later today. Thank you for the reminder, and sorry I forgot

jesterKing commented 5 years ago

With a fresh Blender build on Linux the dependency install isn't working yet. Investigating.

jesterKing commented 5 years ago

@tsvilans I have been trying to get this to work on my linux box as well. I got as far as having the pip dependency installed, but the pip install of rhino3dm fails.

The error that persists is:

Could not install packages due to an EnvironmentError: [Errno 2] No such file or directory: '/tmp/pip-req-tracker-m_hc2_0n/bcfbd6ee60f98835718df79377a8340417530d1465879ee428765018'

This is the patch sofar. It improves the way pip dependency is satisfied so that it works at least also on my Linux box. - please review and apply the changes that also work still on your machine:

index b3cdfd9..ea92404 100644
--- a/import_3dm/read3dm.py
+++ b/import_3dm/read3dm.py
@@ -24,27 +24,44 @@ import os.path
 import bpy

 def install_dependencies():
+    import sys
+    import os
     try:
         try:
             import pip
         except:
             from subprocess import call
             print("Installing pip... "),
-            res = call("{} -m ensurepip".format(bpy.app.binary_path_python))
+            ensurepip = os.path.normpath(
+                    os.path.join(
+                        os.path.dirname(bpy.app.binary_path_python),
+                        "../lib/python{}.{}/ensurepip".format(
+                            sys.version_info.major,
+                            sys.version_info.minor)
+                        )
+                    )
+            res = call(bpy.app.binary_path_python, ensurepip)

             if res == 0:
                 import pip
             else:
                 raise Exception("Failed to install pip.")

-        print("Installing rhino3dm... "),
+        modulespath = os.path.normpath(
+                os.path.join(
+                    bpy.utils.script_path_user(),
+                    "addons/modules"
+                    )
+        if not os.path.exists(modulespath):
+           os.makedirs(modulespath) 
+        print("Installing rhino3dm to {}... ".format(modulespath)),

         try:
             from pip import main as pipmain
         except:
             from pip._internal import main as pipmain

-        res = pipmain(["install", "rhino3dm"])
+        res = pipmain(["install", "--upgrade", "--target", modulespath, "rhino3dm"])
         if res > 0:
             raise Exception("Failed to install rhino3dm.")
     except:
jesterKing commented 5 years ago

I just realise that in my code the os.sep should probably be used instead of forward slash.

tsvilans commented 5 years ago

Hey @jesterKing , did you try running Blender with administrator privileges? I noticed I needed those to install anything via pip through Blender.

Then again... that whole thing must be different on Linux... I have very limited LInux knowledge :/

tsvilans commented 5 years ago
index b3cdfd9..ea92404 100644
--- a/import_3dm/read3dm.py
+++ b/import_3dm/read3dm.py
@@ -24,27 +24,44 @@ import os.path
 import bpy

 def install_dependencies():
+    import sys
+    import os
     try:
         try:
             import pip
         except:
             from subprocess import call
             print("Installing pip... "),
-            res = call("{} -m ensurepip".format(bpy.app.binary_path_python))
+            ensurepip = os.path.normpath(
+                    os.path.join(
+                        os.path.dirname(bpy.app.binary_path_python),
+                        "../lib/python{}.{}/ensurepip".format(
+                            sys.version_info.major,
+                            sys.version_info.minor)
+                        )
+                    )

This expands to something liketh 2.80/python/lib/python3.7/ensurepip. On Windows, there is no python3.7 folder or similar.. the libs folder contains ensurepip directly. Should we put an OS check?

            res = call(bpy.app.binary_path_python, ensurepip)

             if res == 0:
                 import pip
             else:
                 raise Exception("Failed to install pip.")

-        print("Installing rhino3dm... "),
+        modulespath = os.path.normpath(
+                os.path.join(
+                    bpy.utils.script_path_user(),
+                    "addons/modules"
+                    )
+        if not os.path.exists(modulespath):
+           os.makedirs(modulespath) 
+        print("Installing rhino3dm to {}... ".format(modulespath)),

This is so that rhino3dm gets installed to a separate modules directory? I wasn't aware of that, I thought it always just went into the site-packages folder in the Python dir. Nice!

tsvilans commented 5 years ago

I just realise that in my code the os.sep should probably be used instead of forward slash.

os.path.join takes a list too, so you could just do:

                os.path.join(
                    os.path.dirname(bpy.app.binary_path_python),
                    "..",
                    "lib",
                    "python{}.{}".format(
                        sys.version_info.major,
                        sys.version_info.minor),
                    "ensurepip"
                 )
jesterKing commented 5 years ago

@tsvilans it is not necessary to run Blender with elevated access on Linux. The changes I propose also try to install in the user add-ons folder, which is either under %APPDATA% or under .config/, which is all writable by the user (or should be anyway).

I haven't managed to find a fix for the problem with the pip install command from Blender. On the command-line it all worked correctly.

tsvilans commented 5 years ago

@tsvilans it is not necessary to run Blender with elevated access on Linux. The changes I propose also try to install in the user add-ons folder, which is either under %APPDATA% or under .config/, which is all writable by the user (or should be anyway).

I haven't managed to find a fix for the problem with the pip install command from Blender. On the command-line it all worked correctly.

@jesterKing Yeah, installing to the user add-ons / modules folder sounds much better. Good to keep extra modules separate from the Blender Python stuff.

Regarding the pip install problem, is this still only on Linux?

I found some links to similar problems:

tsvilans commented 5 years ago

which is either under %APPDATA% or under .config/, which is all writable by the user (or should be anyway).

@jesterKing Also, do you know if this is stored in Blender somewhere? If there was a variable similar to bpy.app.binary_path_python - something like bpy.app.user_data_directory - that would be safer.

tsvilans commented 5 years ago

which is either under %APPDATA% or under .config/, which is all writable by the user (or should be anyway).

@jesterKing Also, do you know if this is stored in Blender somewhere? If there was a variable similar to bpy.app.binary_path_python - something like bpy.app.user_data_directory - that would be safer.

@jesterKing found it: bpy.utils.resource_path('USER') -> C:\\Users\\tsvi\\AppData\\Roaming\\Blender Foundation\\Blender\\2.80

Or even better: bpy.utils.script_path_user() -> C:\\Users\\tsvi\\AppData\\Roaming\\Blender Foundation\\Blender\\2.80\\scripts

Sorry for spamming you this morning...

jesterKing commented 5 years ago

@jesterKing found it: bpy.utils.resource_path('USER')

I used bpy.utils.script_path_user() in my diff. I suppose resource_path('USER')  works too, maybe they even translate to the same.

I found some links to similar problems

Thanks, going through them.

tsvilans commented 5 years ago

I used bpy.utils.script_path_user() in my diff. I suppose resource_path('USER') works too, maybe they even translate to the same.

My bad, I didn't catch this. Yep, you're already using the right one. Sorry!

jesterKing commented 5 years ago

To get this forward and finally merged, could you please put the automatic dependency installation code behind a platform specific check? For now it should be ok to have that only on Windows, where there is probably the most benefit gained anyway.

tsvilans commented 5 years ago

@jesterKing Ok, that should hopefully do it..

Does your patch get added in as well?

jesterKing commented 5 years ago

@tsvilans I am trying to get it to run, but on my windows box I see this error:

res = call(bpy.app.binary_path_python, ensurepip) Traceback (most recent call last): File "", line 1, in File "C:\Users\Nathan\PortableApps\blender-2.80-windows64\2.80\python\lib\subprocess.py", line 304, in call with Popen(*popenargs, **kwargs) as p: File "C:\Users\Nathan\PortableApps\blender-2.80-windows64\2.80\python\lib\subprocess.py", line 658, in init raise TypeError("bufsize must be an integer") TypeError: bufsize must be an integer

FWIW the current patch for read3dm.py I am working with is


@@ -22,31 +22,55 @@

 import os.path
 import bpy

 def install_dependencies():
+    import sys
+    import os
     try:
         try:
             import pip
         except:
             from subprocess import call
             print("Installing pip... "),
-            res = call("{} -m ensurepip".format(bpy.app.binary_path_python))
+            pyver = ""
+            if sys.platform != "win32":
+                pyver = "python{}.{}".format(
+                    sys.version_info.major,
+                    sys.version_info.minor
+                )
+
+            ensurepip = os.path.normpath(
+                os.path.join(
+                    os.path.dirname(bpy.app.binary_path_python),
+                    "..", "lib", pyver, "ensurepip"
+                )
+            )
+            res = call(bpy.app.binary_path_python, ensurepip)

             if res == 0:
                 import pip
             else:
                 raise Exception("Failed to install pip.")

-        print("Installing rhino3dm... "),
+        modulespath = os.path.normpath(
+            os.path.join(
+                bpy.utils.script_path_user(),
+                "addons",
+                "modules"
+            )
+        )
+        if not os.path.exists(modulespath):
+           os.makedirs(modulespath) 
+        print("Installing rhino3dm to {}... ".format(modulespath)),

         try:
             from pip import main as pipmain
         except:
             from pip._internal import main as pipmain

-        res = pipmain(["install", "rhino3dm"])
+        res = pipmain(["install", "--upgrade", "--target", modulespath, "rhino3dm"])
         if res > 0:
             raise Exception("Failed to install rhino3dm.")
     except:
         raise Exception("Failed to install dependencies. Please make sure you have pip installed.")

Does this work for you?

jesterKing commented 5 years ago

There, I managed to fix this and have pip  and rhino3dm nicely install automatically on Windows.

We can use this as basis for getting something similar to work on Linux and MacOS.

@tsvilans thanks for your patch! And apologies it took so long to get it done.