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():
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')
351 preview_frame_number = self.
win.sliderPreview.value()
354 self.
Render(preview_frame_number)
367 animation_title = self.
blender_model.model.item(ItemRow, 1).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)
448 project = self.
app.project
452 project_params[
"fps"] = project.get([
"fps"])
453 project_params[
"resolution_x"] = project.get([
"width"])
454 project_params[
"resolution_y"] = project.get([
"height"])
457 project_params[
"resolution_percentage"] = 50
459 project_params[
"resolution_percentage"] = 100
460 project_params[
"quality"] = 100
461 project_params[
"file_format"] =
"PNG" 464 project_params[
"color_mode"] =
"RGB" 465 project_params[
"alpha_mode"] =
"SKY" 468 project_params[
"color_mode"] =
"RGBA" 469 project_params[
"alpha_mode"] =
"TRANSPARENT" 470 project_params[
"horizon_color"] = (0.57, 0.57, 0.57)
471 project_params[
"animation"] =
True 472 project_params[
"output_path"] = os.path.join(info.BLENDER_PATH, self.
unique_folder_name,
476 return project_params
486 version_message = _(
"\n\nVersion Detected:\n{}").format(version)
489 version_message = _(
"\n\nError Output:\n{}").format(command_output)
492 blender_version =
"2.78" 496 "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(
497 blender_version, s.get(
"blender_command"), version_message))
512 user_params =
"\n#BEGIN INJECTING PARAMS\n" 513 for k, v
in self.
params.items():
514 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
515 user_params +=
"params['{}'] = {}\n".format(k, v)
517 user_params +=
"params['{}'] = u'{}'\n".format(k, v.replace(
"'",
r"\'"))
520 if type(v) == int
or type(v) == float
or type(v) == list
or type(v) == bool:
521 user_params +=
"params['{}'] = {}\n".format(k, v)
523 user_params +=
"params['{}'] = u'{}'\n".format(k, v.replace(
"'",
r"\'").replace(
"\\",
"\\\\"))
524 user_params +=
"#END INJECTING PARAMS\n" 528 user_params +=
"\n\n#ONLY RENDER 1 FRAME FOR PREVIEW\n" 529 user_params +=
"params['{}'] = {}\n".format(
"start_frame", frame)
530 user_params +=
"params['{}'] = {}\n".format(
"end_frame", frame)
531 user_params +=
"\n\n#END ONLY RENDER 1 FRAME FOR PREVIEW\n" 534 with open(path,
'r') as f: 535 script_body = f.read() 538 script_body = script_body.replace(
"#INJECT_PARAMS_HERE", user_params)
541 with codecs.open(path,
"w", encoding=
"UTF-8")
as f:
547 image = QImage(image_path)
548 scaled_image = image.scaledToHeight(self.
win.imgPreview.height(), Qt.SmoothTransformation);
549 pixmap = QPixmap.fromImage(scaled_image)
550 self.
win.imgPreview.setPixmap(pixmap)
561 blend_file_path = os.path.join(info.PATH,
"blender",
"blend", self.
selected_template)
562 source_script = os.path.join(info.PATH,
"blender",
"scripts", self.
selected_template.replace(
".blend",
".py"))
568 shutil.copy(source_script, target_script)
576 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
577 Q_ARG(str, blend_file_path),
578 Q_ARG(str, target_script),
583 QMetaObject.invokeMethod(self.
worker,
'Render', Qt.QueuedConnection,
584 Q_ARG(str, blend_file_path),
585 Q_ARG(str, target_script),
590 QTreeView.__init__(self, *args)
620 self.setIconSize(QSize(131, 108))
621 self.setGridSize(QSize(102, 92))
622 self.setViewMode(QListView.IconMode)
623 self.setResizeMode(QListView.Adjust)
624 self.setUniformItemSizes(
False)
625 self.setWordWrap(
True)
626 self.setTextElideMode(Qt.ElideRight)
627 self.setStyleSheet(
'QTreeView::item { padding-top: 2px; }')
657 log.info(
'onCloseWindow')
662 log.info(
'onRenderFinish')
667 log.info(
'onBlenderVersionError')
672 log.info(
'onBlenderErrorNoData')
687 log.info(
'onBlenderErrorMessage')
692 log.info(
'onRenableInterface')
700 closed = pyqtSignal()
701 finished = pyqtSignal()
702 blender_version_error = pyqtSignal(str)
703 blender_error_nodata = pyqtSignal()
704 progress = pyqtSignal(int, int, int)
705 image_updated = pyqtSignal(str)
706 blender_error_with_data = pyqtSignal(str)
707 enable_interface = pyqtSignal()
709 @pyqtSlot(str, str, bool)
712 def Render(self, blend_file_path, target_script, preview_mode=False):
713 log.info(
"QThread Render Method Invoked")
737 self.
process = subprocess.Popen(command_get_version, stdout=subprocess.PIPE)
743 if float(self.
version[0]) < 2.78:
753 "Blender command: {} {} '{}' {} '{}'".format(command_render[0], command_render[1], command_render[2],
754 command_render[3], command_render[4]))
757 self.
process = subprocess.Popen(command_render, stdout=subprocess.PIPE)
769 line = str(self.
process.stdout.readline())
777 current_frame = output_frame[0][0]
778 memory = output_frame[0][1]
779 current_part = output_frame[0][2]
780 max_parts = output_frame[0][3]
785 self.
progress.emit(float(current_frame), float(current_part), float(max_parts))
789 log.info(
"Image detected from blender regex: %s" % output_saved)
795 image_path = output_saved[0][0]
796 time_saved = output_saved[0][1]
817 log.info(
"Blender render thread finished")
def update_progress_bar(self, current_frame, current_part, max_parts)
def currentChanged(self, selected, deselected)
def color_button_clicked(self, widget, param, index)
def onBlenderErrorMessage(self, error)
def onUpdateImage(self, image_path)
def get_app()
Returns the current QApplication instance of OpenShot.
def Render(self, frame=None)
Render an images sequence of the current template using Blender 2.62+ and the Blender Python API...
def __init__(self, id, data=None, args)
def update_image(self, image_path)
A TreeView QWidget used on the animated title window.
def spinner_value_changed(self, param, value)
def dropdown_index_changed(self, widget, param, index)
def preview_timer_onTimeout(self)
Timer is ready to Render frame.
def generateUniqueFolder(self)
Generate a new, unique folder name to contain Blender frames.
def disable_interface(self, cursor=True)
Disable all controls on interface.
def enable_interface(self)
Disable all controls on interface.
def onBlenderErrorNoData(self)
def text_value_changed(self, widget, param, value=None)
def init_slider_values(self)
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 sliderPreview_valueChanged(self, new_value)
Get new value of preview slider, and start timer to Render frame.
def Render(self, blend_file_path, target_script, preview_mode=False)
Worker's Render method which invokes the Blender rendering commands.
def get_animation_details(self)
Build a dictionary of all animation settings and properties from XML.
def get_settings()
Get the current QApplication's settings instance.
def mousePressEvent(self, event)
def render_finished(self)
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 error_with_blender(self, version=None, command_output=None)
Show a friendly error message regarding the blender executable or version.
def btnRefresh_clicked(self, checked)
Background Worker Object (to run the Blender commands)
def onBlenderVersionError(self, version)
def inject_params(self, path, frame=None)
def onUpdateProgress(self, current_frame, current_part, max_parts)