18
18
19
19
import os
20
20
import sys
21
- import subprocess
22
21
23
22
from . import (
24
23
engine , nodes , properties , operators , ui
25
24
)
26
25
26
+ from .utils import pip_ensure , pip_has_package , pip_install_package , pip_is_package_up_to_date
27
+
27
28
def get_addon_preferences (context ):
28
29
return context .preferences .addons [__name__ ].preferences
29
30
30
- def init_mitsuba (context ):
31
- # Make sure we can load mitsuba from blender
31
+ def get_addon_version_string ():
32
+ return f'{ "." .join (str (e ) for e in bl_info ["version" ])} { bl_info ["warning" ] if "warning" in bl_info else "" } '
33
+
34
+ def get_mitsuba_version_string ():
35
+ import mitsuba
36
+ return mitsuba .__version__
37
+
38
+ def get_addon_info_string ():
39
+ return f'mitsuba-blender v{ get_addon_version_string ()} registered (with mitsuba v{ get_mitsuba_version_string ()} )'
40
+
41
+ def init_mitsuba ():
42
+ # Make sure we can load Mitsuba from Blender
32
43
try :
33
44
should_reload_mitsuba = 'mitsuba' in sys .modules
34
45
import mitsuba
35
- # If mitsuba was already loaded and we change the path, we need to reload it, since the import above will be ignored
46
+ # If Mitsuba was already loaded and we change the path, we need to reload it, since the import above will be ignored
36
47
if should_reload_mitsuba :
37
48
import importlib
38
49
importlib .reload (mitsuba )
@@ -44,29 +55,26 @@ def init_mitsuba(context):
44
55
except ModuleNotFoundError :
45
56
return False
46
57
47
- def try_register_mitsuba (context ):
58
+ def register_addon (context ):
48
59
prefs = get_addon_preferences (context )
49
- prefs .mitsuba_dependencies_status_message = ''
60
+ prefs .status_message = ''
50
61
51
- could_init_mitsuba = False
52
62
if prefs .using_mitsuba_custom_path :
53
- update_additional_custom_paths (prefs , context )
54
- could_init_mitsuba = init_mitsuba (context )
63
+ prefs . update_additional_custom_paths (context )
64
+ could_init_mitsuba = init_mitsuba ()
55
65
prefs .has_valid_mitsuba_custom_path = could_init_mitsuba
56
66
if could_init_mitsuba :
57
- import mitsuba
58
- prefs .mitsuba_dependencies_status_message = f'Found custom Mitsuba v{ mitsuba .__version__ } .'
67
+ prefs .status_message = f'Found custom Mitsuba v{ get_mitsuba_version_string ()} .'
59
68
else :
60
- prefs .mitsuba_dependencies_status_message = 'Failed to load custom Mitsuba. Please verify the path to the build directory.'
61
- elif prefs .has_pip_package :
62
- could_init_mitsuba = init_mitsuba (context )
69
+ prefs .status_message = 'Failed to load custom Mitsuba. Please verify the path to the build directory.'
70
+ elif prefs .is_mitsuba_installed :
71
+ could_init_mitsuba = init_mitsuba ()
63
72
if could_init_mitsuba :
64
- import mitsuba
65
- prefs .mitsuba_dependencies_status_message = f'Found pip Mitsuba v{ mitsuba .__version__ } .'
73
+ prefs .status_message = f'Found pip Mitsuba v{ get_mitsuba_version_string ()} .'
66
74
else :
67
- prefs .mitsuba_dependencies_status_message = 'Failed to load Mitsuba package.'
75
+ prefs .status_message = 'Failed to load Mitsuba package.'
68
76
else :
69
- prefs .mitsuba_dependencies_status_message = 'Mitsuba dependencies not installed.'
77
+ prefs .status_message = 'Mitsuba dependencies not installed.'
70
78
71
79
prefs .is_mitsuba_initialized = could_init_mitsuba
72
80
@@ -79,7 +87,7 @@ def try_register_mitsuba(context):
79
87
80
88
return could_init_mitsuba
81
89
82
- def try_unregister_mitsuba ():
90
+ def unregister_addon ():
83
91
'''
84
92
Try unregistering Addon classes.
85
93
This may fail if Mitsuba wasn't found, hence the try catch guard
@@ -94,108 +102,107 @@ def try_unregister_mitsuba():
94
102
except RuntimeError :
95
103
return False
96
104
97
- def try_reload_mitsuba (context ):
98
- try_unregister_mitsuba ()
99
- if try_register_mitsuba (context ):
105
+ def reload_addon (context ):
106
+ unregister_addon ()
107
+ if register_addon (context ):
100
108
# Save user preferences
101
109
bpy .ops .wm .save_userpref ()
102
110
103
- def ensure_pip ():
104
- result = subprocess .run ([sys .executable , '-m' , 'ensurepip' ], capture_output = True )
105
- return result .returncode == 0
106
-
107
- def check_pip_dependencies (context ):
108
- prefs = get_addon_preferences (context )
109
- result = subprocess .run ([sys .executable , '-m' , 'pip' , 'show' , 'mitsuba' ], capture_output = True )
110
- prefs .has_pip_package = result .returncode == 0
111
-
112
- def clean_additional_custom_paths (self , context ):
113
- # Remove old values from system PATH and sys.path
114
- if self .additional_python_path in sys .path :
115
- sys .path .remove (self .additional_python_path )
116
- if self .additional_path and self .additional_path in os .environ ['PATH' ]:
117
- items = os .environ ['PATH' ].split (os .pathsep )
118
- items .remove (self .additional_path )
119
- os .environ ['PATH' ] = os .pathsep .join (items )
120
-
121
- def update_additional_custom_paths (self , context ):
122
- build_path = bpy .path .abspath (self .mitsuba_custom_path )
123
- if len (build_path ) > 0 :
124
- clean_additional_custom_paths (self , context )
125
-
126
- # Add path to the binaries to the system PATH
127
- self .additional_path = build_path
128
- if self .additional_path not in os .environ ['PATH' ]:
129
- os .environ ['PATH' ] += os .pathsep + self .additional_path
130
-
131
- # Add path to python libs to sys.path
132
- self .additional_python_path = os .path .join (build_path , 'python' )
133
- if self .additional_python_path not in sys .path :
134
- # NOTE: We insert in the first position here, so that the custom path
135
- # supersede the pip version
136
- sys .path .insert (0 , self .additional_python_path )
137
-
138
- class MITSUBA_OT_install_pip_dependencies (Operator ):
139
- bl_idname = 'mitsuba.install_pip_dependencies'
140
- bl_label = 'Install Mitsuba pip dependencies'
141
- bl_description = 'Use pip to install the add-on\' s required dependencies'
111
+ class MITSUBA_OT_download_package_dependencies (Operator ):
112
+ bl_idname = 'mitsuba.download_package_dependencies'
113
+ bl_label = 'Download the latest package dependencies'
114
+ bl_description = 'Use pip to download the add-on\' s latest required dependencies'
142
115
143
116
@classmethod
144
117
def poll (cls , context ):
145
118
prefs = get_addon_preferences (context )
146
- return not prefs .has_pip_package
119
+ return not prefs .is_mitsuba_installed or not prefs . is_mitsuba_uptodate
147
120
148
121
def execute (self , context ):
149
- result = subprocess .run ([sys .executable , '-m' , 'pip' , 'install' , 'mitsuba' ], capture_output = True )
150
- if result .returncode != 0 :
151
- self .report ({'ERROR' }, f'Failed to install Mitsuba with return code { result .returncode } .' )
122
+ if not pip_install_package ('mitsuba' ):
123
+ self .report ({'ERROR' }, 'Failed to download Mitsuba package with pip.' )
152
124
return {'CANCELLED' }
153
125
154
126
prefs = get_addon_preferences (context )
155
- prefs .has_pip_package = True
156
-
157
- try_reload_mitsuba (context )
127
+ if prefs .is_mitsuba_initialized and not prefs .is_mitsuba_uptodate :
128
+ # If the package was updated, require a restart to use the new version.
129
+ prefs .is_restart_required = True
130
+ else :
131
+ reload_addon (context )
132
+
133
+ prefs .is_mitsuba_installed = True
134
+ prefs .is_mitsuba_uptodate = True
158
135
159
136
return {'FINISHED' }
160
137
161
- def update_using_mitsuba_custom_path (self , context ):
162
- if self .is_mitsuba_initialized :
163
- self .require_restart = True
164
- if self .using_mitsuba_custom_path :
165
- update_mitsuba_custom_path (self , context )
166
- else :
167
- clean_additional_custom_paths (self , context )
168
-
169
- def update_mitsuba_custom_path (self , context ):
170
- if self .is_mitsuba_initialized :
171
- self .require_restart = True
172
- if self .using_mitsuba_custom_path and len (self .mitsuba_custom_path ) > 0 :
173
- update_additional_custom_paths (self , context )
174
- if not self .is_mitsuba_initialized :
175
- try_reload_mitsuba (context )
176
-
177
138
class MitsubaPreferences (AddonPreferences ):
178
139
bl_idname = __name__
179
140
180
141
is_mitsuba_initialized : BoolProperty (
181
142
name = 'Is Mitsuba initialized' ,
182
143
)
183
144
184
- has_pip_package : BoolProperty (
185
- name = 'Has pip dependencies installed' ,
145
+ is_mitsuba_installed : BoolProperty (
146
+ name = 'Is the Mitsuba package installed' ,
186
147
)
187
148
188
- mitsuba_dependencies_status_message : StringProperty (
189
- name = 'Mitsuba dependencies status message' ,
190
- default = '' ,
149
+ is_mitsuba_uptodate : BoolProperty (
150
+ name = 'Is the Mitsuba package up-to-date' ,
191
151
)
192
152
193
- require_restart : BoolProperty (
194
- name = 'Require a Blender restart' ,
153
+ is_restart_required : BoolProperty (
154
+ name = 'Is a Blender restart required' ,
155
+ )
156
+
157
+ status_message : StringProperty (
158
+ name = 'Add-on status message' ,
159
+ default = '' ,
195
160
)
196
161
197
162
# Advanced settings
198
163
164
+ def clean_additional_custom_paths (self , context ):
165
+ # Remove old values from system PATH and sys.path
166
+ if self .additional_python_path in sys .path :
167
+ sys .path .remove (self .additional_python_path )
168
+ if self .additional_path and self .additional_path in os .environ ['PATH' ]:
169
+ items = os .environ ['PATH' ].split (os .pathsep )
170
+ items .remove (self .additional_path )
171
+ os .environ ['PATH' ] = os .pathsep .join (items )
172
+
173
+ def update_additional_custom_paths (self , context ):
174
+ build_path = bpy .path .abspath (self .mitsuba_custom_path )
175
+ if len (build_path ) > 0 :
176
+ self .clean_additional_custom_paths (context )
177
+
178
+ # Add path to the binaries to the system PATH
179
+ self .additional_path = build_path
180
+ if self .additional_path not in os .environ ['PATH' ]:
181
+ os .environ ['PATH' ] += os .pathsep + self .additional_path
182
+
183
+ # Add path to python libs to sys.path
184
+ self .additional_python_path = os .path .join (build_path , 'python' )
185
+ if self .additional_python_path not in sys .path :
186
+ # NOTE: We insert in the first position here, so that the custom path
187
+ # supersede the pip version
188
+ sys .path .insert (0 , self .additional_python_path )
189
+
190
+ def update_mitsuba_custom_path (self , context ):
191
+ if self .is_mitsuba_initialized :
192
+ self .is_restart_required = True
193
+ if self .using_mitsuba_custom_path and len (self .mitsuba_custom_path ) > 0 :
194
+ self .update_additional_custom_paths (context )
195
+ if not self .is_mitsuba_initialized :
196
+ reload_addon (context )
197
+
198
+ def update_using_mitsuba_custom_path (self , context ):
199
+ if self .is_mitsuba_initialized :
200
+ self .is_restart_required = True
201
+ if self .using_mitsuba_custom_path :
202
+ self .update_mitsuba_custom_path (context )
203
+ else :
204
+ self .clean_additional_custom_paths (context )
205
+
199
206
using_mitsuba_custom_path : BoolProperty (
200
207
name = 'Using custom Mitsuba path' ,
201
208
update = update_using_mitsuba_custom_path ,
@@ -229,18 +236,23 @@ def draw(self, context):
229
236
layout = self .layout
230
237
231
238
row = layout .row ()
232
- if self .require_restart :
233
- self .mitsuba_dependencies_status_message = 'A restart is required to apply the changes.'
239
+ if self .is_restart_required :
240
+ self .status_message = 'A restart is required to apply the changes.'
234
241
row .alert = True
235
242
icon = 'ERROR'
236
- elif self .has_pip_package or self . has_valid_mitsuba_custom_path :
243
+ elif self .is_mitsuba_initialized :
237
244
icon = 'CHECKMARK'
238
245
else :
239
246
icon = 'CANCEL'
240
247
row .alert = True
241
- row .label (text = self .mitsuba_dependencies_status_message , icon = icon )
248
+ row .label (text = self .status_message , icon = icon )
242
249
243
- layout .operator (MITSUBA_OT_install_pip_dependencies .bl_idname , text = 'Install dependencies using pip' )
250
+ if not self .is_mitsuba_installed :
251
+ download_operator_text = 'Install Mitsuba'
252
+ else :
253
+ download_operator_text = 'Update Mitsuba'
254
+
255
+ layout .operator (MITSUBA_OT_download_package_dependencies .bl_idname , text = download_operator_text )
244
256
245
257
box = layout .box ()
246
258
box .label (text = 'Advanced Settings' )
@@ -249,27 +261,35 @@ def draw(self, context):
249
261
box .prop (self , 'mitsuba_custom_path' )
250
262
251
263
classes = (
252
- MITSUBA_OT_install_pip_dependencies ,
264
+ MITSUBA_OT_download_package_dependencies ,
253
265
MitsubaPreferences ,
254
266
)
255
267
256
268
def register ():
257
269
for cls in classes :
258
270
register_class (cls )
259
271
272
+ if not pip_ensure ():
273
+ raise RuntimeError ('Cannot activate mitsuba-blender add-on. Python pip module cannot be initialized.' )
274
+
260
275
context = bpy .context
261
276
prefs = get_addon_preferences (context )
262
- prefs .require_restart = False
277
+ prefs .is_mitsuba_initialized = False
278
+ prefs .is_mitsuba_uptodate = False
279
+ prefs .is_restart_required = False
280
+ prefs .has_valid_mitsuba_custom_path = False
281
+ prefs .is_mitsuba_installed = pip_has_package ('mitsuba' )
263
282
264
- if not ensure_pip () :
265
- raise RuntimeError ( 'Cannot activate mitsuba-blender add-on. Python pip module cannot be initialized. ' )
283
+ if prefs . is_mitsuba_installed :
284
+ prefs . is_mitsuba_uptodate = pip_is_package_up_to_date ( ' mitsuba' )
266
285
267
- check_pip_dependencies (context )
268
- if try_register_mitsuba (context ):
269
- import mitsuba
270
- print (f'mitsuba-blender v{ "." .join (str (e ) for e in bl_info ["version" ])} { bl_info ["warning" ] if "warning" in bl_info else "" } registered (with mitsuba v{ mitsuba .__version__ } )' )
286
+ if register_addon (context ):
287
+ print (get_addon_info_string ())
271
288
272
289
def unregister ():
273
290
for cls in classes :
274
291
unregister_class (cls )
275
- try_unregister_mitsuba ()
292
+ unregister_addon ()
293
+
294
+ if __name__ == '__main__' :
295
+ register ()
0 commit comments