35 import xml.dom.minidom
as xml
38 from PyQt5.QtCore import QSize, Qt, QEvent, QObject, QThread, pyqtSlot, pyqtSignal, QMetaObject, Q_ARG, QTimer
42 from classes
import info
43 from classes.logger
import log
44 from classes
import settings
45 from classes.query
import File
46 from classes.app
import get_app
47 from windows.models.blender_model
import BlenderModel
52 import simplejson
as json
61 QEvent.__init__(self, id)
79 self.win.clear_effect_controls()
94 for param
in animation.get(
"params",[]):
95 log.info(param[
"title"])
98 if param[
"name"] ==
"start_frame" or param[
"name"] ==
"end_frame":
100 self.
params[param[
"name"]] = int(param[
"default"])
108 label.setText(_(param[
"title"]))
109 label.setToolTip(_(param[
"title"]))
111 if param[
"type"] ==
"spinner":
113 self.
params[param[
"name"]] = float(param[
"default"])
116 widget = QDoubleSpinBox()
117 widget.setMinimum(float(param[
"min"]))
118 widget.setMaximum(float(param[
"max"]))
119 widget.setValue(float(param[
"default"]))
120 widget.setSingleStep(0.01)
121 widget.setToolTip(param[
"title"])
124 elif param[
"type"] ==
"text":
126 self.
params[param[
"name"]] = _(param[
"default"])
130 widget.setText(_(param[
"default"]))
133 elif param[
"type"] ==
"multiline":
135 self.
params[param[
"name"]] = _(param[
"default"])
139 widget.setText(_(param[
"default"]).replace(
"\\n",
"\n"))
142 elif param[
"type"] ==
"dropdown":
144 self.
params[param[
"name"]] = param[
"default"]
151 if "project_files" in param[
"name"]:
154 for file
in File.filter():
155 if file.data[
"media_type"]
in (
"image",
"video"):
156 (dirName, fileName) = os.path.split(file.data[
"path"])
157 (fileBaseName, fileExtension) = os.path.splitext(fileName)
159 if fileExtension.lower()
not in (
".svg"):
160 param[
"values"][fileName] =
"|".join((file.data[
"path"], str(file.data[
"height"]),
161 str(file.data[
"width"]), file.data[
"media_type"],
162 str(file.data[
"fps"][
"num"] / file.data[
"fps"][
167 for k, v
in sorted(param[
"values"].items()):
169 widget.addItem(_(k), v)
172 if v == param[
"default"]:
173 widget.setCurrentIndex(box_index)
174 box_index = box_index + 1
176 if not param[
"values"]:
177 widget.addItem(_(
"No Files Found"),
"")
178 widget.setEnabled(
False)
180 elif param[
"type"] ==
"color":
182 color = QColor(param[
"default"])
183 self.
params[param[
"name"]] = [color.redF(), color.greenF(), color.blueF()]
185 widget = QPushButton()
187 widget.setStyleSheet(
"background-color: {}".
format(param[
"default"]))
191 if (widget
and label):
192 self.win.settingsContainer.layout().addRow(label, widget)
194 self.win.settingsContainer.layout().addRow(label)
203 self.
params[param[
"name"]] = value
210 value = widget.toPlainText()
213 self.
params[param[
"name"]] = value.replace(
"\n",
"\\n")
217 value = widget.itemData(index)
218 self.
params[param[
"name"]] = value
223 log.info(
'Animation param being changed: %s' % param[
"name"])
224 color_value = self.
params[param[
"name"]]
225 log.info(
'Value of param: %s' % color_value)
226 currentColor = QColor(
"#FFFFFF")
227 if len(color_value) == 3:
228 log.info(
'Using previous color: %s' % color_value)
230 currentColor.setRgbF(color_value[0], color_value[1], color_value[2])
231 newColor = QColorDialog.getColor(currentColor)
232 if newColor.isValid():
233 widget.setStyleSheet(
"background-color: {}".
format(newColor.name()))
234 self.
params[param[
"name"]] = [newColor.redF(), newColor.greenF(), newColor.blueF()]
235 log.info(newColor.name())
251 self.win.btnRefresh.setEnabled(
False)
252 self.win.sliderPreview.setEnabled(
False)
253 self.win.buttonBox.setEnabled(
False)
257 QApplication.setOverrideCursor(Qt.WaitCursor)
262 self.win.btnRefresh.setEnabled(
True)
263 self.win.sliderPreview.setEnabled(
True)
264 self.win.buttonBox.setEnabled(
True)
267 QApplication.restoreOverrideCursor()
272 log.info(
"init_slider_values")
275 preview_frame_number = self.win.sliderPreview.value()
276 length = int(self.params.get(
"end_frame", 1))
279 if not self.params.get(
"animation_speed"):
280 self.
params[
"animation_speed"] = 1
283 length *= int(self.
params[
"animation_speed"])
286 middle_frame = int(length / 2)
288 self.win.sliderPreview.setMinimum(self.params.get(
"start_frame", 1))
289 self.win.sliderPreview.setMaximum(length)
290 self.win.sliderPreview.setValue(middle_frame)
293 self.win.lblFrame.setText(
"{}/{}".
format(middle_frame, length))
301 log.info(
"btnRefresh_clicked")
302 preview_frame_number = self.win.sliderPreview.value()
303 self.
Render(preview_frame_number)
306 log.info(
"RENDER FINISHED!")
313 self.win.add_file(final_path)
319 log.info(
"CLOSING WINDOW")
327 self.win.sliderPreview.setValue(current_frame)
329 length = int(self.
params[
"end_frame"])
330 self.win.lblFrame.setText(
"{}/{}".
format(current_frame, length))
335 log.info(
'sliderPreview_valueChanged: %s' % new_value)
336 if self.win.sliderPreview.isEnabled():
337 self.preview_timer.start()
340 preview_frame_number = new_value
341 length = int(self.
params[
"end_frame"])
342 self.win.lblFrame.setText(
"{}/{}".
format(preview_frame_number, length))
347 log.info(
'preview_timer_onTimeout')
348 self.preview_timer.stop()
351 preview_frame_number = self.win.sliderPreview.value()
354 self.
Render(preview_frame_number)
362 elif self.
selected and self.selected.row() == -1:
366 ItemRow = self.blender_model.model.itemFromIndex(self.
selected).row()
367 animation_title = self.blender_model.model.item(ItemRow, 1).text()
368 xml_path = self.blender_model.model.item(ItemRow, 2).text()
369 service = self.blender_model.model.item(ItemRow, 3).text()
372 xmldoc = xml.parse(xml_path)
375 animation = {
"title": animation_title,
"path": xml_path,
"service": service,
"params": []}
376 xml_params = xmldoc.getElementsByTagName(
"param")
379 for param
in xml_params:
383 if param.attributes[
"title"]:
384 param_item[
"title"] = param.attributes[
"title"].value
386 if param.attributes[
"description"]:
387 param_item[
"description"] = param.attributes[
"description"].value
389 if param.attributes[
"name"]:
390 param_item[
"name"] = param.attributes[
"name"].value
392 if param.attributes[
"type"]:
393 param_item[
"type"] = param.attributes[
"type"].value
395 if param.getElementsByTagName(
"min"):
396 param_item[
"min"] = param.getElementsByTagName(
"min")[0].childNodes[0].data
398 if param.getElementsByTagName(
"max"):
399 param_item[
"max"] = param.getElementsByTagName(
"max")[0].childNodes[0].data
401 if param.getElementsByTagName(
"step"):
402 param_item[
"step"] = param.getElementsByTagName(
"step")[0].childNodes[0].data
404 if param.getElementsByTagName(
"digits"):
405 param_item[
"digits"] = param.getElementsByTagName(
"digits")[0].childNodes[0].data
407 if param.getElementsByTagName(
"default"):
408 if param.getElementsByTagName(
"default")[0].childNodes:
409 param_item[
"default"] = param.getElementsByTagName(
"default")[0].childNodes[0].data
411 param_item[
"default"] =
"" 413 param_item[
"values"] = {}
414 values = param.getElementsByTagName(
"value")
420 if value.attributes[
"name"]:
421 name = value.attributes[
"name"].value
423 if value.attributes[
"num"]:
424 num = value.attributes[
"num"].value
427 param_item[
"values"][name] = num
430 animation[
"params"].append(param_item)
437 menu.addAction(self.win.actionDetailsView)
438 menu.addAction(self.win.actionThumbnailView)
439 menu.exec_(QCursor.pos())
443 super().mouseMoveEvent(event)
452 self.blender_model.update_model()
460 project = self.app.project
464 project_params[
"fps"] = project.get([
"fps"])
465 project_params[
"resolution_x"] = project.get([
"width"])
466 project_params[
"resolution_y"] = project.get([
"height"])
469 project_params[
"resolution_percentage"] = 50
471 project_params[
"resolution_percentage"] = 100
472 project_params[
"quality"] = 100
473 project_params[
"file_format"] =
"PNG" 476 project_params[
"color_mode"] =
"RGB" 479 project_params[
"color_mode"] =
"RGBA" 480 project_params[
"horizon_color"] = (0.57, 0.57, 0.57)
481 project_params[
"animation"] =
True 482 project_params[
"output_path"] = os.path.join(info.BLENDER_PATH, self.
unique_folder_name,
486 return project_params
496 version_message = _(
"\n\nVersion Detected:\n{}").
format(version)
499 version_message = _(
"\n\nError Output:\n{}").
format(command_output)
502 blender_version =
"2.62" 506 "Blender, the free open source 3D content creation suite is required for this action (http://www.blender.org).\n\nPlease check the preferences in OpenShot and be sure the Blender executable is correct. This setting should be the path of the 'blender' executable on your computer. Also, please be sure that it is pointing to Blender version {} or greater.\n\nBlender Path:\n{}{}").
format(
507 blender_version, s.get(
"blender_command"), version_message))
522 user_params =
"\n#BEGIN INJECTING PARAMS\n" 523 for k, v
in self.params.items():
524 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
525 user_params +=
"params['{}'] = {}\n".
format(k, v)
527 user_params +=
"params['{}'] = u'{}'\n".
format(k, v.replace(
"'",
r"\'"))
530 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
531 user_params +=
"params['{}'] = {}\n".
format(k, v)
533 user_params +=
"params['{}'] = u'{}'\n".
format(k, v.replace(
"'",
r"\'").replace(
"\\",
"\\\\"))
534 user_params +=
"#END INJECTING PARAMS\n" 538 user_params +=
"\n\n#ONLY RENDER 1 FRAME FOR PREVIEW\n" 539 user_params +=
"params['{}'] = {}\n".
format(
"start_frame", frame)
540 user_params +=
"params['{}'] = {}\n".
format(
"end_frame", frame)
541 user_params +=
"\n\n#END ONLY RENDER 1 FRAME FOR PREVIEW\n" 544 with open(path,
'r') as f: 545 script_body = f.read() 548 script_body = script_body.replace(
"#INJECT_PARAMS_HERE", user_params)
551 with codecs.open(path,
"w", encoding=
"UTF-8")
as f:
557 image = QImage(image_path)
558 scaled_image = image.scaledToHeight(self.win.imgPreview.height(), Qt.SmoothTransformation);
559 pixmap = QPixmap.fromImage(scaled_image)
560 self.win.imgPreview.setPixmap(pixmap)
571 blend_file_path = os.path.join(info.PATH,
"blender",
"blend", self.
selected_template)
572 source_script = os.path.join(info.PATH,
"blender",
"scripts", self.selected_template.replace(
".blend",
".py"))
574 self.selected_template.replace(
".blend",
".py"))
578 shutil.copy(source_script, target_script)
586 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
587 Q_ARG(str, blend_file_path),
588 Q_ARG(str, target_script),
593 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
594 Q_ARG(str, blend_file_path),
595 Q_ARG(str, target_script),
600 QTreeView.__init__(self, *args)
615 self.preview_timer.setInterval(300)
629 self.setModel(self.blender_model.model)
630 self.setIconSize(QSize(131, 108))
631 self.setIndentation(0)
632 self.setSelectionBehavior(QTreeView.SelectRows)
633 self.setSelectionBehavior(QAbstractItemView.SelectRows)
634 self.setWordWrap(
True)
635 self.setStyleSheet(
'QTreeView::item { padding-top: 2px; }')
661 self.background.start()
665 log.info(
'onCloseWindow')
670 log.info(
'onRenderFinish')
675 log.info(
'onBlenderVersionError')
680 log.info(
'onBlenderErrorNoData')
695 log.info(
'onBlenderErrorMessage')
700 log.info(
'onRenableInterface')
708 closed = pyqtSignal()
709 finished = pyqtSignal()
710 blender_version_error = pyqtSignal(str)
711 blender_error_nodata = pyqtSignal()
712 progress = pyqtSignal(int, int, int)
713 image_updated = pyqtSignal(str)
714 blender_error_with_data = pyqtSignal(str)
715 enable_interface = pyqtSignal()
717 @pyqtSlot(str, str, bool)
720 def Render(self, blend_file_path, target_script, preview_mode=False):
721 log.info(
"QThread Render Method Invoked")
745 self.
process = subprocess.Popen(command_get_version, stdout=subprocess.PIPE)
748 self.
version = self.blender_version.findall(str(self.process.stdout.readline()))
751 if float(self.
version[0]) < 2.62:
756 self.blender_version_error.emit(float(self.
version[0]))
761 "Blender command: {} {} '{}' {} '{}'".
format(command_render[0], command_render[1], command_render[2],
762 command_render[3], command_render[4]))
765 self.
process = subprocess.Popen(command_render, stdout=subprocess.PIPE)
771 self.blender_error_nodata.emit()
774 while self.
is_running and self.process.poll()
is None:
777 line = str(self.process.stdout.readline())
779 output_frame = self.blender_frame_expression.findall(line)
785 current_frame = output_frame[0][0]
786 memory = output_frame[0][1]
787 current_part = output_frame[0][2]
788 max_parts = output_frame[0][3]
793 self.progress.emit(float(current_frame), float(current_part), float(max_parts))
796 output_saved = self.blender_saved_expression.findall(str(line))
802 image_path = output_saved[0][0]
803 time_saved = output_saved[0][1]
806 self.image_updated.emit(image_path)
810 self.enable_interface.emit()
816 self.blender_error_with_data.emit(_(
"No frame was found in the output from Blender"))
824 log.info(
"Blender render thread finished")
Background Worker Object (to run the Blender commands)
def Render(self, blend_file_path, target_script, preview_mode=False)
Worker's Render method which invokes the Blender rendering commands.
def inject_params(self, path, frame=None)
def render_finished(self)
def currentChanged(self, selected, deselected)
def get_app()
Returns the current QApplication instance of OpenShot.
def disable_interface(self, cursor=True)
Disable all controls on interface.
def sliderPreview_valueChanged(self, new_value)
Get new value of preview slider, and start timer to Render frame.
A custom Blender QEvent, which can safely be sent from the Blender thread to the Qt thread (to commun...
A TreeView QWidget used on the animated title window.
def contextMenuEvent(self, event)
def onUpdateProgress(self, current_frame, current_part, max_parts)
def Render(self, frame=None)
Render an images sequence of the current template using Blender 2.62+ and the Blender Python API...
def onUpdateImage(self, image_path)
def get_animation_details(self)
Build a dictionary of all animation settings and properties from XML.
def onBlenderErrorMessage(self, error)
def error_with_blender(self, version=None, command_output=None)
Show a friendly error message regarding the blender executable or version.
def dropdown_index_changed(self, widget, param, index)
def update_progress_bar(self, current_frame, current_part, max_parts)
def preview_timer_onTimeout(self)
Timer is ready to Render frame.
def btnRefresh_clicked(self, checked)
def text_value_changed(self, widget, param, value=None)
def onBlenderErrorNoData(self)
def enable_interface(self)
Disable all controls on interface.
def onBlenderVersionError(self, version)
def spinner_value_changed(self, param, value)
def mousePressEvent(self, event)
def generateUniqueFolder(self)
Generate a new, unique folder name to contain Blender frames.
def get_settings()
Get the current QApplication's settings instance.
def __init__(self, id, data=None, args)
def onRenableInterface(self)
def get_project_params(self, is_preview=True)
Return a dictionary of project related settings, needed by the Blender python script.
def update_image(self, image_path)
def color_button_clicked(self, widget, param, index)
def init_slider_values(self)
Init the slider and preview frame label to the currently selected animation.