-
Notifications
You must be signed in to change notification settings - Fork 233
[plugin] Draft: Develop Blender plugin #1309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0628f08
ef55258
cec3089
2eb2927
77f9717
444bbc8
8fa46dd
134c3ce
b4a21ab
2656031
6d12198
a58b3d8
9c3c0d0
4162226
a46f2bf
be6c6e5
f9b1ea7
96c45b0
9f4a668
f06019c
734c677
c1b7ea1
9b69961
2bdd58b
7cbb52e
370ff6c
fe2e08a
96608df
16e6c79
949cb2a
4f9f17a
27991c1
e102e7d
8e3e12d
fff01a8
1a63e39
8ae92ab
d84505f
43f125b
65726ef
b90a212
2485e66
a99108a
e7bf1c2
a1e922d
7b0f6df
018e604
1e4cd63
e384e3b
5a7c837
13c2959
258235e
aeee004
f7e6bfa
ae7faa7
d1dd023
7d47230
fb0a902
e0d34cc
3e95db6
a7b61fb
3b515c6
b657ea8
895b066
e4a176c
8dcdc31
e01daeb
44950d2
7fa8a25
f809128
b01400a
b05f70a
8c13425
0fde3e7
f87d65b
66cad82
4cea175
7d8518c
5c7870a
3cae77a
dd66ec9
4cc8bf5
5c36ced
fb3f6cb
bb099f8
e3f4aca
f545809
007e66e
1e5d8d3
e7bf2c6
128eeac
1248c5a
723f03a
da7104f
35f0410
d3a4d3f
2dcfd92
2830d51
18ae9df
f0a14fa
ba4fa97
8582eba
bf982bf
4268906
f8b4c9d
3207241
cf29086
06cf224
267d70e
2bf06f2
4b45c2d
9db01e8
3a8fe6a
d5263a7
d109eea
d9d915a
7fa2269
bc34849
021f527
6733231
9316aa7
54d2245
7529e81
d496c3e
cbd97b8
f07cd4b
794909c
becf3be
43075cb
59a18e4
0e1bf72
ae9b94b
6ed032c
4446292
469af67
c779c60
d08e1dc
604a44b
89e7963
4161535
9ad0725
17a6d18
3d31512
f9187f9
dc9d99b
31c2f97
d74905c
1f8d35c
fb62c9e
e81ed48
23a5114
2dda8c3
afe441e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| #!/bin/bash | ||
| # Copyright Contributors to the OpenCue Project | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| # OpenCue integration test script | ||
| # | ||
| # Stands up a clean environment using Docker compose and verifies all | ||
| # components are functioning as expected. | ||
| # | ||
| # Run with: | ||
| # ./ci/build_blender_addon.sh | ||
|
|
||
| set -e | ||
|
|
||
| PYOUTLINE_PATH="pyoutline/outline" | ||
| FILESEQUENCE_PATH="pycue/FileSequence" | ||
| OPENCUE_PATH="pycue/opencue" | ||
|
|
||
| ADDON_PATH="cuesubmit/plugins/blender/OpenCue-Blender/" | ||
|
|
||
| log() { | ||
| echo "$(date "+%Y-%m-%d %H:%M:%S") $1 $2" | ||
| } | ||
|
|
||
| copy_dependencies() { | ||
| DEPENDENCIES_PATH="${ADDON_PATH}/dependencies" | ||
| mkdir -p "${DEPENDENCIES_PATH}" | ||
| cp -r "${PYOUTLINE_PATH}" "${DEPENDENCIES_PATH}" | ||
| cp -r "${FILESEQUENCE_PATH}" "${DEPENDENCIES_PATH}" | ||
| cp -r "${OPENCUE_PATH}" "${DEPENDENCIES_PATH}" | ||
| } | ||
|
|
||
| main() { | ||
| log INFO "Copying dependencies into ${ADDON_PATH}" | ||
| copy_dependencies | ||
| # Check if script is running within GitHub Actions pipeline | ||
| if [ "$GITHUB_ACTIONS" == "true" ]; then | ||
| log INFO "Generating zip in ${GITHUB_WORKSPACE}/artifacts/" | ||
| mkdir -p "${GITHUB_WORKSPACE}/artifacts/" | ||
| cd cuesubmit/plugins/blender | ||
| zip -r "${GITHUB_WORKSPACE}/artifacts/OpenCue-Blender.zip" OpenCue-Blender/ | ||
| else | ||
| log INFO "Generating zip in cuesubmit/plugins/blender/" | ||
| cd cuesubmit/plugins/blender | ||
| zip -r "OpenCue-Blender.zip" OpenCue-Blender/ | ||
| fi | ||
| } | ||
|
|
||
| main | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -188,8 +188,40 @@ run_job() { | |
| log INFO "Job succeeded (PASS)" | ||
| } | ||
|
|
||
| run_blender_job() { | ||
| cp samples/pyoutline/sample.blend $RQD_ROOT | ||
| samples/pyoutline/blender_job.py | ||
| job_name="testing-shot02-${USER}_blender_job" | ||
| samples/pycue/wait_for_job.py "${job_name}" --timeout 300 | ||
| log INFO "Blender job succeeded (PASS)" | ||
| } | ||
|
|
||
| add_RQD_tag() { | ||
| container_id=$(docker ps --filter "name=blender" --format "{{.ID}}") | ||
| got_hosts=$(python -c 'import opencue; print([host.name() for host in opencue.api.getHosts()])') | ||
| host_exists=false | ||
| for host in $(echo "${got_hosts}" | tr -d '[],' | tr -d "'" | tr ' ' '\n'); do | ||
| if [[ "$host" == "$container_id" ]]; then | ||
| host_exists=true | ||
| break | ||
| fi | ||
| done | ||
|
|
||
| if [[ "$host_exists" == true ]]; then | ||
| log INFO "Adding tag to Blender RQD" | ||
| python -c "import opencue; import opencue.wrappers.host; \ | ||
| host=opencue.api.findHost('${container_id}'); \ | ||
| tags = ['blender']; \ | ||
| host.addTags(tags)" | ||
| else | ||
| log ERROR "Blender RQD not detected by Cuebot. Unable to add tag" | ||
| exit 1 | ||
| fi | ||
| } | ||
|
|
||
| cleanup() { | ||
| docker compose rm --stop --force >>"${DOCKER_COMPOSE_LOG}" 2>&1 | ||
| docker rm -f blender | ||
| rm -rf "${RQD_ROOT}" || true | ||
| rm -rf "${DB_DATA_DIR}" || true | ||
| rm -rf "${VENV}" || true | ||
|
|
@@ -265,6 +297,25 @@ main() { | |
|
|
||
| run_job | ||
|
|
||
| log INFO "Starting RQD Blender..." | ||
| docker run -td --name blender \ | ||
| --env CUEBOT_HOSTNAME=cuebot \ | ||
| --volume "/tmp/rqd/shots:/tmp/rqd/shots" \ | ||
| --volume "/tmp/rqd/logs:/tmp/rqd/logs" \ | ||
| -p 8441:8441 \ | ||
| --network opencue_default \ | ||
| opencue/blender | ||
|
|
||
| docker exec blender sh -c 'echo "RQD_USE_IP_AS_HOSTNAME=False" >> /etc/opencue/rqd.conf' | ||
| log INFO "Restarting RQD Blender..." | ||
| docker restart blender | ||
| sleep 3 | ||
|
|
||
| add_RQD_tag | ||
|
|
||
| log INFO "Testing Blender job..." | ||
| run_blender_job | ||
|
Comment on lines
+300
to
+317
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This PR ends up polluting the integration test script with several blender specific steps. I feel the changes are impactful enough that it makes sense to isolate blender in its own integration test. I suggest creating ci/run_blender_integration_test.sh to maintain the general integration test script simple. |
||
|
|
||
| cleanup | ||
|
|
||
| log INFO "Success" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # Copyright Contributors to the OpenCue Project | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import shutil | ||
| import sys | ||
| import subprocess | ||
| import os | ||
| import platform | ||
| import bpy | ||
|
|
||
| blender_dependencies_directory = "lib/python3.10/site-packages" | ||
| blender_dependencies_path = os.path.join(sys.prefix, blender_dependencies_directory) | ||
| opencue_blender_path = os.path.dirname(__file__) | ||
|
|
||
| pyoutline_path = os.path.join(opencue_blender_path, "dependencies", "outline") | ||
| filesequence_path = os.path.join(opencue_blender_path, "dependencies", "FileSequence") | ||
| opencue_path = os.path.join(opencue_blender_path, "dependencies", "opencue") | ||
|
|
||
| pyoutline_directory = "outline" | ||
| opencue_imported_directory = "opencue" | ||
| filesequence_imported_directory = "FileSequence" | ||
| pyoutline_directory_path = os.path.join(sys.prefix, blender_dependencies_directory, pyoutline_directory) | ||
| opencue_directory_path = os.path.join(sys.prefix, blender_dependencies_directory, opencue_imported_directory) | ||
| filesequence_directory_path = os.path.join(sys.prefix, blender_dependencies_directory, filesequence_imported_directory) | ||
|
|
||
|
|
||
| def isWindows(): | ||
| """Checks if host OS is Windows""" | ||
| return os.name == 'nt' | ||
|
|
||
|
|
||
| def isMacOS(): | ||
| """Checks if host OS is macOS""" | ||
| return os.name == 'posix' and platform.system() == "Darwin" | ||
|
|
||
|
|
||
| def isLinux(): | ||
| """Checks if host OS is Linux""" | ||
| return os.name == 'posix' and platform.system() == "Linux" | ||
|
Comment on lines
+38
to
+50
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
|
|
||
|
|
||
| def python_exec(): | ||
| """Sets Blender local python based on host OS""" | ||
| if isWindows(): | ||
| return os.path.join(sys.prefix, 'bin', 'python.exe') | ||
| elif isMacOS(): | ||
| try: | ||
| # 2.92 and older | ||
| path = bpy.app.binary_path_python | ||
| except AttributeError: | ||
| # 2.93 and later | ||
| path = sys.executable | ||
| return os.path.abspath(path) | ||
| elif isLinux(): | ||
| return os.path.join(sys.prefix, 'bin', 'python3.10') # Works on Blender 3.3.1 LTS | ||
| else: | ||
| print("sorry, still not implemented for ", os.name, " - ", platform.system) | ||
| return os.path.join(sys.prefix, 'sys.prefix/bin', 'python') | ||
|
|
||
|
|
||
| def installModule(): | ||
| """Installs all addon dependencies during registration""" | ||
| # Install OpenCue dependencies | ||
| installOpencueModules() | ||
|
|
||
| # installs External modules from requirements.txt file | ||
| installExternalModules() | ||
|
|
||
| print("\n----- OpenCue-Blender Installed Successfully -----") | ||
|
|
||
|
|
||
| def installExternalModules(): | ||
| """Installs externals dependencies onto Blender python environment with pip""" | ||
| # Get path of requirements file | ||
| script_file = os.path.realpath(__file__) | ||
| directory = os.path.dirname(script_file) | ||
| file_name = "requirements.txt" | ||
| requirements = os.path.join(directory, file_name) | ||
|
|
||
| # identify for platform | ||
| python_exe = python_exec() | ||
|
|
||
| # upgrade pip | ||
| print("\n----- Installing External Dependencies -----") | ||
| subprocess.call([python_exe, "-m", "ensurepip"]) | ||
| subprocess.call([python_exe, "-m", "pip", "install", "--upgrade", "pip"]) | ||
| # install required external packages | ||
| subprocess.call([python_exe, "-m", "pip", "install", "-r", requirements, "-t", blender_dependencies_path]) | ||
| print("\n----- External Dependencies Installed Successfully -----") | ||
|
|
||
|
|
||
| def installOpencueModules(): | ||
| """Installs OpenCue dependencies onto Blender python environment""" | ||
| print("----- Installing OpenCue Dependencies -----") | ||
| shutil.copytree(pyoutline_path, pyoutline_directory_path) | ||
| shutil.copytree(opencue_path, opencue_directory_path) | ||
| shutil.copytree(filesequence_path, filesequence_directory_path) | ||
| print("\n----- OpenCue Dependencies Installed Successfully -----") | ||
|
|
||
|
Comment on lines
+103
to
+110
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this will work when it's packaged up as a zip file. |
||
|
|
||
| def removeOpencueModules(): | ||
| """Removes OpenCue dependencies from Blender python environment""" | ||
| # remove installed opencue dependencies | ||
| shutil.rmtree(pyoutline_directory_path) | ||
| shutil.rmtree(opencue_directory_path) | ||
| shutil.rmtree(filesequence_directory_path) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| # Copyright Contributors to the OpenCue Project | ||
| # | ||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| # you may not use this file except in compliance with the License. | ||
| # You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, software | ||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| # See the License for the specific language governing permissions and | ||
| # limitations under the License. | ||
|
|
||
| import outline | ||
| import outline.cuerun | ||
| import outline.modules.shell | ||
|
|
||
| import bpy | ||
| import re | ||
|
|
||
| def buildBlenderCmd(layerData): | ||
| """Builds the Blender command from layerdata | ||
|
|
||
| @param layerData: layer data from the ui | ||
| """ | ||
| blenderFile = layerData.get('cmd').get('blenderFile') | ||
| outputPath = layerData.get('cmd').get('outputPath') | ||
| outputFormat = layerData.get('cmd').get('outputFormat') | ||
| frameRange = layerData.get('layerRange') | ||
|
|
||
| # Hardware use for rendering | ||
| addon_prefs = bpy.context.preferences.addons['OpenCue-Blender'].preferences | ||
| use_gpu = addon_prefs.use_gpu | ||
| if use_gpu: | ||
| renderHW = "CUDA" | ||
| else: | ||
| renderHW = "CPU" | ||
|
|
||
| if not blenderFile: | ||
| raise ValueError('No Blender file provided. Cannot submit job.') | ||
|
|
||
| renderCommand = '{renderCmd} -b -noaudio {blenderFile}'.format( | ||
| renderCmd="blender", blenderFile=blenderFile) | ||
| if outputPath: | ||
| renderCommand += ' -o {}'.format(outputPath) | ||
| if outputFormat: | ||
| renderCommand += ' -F {}'.format(outputFormat) | ||
| # Option to render still frame or animation must come after the scene and output | ||
| if re.match(r"^\d+-\d+$", frameRange): | ||
| renderCommand += ' -s {frameStart} -e {frameEnd} -a'.format( | ||
| frameStart="#FRAME_START#", | ||
| frameEnd="#FRAME_END#") | ||
| else: | ||
| renderCommand += ' -f {frameToken}'.format(frameToken="#IFRAME#") | ||
| renderCommand += ' -E CYCLES -- --cycles-device {renderHW}'.format( | ||
| renderHW=renderHW) | ||
| return renderCommand | ||
|
|
||
|
|
||
| def buildLayer(layerData, command, lastLayer=None): | ||
| """Creates a PyOutline Layer for the given layerData. | ||
|
|
||
| @type layerData: ui.Layer.LayerData | ||
| @param layerData: layer data from the ui | ||
| @type command: str | ||
| @param command: command to run | ||
| @type lastLayer: outline.layer.Layer | ||
| @param lastLayer: layer that this new layer should be dependent on if dependType is set. | ||
| """ | ||
| threadable = float(layerData.get('cores')) >= 2 | ||
| layer = outline.modules.shell.Shell( | ||
| layerData.get('name'), command=command.split(), chunk=layerData.get('chunk'), | ||
| threads=float(layerData.get('cores')), range=str(layerData.get('layerRange')), threadable=threadable) | ||
| if layerData.get('services'): | ||
| layer.set_service(layerData.services[0]) | ||
| if layerData.get('limits'): | ||
| layer.set_limits(layerData.limits) | ||
| if layerData.get('dependType') and lastLayer: | ||
| if layerData.dependType == 'Layer': | ||
| layer.depend_all(lastLayer) | ||
| else: | ||
| layer.depend_on(lastLayer) | ||
| return layer | ||
|
|
||
|
|
||
| def buildBlenderLayer(layerData, lastLayer): | ||
| """Builds a PyOutline layer running a Blender command.""" | ||
| blenderCmd = buildBlenderCmd(layerData) | ||
| return buildLayer(layerData, blenderCmd, lastLayer) | ||
|
|
||
|
|
||
| def submit(jobData): | ||
| """Submits the job using the PyOutline API.""" | ||
| ol = outline.Outline( | ||
| jobData['name'], shot=jobData['shot'], show=jobData['show'], user=jobData['username']) | ||
| lastLayer = None | ||
| layerData = jobData['layers'] | ||
| layer = buildBlenderLayer(layerData, lastLayer) | ||
| ol.add_layer(layer) | ||
| lastLayer = layer | ||
|
|
||
| return outline.cuerun.launch(ol, use_pycuerun=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure it's a good idea to copy the modules into the addon. You will want to be able to use the addon with a newer version of opencue, without having to update the addon.