File indexing completed on 2024-12-15 04:01:43
0001 /* 0002 * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best> 0003 * 0004 * SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include <pybind11/operators.h> 0008 0009 #include "model/document.hpp" 0010 #include "model/visitor.hpp" 0011 0012 #include "model/shapes/group.hpp" 0013 #include "model/shapes/layer.hpp" 0014 #include "model/shapes/precomp_layer.hpp" 0015 #include "model/shapes/rect.hpp" 0016 #include "model/shapes/ellipse.hpp" 0017 #include "model/shapes/path.hpp" 0018 #include "model/shapes/polystar.hpp" 0019 #include "model/shapes/fill.hpp" 0020 #include "model/shapes/stroke.hpp" 0021 #include "model/shapes/image.hpp" 0022 #include "model/shapes/repeater.hpp" 0023 #include "model/shapes/trim.hpp" 0024 #include "model/shapes/inflate_deflate.hpp" 0025 #include "model/shapes/round_corners.hpp" 0026 #include "model/shapes/offset_path.hpp" 0027 #include "model/shapes/zig_zag.hpp" 0028 0029 #include "model/assets/assets.hpp" 0030 #include "model/assets/named_color.hpp" 0031 #include "model/assets/composition.hpp" 0032 0033 #include "command/animation_commands.hpp" 0034 #include "command/undo_macro_guard.hpp" 0035 #include "command/object_list_commands.hpp" 0036 0037 #include "io/glaxnimate/glaxnimate_format.hpp" 0038 #include "io/raster/raster_format.hpp" 0039 #include "io/raster/raster_mime.hpp" 0040 #include "io/svg/svg_format.hpp" 0041 #include "io/svg/svg_renderer.hpp" 0042 #include "io/rive/rive_format.hpp" 0043 0044 0045 #include "plugin/io.hpp" 0046 #include "app_info.hpp" 0047 0048 #include "miscdefs.hpp" 0049 0050 using namespace app::scripting::python; 0051 using namespace glaxnimate; 0052 0053 namespace { 0054 0055 template<class T, class Base=model::AnimatableBase> 0056 void register_animatable(py::module& m) 0057 { 0058 std::string name = "AnimatedProperty<"; 0059 #if QT_VERSION_MAJOR < 6 0060 name += QMetaType::typeName(qMetaTypeId<T>()); 0061 #else 0062 name += QMetaType::fromType<T>().name(); 0063 #endif 0064 name += ">"; 0065 py::class_<model::AnimatedProperty<T>, Base>(m, name.c_str()); 0066 } 0067 0068 static QImage doc_to_image(model::Composition* comp) 0069 { 0070 return io::raster::RasterMime::to_image({comp}); 0071 } 0072 0073 static QByteArray frame_to_svg(model::Composition* comp) 0074 { 0075 QByteArray data; 0076 QBuffer file(&data); 0077 file.open(QIODevice::WriteOnly); 0078 0079 io::svg::SvgRenderer rend(io::svg::NotAnimated, io::svg::CssFontType::FontFace); 0080 rend.write_main(comp); 0081 rend.write(&file, true); 0082 0083 return data; 0084 } 0085 0086 void define_io(py::module& m) 0087 { 0088 py::module io = m.def_submodule("io", "Input/Output utilities"); 0089 0090 py::class_<io::mime::MimeSerializer>(io, "MimeSerializer") 0091 .def_property_readonly("slug", &io::mime::MimeSerializer::slug) 0092 .def_property_readonly("name", &io::mime::MimeSerializer::name) 0093 .def_property_readonly("mime_types", &io::mime::MimeSerializer::mime_types) 0094 .def("serialize", &io::mime::MimeSerializer::serialize) 0095 ; 0096 0097 const char* to_image_docstring = "Renders the current frame to an image"; 0098 py::class_<io::raster::RasterMime, io::mime::MimeSerializer>(io, "RasterMime") 0099 .def_static("render_frame", &io::raster::RasterMime::to_image, to_image_docstring) 0100 .def_static("render_frame", &doc_to_image, to_image_docstring) 0101 .def_static("render_frame", &io::raster::RasterMime::frame_to_image, 0102 "Renders the given frame to image", 0103 py::arg("node"), py::arg("frame")) 0104 ; 0105 0106 using Fac = io::IoRegistry; 0107 py::class_<Fac, std::unique_ptr<Fac, py::nodelete>>(io, "IoRegistry") 0108 .def("importers", &Fac::importers, no_own) 0109 .def("exporters", &Fac::exporters, no_own) 0110 .def("from_extension", &Fac::from_extension, no_own) 0111 .def("from_filename", &Fac::from_filename, no_own) 0112 .def("from_slug", &Fac::from_slug, no_own) 0113 .def("__getitem__", &Fac::from_slug, no_own) 0114 .def("serializers", &Fac::serializers, no_own) 0115 .def("serializer_from_slug", &Fac::serializer_from_slug, no_own) 0116 ; 0117 0118 io.attr("registry") = std::unique_ptr<Fac, py::nodelete>(&io::IoRegistry::instance()); 0119 0120 auto import_export = register_from_meta<io::ImportExport, QObject>(io, enums<io::ImportExport::Direction>{}) 0121 .def("progress_max_changed", &io::ImportExport::progress_max_changed) 0122 .def("progress", &io::ImportExport::progress) 0123 ; 0124 io.attr("Direction") = import_export.attr("Direction"); 0125 0126 register_from_meta<io::glaxnimate::GlaxnimateFormat, io::ImportExport>(io) 0127 .attr("instance") = std::unique_ptr<io::glaxnimate::GlaxnimateFormat, py::nodelete>(io::glaxnimate::GlaxnimateFormat::instance()) 0128 ; 0129 0130 register_from_meta<io::raster::RasterFormat, io::ImportExport>(io) 0131 .def_static("render_frame", &io::raster::RasterMime::to_image, to_image_docstring) 0132 .def_static("render_frame", &doc_to_image, to_image_docstring) 0133 ; 0134 0135 register_from_meta<io::svg::SvgFormat, io::ImportExport>(io) 0136 .def_static("render_frame", &frame_to_svg, "renders the current frame to SVG") 0137 ; 0138 0139 register_from_meta<plugin::IoFormat, io::ImportExport>(io); 0140 0141 0142 register_from_meta<io::rive::RiveFormat, io::ImportExport>(io) 0143 .def("to_json_data", [](io::rive::RiveFormat& self, const QByteArray& data){ 0144 return self.to_json(data).toJson(); 0145 }, py::arg("binary_data")) 0146 ; 0147 } 0148 0149 0150 void define_animatable(py::module& m) 0151 { 0152 py::class_<model::KeyframeTransition> kt(m, "KeyframeTransition"); 0153 kt.attr("Descriptive") = py::enum_<model::KeyframeTransition::Descriptive>(kt, "Descriptive") 0154 .value("Hold", model::KeyframeTransition::Hold) 0155 .value("Linear", model::KeyframeTransition::Linear) 0156 .value("Ease", model::KeyframeTransition::Ease) 0157 .value("Fast", model::KeyframeTransition::Fast) 0158 .value("Custom", model::KeyframeTransition::Custom) 0159 ; 0160 kt 0161 .def(py::init<>()) 0162 .def(py::init<const QPointF&, const QPointF&>()) 0163 .def_property("hold", &model::KeyframeTransition::hold, &model::KeyframeTransition::set_hold) 0164 .def_property("before", &model::KeyframeTransition::before, &model::KeyframeTransition::set_before) 0165 .def_property("after", &model::KeyframeTransition::after, &model::KeyframeTransition::set_after) 0166 .def_property("before_descriptive", &model::KeyframeTransition::before_descriptive, &model::KeyframeTransition::set_before_descriptive) 0167 .def_property("after_descriptive", &model::KeyframeTransition::after_descriptive, &model::KeyframeTransition::set_after_descriptive) 0168 .def("lerp_factor", &model::KeyframeTransition::lerp_factor) 0169 .def("bezier_parameter", &model::KeyframeTransition::bezier_parameter) 0170 ; 0171 0172 py::class_<model::KeyframeBase>(m, "Keyframe") 0173 .def_property_readonly("time", &model::KeyframeBase::time) 0174 .def_property_readonly("value", &model::KeyframeBase::value) 0175 .def_property("transition", 0176 &model::KeyframeBase::transition, 0177 &model::KeyframeBase::set_transition, 0178 no_own 0179 ) 0180 ; 0181 register_from_meta<model::AnimatableBase, QObject>(m) 0182 .def("keyframe", [](const model::AnimatableBase& a, model::FrameTime t){ return a.keyframe(t); }, no_own, py::arg("time")) 0183 .def("set_keyframe", [](model::AnimatableBase& a, model::FrameTime time, const QVariant& value){ 0184 a.object()->document()->undo_stack().push( 0185 new command::SetKeyframe(&a, time, value, true) 0186 ); 0187 return a.keyframe(a.keyframe_index(time)); 0188 }, no_own, py::arg("time"), py::arg("value")) 0189 .def("remove_keyframe_at_time", [](model::AnimatableBase& a, model::FrameTime time){ 0190 a.object()->document()->undo_stack().push( 0191 new command::RemoveKeyframeTime(&a, time) 0192 ); 0193 }, py::arg("time")) 0194 .def("clear_keyframes", &model::AnimatableBase::clear_keyframes_undoable, py::arg("value") = py::none()) 0195 ; 0196 } 0197 0198 class PyVisitorPublic : public model::Visitor 0199 { 0200 public: 0201 virtual void on_visit_doc(model::Document *){} 0202 virtual void on_visit_node(model::DocumentNode*){} 0203 0204 private: 0205 void on_visit_document(model::Document * document, model::Composition*) override 0206 { 0207 on_visit_doc(document); 0208 } 0209 0210 void on_visit(model::DocumentNode * node) override 0211 { 0212 on_visit_node(node); 0213 } 0214 }; 0215 0216 class PyVisitorTrampoline : public PyVisitorPublic 0217 { 0218 public: 0219 void on_visit_doc(model::Document * document) override 0220 { 0221 PYBIND11_OVERLOAD(void, PyVisitorPublic, on_visit_doc, document); 0222 } 0223 0224 void on_visit(model::DocumentNode * node) override 0225 { 0226 PYBIND11_OVERLOAD_PURE(void, PyVisitorPublic, on_visit_node, node); 0227 } 0228 }; 0229 0230 template<class Owner, class PropT, class ItemT = typename PropT::value_type> 0231 class AddShapeBase 0232 { 0233 public: 0234 using PtrMem = PropT Owner::*; 0235 0236 AddShapeBase(PtrMem p) noexcept : ptr(p) {} 0237 0238 protected: 0239 ItemT* create(model::Document* doc, PropT& prop, const QString& clsname, int index) const 0240 { 0241 auto obj = model::Factory::static_build(clsname, doc); 0242 if ( !obj ) 0243 return nullptr; 0244 0245 auto cast = obj->cast<ItemT>(); 0246 0247 if ( !cast ) 0248 { 0249 delete obj; 0250 return nullptr; 0251 } 0252 0253 if constexpr ( std::is_base_of_v<model::DocumentNode, ItemT> ) 0254 doc->set_best_name(static_cast<model::DocumentNode*>(cast)); 0255 else 0256 cast->name.set(cast->type_name_human()); 0257 0258 doc->push_command(new command::AddObject<ItemT, PropT>(&prop, std::unique_ptr<ItemT>(cast), index)); 0259 0260 return cast; 0261 } 0262 0263 PtrMem ptr; 0264 }; 0265 0266 template<class Owner, class PropT, class ItemT = typename PropT::value_type> 0267 class AddShapeName : public AddShapeBase<Owner, PropT, ItemT> 0268 { 0269 public: 0270 using AddShapeBase<Owner, PropT, ItemT>::AddShapeBase; 0271 0272 ItemT* operator() (Owner* owner, const QString& clsname, int index = -1) const 0273 { 0274 return this->create(owner->document(), owner->*(this->ptr), clsname, index); 0275 } 0276 }; 0277 0278 template<class Owner, class PropT, class ItemT = typename PropT::value_type> 0279 class AddShapeClass : public AddShapeBase<Owner, PropT, ItemT> 0280 { 0281 public: 0282 using AddShapeBase<Owner, PropT, ItemT>::AddShapeBase; 0283 0284 ItemT* operator() (Owner* owner, const py::object& cls, int index = -1) const 0285 { 0286 pybind11::detail::type_caster<QString> cast; 0287 cast.load(cls.attr("__name__"), true); 0288 return this->create(owner->document(), owner->*(this->ptr), cast, index); 0289 } 0290 }; 0291 0292 template<class Owner, class PropT, class ItemT = typename PropT::value_type> 0293 class AddShapeClone 0294 { 0295 public: 0296 using PtrMem = PropT Owner::*; 0297 0298 AddShapeClone(PtrMem p) noexcept : ptr(p) {} 0299 0300 ItemT* operator() (Owner* owner, ItemT* object, int index = -1) const 0301 { 0302 if ( !object ) 0303 return nullptr; 0304 0305 std::unique_ptr<ItemT> clone(static_cast<ItemT*>(object->clone().release())); 0306 if ( clone->document() != owner->document() ) 0307 clone->transfer(owner->document()); 0308 0309 auto ptr = clone.get(); 0310 0311 owner->push_command(new command::AddObject<ItemT, PropT>(&(owner->*(this->ptr)), std::move(clone), index)); 0312 0313 return ptr; 0314 } 0315 0316 private: 0317 0318 PtrMem ptr; 0319 }; 0320 0321 template< 0322 class PyClass, 0323 class PropT = model::ObjectListProperty<model::ShapeElement>, 0324 class Owner = typename PyClass::type, 0325 class ItemT = typename PropT::value_type 0326 > 0327 void define_add_shape(PyClass& cls, PropT Owner::* prop = &Owner::shapes, const std::string& name = "add_shape") 0328 { 0329 cls.def( 0330 name.c_str(), 0331 AddShapeName<Owner, PropT, ItemT>(prop), 0332 no_own, 0333 "Adds a shape from its class name", 0334 py::arg("type_name"), 0335 py::arg("index") = -1 0336 ) 0337 .def( 0338 name.c_str(), 0339 AddShapeClone<Owner, PropT, ItemT>(prop), 0340 no_own, 0341 "Adds a shape, note that the input object is cloned, and the clone is returned. The document will have ownership over the clone.", 0342 py::arg("object"), 0343 py::arg("index") = -1 0344 ) 0345 .def( 0346 name.c_str(), 0347 AddShapeClass<Owner, PropT, ItemT>(prop), 0348 no_own, 0349 "Adds a shape from its class", 0350 py::arg("cls"), 0351 py::arg("index") = -1 0352 ) 0353 ; 0354 } 0355 0356 template<class Cls, class... Args, class... FwArgs> 0357 auto register_constructible(py::module& module, FwArgs&&... args) 0358 { 0359 return register_from_meta<Cls, Args...>(module, std::forward<FwArgs>(args)...) 0360 .def(py::init([](model::Document* doc) -> std::unique_ptr<Cls> { 0361 if ( !doc ) 0362 return {}; 0363 return std::make_unique<Cls>(doc); 0364 })); 0365 } 0366 0367 } // namespace 0368 0369 0370 void register_py_module(py::module& glaxnimate_module) 0371 { 0372 glaxnimate_module.attr("__version__") = AppInfo::instance().version(); 0373 0374 define_utils(glaxnimate_module); 0375 define_log(glaxnimate_module); 0376 py::module detail = define_detail(glaxnimate_module); 0377 define_environment(glaxnimate_module); 0378 0379 // for some reason some classes aren't seen without this o_O 0380 static std::vector<int> foo = { 0381 qMetaTypeId<model::DocumentNode*>(), 0382 qMetaTypeId<model::NamedColor*>(), 0383 qMetaTypeId<model::Bitmap*>(), 0384 qMetaTypeId<model::Gradient*>(), 0385 qMetaTypeId<model::EmbeddedFont*>(), 0386 qMetaTypeId<io::ImportExport::Direction>(), 0387 }; 0388 0389 py::module model = glaxnimate_module.def_submodule("model", ""); 0390 py::class_<model::Object, QObject>(model, "Object") 0391 .def( 0392 "stretch_time", 0393 [](model::Object* object, double multiplier){ 0394 if ( multiplier > 0 ) 0395 object->push_command(new command::StretchTimeCommand(object, multiplier)); 0396 }, 0397 py::arg("multiplier"), 0398 "Stretches animation timings by the given factor" 0399 ) 0400 ; 0401 0402 py::class_<command::UndoMacroGuard>(model, "UndoMacroGuard") 0403 .def("__enter__", &command::UndoMacroGuard::start) 0404 .def("__exit__", [](command::UndoMacroGuard& g, pybind11::object, pybind11::object, pybind11::object){ 0405 g.finish(); 0406 }) 0407 .def("start", &command::UndoMacroGuard::start) 0408 .def("finish", &command::UndoMacroGuard::finish) 0409 .attr("__doc__") = "Context manager that creates undo macros" 0410 ; 0411 0412 register_from_meta<model::DocumentNode, model::Object>(model) 0413 .def_property_readonly("users", &model::DocumentNode::users, "List of properties pointing to this object") 0414 ; 0415 0416 auto document = register_from_meta<model::Document, QObject>(model) 0417 .def(py::init<QString>()) 0418 .def(py::init<>()) 0419 .def( 0420 "macro", 0421 [](model::Document* document, const QString& str){ 0422 return new command::UndoMacroGuard(str, document, false); 0423 }, 0424 py::return_value_policy::take_ownership, 0425 "Context manager to group changes into a single undo command" 0426 ) 0427 .def( 0428 "stretch_time", 0429 [](model::Document* document, double multiplier){ 0430 if ( multiplier > 0 ) 0431 document->push_command(new command::StretchTimeCommand(document, multiplier)); 0432 }, 0433 py::arg("multiplier"), 0434 "Stretches animation timings by the given factor" 0435 ) 0436 .def_property("metadata", &model::Document::metadata, &model::Document::set_metadata, no_own) 0437 .def_property("info", &model::Document::info, [](model::Document* doc, const model::Document::DocumentInfo& info){ doc->info() = info; }, no_own) 0438 ; 0439 py::class_<model::Document::DocumentInfo>(document, "DocumentInfo") 0440 .def_readwrite("description", &model::Document::DocumentInfo::description) 0441 .def_readwrite("author", &model::Document::DocumentInfo::author) 0442 .def_readwrite("keywords", &model::Document::DocumentInfo::keywords) 0443 ; 0444 0445 register_from_meta<model::VisualNode, model::DocumentNode>(model); 0446 register_from_meta<model::AnimationContainer, model::Object>(model); 0447 register_from_meta<model::StretchableTime, model::Object>(model); 0448 register_from_meta<model::Transform, model::Object>(model); 0449 register_from_meta<model::MaskSettings, model::Object>(model); 0450 0451 py::module shapes = model.def_submodule("shapes", ""); 0452 register_from_meta<model::ShapeElement, model::VisualNode>(shapes) 0453 .def("to_path", &model::ShapeElement::to_path) 0454 ; 0455 0456 py::module defs = model.def_submodule("assets", ""); 0457 py::class_<model::AssetBase>(defs, "AssetBase"); 0458 auto cls_comp = register_from_meta<model::Composition, model::VisualNode, model::AssetBase>(model); 0459 define_add_shape(cls_comp); 0460 0461 define_io(glaxnimate_module); 0462 0463 define_animatable(model); 0464 register_from_meta<model::detail::AnimatedPropertyPosition, model::AnimatableBase>(detail); 0465 register_animatable<QPointF, model::detail::AnimatedPropertyPosition>(detail); 0466 register_animatable<QSizeF>(detail); 0467 register_animatable<QVector2D>(detail); 0468 register_animatable<QColor>(detail); 0469 register_animatable<float>(detail); 0470 register_animatable<QGradientStops>(detail); 0471 register_from_meta<model::detail::AnimatedPropertyBezier, model::AnimatableBase>(detail); 0472 register_animatable<math::bezier::Bezier, model::detail::AnimatedPropertyBezier>(detail); 0473 0474 py::class_<PyVisitorPublic, PyVisitorTrampoline>(model, "Visitor") 0475 .def(py::init()) 0476 .def("visit", (void (PyVisitorPublic::*)(model::Document*, model::Composition*, bool))&PyVisitorPublic::visit, py::arg("document"), py::arg("composition"), py::arg("skip_locked")) 0477 .def("visit", (void (PyVisitorPublic::*)(model::DocumentNode*, bool))&PyVisitorPublic::visit, py::arg("node"), py::arg("skip_locked")) 0478 .def("on_visit_document", &PyVisitorPublic::on_visit_doc) 0479 .def("on_visit_node", &PyVisitorPublic::on_visit_node) 0480 ; 0481 0482 register_from_meta<model::Asset, model::DocumentNode, model::AssetBase>(defs); 0483 register_from_meta<model::BrushStyle, model::Asset>(defs); 0484 register_constructible<model::NamedColor, model::BrushStyle>(defs); 0485 register_constructible<model::GradientColors, model::Asset>(defs); 0486 register_constructible<model::Gradient, model::BrushStyle>(defs, enums<model::Gradient::GradientType>{}); 0487 register_constructible<model::Bitmap, model::Asset>(defs); 0488 register_from_meta<model::EmbeddedFont, model::Asset>(defs); 0489 register_from_meta<model::BitmapList, model::DocumentNode>(defs); 0490 register_from_meta<model::NamedColorList, model::DocumentNode>(defs); 0491 register_from_meta<model::GradientList, model::DocumentNode>(defs); 0492 register_from_meta<model::GradientColorsList, model::DocumentNode>(defs); 0493 register_from_meta<model::CompositionList, model::DocumentNode>(defs); 0494 register_from_meta<model::FontList, model::DocumentNode>(defs); 0495 register_from_meta<model::Assets, model::DocumentNode>(defs); 0496 0497 register_from_meta<model::Shape, model::ShapeElement>(shapes); 0498 register_from_meta<model::Modifier, model::ShapeElement>(shapes); 0499 register_from_meta<model::Styler, model::ShapeElement>(shapes); 0500 0501 register_constructible<model::Rect, model::Shape>(shapes); 0502 register_constructible<model::Ellipse, model::Shape>(shapes); 0503 register_constructible<model::PolyStar, model::Shape>(shapes, enums<model::PolyStar::StarType>{}); 0504 register_constructible<model::Path, model::Shape>(shapes); 0505 0506 auto cls_group = register_constructible<model::Group, model::ShapeElement>(shapes); 0507 define_add_shape(cls_group); 0508 0509 register_constructible<model::Layer, model::Group>(shapes); 0510 register_constructible<model::PreCompLayer, model::ShapeElement>(shapes); 0511 register_constructible<model::Image, model::ShapeElement>(shapes); 0512 0513 register_constructible<model::Fill, model::Styler>(shapes, enums<model::Fill::Rule>{}); 0514 register_constructible<model::Stroke, model::Styler>(shapes, enums<model::Stroke::Cap, model::Stroke::Join>{}); 0515 register_constructible<model::Repeater, model::Modifier>(shapes); 0516 0517 register_from_meta<model::PathModifier, model::Modifier>(shapes); 0518 register_constructible<model::Trim, model::PathModifier>(shapes); 0519 register_constructible<model::InflateDeflate, model::PathModifier>(shapes); 0520 register_constructible<model::RoundCorners, model::PathModifier>(shapes); 0521 register_constructible<model::OffsetPath, model::PathModifier>(shapes); 0522 register_constructible<model::ZigZag, model::PathModifier>(shapes); 0523 }