36 import xml.dom.minidom
as xml
39 from PyQt5.QtCore import QSize, Qt, QEvent, QObject, QThread, pyqtSlot, pyqtSignal, QMetaObject, Q_ARG, QTimer
43 from classes
import info
44 from classes.logger
import log
45 from classes
import settings
46 from classes.query
import File
47 from classes.app
import get_app
48 from windows.models.blender_model
import BlenderModel
53 import simplejson
as json
62 QEvent.__init__(self, id)
80 self.win.clear_effect_controls()
95 for param
in animation.get(
"params",[]):
96 log.info(
'Using parameter %s: %s' % (param[
"name"], param[
"title"]))
99 if param[
"name"] ==
"start_frame" or param[
"name"] ==
"end_frame":
101 self.
params[param[
"name"]] = int(param[
"default"])
109 label.setText(_(param[
"title"]))
110 label.setToolTip(_(param[
"title"]))
112 if param[
"type"] ==
"spinner":
114 self.
params[param[
"name"]] = float(param[
"default"])
117 widget = QDoubleSpinBox()
118 widget.setMinimum(float(param[
"min"]))
119 widget.setMaximum(float(param[
"max"]))
120 widget.setValue(float(param[
"default"]))
121 widget.setSingleStep(0.01)
122 widget.setToolTip(param[
"title"])
125 elif param[
"type"] ==
"text":
127 self.
params[param[
"name"]] = _(param[
"default"])
131 widget.setText(_(param[
"default"]))
134 elif param[
"type"] ==
"multiline":
136 self.
params[param[
"name"]] = _(param[
"default"])
140 widget.setText(_(param[
"default"]).replace(
"\\n",
"\n"))
143 elif param[
"type"] ==
"dropdown":
145 self.
params[param[
"name"]] = param[
"default"]
152 if "project_files" in param[
"name"]:
155 for file
in File.filter():
156 if file.data[
"media_type"]
in (
"image",
"video"):
157 (dirName, fileName) = os.path.split(file.data[
"path"])
158 (fileBaseName, fileExtension) = os.path.splitext(fileName)
160 if fileExtension.lower()
not in (
".svg"):
161 param[
"values"][fileName] =
"|".join((file.data[
"path"], str(file.data[
"height"]),
162 str(file.data[
"width"]), file.data[
"media_type"],
163 str(file.data[
"fps"][
"num"] / file.data[
"fps"][
168 for k, v
in sorted(param[
"values"].items()):
170 widget.addItem(_(k), v)
173 if v == param[
"default"]:
174 widget.setCurrentIndex(box_index)
175 box_index = box_index + 1
177 if not param[
"values"]:
178 widget.addItem(_(
"No Files Found"),
"")
179 widget.setEnabled(
False)
181 elif param[
"type"] ==
"color":
183 color = QColor(param[
"default"])
184 self.
params[param[
"name"]] = [color.redF(), color.greenF(), color.blueF()]
186 widget = QPushButton()
188 widget.setStyleSheet(
"background-color: {}".format(param[
"default"]))
192 if (widget
and label):
193 self.win.settingsContainer.layout().addRow(label, widget)
195 self.win.settingsContainer.layout().addRow(label)
204 log.info(
'Animation param being changed: %s' % param[
"name"])
205 self.
params[param[
"name"]] = value
206 log.info(
'New value of param: %s' % value)
212 value = widget.toPlainText()
215 log.info(
'Animation param being changed: %s' % param[
"name"])
216 self.
params[param[
"name"]] = value.replace(
"\n",
"\\n")
217 log.info(
'New value of param: %s' % value)
220 log.info(
'Animation param being changed: %s' % param[
"name"])
221 value = widget.itemData(index)
222 self.
params[param[
"name"]] = value
223 log.info(
'New value of param: %s' % value)
230 log.info(
'Animation param being changed: %s' % param[
"name"])
231 color_value = self.
params[param[
"name"]]
232 log.info(
'Value of param: %s' % color_value)
233 currentColor = QColor(
"#FFFFFF")
234 if len(color_value) == 3:
236 currentColor.setRgbF(color_value[0], color_value[1], color_value[2])
237 newColor = QColorDialog.getColor(currentColor, self, _(
"Select a Color"),
238 QColorDialog.DontUseNativeDialog)
239 if newColor.isValid():
240 widget.setStyleSheet(
"background-color: {}".format(newColor.name()))
241 self.
params[param[
"name"]] = [newColor.redF(), newColor.greenF(), newColor.blueF()]
242 log.info(
'New value of param: %s' % newColor.name())
258 self.win.btnRefresh.setEnabled(
False)
259 self.win.sliderPreview.setEnabled(
False)
260 self.win.buttonBox.setEnabled(
False)
264 QApplication.setOverrideCursor(Qt.WaitCursor)
269 self.win.btnRefresh.setEnabled(
True)
270 self.win.sliderPreview.setEnabled(
True)
271 self.win.buttonBox.setEnabled(
True)
274 QApplication.restoreOverrideCursor()
279 log.info(
"init_slider_values")
282 preview_frame_number = self.win.sliderPreview.value()
283 length = int(self.params.get(
"end_frame", 1))
286 if not self.params.get(
"animation_speed"):
287 self.
params[
"animation_speed"] = 1
290 length *= int(self.
params[
"animation_speed"])
293 middle_frame = int(length / 2)
295 self.win.sliderPreview.setMinimum(self.params.get(
"start_frame", 1))
296 self.win.sliderPreview.setMaximum(length)
297 self.win.sliderPreview.setValue(middle_frame)
300 self.win.lblFrame.setText(
"{}/{}".format(middle_frame, length))
308 log.info(
"btnRefresh_clicked")
309 preview_frame_number = self.win.sliderPreview.value()
310 self.
Render(preview_frame_number)
313 log.info(
"RENDER FINISHED!")
317 log.info(
'Adding to project files: %s' % final_path)
320 self.win.add_file(final_path)
326 log.info(
"CLOSING WINDOW")
334 self.win.sliderPreview.setValue(current_frame)
336 length = int(self.
params[
"end_frame"])
337 self.win.lblFrame.setText(
"{}/{}".format(current_frame, length))
342 log.info(
'sliderPreview_valueChanged: %s' % new_value)
343 if self.win.sliderPreview.isEnabled():
344 self.preview_timer.start()
347 preview_frame_number = new_value
348 length = int(self.
params[
"end_frame"])
349 self.win.lblFrame.setText(
"{}/{}".format(preview_frame_number, length))
354 log.info(
'preview_timer_onTimeout')
355 self.preview_timer.stop()
358 preview_frame_number = self.win.sliderPreview.value()
361 self.
Render(preview_frame_number)
369 elif self.
selected and self.selected.row() == -1:
373 ItemRow = self.blender_model.model.itemFromIndex(self.
selected).row()
374 animation_title = self.blender_model.model.item(ItemRow, 1).text()
375 xml_path = self.blender_model.model.item(ItemRow, 2).text()
376 service = self.blender_model.model.item(ItemRow, 3).text()
379 xmldoc = xml.parse(xml_path)
382 animation = {
"title": animation_title,
"path": xml_path,
"service": service,
"params": []}
383 xml_params = xmldoc.getElementsByTagName(
"param")
386 for param
in xml_params:
390 if param.attributes[
"title"]:
391 param_item[
"title"] = param.attributes[
"title"].value
393 if param.attributes[
"description"]:
394 param_item[
"description"] = param.attributes[
"description"].value
396 if param.attributes[
"name"]:
397 param_item[
"name"] = param.attributes[
"name"].value
399 if param.attributes[
"type"]:
400 param_item[
"type"] = param.attributes[
"type"].value
402 if param.getElementsByTagName(
"min"):
403 param_item[
"min"] = param.getElementsByTagName(
"min")[0].childNodes[0].data
405 if param.getElementsByTagName(
"max"):
406 param_item[
"max"] = param.getElementsByTagName(
"max")[0].childNodes[0].data
408 if param.getElementsByTagName(
"step"):
409 param_item[
"step"] = param.getElementsByTagName(
"step")[0].childNodes[0].data
411 if param.getElementsByTagName(
"digits"):
412 param_item[
"digits"] = param.getElementsByTagName(
"digits")[0].childNodes[0].data
414 if param.getElementsByTagName(
"default"):
415 if param.getElementsByTagName(
"default")[0].childNodes:
416 param_item[
"default"] = param.getElementsByTagName(
"default")[0].childNodes[0].data
418 param_item[
"default"] =
""
420 param_item[
"values"] = {}
421 values = param.getElementsByTagName(
"value")
427 if value.attributes[
"name"]:
428 name = value.attributes[
"name"].value
430 if value.attributes[
"num"]:
431 num = value.attributes[
"num"].value
434 param_item[
"values"][name] = num
437 animation[
"params"].append(param_item)
449 self.blender_model.update_model()
455 project = self.app.project
459 project_params[
"fps"] = project.get([
"fps"])
460 project_params[
"resolution_x"] = project.get([
"width"])
461 project_params[
"resolution_y"] = project.get([
"height"])
464 project_params[
"resolution_percentage"] = 50
466 project_params[
"resolution_percentage"] = 100
467 project_params[
"quality"] = 100
468 project_params[
"file_format"] =
"PNG"
471 project_params[
"color_mode"] =
"RGB"
472 project_params[
"alpha_mode"] =
"SKY"
475 project_params[
"color_mode"] =
"RGBA"
476 project_params[
"alpha_mode"] =
"TRANSPARENT"
477 project_params[
"horizon_color"] = (0.57, 0.57, 0.57)
478 project_params[
"animation"] =
True
479 project_params[
"output_path"] = os.path.join(info.BLENDER_PATH, self.
unique_folder_name,
483 return project_params
493 version_message = _(
"\n\nVersion Detected:\n{}").format(version)
496 version_message = _(
"\n\nError Output:\n{}").format(command_output)
499 blender_version =
"2.78"
503 "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(
504 blender_version, s.get(
"blender_command"), version_message))
519 user_params =
"\n#BEGIN INJECTING PARAMS\n"
520 for k, v
in self.params.items():
521 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
522 user_params +=
"params['{}'] = {}\n".format(k, v)
524 user_params +=
"params['{}'] = u'{}'\n".format(k, v.replace(
"'",
r"\'"))
527 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
528 user_params +=
"params['{}'] = {}\n".format(k, v)
530 user_params +=
"params['{}'] = u'{}'\n".format(k, v.replace(
"'",
r"\'").replace(
"\\",
"\\\\"))
531 user_params +=
"#END INJECTING PARAMS\n"
535 user_params +=
"\n\n#ONLY RENDER 1 FRAME FOR PREVIEW\n"
536 user_params +=
"params['{}'] = {}\n".format(
"start_frame", frame)
537 user_params +=
"params['{}'] = {}\n".format(
"end_frame", frame)
538 user_params +=
"\n\n#END ONLY RENDER 1 FRAME FOR PREVIEW\n"
541 with open(path,
'r') as f:
542 script_body = f.read()
545 script_body = script_body.replace(
"#INJECT_PARAMS_HERE", user_params)
548 with codecs.open(path,
"w", encoding=
"UTF-8")
as f:
554 image = QImage(image_path)
555 scaled_image = image.scaledToHeight(self.win.imgPreview.height(), Qt.SmoothTransformation);
556 pixmap = QPixmap.fromImage(scaled_image)
557 self.win.imgPreview.setPixmap(pixmap)
568 blend_file_path = os.path.join(info.PATH,
"blender",
"blend", self.
selected_template)
569 source_script = os.path.join(info.PATH,
"blender",
"scripts", self.selected_template.replace(
".blend",
".py"))
571 self.selected_template.replace(
".blend",
".py"))
575 shutil.copy(source_script, target_script)
583 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
584 Q_ARG(str, blend_file_path),
585 Q_ARG(str, target_script),
590 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
591 Q_ARG(str, blend_file_path),
592 Q_ARG(str, target_script),
597 QTreeView.__init__(self, *args)
612 self.preview_timer.setInterval(300)
626 self.setModel(self.blender_model.model)
627 self.setIconSize(QSize(131, 108))
628 self.setGridSize(QSize(102, 92))
629 self.setViewMode(QListView.IconMode)
630 self.setResizeMode(QListView.Adjust)
631 self.setUniformItemSizes(
True)
632 self.setWordWrap(
True)
633 self.setTextElideMode(Qt.ElideRight)
659 self.background.start()
663 log.info(
'onCloseWindow')
668 log.info(
'onRenderFinish')
673 log.info(
'onBlenderVersionError: %s' % version)
678 log.info(
'onBlenderErrorNoData')
691 log.info(
'onBlenderErrorMessage')
696 log.info(
'onRenableInterface')
704 closed = pyqtSignal()
705 finished = pyqtSignal()
706 blender_version_error = pyqtSignal(str)
707 blender_error_nodata = pyqtSignal()
708 progress = pyqtSignal(int, int, int)
709 image_updated = pyqtSignal(str)
710 blender_error_with_data = pyqtSignal(str)
711 enable_interface = pyqtSignal()
713 @pyqtSlot(str, str, bool)
716 def Render(self, blend_file_path, target_script, preview_mode=False):
717 log.info(
"QThread Render Method Invoked")
738 if sys.platform ==
'win32':
739 startupinfo = subprocess.STARTUPINFO()
740 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
746 self.
process = subprocess.Popen(command_get_version, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
749 self.
version = self.blender_version.findall(str(self.process.stdout.readline()))
752 if float(self.
version[0]) < 2.78:
757 self.blender_version_error.emit(float(self.
version[0]))
762 "Blender command: {} {} '{}' {} '{}'".format(command_render[0], command_render[1], command_render[2],
763 command_render[3], command_render[4]))
766 self.
process = subprocess.Popen(command_render, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=startupinfo)
772 self.blender_error_nodata.emit()
775 while self.
is_running and self.process.poll()
is None:
778 line = str(self.process.stdout.readline())
780 output_frame = self.blender_frame_expression.findall(line)
786 current_frame = output_frame[0][0]
787 memory = output_frame[0][1]
788 current_part = output_frame[0][2]
789 max_parts = output_frame[0][3]
794 self.progress.emit(float(current_frame), float(current_part), float(max_parts))
797 output_saved = self.blender_saved_expression.findall(str(line))
802 log.info(
"Image detected from blender regex: %s" % output_saved)
804 image_path = output_saved[0][0]
805 time_saved = output_saved[0][1]
808 self.image_updated.emit(image_path)
812 self.enable_interface.emit()
818 self.blender_error_with_data.emit(_(
"No frame was found in the output from Blender"))
826 log.info(
"Blender render thread finished")
def sliderPreview_valueChanged
Get new value of preview slider, and start timer to Render frame.
A TreeView QWidget used on the animated title window.
def get_app
Returns the current QApplication instance of OpenShot.
def error_with_blender
Show a friendly error message regarding the blender executable or version.
def spinner_value_changed
def generateUniqueFolder
Generate a new, unique folder name to contain Blender frames.
def dropdown_index_changed
def enable_interface
Disable all controls on interface.
def init_slider_values
Init the slider and preview frame label to the currently selected animation.
A custom Blender QEvent, which can safely be sent from the Blender thread to the Qt thread (to commun...
def get_project_params
Return a dictionary of project related settings, needed by the Blender python script.
def Render
Render an images sequence of the current template using Blender 2.62+ and the Blender Python API...
def onBlenderVersionError
def Render
Worker's Render method which invokes the Blender rendering commands.
Background Worker Object (to run the Blender commands)
def disable_interface
Disable all controls on interface.
def onBlenderErrorMessage
def get_settings
Get the current QApplication's settings instance.
def get_animation_details
Build a dictionary of all animation settings and properties from XML.
def preview_timer_onTimeout
Timer is ready to Render frame.