mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 21:42:43 +00:00
Merge branch 'main' into feat/printer-agent-impl
This commit is contained in:
+15
-1
@@ -75,7 +75,7 @@ if (SLIC3R_GUI)
|
||||
list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat)
|
||||
list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES})
|
||||
endif ()
|
||||
|
||||
|
||||
# This is an issue in the new wxWidgets cmake build, doesn't deal with librt
|
||||
find_library(LIBRT rt)
|
||||
if(LIBRT)
|
||||
@@ -294,6 +294,16 @@ if (WIN32)
|
||||
endif()
|
||||
|
||||
else ()
|
||||
if (NOT APPLE)
|
||||
set(output_sos_Release "")
|
||||
set(output_sos_Debug "")
|
||||
add_custom_target(OrcaSlicerSosCopy ALL DEPENDS OrcaSlicer)
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
orcaslicer_copy_sos(OrcaSlicerSosCopy "Debug" "d" output_sos_Debug)
|
||||
else()
|
||||
orcaslicer_copy_sos(OrcaSlicerSosCopy "Release" "" output_sos_Release)
|
||||
endif()
|
||||
endif()
|
||||
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
|
||||
# On OSX, the name of the binary matches the name of the Application.
|
||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||
@@ -372,5 +382,9 @@ if (WIN32)
|
||||
install(FILES ${output_dlls_${build_type}} DESTINATION ".")
|
||||
install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python")
|
||||
else ()
|
||||
if (APPLE)
|
||||
else()
|
||||
install(FILES ${output_sos_${build_type}} DESTINATION "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
endif ()
|
||||
|
||||
@@ -83,6 +83,10 @@ copy_shared_object_to_dir() {
|
||||
src_real="$(readlink -f "$src")"
|
||||
dst_name="$(basename "$src_real")"
|
||||
mkdir -p "$dst_dir"
|
||||
if [ "$src_real" = "$dst_dir/$dst_name" ]; then
|
||||
# Already bundled; the dependency resolved from the bundle directory.
|
||||
return 0
|
||||
fi
|
||||
cp -fL "$src_real" "$dst_dir/$dst_name"
|
||||
|
||||
if [ -L "$src" ]; then
|
||||
@@ -96,12 +100,23 @@ copy_shared_object_to_dir() {
|
||||
}
|
||||
|
||||
bundle_dependency_closure() {
|
||||
local dst_dir="$1"
|
||||
local dst_dir
|
||||
dst_dir="$(cd -- "$1" && pwd)"
|
||||
shift
|
||||
|
||||
local -a queue=("$@")
|
||||
local target dep dep_real copied_path
|
||||
local target dep dep_real dep_key copied_path
|
||||
declare -A seen=()
|
||||
# Dependencies are resolved with ldd, which only searches the default
|
||||
# loader path. Deps-built shared libraries (e.g. the FFmpeg stack) are not
|
||||
# installed there and carry no RUNPATH of their own, so once copied into
|
||||
# the bundle ldd can no longer resolve one sibling from another
|
||||
# (libavcodec -> libavutil) and reports it as missing. Extend the loader
|
||||
# path with the bundle directory plus the source directories of files
|
||||
# already bundled, so every library that was resolved once keeps resolving
|
||||
# for its own dependencies. The audit script does the same
|
||||
# (scripts/check_appimage_libs.sh).
|
||||
local -a search_dirs=("$dst_dir")
|
||||
|
||||
while [ ${#queue[@]} -gt 0 ]; do
|
||||
target="${queue[0]}"
|
||||
@@ -122,17 +137,24 @@ bundle_dependency_closure() {
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "${seen[$dep_real]}" ]; then
|
||||
# Key dedup on the bundled file rather than the source path: once
|
||||
# ldd resolves a library from the bundle directory (via the
|
||||
# LD_LIBRARY_PATH above) its path is a dst_dir path, which differs
|
||||
# from the source path the first resolution returned. Keying on
|
||||
# the source path would re-copy the file onto itself.
|
||||
dep_key="$dst_dir/$(basename "$dep_real")"
|
||||
if [ -n "${seen[$dep_key]}" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
seen[$dep_real]=1
|
||||
seen[$dep_key]=1
|
||||
copy_shared_object_to_dir "$dep" "$dst_dir"
|
||||
search_dirs+=("$(dirname "$dep_real")")
|
||||
copied_path="$dst_dir/$(basename "$dep_real")"
|
||||
if [ -e "$copied_path" ]; then
|
||||
queue+=("$copied_path")
|
||||
fi
|
||||
done < <(appimage_list_direct_dependencies "$target")
|
||||
done < <(LD_LIBRARY_PATH="$(IFS=:; printf '%s' "${search_dirs[*]}")${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" appimage_list_direct_dependencies "$target")
|
||||
done
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ public:
|
||||
m_bbox(bbox.min - Point(SCALED_EPSILON, SCALED_EPSILON), bbox.max + Point(SCALED_EPSILON, SCALED_EPSILON)) {}
|
||||
size_t idx() const { return m_idx; }
|
||||
const BoundingBox& bbox() const { return m_bbox; }
|
||||
Point centroid() const { return (m_bbox.min() + m_bbox.max() / 2); }
|
||||
Point centroid() const { return (m_bbox.min() + m_bbox.max()) / 2; }
|
||||
private:
|
||||
size_t m_idx;
|
||||
BoundingBox m_bbox;
|
||||
|
||||
@@ -57,24 +57,24 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData
|
||||
//BBS: can be fit as arc, then save arc data temperarily
|
||||
last_arc = target_arc;
|
||||
if (back_index == points.size() - 1) {
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
result.emplace_back(PathFittingData{ front_index,
|
||||
back_index,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
last_arc });
|
||||
front_index = back_index;
|
||||
}
|
||||
} else {
|
||||
if (back_index - front_index > 2) {
|
||||
//BBS: althought current point_stack can't be fit as arc,
|
||||
//but previous must can be fit if removing the top in stack, so save last arc
|
||||
result.emplace_back(std::move(PathFittingData{ front_index,
|
||||
result.emplace_back(PathFittingData{ front_index,
|
||||
back_index - 1,
|
||||
last_arc.direction == ArcDirection::Arc_Dir_CCW ? EMovePathType::Arc_move_ccw : EMovePathType::Arc_move_cw,
|
||||
last_arc }));
|
||||
last_arc });
|
||||
} else {
|
||||
//BBS: save the first segment as line move when 3 point-line can't be fit as arc move
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()}));
|
||||
result.emplace_back(PathFittingData{front_index, front_index + 1, EMovePathType::Linear_move, ArcSegment()});
|
||||
else if(result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = front_index + 1;
|
||||
}
|
||||
@@ -87,7 +87,7 @@ void ArcFitter::do_arc_fitting(const Points& points, std::vector<PathFittingData
|
||||
//BBS: handle the remain data
|
||||
if (front_index != back_index) {
|
||||
if (result.empty() || result.back().path_type != EMovePathType::Linear_move)
|
||||
result.emplace_back(std::move(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()}));
|
||||
result.emplace_back(PathFittingData{front_index, back_index, EMovePathType::Linear_move, ArcSegment()});
|
||||
else if (result.back().path_type == EMovePathType::Linear_move)
|
||||
result.back().end_point_index = back_index;
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ Slic3r::Polylines Paths64_to_polylines(const Clipper2Lib::Paths64& in)
|
||||
Slic3r::Points points;
|
||||
points.reserve(path64.size());
|
||||
for (const Clipper2Lib::Point64& point64 : path64)
|
||||
points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y)));
|
||||
out.emplace_back(std::move(Slic3r::Polyline(points)));
|
||||
points.emplace_back(Slic3r::Point(point64.x, point64.y));
|
||||
out.emplace_back(Slic3r::Polyline(points));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -29,7 +29,7 @@ Clipper2Lib::Paths64 Slic3rPoints_to_Paths64(const Container& in)
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(item.size());
|
||||
for (const Slic3r::Point& point : item.points)
|
||||
path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
|
||||
path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
return out;
|
||||
@@ -44,7 +44,7 @@ Points Path64ToPoints(const Clipper2Lib::Path64& path64)
|
||||
{
|
||||
Points points;
|
||||
points.reserve(path64.size());
|
||||
for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(std::move(Slic3r::Point(point64.x, point64.y)));
|
||||
for (const Clipper2Lib::Point64 &point64 : path64) points.emplace_back(Slic3r::Point(point64.x, point64.y));
|
||||
return points;
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ Clipper2Lib::Paths64 Slic3rPolygons_to_Paths64(const Polygons &in)
|
||||
for (const Polygon &poly : in) {
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(poly.points.size());
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
return out;
|
||||
@@ -114,7 +114,7 @@ Clipper2Lib::Paths64 Slic3rExPolygons_to_Paths64(const ExPolygons& in)
|
||||
const auto &poly = expolygon.contour_or_hole(i);
|
||||
Clipper2Lib::Path64 path;
|
||||
path.reserve(poly.points.size());
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(std::move(Clipper2Lib::Point64(point.x(), point.y())));
|
||||
for (const Slic3r::Point &point : poly.points) path.emplace_back(Clipper2Lib::Point64(point.x(), point.y()));
|
||||
out.emplace_back(std::move(path));
|
||||
}
|
||||
}
|
||||
@@ -134,8 +134,8 @@ Polylines _clipper2_pl_open(Clipper2Lib::ClipType clipType, const Slic3r::Polyli
|
||||
|
||||
Slic3r::Polylines out;
|
||||
out.reserve(solution.size() + solution_open.size());
|
||||
polylines_append(out, std::move(Paths64_to_polylines(solution)));
|
||||
polylines_append(out, std::move(Paths64_to_polylines(solution_open)));
|
||||
polylines_append(out, Paths64_to_polylines(solution));
|
||||
polylines_append(out, Paths64_to_polylines(solution_open));
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ bool load_obj(const char *path, TriangleMesh *meshptr, ObjInfo& obj_info, std::s
|
||||
boost::filesystem::path temp_mtl_path(mtl_file);
|
||||
mtl_path = temp_mtl_path;
|
||||
}
|
||||
auto _mtl_path = mtl_name_is_path ? mtl_abs_path.string().c_str() : mtl_path.string().c_str();
|
||||
const std::string _mtl_path = (mtl_name_is_path ? mtl_abs_path : mtl_path).string();
|
||||
if (boost::filesystem::exists(mtl_name_is_path ? mtl_abs_path : mtl_path)) {
|
||||
if (!ObjParser::mtlparse(_mtl_path, mtl_data)) {
|
||||
if (!ObjParser::mtlparse(_mtl_path.c_str(), mtl_data)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "load_obj:load_mtl: failed to parse " << _mtl_path;
|
||||
message = _L("load mtl in obj: failed to parse");
|
||||
return false;
|
||||
|
||||
@@ -111,14 +111,19 @@ bool StepPreProcessor::isUtf8File(const char* path)
|
||||
bool StepPreProcessor::isUtf8(const std::string str)
|
||||
{
|
||||
size_t num = 0;
|
||||
int i = 0;
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if ((str[i] & 0x80) == 0x00) {
|
||||
const unsigned char lead = static_cast<unsigned char>(str[i]);
|
||||
if ((lead & 0x80) == 0x00) {
|
||||
i++;
|
||||
} else if ((num = preNum(str[i])) > 2) {
|
||||
// preNum() counts the leading 1 bits, and a multi-byte sequence is 2 to 4
|
||||
// bytes long, so anything outside that range is not a lead byte.
|
||||
} else if ((num = preNum(lead)) >= 2 && num <= 4) {
|
||||
if (i + num > str.length())
|
||||
return false;
|
||||
i++;
|
||||
for (int j = 0; j < num - 1; j++) {
|
||||
if ((str[i] & 0xc0) != 0x80)
|
||||
for (size_t j = 0; j < num - 1; j++) {
|
||||
if ((static_cast<unsigned char>(str[i]) & 0xc0) != 0x80)
|
||||
return false;
|
||||
i++;
|
||||
}
|
||||
@@ -132,15 +137,20 @@ bool StepPreProcessor::isUtf8(const std::string str)
|
||||
bool StepPreProcessor::isGBK(const std::string str) {
|
||||
size_t i = 0;
|
||||
while (i < str.length()) {
|
||||
if (str[i] <= 0x7f) {
|
||||
// char is signed here, so every byte compares <= 0x7f unless widened first.
|
||||
const unsigned char lead = static_cast<unsigned char>(str[i]);
|
||||
if (lead <= 0x7f) {
|
||||
i++;
|
||||
continue;
|
||||
} else {
|
||||
if (str[i] >= 0x81 &&
|
||||
str[i] <= 0xfe &&
|
||||
str[i + 1] >= 0x40 &&
|
||||
str[i + 1] <= 0xfe &&
|
||||
str[i + 1] != 0xf7) {
|
||||
if (i + 1 >= str.length())
|
||||
return false;
|
||||
const unsigned char trail = static_cast<unsigned char>(str[i + 1]);
|
||||
if (lead >= 0x81 &&
|
||||
lead <= 0xfe &&
|
||||
trail >= 0x40 &&
|
||||
trail <= 0xfe &&
|
||||
trail != 0xf7) {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
@@ -586,7 +596,7 @@ Step::Step_Status Step::mesh(Model* model,
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
|
||||
}
|
||||
// BBS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
|
||||
@@ -8963,7 +8963,7 @@ private:
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " inital and interval = " << m_interval;
|
||||
m_next_backup = boost::get_system_time() + boost::posix_time::seconds(m_interval);
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_thread = std::move(boost::thread(boost::ref(*this)));
|
||||
m_thread = boost::thread(boost::ref(*this));
|
||||
}
|
||||
|
||||
~_BBS_Backup_Manager() {
|
||||
|
||||
@@ -352,7 +352,7 @@ bool load_svg(const char *path, Model *model, std::string &message)
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
|
||||
}
|
||||
// BBS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
|
||||
@@ -9079,7 +9079,7 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role, LiftTyp
|
||||
continue;
|
||||
|
||||
Polygons temp;
|
||||
temp.emplace_back(std::move(instance_bbox.polygon()));
|
||||
temp.emplace_back(instance_bbox.polygon());
|
||||
if (intersection_pl(travel, temp).empty())
|
||||
continue;
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ struct PlateBBoxData
|
||||
int first_extruder = 0;
|
||||
float nozzle_diameter = 0.4;
|
||||
std::string bed_type;
|
||||
float first_layer_time;
|
||||
float first_layer_time = 0.0f;
|
||||
// version 1: use view type ColorPrint (filament color)
|
||||
// version 2: use view type FilamentId (filament id)
|
||||
int version = 2;
|
||||
|
||||
@@ -165,7 +165,6 @@ public:
|
||||
|
||||
|
||||
//BBS
|
||||
unsigned int m_last_additional_fan_speed;
|
||||
int m_last_bed_temperature;
|
||||
bool m_last_bed_temperature_reached;
|
||||
double m_lifted;
|
||||
|
||||
@@ -352,7 +352,7 @@ void segment(CGALMesh& src, std::vector<CGALMesh>& dst, double smoothing_alpha =
|
||||
//}
|
||||
//else
|
||||
{
|
||||
dst.emplace_back(std::move(CGALMesh(out)));
|
||||
dst.emplace_back(CGALMesh(out));
|
||||
}
|
||||
}
|
||||
//if (mesh_merged.is_empty() == false) {
|
||||
@@ -371,7 +371,7 @@ std::vector<TriangleMesh> segment(const TriangleMesh& src, double smoothing_alph
|
||||
std::vector<TriangleMesh> out_meshes;
|
||||
for (auto& outf_cgal_mesh: out_cgal_meshes)
|
||||
{
|
||||
out_meshes.emplace_back(std::move(cgal_to_triangle_mesh(outf_cgal_mesh.m)));
|
||||
out_meshes.emplace_back(cgal_to_triangle_mesh(outf_cgal_mesh.m));
|
||||
}
|
||||
|
||||
return out_meshes;
|
||||
|
||||
@@ -261,7 +261,7 @@ static void add_textured_mesh_to_model(Model& model, const TexturedMesh& tex_mes
|
||||
its_remove_degenerate_faces(its);
|
||||
its_compactify_vertices(its);
|
||||
|
||||
model.add_object(object_name.c_str(), input_file.c_str(), std::move(TriangleMesh(std::move(its))));
|
||||
model.add_object(object_name.c_str(), input_file.c_str(), TriangleMesh(std::move(its)));
|
||||
}
|
||||
|
||||
Model Model::read_from_file(const std::string& input_file,
|
||||
|
||||
@@ -3790,7 +3790,7 @@ std::vector<Polygons> Print::get_extruder_printable_polygons() const
|
||||
Polygons ploys = {Polygon::new_scale(e_printable_area)};
|
||||
extruder_printable_polys.emplace_back(ploys);
|
||||
}
|
||||
return std::move(extruder_printable_polys);
|
||||
return extruder_printable_polys;
|
||||
}
|
||||
|
||||
std::vector<Polygons> Print::get_extruder_unprintable_polygons() const
|
||||
@@ -3803,7 +3803,7 @@ std::vector<Polygons> Print::get_extruder_unprintable_polygons() const
|
||||
Polygons ploys = diff(printable_poly, Polygon::new_scale(e_printable_area));
|
||||
extruder_unprintable_polys.emplace_back(ploys);
|
||||
}
|
||||
return std::move(extruder_unprintable_polys);
|
||||
return extruder_unprintable_polys;
|
||||
}
|
||||
|
||||
size_t Print::get_extruder_id(unsigned int filament_id) const
|
||||
|
||||
@@ -906,7 +906,7 @@ void PrintObject::detect_overhangs_for_lift()
|
||||
Layer& lower_layer = *layer.lower_layer;
|
||||
|
||||
ExPolygons overhangs = diff_ex(layer.lslices, offset_ex(lower_layer.lslices, scale_(min_overlap)));
|
||||
layer.loverhangs = std::move(offset2_ex(overhangs, -0.1f * scale_(line_width), 0.1f * scale_(line_width)));
|
||||
layer.loverhangs = offset2_ex(overhangs, -0.1f * scale_(line_width), 0.1f * scale_(line_width));
|
||||
layer.loverhangs_bbox = get_extents(layer.loverhangs);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ static void MakeMesh(TopoDS_Shape& theSolid, TriangleMesh& theMesh)
|
||||
for (Standard_Integer aNodeIter = 1; aNodeIter <= aTriangulation->NbNodes(); ++aNodeIter) {
|
||||
gp_Pnt aPnt = aTriangulation->Node(aNodeIter);
|
||||
aPnt.Transform(aTrsf);
|
||||
points.emplace_back(std::move(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z())));
|
||||
points.emplace_back(Vec3f(aPnt.X(), aPnt.Y(), aPnt.Z()));
|
||||
}
|
||||
//BBS: copy triangles
|
||||
const TopAbs_Orientation anOrientation = anExpSF.Current().Orientation();
|
||||
|
||||
@@ -842,7 +842,7 @@ void TreeSupport::detect_overhangs(bool check_support_necessity/* = false*/)
|
||||
|
||||
// normal overhang
|
||||
ExPolygons lower_layer_offseted = offset_ex(lower_polys, support_offset_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS);
|
||||
overhangs_all_layers[layer_nr] = std::move(diff_ex(curr_polys, lower_layer_offseted));
|
||||
overhangs_all_layers[layer_nr] = diff_ex(curr_polys, lower_layer_offseted);
|
||||
|
||||
double duration{ std::chrono::duration_cast<second_>(clock_::now() - t0).count() };
|
||||
if (duration > 30 || overhangs_all_layers[layer_nr].size() > 100) {
|
||||
@@ -1396,7 +1396,7 @@ void TreeSupport::generate_toolpaths()
|
||||
raft_areas.push_back(expoly);
|
||||
}
|
||||
|
||||
raft_areas = std::move(offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion)));
|
||||
raft_areas = offset_ex(raft_areas, scale_(object_config.raft_first_layer_expansion));
|
||||
|
||||
size_t layer_nr = 0;
|
||||
for (; layer_nr < m_slicing_params.base_raft_layers; layer_nr++) {
|
||||
@@ -1522,9 +1522,9 @@ void TreeSupport::generate_toolpaths()
|
||||
erSupportMaterialInterface : erSupportMaterial;
|
||||
make_perimeter_and_inner_brim(ts_layer->support_fills.entities, poly, wall_count, flow,
|
||||
brim_role);
|
||||
polys = std::move(offset_ex(poly, -flow.scaled_spacing()));
|
||||
polys = offset_ex(poly, -flow.scaled_spacing());
|
||||
} else if (area_group.type == SupportLayer::Roof1stLayer) {
|
||||
polys = std::move(offset_ex(poly, 0.5*support_flow.scaled_width()));
|
||||
polys = offset_ex(poly, 0.5*support_flow.scaled_width());
|
||||
}
|
||||
else {
|
||||
polys.push_back(poly);
|
||||
@@ -2269,7 +2269,7 @@ void TreeSupport::draw_circles()
|
||||
// Inside the gap: remove only the part overlapping the contact surface, keep the rest.
|
||||
if (bottom_gap_height > EPSILON && layer_bottom_z < band_gap_top - EPSILON) {
|
||||
any_gap_cleared = true;
|
||||
comp_poly = std::move(diff_ex(comp_poly, band.surfaces));
|
||||
comp_poly = diff_ex(comp_poly, band.surfaces);
|
||||
}
|
||||
|
||||
// Overlaps interface band
|
||||
@@ -2304,7 +2304,7 @@ void TreeSupport::draw_circles()
|
||||
ExPolygons comp_interface = band_ex.empty() ? ExPolygons {} : intersection_ex(comp_poly, band_ex);
|
||||
if (!comp_interface.empty()) {
|
||||
append(new_floor_areas, comp_interface);
|
||||
comp_poly = std::move(diff_ex(comp_poly, offset_ex(comp_interface, 10)));
|
||||
comp_poly = diff_ex(comp_poly, offset_ex(comp_interface, 10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2396,7 +2396,7 @@ void TreeSupport::draw_circles()
|
||||
ts_layer->lslices.emplace_back(*expoly);
|
||||
}
|
||||
|
||||
ts_layer->lslices = std::move(union_ex(ts_layer->lslices));
|
||||
ts_layer->lslices = union_ex(ts_layer->lslices);
|
||||
//Must update bounding box which is used in avoid crossing perimeter
|
||||
ts_layer->lslices_bboxes.clear();
|
||||
ts_layer->lslices_bboxes.reserve(ts_layer->lslices.size());
|
||||
@@ -2474,7 +2474,7 @@ void TreeSupport::draw_circles()
|
||||
if (global_lightning_infill)
|
||||
{
|
||||
//search overhangs globally
|
||||
overhang = std::move(diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas));
|
||||
overhang = diff_ex(offset_ex(base_areas_lower, -2.0 * scale_(support_extrusion_width)), base_areas);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2485,13 +2485,13 @@ void TreeSupport::draw_circles()
|
||||
Polygon rev_hole = hole;
|
||||
rev_hole.make_counter_clockwise();
|
||||
ExPolygons ex_hole;
|
||||
ex_hole.emplace_back(std::move(ExPolygon(rev_hole)));
|
||||
ex_hole.emplace_back(ExPolygon(rev_hole));
|
||||
for (auto& other_area : base_areas)
|
||||
//if (&other_area != &base_area)
|
||||
ex_hole = std::move(diff_ex(ex_hole, other_area));
|
||||
overhang = std::move(union_ex(overhang, ex_hole));
|
||||
ex_hole = diff_ex(ex_hole, other_area);
|
||||
overhang = union_ex(overhang, ex_hole);
|
||||
}
|
||||
overhang = std::move(intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width))));
|
||||
overhang = intersection_ex(overhang, offset_ex(base_areas_lower, -0.5 * scale_(support_extrusion_width)));
|
||||
}
|
||||
|
||||
overhangs.emplace_back(to_polygons(overhang));
|
||||
@@ -2746,7 +2746,7 @@ void TreeSupport::drop_nodes()
|
||||
|
||||
m_object->print()->set_status(60 + int(10 * (1 - float(layer_nr) / contact_nodes.size())), _u8L("Generating support"));// (boost::format(_u8L("Support: propagate branches at layer %d")) % layer_nr).str());
|
||||
|
||||
Polygons layer_contours = std::move(m_ts_data->get_contours_with_holes(obj_layer_nr));
|
||||
Polygons layer_contours = m_ts_data->get_contours_with_holes(obj_layer_nr);
|
||||
//std::unordered_map<Line, bool, LineHash>& mst_line_x_layer_contour_cache = m_mst_line_x_layer_contour_caches[layer_nr];
|
||||
tbb::concurrent_unordered_map<Line, bool, LineHash> mst_line_x_layer_contour_cache;
|
||||
auto is_line_cut_by_contour = [&mst_line_x_layer_contour_cache,&layer_contours](Point a, Point b)
|
||||
@@ -3763,7 +3763,7 @@ const ExPolygons& TreeSupportData::calculate_avoidance(const RadiusLayerPair& ke
|
||||
}
|
||||
const ExPolygons &collision = get_collision(radius, layer_nr);
|
||||
avoidance_areas.insert(avoidance_areas.end(), collision.begin(), collision.end());
|
||||
avoidance_areas = std::move(union_ex(avoidance_areas));
|
||||
avoidance_areas = union_ex(avoidance_areas);
|
||||
auto ret = m_avoidance_cache.insert({key, std::move(avoidance_areas)});
|
||||
//assert(ret.second);
|
||||
return ret.first->second;
|
||||
|
||||
+36
-18
@@ -38,6 +38,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/AuxiliaryDataViewModel.hpp
|
||||
GUI/AuxiliaryDialog.cpp
|
||||
GUI/AuxiliaryDialog.hpp
|
||||
GUI/AVVideoDecoder.cpp
|
||||
GUI/AVVideoDecoder.hpp
|
||||
GUI/Auxiliary.hpp
|
||||
GUI/BackgroundSlicingProcess.cpp
|
||||
GUI/BackgroundSlicingProcess.hpp
|
||||
@@ -611,6 +613,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/WipeTowerDialog.cpp
|
||||
GUI/wxExtensions.cpp
|
||||
GUI/wxExtensions.hpp
|
||||
GUI/wxMediaCtrl3.cpp
|
||||
GUI/wxMediaCtrl3.h
|
||||
plugin/PythonInterpreter.cpp
|
||||
plugin/PythonInterpreter.hpp
|
||||
plugin/PythonPluginBridge.cpp
|
||||
@@ -799,21 +803,8 @@ if (APPLE)
|
||||
GUI/DeepLinkHandlerMac.mm
|
||||
GUI/DeepLinkHandlerMac.h
|
||||
GUI/GUI_UtilsMac.mm
|
||||
GUI/wxMediaCtrl2.mm
|
||||
GUI/wxMediaCtrl2.h
|
||||
)
|
||||
FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration)
|
||||
else ()
|
||||
list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/wxMediaCtrl2.cpp
|
||||
GUI/wxMediaCtrl2.h
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
list(APPEND SLIC3R_GUI_SOURCES
|
||||
GUI/Printer/gstbambusrc.c
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY}")
|
||||
@@ -913,6 +904,38 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
# Static FFmpeg from the deps install: nothing to bundle into the .app,
|
||||
# no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
|
||||
find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.")
|
||||
endif ()
|
||||
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||
elseif (WIN32)
|
||||
# Prebuilt shared FFmpeg from the deps install. Windows has no pkg-config,
|
||||
# so resolve the import libraries out of the deps prefix directly; the DLLs
|
||||
# are copied next to the executable by the top level CMakeLists.
|
||||
find_library(LIBAVCODEC_LIBRARY NAMES avcodec PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBSWSCALE_LIBRARY NAMES swscale PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
find_library(LIBAVUTIL_LIBRARY NAMES avutil PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
|
||||
if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY)
|
||||
message(FATAL_ERROR "FFmpeg (avcodec/swscale/avutil) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps.")
|
||||
endif ()
|
||||
target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
|
||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
|
||||
else ()
|
||||
pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
|
||||
libavcodec
|
||||
libswscale
|
||||
libavutil
|
||||
)
|
||||
target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
|
||||
endif()
|
||||
|
||||
# We need to implement some hacks for wxWidgets and touch the underlying GTK
|
||||
# layer and sub-libraries. This forces us to use the include locations and
|
||||
# link these libraries.
|
||||
@@ -939,11 +962,6 @@ if (UNIX AND NOT APPLE)
|
||||
target_compile_definitions(libslic3r_gui PRIVATE wxHAVE_GDK_WAYLAND)
|
||||
endif ()
|
||||
|
||||
# We add GStreamer for bambu:/// support.
|
||||
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
|
||||
pkg_check_modules(GST_BASE REQUIRED gstreamer-base-1.0)
|
||||
target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES})
|
||||
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS})
|
||||
endif ()
|
||||
|
||||
# Add a definition so that we can tell we are compiling slic3r.
|
||||
|
||||
@@ -55,7 +55,6 @@ private:
|
||||
|
||||
Label* m_text_label;
|
||||
wxStaticBitmap* m_icon_bitmap;
|
||||
int m_target_size;
|
||||
std::string m_icon_name;
|
||||
ScalableBitmap m_icon;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
#include "AVVideoDecoder.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
}
|
||||
|
||||
AVVideoDecoder::AVVideoDecoder()
|
||||
{
|
||||
codec_ctx_ = avcodec_alloc_context3(nullptr);
|
||||
}
|
||||
|
||||
AVVideoDecoder::~AVVideoDecoder()
|
||||
{
|
||||
if (sws_ctx_)
|
||||
sws_freeContext(sws_ctx_);
|
||||
if (frame_)
|
||||
av_frame_free(&frame_);
|
||||
if (codec_ctx_)
|
||||
avcodec_free_context(&codec_ctx_);
|
||||
}
|
||||
|
||||
int AVVideoDecoder::open(Bambu_StreamInfo const &info)
|
||||
{
|
||||
auto codec_id = info.sub_type == AVC1 ? AV_CODEC_ID_H264 : AV_CODEC_ID_MJPEG;
|
||||
auto codec = avcodec_find_decoder(codec_id);
|
||||
if (codec == nullptr) {
|
||||
fprintf(stderr, "AVVideoDecoder: unsupported codec!\n");
|
||||
return -1; // Codec not found
|
||||
}
|
||||
/* open the coderc */
|
||||
if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) {
|
||||
fprintf(stderr, "AVVideoDecoder: could not open codec\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Allocate an AVFrame structure
|
||||
frame_ = av_frame_alloc();
|
||||
if (frame_ == nullptr)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::decode(const Bambu_Sample &sample)
|
||||
{
|
||||
int ret = -1;
|
||||
AVPacket *pkt = av_packet_alloc();
|
||||
if (!pkt) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = av_new_packet(pkt, sample.size);
|
||||
if (ret != 0) {
|
||||
av_packet_free(&pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
memcpy(pkt->data, sample.buffer, size_t(sample.size));
|
||||
|
||||
ret = avcodec_send_packet(codec_ctx_, pkt);
|
||||
if (ret == 0) {
|
||||
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
|
||||
}
|
||||
|
||||
av_packet_unref(pkt);
|
||||
av_packet_free(&pkt);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int AVVideoDecoder::flush()
|
||||
{
|
||||
int ret = avcodec_send_packet(codec_ctx_, nullptr);
|
||||
got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AVVideoDecoder::close()
|
||||
{
|
||||
}
|
||||
|
||||
bool AVVideoDecoder::toWxImage(wxImage &image, wxSize const &size2)
|
||||
{
|
||||
if (!got_frame_)
|
||||
return false;
|
||||
|
||||
auto size1 = size2;
|
||||
if (!size1.IsFullySpecified())
|
||||
size1 = {frame_->width, frame_->height };
|
||||
auto size = size1;
|
||||
if (size.GetWidth() & 0x0f) {
|
||||
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
|
||||
if (size.GetWidth() != width_) {
|
||||
std::fill(bits_.begin(), bits_.end(), 0);
|
||||
width_ = size.GetWidth();
|
||||
}
|
||||
}
|
||||
AVPixelFormat wxFmt = AV_PIX_FMT_RGB24;
|
||||
sws_ctx_ = sws_getCachedContext(sws_ctx_,
|
||||
frame_->width, frame_->height, AVPixelFormat(frame_->format),
|
||||
size1.GetWidth(), size1.GetHeight(), wxFmt,
|
||||
SWS_GAUSS,
|
||||
nullptr, nullptr, nullptr);
|
||||
if (sws_ctx_ == nullptr)
|
||||
return false;
|
||||
int length = size.GetWidth() * size.GetHeight() * 3;
|
||||
if (bits_.size() < length)
|
||||
bits_.resize(length);
|
||||
uint8_t * datas[] = { bits_.data() };
|
||||
int strides[] = { size.GetWidth() * 3 };
|
||||
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
|
||||
if (result_h != size.GetHeight()) {
|
||||
return false;
|
||||
}
|
||||
// Copy: the frame outlives this decoder and is painted by the GUI thread while the
|
||||
// next sws_scale is already overwriting bits_, so it must own its pixels. The Windows
|
||||
// path below needs no equivalent, wxBitmap copies the bits into GDI.
|
||||
image = wxImage(size.GetWidth(), size.GetHeight(), bits_.data(), true).Copy();
|
||||
if (!image.IsOk()) {
|
||||
fprintf(stderr, "AVVideoDecoder: image not ok %dx%d\n", size.GetWidth(), size.GetHeight());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVVideoDecoder::toWxBitmap(wxBitmap &bitmap, wxSize const &size2)
|
||||
{
|
||||
if (!got_frame_)
|
||||
return false;
|
||||
|
||||
auto size1 = size2;
|
||||
if (!size1.IsFullySpecified())
|
||||
size1 = {frame_->width, frame_->height };
|
||||
auto size = size1;
|
||||
if (size.GetWidth() & 0x0f) {
|
||||
size.SetWidth((size.GetWidth() & ~0x0f) + 0x10);
|
||||
if (size.GetWidth() != width_) {
|
||||
std::fill(bits_.begin(), bits_.end(), 0);
|
||||
width_ = size.GetWidth();
|
||||
}
|
||||
}
|
||||
AVPixelFormat wxFmt = AV_PIX_FMT_RGB32;
|
||||
sws_ctx_ = sws_getCachedContext(sws_ctx_,
|
||||
frame_->width, frame_->height, AVPixelFormat(frame_->format),
|
||||
size1.GetWidth(), size1.GetHeight(), wxFmt,
|
||||
SWS_GAUSS,
|
||||
nullptr, nullptr, nullptr);
|
||||
if (sws_ctx_ == nullptr)
|
||||
return false;
|
||||
int length = size.GetWidth() * size.GetHeight() * 4;
|
||||
if (bits_.size() < length)
|
||||
bits_.resize(length);
|
||||
uint8_t *datas[] = { bits_.data() };
|
||||
int strides[] = { size.GetWidth() * 4 };
|
||||
int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides);
|
||||
if (result_h != size.GetHeight()) {
|
||||
fprintf(stderr, "AVVideoDecoder: result_h %d %d\n", result_h, size.GetHeight());
|
||||
return false;
|
||||
}
|
||||
bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
|
||||
assert(bitmap.IsOk());
|
||||
if (!bitmap.IsOk()) {
|
||||
fprintf(stderr, "AVVideoDecoder: bitmap not ok %dx%d\n", size.GetWidth(), size.GetHeight());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef AVVIDEODECODER_HPP
|
||||
#define AVVIDEODECODER_HPP
|
||||
|
||||
#include "Printer/BambuTunnel.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
#include <vector>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
class wxBitmap;
|
||||
|
||||
class AVVideoDecoder
|
||||
{
|
||||
public:
|
||||
AVVideoDecoder();
|
||||
|
||||
~AVVideoDecoder();
|
||||
|
||||
public:
|
||||
int open(Bambu_StreamInfo const &info);
|
||||
|
||||
int decode(Bambu_Sample const &sample);
|
||||
|
||||
int flush();
|
||||
|
||||
void close();
|
||||
|
||||
bool toWxImage(wxImage &image, wxSize const &size);
|
||||
|
||||
bool toWxBitmap(wxBitmap &bitmap, wxSize const & size);
|
||||
|
||||
private:
|
||||
AVCodecContext *codec_ctx_ = nullptr;
|
||||
AVFrame * frame_ = nullptr;
|
||||
SwsContext * sws_ctx_ = nullptr;
|
||||
bool got_frame_ = false;
|
||||
int width_ { 0 }; // scale result width
|
||||
std::vector<uint8_t> bits_;
|
||||
};
|
||||
|
||||
#endif // AVVIDEODECODER_HPP
|
||||
@@ -60,7 +60,6 @@ class AboutDialog : public DPIDialog
|
||||
wxHtmlWindow* m_html;
|
||||
wxStaticBitmap* m_logo;
|
||||
int m_copy_rights_btn_id { wxID_ANY };
|
||||
int m_copy_version_btn_id { wxID_ANY };
|
||||
public:
|
||||
AboutDialog();
|
||||
|
||||
|
||||
@@ -93,7 +93,6 @@ private:
|
||||
CenteredTitle* m_title_ctrl { nullptr };
|
||||
wxString m_titleText;
|
||||
|
||||
wxAuiToolBarItem* m_account_item;
|
||||
wxAuiToolBarItem* m_model_store_item;
|
||||
|
||||
//wxAuiToolBarItem *m_publish_item;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// BambuPlayer.h
|
||||
// BambuPlayer
|
||||
//
|
||||
// Created by cmguo on 2021/12/6.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVSampleBufferDisplayLayer.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface BambuPlayer : NSObject
|
||||
|
||||
+ (void) initialize;
|
||||
|
||||
- (instancetype) initWithDisplayLayer: (AVSampleBufferDisplayLayer*) layer;
|
||||
- (instancetype) initWithImageView: (NSView*) view;
|
||||
- (int) open: (char const *) url;
|
||||
- (NSSize) videoSize;
|
||||
- (int) play;
|
||||
- (void) stop;
|
||||
- (void) close;
|
||||
|
||||
- (void) setLogger: (void (*)(void const * context, int level, char const * msg)) logger withContext: (void const *) context;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -74,12 +74,10 @@ private:
|
||||
std::unordered_set<std::string> m_system_filament_types_set;
|
||||
std::set<std::string> m_visible_printers;
|
||||
CreateType m_create_type;
|
||||
Button * m_button_create = nullptr;
|
||||
Button * m_button_cancel = nullptr;
|
||||
ComboBox * m_filament_vendor_combobox = nullptr;
|
||||
::CheckBox * m_can_not_find_vendor_checkbox = nullptr;
|
||||
ComboBox * m_filament_type_combobox = nullptr;
|
||||
ComboBox * m_exist_vendor_combobox = nullptr;
|
||||
ComboBox * m_filament_preset_combobox = nullptr;
|
||||
TextInput * m_filament_custom_vendor_input = nullptr;
|
||||
wxGridSizer * m_filament_presets_sizer = nullptr;
|
||||
|
||||
@@ -16,24 +16,27 @@ namespace Slic3r
|
||||
// This block is never executed at runtime.
|
||||
static void _toolhead_translation_markers()
|
||||
{
|
||||
// Dynamic toolhead display names from JSON config — xgettext cannot scan these
|
||||
L("Main Extruder"); L("Main extruder"); L("main extruder");
|
||||
L("Auxiliary Extruder"); L("Auxiliary extruder"); L("auxiliary extruder");
|
||||
L("Left Extruder"); L("Left extruder"); L("left extruder");
|
||||
L("Right Extruder"); L("Right extruder"); L("right extruder");
|
||||
L("Main Nozzle"); L("Main nozzle"); L("main nozzle");
|
||||
L("Auxiliary Nozzle"); L("Auxiliary nozzle"); L("auxiliary nozzle");
|
||||
L("Left Nozzle"); L("Left nozzle"); L("left nozzle");
|
||||
L("Right Nozzle"); L("Right nozzle"); L("right nozzle");
|
||||
L("Main Hotend"); L("Main hotend"); L("main hotend");
|
||||
L("Auxiliary Hotend"); L("Auxiliary hotend"); L("auxiliary hotend");
|
||||
L("Left Hotend"); L("Left hotend"); L("left hotend");
|
||||
L("Right Hotend"); L("Right hotend"); L("right hotend");
|
||||
// standalone position words (short_name=true runtime results)
|
||||
L("main"); L("auxiliary");
|
||||
L("Main"); L("Auxiliary");
|
||||
L("left"); L("right");
|
||||
L("Left"); L("Right");
|
||||
// Possible runtime values of tool_head_display_names, marked for extraction.
|
||||
static const char *const markers[] = {
|
||||
L("Main Extruder"), L("Main extruder"), L("main extruder"),
|
||||
L("Auxiliary Extruder"), L("Auxiliary extruder"), L("auxiliary extruder"),
|
||||
L("Left Extruder"), L("Left extruder"), L("left extruder"),
|
||||
L("Right Extruder"), L("Right extruder"), L("right extruder"),
|
||||
L("Main Nozzle"), L("Main nozzle"), L("main nozzle"),
|
||||
L("Auxiliary Nozzle"), L("Auxiliary nozzle"), L("auxiliary nozzle"),
|
||||
L("Left Nozzle"), L("Left nozzle"), L("left nozzle"),
|
||||
L("Right Nozzle"), L("Right nozzle"), L("right nozzle"),
|
||||
L("Main Hotend"), L("Main hotend"), L("main hotend"),
|
||||
L("Auxiliary Hotend"), L("Auxiliary hotend"), L("auxiliary hotend"),
|
||||
L("Left Hotend"), L("Left hotend"), L("left hotend"),
|
||||
L("Right Hotend"), L("Right hotend"), L("right hotend"),
|
||||
// standalone position words (short_name=true runtime results)
|
||||
L("main"), L("auxiliary"),
|
||||
L("Main"), L("Auxiliary"),
|
||||
L("left"), L("right"),
|
||||
L("Left"), L("Right"),
|
||||
};
|
||||
(void) markers;
|
||||
}
|
||||
|
||||
std::string DevPrinterConfigUtil::m_resource_file_path = "";
|
||||
|
||||
@@ -199,7 +199,6 @@ private:
|
||||
void OnRefreshButton(wxCommandEvent& event);
|
||||
|
||||
private:
|
||||
int saveTimes{0};
|
||||
wxBoxSizer* mainSizer{nullptr};
|
||||
wxPanel* textPanel{nullptr};
|
||||
wxBoxSizer* textSizer{nullptr};
|
||||
|
||||
@@ -122,7 +122,6 @@ private:
|
||||
private:
|
||||
int m_ext_nozzle_id = -1;
|
||||
int m_rack_nozzle_id = -1;
|
||||
bool m_isRefreshFinish = false;
|
||||
bool findNozzleImage = false;
|
||||
|
||||
NozzleStatus m_nozzle_status = NOZZLE_STATUS_DC;
|
||||
@@ -154,7 +153,6 @@ private:
|
||||
Label* m_diameter_label;
|
||||
Label* m_flowtype_label;
|
||||
Label* m_type_label;
|
||||
ScalableButton* m_error_button{ nullptr };
|
||||
|
||||
Label* m_sn_label;
|
||||
Label* m_version_label;
|
||||
|
||||
@@ -266,7 +266,7 @@ void wgtDeviceNozzleRackSelect::OnNozzleItemSelected(wxCommandEvent &evt)
|
||||
}
|
||||
|
||||
auto *item = dynamic_cast<wgtDeviceNozzleRackNozzleItem *>(evt.GetEventObject());
|
||||
if (item; auto ptr = m_nozzle_rack.lock()) {
|
||||
if (auto ptr = m_nozzle_rack.lock(); item && ptr) {
|
||||
int to_select_pos_id = sGetNozzlePosId(item, m_toolhead_nozzle_l, m_toolhead_nozzle_r);
|
||||
if (to_select_pos_id > -1 && to_select_pos_id != GetSelectedNozzlePosID()) {
|
||||
SetSelectedNozzle(ptr->GetNozzleSystem()->GetNozzleByPosId(to_select_pos_id));
|
||||
|
||||
@@ -72,7 +72,6 @@ public:
|
||||
float m_model_z_offset{ 0.5f };
|
||||
bool m_visible{ true };
|
||||
bool m_is_dark = false;
|
||||
bool m_fixed_screen_size{ false };
|
||||
float m_scale_factor{ 1.0f };
|
||||
#if ENABLE_ACTUAL_SPEED_DEBUG
|
||||
ActualSpeedImguiWidget m_actual_speed_imgui_widget;
|
||||
|
||||
@@ -327,7 +327,6 @@ private:
|
||||
GLTexture m_icons_texture;
|
||||
bool m_icons_texture_dirty;
|
||||
mutable GLTexture m_images_texture;
|
||||
mutable bool m_images_texture_dirty;
|
||||
BackgroundTexture m_background_texture;
|
||||
GLTexture m_arrow_texture;
|
||||
Layout m_layout;
|
||||
|
||||
@@ -86,7 +86,6 @@ private:
|
||||
boost::thread m_thread;
|
||||
// Mutex and condition variable to synchronize m_thread with the UI thread.
|
||||
std::mutex m_mutex;
|
||||
int m_generate_count;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
|
||||
@@ -176,7 +176,6 @@ private:
|
||||
// Use those values to disable selection of active extruders
|
||||
bool m_is_dark = false;
|
||||
|
||||
bool is_osx{false};
|
||||
int m_min_value;
|
||||
int m_max_value;
|
||||
int m_lower_value;
|
||||
@@ -201,10 +200,6 @@ private:
|
||||
void *m_one_layer_on_hover_id;
|
||||
void *m_one_layer_off_id;
|
||||
void *m_one_layer_off_hover_id;
|
||||
void* m_one_layer_on_light_id;
|
||||
void* m_one_layer_on_hover_light_id;
|
||||
void* m_one_layer_off_light_id;
|
||||
void* m_one_layer_off_hover_light_id;
|
||||
void* m_one_layer_on_dark_id;
|
||||
void* m_one_layer_on_hover_dark_id;
|
||||
void* m_one_layer_off_dark_id;
|
||||
|
||||
@@ -2482,6 +2482,19 @@ static const ImWchar ranges_keyboard_shortcuts[] =
|
||||
};
|
||||
#endif // __APPLE__
|
||||
|
||||
// Names drawn through the atlas come from file names and CAD data, not from the UI language.
|
||||
// GetGlyphRangesDefault() already gives every language the CJK ideographs, which is why a
|
||||
// Chinese file name renders under an English UI; these are the alphabetic scripts it omits.
|
||||
// Codepoints the font lacks are skipped at build time, so only existing glyphs cost anything.
|
||||
static const ImWchar ranges_language_independent[] =
|
||||
{
|
||||
0x0100, 0x024F, // Latin Extended-A and Extended-B
|
||||
0x0370, 0x03FF, // Greek and Coptic
|
||||
0x0400, 0x04FF, // Cyrillic
|
||||
0x1E00, 0x1EFF, // Latin Extended Additional (Vietnamese)
|
||||
0,
|
||||
};
|
||||
|
||||
|
||||
std::vector<unsigned char> ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, unsigned *outwidth, unsigned *outheight)
|
||||
{
|
||||
@@ -2792,6 +2805,7 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
ImFontAtlas::GlyphRangesBuilder builder;
|
||||
builder.AddRanges(m_glyph_ranges);
|
||||
builder.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesDefault());
|
||||
builder.AddRanges(ranges_language_independent);
|
||||
#ifdef __APPLE__
|
||||
if (m_font_cjk)
|
||||
// Apple keyboard shortcuts are only contained in the CJK fonts.
|
||||
@@ -2813,12 +2827,17 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
// Orca: temp fix for Korean font
|
||||
auto font_name_regular = "HarmonyOS_Sans_SC_Regular.ttf";
|
||||
auto font_name_bold = "HarmonyOS_Sans_SC_Bold.ttf";
|
||||
// The Korean and Thai fonts cover their own script and little else, so they need the
|
||||
// default font merged in behind them to reach the full range.
|
||||
bool needs_glyph_fallback = false;
|
||||
if(m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesKorean()) {
|
||||
font_name_regular = "NanumGothic-Regular.ttf";
|
||||
font_name_bold = "NanumGothic-Bold.ttf";
|
||||
needs_glyph_fallback = true;
|
||||
} else if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
font_name_regular = "Sarabun-Medium.ttf";
|
||||
font_name_bold = "Sarabun-SemiBold.ttf";
|
||||
needs_glyph_fallback = true;
|
||||
}
|
||||
default_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + font_name_regular).c_str(), m_font_size, &cfg, ranges.Data);
|
||||
if (default_font == nullptr) {
|
||||
@@ -2828,11 +2847,12 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
}
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
// A merged font only supplies glyphs the font ahead of it lacks, so this fills the gaps
|
||||
// without restyling anything the script font already covers.
|
||||
if (needs_glyph_fallback) {
|
||||
ImFontConfig fallback_cfg = cfg;
|
||||
fallback_cfg.MergeMode = true;
|
||||
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Regular.ttf").c_str(), m_font_size, &fallback_cfg, ranges.Data);
|
||||
}
|
||||
|
||||
bold_font = io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/" + font_name_bold).c_str(), m_font_size, &cfg, ranges.Data);
|
||||
@@ -2841,11 +2861,10 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
if (bold_font == nullptr) { throw Slic3r::RuntimeError("ImGui: Could not load deafult font"); }
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
if (needs_glyph_fallback) {
|
||||
ImFontConfig fallback_cfg = cfg;
|
||||
fallback_cfg.MergeMode = true;
|
||||
static constexpr ImWchar celsius_range[] = { 0x2103, 0x2103, 0 };
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &fallback_cfg, celsius_range);
|
||||
io.Fonts->AddFontFromFileTTF((Slic3r::resources_dir() + "/fonts/HarmonyOS_Sans_SC_Bold.ttf").c_str(), m_font_size, &fallback_cfg, ranges.Data);
|
||||
}
|
||||
|
||||
if (m_glyph_ranges == ImGui::GetIO().Fonts->GetGlyphRangesThai()) {
|
||||
@@ -2897,13 +2916,18 @@ void ImGuiWrapper::init_font(bool compress)
|
||||
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_tex_size));
|
||||
constexpr int max_retries = 6;
|
||||
for (int attempt = 0; attempt < max_retries && io.Fonts->TexHeight > gl_max_tex_size; ++attempt) {
|
||||
io.Fonts->TexDesiredWidth = (io.Fonts->TexDesiredWidth > 0 ? io.Fonts->TexDesiredWidth : io.Fonts->TexWidth) * 2;
|
||||
const int width = io.Fonts->TexDesiredWidth > 0 ? io.Fonts->TexDesiredWidth : io.Fonts->TexWidth;
|
||||
// Both dimensions share the same limit, so widening past it would only trade an
|
||||
// illegal height for an illegal width.
|
||||
if (width * 2 > gl_max_tex_size)
|
||||
break;
|
||||
io.Fonts->TexDesiredWidth = width * 2;
|
||||
io.Fonts->Build();
|
||||
}
|
||||
if (io.Fonts->TexHeight > gl_max_tex_size) {
|
||||
// Shouldn't really happen
|
||||
BOOST_LOG_TRIVIAL(error) << "Font atlas height " << io.Fonts->TexHeight
|
||||
<< " still exceeds GL_MAX_TEXTURE_SIZE (" << gl_max_tex_size << ")"
|
||||
// Needs both a very large glyph set and a small GL_MAX_TEXTURE_SIZE.
|
||||
BOOST_LOG_TRIVIAL(error) << "Font atlas " << io.Fonts->TexWidth << "x" << io.Fonts->TexHeight
|
||||
<< " does not fit GL_MAX_TEXTURE_SIZE (" << gl_max_tex_size << ")"
|
||||
<< " after " << max_retries << " attempts; rendering may be incomplete";
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ bool ImageDPIFrame::Show(bool show)
|
||||
}
|
||||
|
||||
void ImageDPIFrame::set_bitmap(const wxBitmap &bit_map) {
|
||||
if (&bit_map && bit_map.IsOk()) {
|
||||
if (bit_map.IsOk()) {
|
||||
m_bitmap->SetBitmap(bit_map);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ private:
|
||||
::Button *m_button_year = nullptr;
|
||||
::Button *m_button_month = nullptr;
|
||||
::Button *m_button_all = nullptr;
|
||||
::Label *m_switch_label = nullptr;
|
||||
|
||||
::StaticBox * m_type_panel = nullptr;
|
||||
::Button * m_button_video = nullptr;
|
||||
|
||||
@@ -40,13 +40,14 @@ static std::map<int, std::string> error_messages = {
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size)
|
||||
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size)
|
||||
: wxPanel(parent, wxID_ANY, pos, size)
|
||||
, m_media_ctrl(media_ctrl)
|
||||
{
|
||||
SetLabel("MediaPlayCtrl");
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this);
|
||||
m_media_ctrl->SetIdleImage(from_u8(resources_dir() + "/images/live_stream_default.png"));
|
||||
|
||||
m_button_play = new Button(this, "", "media_play", wxBORDER_NONE);
|
||||
m_button_play->SetCanFocus(false);
|
||||
@@ -178,13 +179,6 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
|
||||
if (machine == m_machine) {
|
||||
if (m_last_state == MEDIASTATE_IDLE && IsEnabled())
|
||||
Play();
|
||||
else if (m_last_state == MEDIASTATE_LOADING && m_tutk_state == "disable"
|
||||
&& m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) {
|
||||
// resend ttcode to printer
|
||||
if (auto agent = wxGetApp().getAgent())
|
||||
agent->get_camera_url(machine, [](auto) {}, wxGetApp().get_printer_cloud_provider());
|
||||
m_last_user_play = wxDateTime::Now();
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_machine = machine;
|
||||
@@ -352,7 +346,7 @@ void MediaPlayCtrl::Play()
|
||||
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
|
||||
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url,
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url,
|
||||
{"?uid=", "authkey=", "passwd=", "license=", "token="});
|
||||
CallAfter([this, m, url] {
|
||||
if (m != m_machine) {
|
||||
@@ -427,7 +421,7 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2)
|
||||
auto tunnel = m_url.empty() ? "" : into_u8(wxURI(m_url).GetPath()).substr(1);
|
||||
if (auto n = tunnel.find_first_of("/_"); n != std::string::npos)
|
||||
tunnel = tunnel.substr(0, n);
|
||||
if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0
|
||||
if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0
|
||||
&& m_last_failed_codes.find(m_failed_code) == m_last_failed_codes.end()
|
||||
&& (m_user_triggered || m_failed_retry > 3)) {
|
||||
m_last_failed_codes.insert(m_failed_code);
|
||||
@@ -561,7 +555,7 @@ void MediaPlayCtrl::ToggleStream()
|
||||
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
|
||||
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url,
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url,
|
||||
{"?uid=", "authkey=", "passwd=", "license=", "token="});
|
||||
CallAfter([this, m, url] {
|
||||
if (m != m_machine) return;
|
||||
@@ -581,8 +575,8 @@ void MediaPlayCtrl::ToggleStream()
|
||||
}, wxGetApp().get_printer_cloud_provider());
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::msw_rescale() {
|
||||
m_button_play->Rescale();
|
||||
void MediaPlayCtrl::msw_rescale() {
|
||||
m_button_play->Rescale();
|
||||
}
|
||||
|
||||
void MediaPlayCtrl::jump_to_play()
|
||||
@@ -716,7 +710,9 @@ void MediaPlayCtrl::media_proc()
|
||||
break;
|
||||
}
|
||||
else if (url == "<play>") {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play";
|
||||
m_media_ctrl->Play();
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play";
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
|
||||
@@ -772,15 +768,15 @@ bool MediaPlayCtrl::start_stream_service(bool *need_install)
|
||||
if (!boost::filesystem::exists(file_dll) || boost::filesystem::last_write_time(file_dll) != boost::filesystem::last_write_time(file_dll2))
|
||||
boost::filesystem::copy_file(file_dll2, file_dll, boost::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir),
|
||||
boost::process::windows::create_no_window,
|
||||
boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir),
|
||||
boost::process::windows::create_no_window,
|
||||
boost::process::std_out > intermediate, boost::process::limit_handles);
|
||||
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window,
|
||||
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window,
|
||||
boost::process::std_in < intermediate, boost::process::limit_handles);
|
||||
#else
|
||||
boost::filesystem::permissions(file_source, boost::filesystem::owner_exe | boost::filesystem::add_perms);
|
||||
boost::filesystem::permissions(file_ffmpeg, boost::filesystem::owner_exe | boost::filesystem::add_perms);
|
||||
boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir),
|
||||
boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir),
|
||||
boost::process::std_out > intermediate, boost::process::limit_handles);
|
||||
boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::std_in < intermediate, boost::process::limit_handles);
|
||||
#endif
|
||||
@@ -831,27 +827,16 @@ bool MediaPlayCtrl::get_stream_url(std::string *url)
|
||||
|
||||
}}
|
||||
|
||||
void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height)
|
||||
{
|
||||
#ifdef __WXMAC__
|
||||
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
||||
#else
|
||||
wxMediaCtrl::DoSetSize(x, y, width, height, sizeFlags);
|
||||
#endif
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_gtk_video_window) {
|
||||
const wxSize client_size = GetClientSize();
|
||||
m_gtk_video_window->SetSize(0, 0, client_size.GetWidth(), client_size.GetHeight());
|
||||
}
|
||||
#endif
|
||||
if (sizeFlags & wxSIZE_USE_EXISTING) return;
|
||||
wxSize size = m_video_size;
|
||||
wxSize size = videoSize;
|
||||
if (!size.IsFullySpecified()) size = {16, 9};
|
||||
int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth();
|
||||
if (maxHeight != GetMaxHeight()) {
|
||||
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
|
||||
SetMaxSize({-1, maxHeight});
|
||||
CallAfter([this] {
|
||||
if (auto p = GetParent()) {
|
||||
if (maxHeight != ctrl->GetMaxHeight()) {
|
||||
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl_OnSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
|
||||
ctrl->SetMaxSize({-1, maxHeight});
|
||||
ctrl->CallAfter([ctrl] {
|
||||
if (auto p = ctrl->GetParent()) {
|
||||
p->Layout();
|
||||
p->Refresh();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef MediaPlayCtrl_h
|
||||
#define MediaPlayCtrl_h
|
||||
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "wxMediaCtrl3.h"
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace GUI {
|
||||
class MediaPlayCtrl : public wxPanel
|
||||
{
|
||||
public:
|
||||
MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
|
||||
MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize);
|
||||
|
||||
~MediaPlayCtrl();
|
||||
|
||||
@@ -75,7 +75,7 @@ private:
|
||||
// token
|
||||
std::shared_ptr<int> m_token = std::make_shared<int>(0);
|
||||
|
||||
wxMediaCtrl2 * m_media_ctrl;
|
||||
wxMediaCtrl3 * m_media_ctrl;
|
||||
wxMediaState m_last_state = MEDIASTATE_IDLE;
|
||||
std::string m_machine;
|
||||
int m_lan_proto = 0;
|
||||
@@ -90,7 +90,7 @@ private:
|
||||
bool m_device_busy = false;
|
||||
bool m_disable_lan = false;
|
||||
wxString m_url;
|
||||
|
||||
|
||||
std::deque<wxString> m_tasks;
|
||||
boost::mutex m_mutex;
|
||||
boost::condition_variable m_cond;
|
||||
|
||||
@@ -86,11 +86,8 @@ private:
|
||||
|
||||
/* side tools */
|
||||
SideTools* m_side_tools{nullptr};
|
||||
wxStaticBitmap* m_bitmap_printer_type;
|
||||
wxStaticBitmap* m_bitmap_arrow;
|
||||
wxStaticText* m_staticText_printer_name;
|
||||
wxStaticBitmap* m_bitmap_wifi_signal;
|
||||
wxBoxSizer * m_side_tools_sizer;
|
||||
SelectMachinePopup m_select_machine;
|
||||
|
||||
/* images */
|
||||
@@ -101,7 +98,6 @@ private:
|
||||
wxBitmap m_printer_img;
|
||||
wxBitmap m_arrow_img;
|
||||
|
||||
int last_wifi_signal = -1;
|
||||
int last_status;
|
||||
bool m_initialized { false };
|
||||
bool update_flag{false};
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include "Widgets/AxisCtrlButton.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "MediaPlayCtrl.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -88,7 +88,6 @@ private:
|
||||
Button* m_task_name{ nullptr };
|
||||
Button* m_status{ nullptr };
|
||||
Button* m_action{ nullptr };
|
||||
Button* m_stop_all_botton{nullptr};
|
||||
|
||||
// tip when no device
|
||||
wxStaticText* m_tip_text{ nullptr };
|
||||
|
||||
@@ -79,7 +79,6 @@ private:
|
||||
std::vector<wxBoxSizer *> m_row_col_boxsizer_list;
|
||||
std::vector<ButtonState*> m_result_icon_list;
|
||||
int m_last_cluster_num{-1};
|
||||
const int m_combox_width{50};
|
||||
int m_combox_icon_width;
|
||||
int m_combox_icon_height;
|
||||
wxButton * m_image_button = nullptr;
|
||||
|
||||
@@ -6470,7 +6470,7 @@ int PartPlateList::store_to_3mf_structure(PlateDataPtrs& plate_data_list, bool w
|
||||
plate_data_item->filament_change_sequence = m_plate_list[i]->m_gcode_result->filament_change_sequence;
|
||||
plate_data_item->nozzle_change_sequence = m_plate_list[i]->m_gcode_result->nozzle_change_sequence;
|
||||
plate_data_item->optimal_assignment = m_plate_list[i]->m_gcode_result->optimal_assignment;
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->cali_bboxes_data.first_layer_time);
|
||||
plate_data_item->first_layer_time = std::to_string(m_plate_list[i]->get_slice_result()->initial_layer_time);
|
||||
Print *print = nullptr;
|
||||
m_plate_list[i]->get_print((PrintBase **) &print, nullptr, nullptr);
|
||||
if (print) {
|
||||
|
||||
@@ -128,7 +128,6 @@ private:
|
||||
bool is_drag_mode();
|
||||
|
||||
boost::shared_ptr<PrinterFileSystem> m_file_sys;
|
||||
bool m_file_sys_result{false};
|
||||
std::string m_timestamp;
|
||||
std::string m_tmp_path;
|
||||
std::vector<string> m_local_paths;
|
||||
|
||||
@@ -33,7 +33,6 @@ class PhysicalPrinterDialog : public DPIDialog
|
||||
Button* m_printhost_test_btn {nullptr};
|
||||
Button* m_printhost_logout_btn {nullptr};
|
||||
Button* m_printhost_cafile_browse_btn {nullptr};
|
||||
Button* m_printhost_client_cert_browse_btn {nullptr};
|
||||
Button* m_printhost_port_browse_btn {nullptr};
|
||||
|
||||
RoundedRectangle* m_input_area {nullptr};
|
||||
|
||||
@@ -3870,7 +3870,7 @@ bool Sidebar::reset_bed_type_combox_choices(bool is_sidebar_init)
|
||||
}
|
||||
}
|
||||
m_last_combo_bedtype_count = p->combo_printer_bed->GetCount();
|
||||
if (!is_sidebar_init && &p->plater->get_partplate_list()) {
|
||||
if (!is_sidebar_init) {
|
||||
p->plater->get_partplate_list().check_all_plate_local_bed_type(m_cur_combox_bed_types);
|
||||
}
|
||||
return true;
|
||||
@@ -21688,7 +21688,7 @@ void Plater::show_object_info()
|
||||
auto mesh_errors = p->sidebar->obj_list()->get_mesh_errors_info(&info_manifold, &non_manifold_edges);
|
||||
|
||||
if (non_manifold_edges > 0) {
|
||||
info_manifold += into_u8("\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh."));
|
||||
info_manifold += "\n" + _L("Tips:") + "\n" + _L("Use \"Fix Model\" to repair the mesh.");
|
||||
}
|
||||
|
||||
info_manifold = "<Error>" + info_manifold + "</Error>";
|
||||
|
||||
@@ -662,10 +662,10 @@ PrinterFileSystem::File const &PrinterFileSystem::GetFile(size_t index, bool &se
|
||||
void PrinterFileSystem::Attached()
|
||||
{
|
||||
boost::unique_lock lock(m_mutex);
|
||||
m_recv_thread = std::move(boost::thread([w = weak_from_this()] {
|
||||
m_recv_thread = boost::thread([w = weak_from_this()] {
|
||||
boost::shared_ptr<PrinterFileSystem> s = w.lock();
|
||||
if (s) s->RecvMessageThread();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
void PrinterFileSystem::Start()
|
||||
|
||||
@@ -1,657 +0,0 @@
|
||||
/* bambusrc for gstreamer
|
||||
* integration with proprietary Bambu Lab blob for getting raw h.264 video
|
||||
*
|
||||
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the
|
||||
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
||||
* which case the following provisions apply instead of the ones
|
||||
* mentioned above:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstbambusrc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#ifndef EXTERNAL_GST_PLUGIN
|
||||
#define BAMBU_DYNAMIC
|
||||
#endif
|
||||
#include "BambuTunnel.h"
|
||||
|
||||
#ifdef BAMBU_DYNAMIC
|
||||
// From PrinterFileSystem.
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
#else
|
||||
extern
|
||||
#endif
|
||||
BambuLib *bambulib_get();
|
||||
BambuLib *_lib = NULL;
|
||||
#define BAMBULIB(x) (_lib->x)
|
||||
|
||||
#else
|
||||
#define BAMBULIB(x) (x)
|
||||
#endif
|
||||
|
||||
GST_DEBUG_CATEGORY_STATIC (gst_bambusrc_debug);
|
||||
#define GST_CAT_DEFAULT gst_bambusrc_debug
|
||||
|
||||
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
|
||||
GST_PAD_SRC,
|
||||
GST_PAD_ALWAYS,
|
||||
GST_STATIC_CAPS_ANY);
|
||||
//GST_STATIC_CAPS("video/x-h264,framerate=0/1,parsed=(boolean)false,stream-format=(string)byte-stream"));
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_LOCATION,
|
||||
};
|
||||
|
||||
static void gst_bambusrc_uri_handler_init (gpointer g_iface,
|
||||
gpointer iface_data);
|
||||
static void gst_bambusrc_finalize (GObject * gobject);
|
||||
static void gst_bambusrc_dispose (GObject * gobject);
|
||||
|
||||
static void gst_bambusrc_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec);
|
||||
static void gst_bambusrc_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec);
|
||||
|
||||
static GstStateChangeReturn gst_bambusrc_change_state (GstElement *
|
||||
element, GstStateChange transition);
|
||||
static GstFlowReturn gst_bambusrc_create (GstPushSrc * psrc,
|
||||
GstBuffer ** outbuf);
|
||||
static gboolean gst_bambusrc_start (GstBaseSrc * bsrc);
|
||||
static gboolean gst_bambusrc_stop (GstBaseSrc * bsrc);
|
||||
static gboolean gst_bambusrc_is_seekable (GstBaseSrc * bsrc);
|
||||
static gboolean gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query);
|
||||
static gboolean gst_bambusrc_unlock (GstBaseSrc * bsrc);
|
||||
static gboolean gst_bambusrc_unlock_stop (GstBaseSrc * bsrc);
|
||||
static gboolean gst_bambusrc_set_location (GstBambuSrc * src,
|
||||
const gchar * uri, GError ** error);
|
||||
|
||||
#define gst_bambusrc_parent_class parent_class
|
||||
G_DEFINE_TYPE_WITH_CODE (GstBambuSrc, gst_bambusrc, GST_TYPE_PUSH_SRC,
|
||||
G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
|
||||
gst_bambusrc_uri_handler_init));
|
||||
|
||||
static void
|
||||
gst_bambusrc_class_init (GstBambuSrcClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class;
|
||||
GstElementClass *gstelement_class;
|
||||
GstBaseSrcClass *gstbasesrc_class;
|
||||
GstPushSrcClass *gstpushsrc_class;
|
||||
|
||||
gobject_class = (GObjectClass *) klass;
|
||||
gstelement_class = (GstElementClass *) klass;
|
||||
gstbasesrc_class = (GstBaseSrcClass *) klass;
|
||||
gstpushsrc_class = (GstPushSrcClass *) klass;
|
||||
|
||||
gobject_class->set_property = gst_bambusrc_set_property;
|
||||
gobject_class->get_property = gst_bambusrc_get_property;
|
||||
gobject_class->finalize = gst_bambusrc_finalize;
|
||||
gobject_class->dispose = gst_bambusrc_dispose;
|
||||
|
||||
g_object_class_install_property (gobject_class,
|
||||
PROP_LOCATION,
|
||||
g_param_spec_string ("location", "Location",
|
||||
"URI to pass to Bambu Lab blobs", "",
|
||||
(GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||
|
||||
gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
|
||||
|
||||
gst_element_class_set_static_metadata (gstelement_class, "Bambu Lab source",
|
||||
"Source/Network",
|
||||
"Receive data as a client over the network using the proprietary Bambu Lab blobs",
|
||||
"Joshua Wise <joshua@accelerated.tech>");
|
||||
gstelement_class->change_state =
|
||||
GST_DEBUG_FUNCPTR (gst_bambusrc_change_state);
|
||||
|
||||
gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_bambusrc_start);
|
||||
gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_bambusrc_stop);
|
||||
gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_bambusrc_unlock);
|
||||
gstbasesrc_class->unlock_stop =
|
||||
GST_DEBUG_FUNCPTR (gst_bambusrc_unlock_stop);
|
||||
gstbasesrc_class->is_seekable =
|
||||
GST_DEBUG_FUNCPTR (gst_bambusrc_is_seekable);
|
||||
gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_bambusrc_query);
|
||||
|
||||
gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_bambusrc_create);
|
||||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_bambusrc_debug, "bambusrc", 0,
|
||||
"Bambu Lab src");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_reset (GstBambuSrc * src)
|
||||
{
|
||||
gst_caps_replace (&src->src_caps, NULL);
|
||||
if (src->tnl) {
|
||||
BAMBULIB(Bambu_Close)(src->tnl);
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
src->tnl = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_init (GstBambuSrc * src)
|
||||
{
|
||||
src->location = NULL;
|
||||
src->tnl = NULL;
|
||||
|
||||
gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
|
||||
gst_base_src_set_live(GST_BASE_SRC(src), TRUE);
|
||||
|
||||
gst_bambusrc_reset (src);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_dispose (GObject * gobject)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (gobject);
|
||||
|
||||
GST_DEBUG_OBJECT (src, "dispose");
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (gobject);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_finalize (GObject * gobject)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (gobject);
|
||||
|
||||
GST_DEBUG_OBJECT (src, "finalize");
|
||||
|
||||
g_free (src->location);
|
||||
if (src->tnl) {
|
||||
BAMBULIB(Bambu_Close)(src->tnl);
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (gobject);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LOCATION:
|
||||
{
|
||||
const gchar *location;
|
||||
|
||||
location = g_value_get_string (value);
|
||||
|
||||
if (location == NULL) {
|
||||
GST_WARNING ("location property cannot be NULL");
|
||||
goto done;
|
||||
}
|
||||
if (!gst_bambusrc_set_location (src, location, NULL)) {
|
||||
GST_WARNING ("badly formatted location");
|
||||
goto done;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
done:
|
||||
return;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_LOCATION:
|
||||
g_value_set_string (value, src->location);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int gst_bambu_last_error = 0;
|
||||
|
||||
static GstFlowReturn
|
||||
gst_bambusrc_create (GstPushSrc * psrc, GstBuffer ** outbuf)
|
||||
{
|
||||
GstBambuSrc *src;
|
||||
|
||||
src = GST_BAMBUSRC (psrc);
|
||||
|
||||
(void) src;
|
||||
GST_DEBUG_OBJECT (src, "create()");
|
||||
|
||||
int rv;
|
||||
Bambu_Sample sample;
|
||||
|
||||
if (!src->tnl) {
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
while ((rv = BAMBULIB(Bambu_ReadSample)(src->tnl, &sample)) == Bambu_would_block) {
|
||||
GST_DEBUG_OBJECT(src, "create would block");
|
||||
usleep(33333); /* 30Hz */
|
||||
}
|
||||
|
||||
if (rv == Bambu_stream_end) {
|
||||
return GST_FLOW_EOS;
|
||||
}
|
||||
|
||||
if (rv != Bambu_success) {
|
||||
gst_bambu_last_error = rv;
|
||||
return GST_FLOW_ERROR;
|
||||
}
|
||||
|
||||
#if GLIB_CHECK_VERSION(2,68,0)
|
||||
gpointer sbuf = g_memdup2(sample.buffer, sample.size);
|
||||
#else
|
||||
gpointer sbuf = g_memdup(sample.buffer, sample.size);
|
||||
#endif
|
||||
*outbuf = gst_buffer_new_wrapped_full(0, sbuf, sample.size, 0, sample.size, sbuf, g_free);
|
||||
|
||||
/* Synthesize monotonic timestamps at the announced frame rate, anchored
|
||||
* to the first frame's arrival time. The X1C's RTSPS server emits
|
||||
* unreliable decode timestamps (wildly non-monotonic jumps, or sometimes
|
||||
* none at all); forwarding them directly froze the pipeline after a few
|
||||
* seconds. Pacing on a synthesized clock — the same trick mpv uses when
|
||||
* it reports "No video PTS! Making something up." — gives smooth
|
||||
* playback regardless of network jitter, and only drops late frames if
|
||||
* the printer can't keep up. A snap-back resets the anchor if real
|
||||
* arrival drifts more than two frame periods from the synthesized
|
||||
* timeline (e.g. announced framerate was wrong).
|
||||
*/
|
||||
GstClock *clock = GST_ELEMENT_CLOCK(psrc);
|
||||
GstClockTime base_time = gst_element_get_base_time((GstElement *)psrc);
|
||||
GstClockTime running_now = GST_CLOCK_TIME_NONE;
|
||||
if (clock) {
|
||||
GstClockTime now = gst_clock_get_time(clock);
|
||||
if (now != GST_CLOCK_TIME_NONE && now >= base_time)
|
||||
running_now = now - base_time;
|
||||
}
|
||||
|
||||
/* Adapt the period to actual inter-arrival time via EWMA. The announced
|
||||
* frame_rate is unreliable on Bambu printers (X1C announces 30 but
|
||||
* delivers ~28), so trusting it causes the synthesized timeline to drift
|
||||
* relative to real time, which makes the sink consider frames late and
|
||||
* skip pacing entirely. Measuring the real rate keeps PTS in step with
|
||||
* arrival on average, so the sink can pace inside bursts while still
|
||||
* tracking the printer's actual frame cadence.
|
||||
*/
|
||||
if (src->avg_period == 0) {
|
||||
int fps = src->frame_rate > 0 ? src->frame_rate : 30;
|
||||
src->avg_period = GST_SECOND / fps;
|
||||
}
|
||||
if (running_now != GST_CLOCK_TIME_NONE && src->last_arrival != 0) {
|
||||
GstClockTimeDiff delta = GST_CLOCK_DIFF(src->last_arrival, running_now);
|
||||
/* clamp to plausible video frame periods (5..200 ms) so a one-off
|
||||
* burst-of-zero or long stall doesn't poison the average */
|
||||
if (delta > 5 * GST_MSECOND && delta < 200 * GST_MSECOND) {
|
||||
src->avg_period = (src->avg_period * 15 + (GstClockTime)delta) / 16;
|
||||
}
|
||||
}
|
||||
src->last_arrival = (running_now != GST_CLOCK_TIME_NONE) ? running_now : src->last_arrival;
|
||||
GstClockTime period = src->avg_period;
|
||||
|
||||
/* Lead time: schedule frames a few periods in the future of their
|
||||
* arrival, so the sink has a small jitter buffer. Without this, frames
|
||||
* arriving slightly later than expected land behind the running clock
|
||||
* and the sink renders them immediately, producing visible stutter.
|
||||
* 100ms is invisible for a live print-monitor view.
|
||||
*/
|
||||
const GstClockTime LEAD = 100 * GST_MSECOND;
|
||||
|
||||
if (!src->sttime) {
|
||||
src->sttime = (running_now != GST_CLOCK_TIME_NONE) ? running_now + LEAD : LEAD;
|
||||
src->frame_count = 0;
|
||||
}
|
||||
|
||||
GstClockTime pts = src->sttime + src->frame_count * period;
|
||||
|
||||
/* Safety net: with the lead applied, expected drift is roughly -LEAD
|
||||
* (pts sits LEAD ns ahead of running_now). Re-anchor only if the
|
||||
* synthesized timeline diverges from that expectation by several frame
|
||||
* periods, which indicates a real disturbance (printer paused, stream
|
||||
* resumed, large fps change) rather than ordinary jitter.
|
||||
*/
|
||||
if (running_now != GST_CLOCK_TIME_NONE) {
|
||||
GstClockTimeDiff drift = GST_CLOCK_DIFF(pts, running_now);
|
||||
GstClockTimeDiff expected = -(GstClockTimeDiff)LEAD;
|
||||
GstClockTimeDiff slack = (GstClockTimeDiff)(4 * period);
|
||||
if (drift > expected + slack || drift < expected - slack) {
|
||||
GST_DEBUG_OBJECT(src, "ts drift %" G_GINT64_FORMAT " ns; re-anchoring", drift);
|
||||
src->sttime = running_now + LEAD;
|
||||
src->frame_count = 0;
|
||||
pts = src->sttime;
|
||||
}
|
||||
}
|
||||
|
||||
GST_BUFFER_PTS(*outbuf) = pts;
|
||||
GST_BUFFER_DTS(*outbuf) = pts;
|
||||
GST_BUFFER_DURATION(*outbuf) = period;
|
||||
src->frame_count++;
|
||||
GST_DEBUG_OBJECT(src,
|
||||
"sttime:%lu, DTS:%lu, PTS: %lu~",
|
||||
src->sttime, GST_BUFFER_DTS(*outbuf), GST_BUFFER_PTS(*outbuf));
|
||||
|
||||
return GST_FLOW_OK;
|
||||
}
|
||||
|
||||
static void _log(void *ctx, int lvl, const char *msg) {
|
||||
GstBambuSrc *src = (GstBambuSrc *) ctx;
|
||||
GST_DEBUG_OBJECT(src, "bambu: %s", msg);
|
||||
BAMBULIB(Bambu_FreeLogMsg)(msg);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_start (GstBaseSrc * bsrc)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
|
||||
|
||||
GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
|
||||
|
||||
if (src->tnl) {
|
||||
BAMBULIB(Bambu_Close)(src->tnl);
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
src->tnl = NULL;
|
||||
}
|
||||
|
||||
#ifdef BAMBU_DYNAMIC
|
||||
if (!_lib) {
|
||||
_lib = bambulib_get();
|
||||
if (!_lib->Bambu_Open) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (BAMBULIB(Bambu_Create)(&src->tnl, src->location) != Bambu_success) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int rv = 0;
|
||||
BAMBULIB(Bambu_SetLogger)(src->tnl, _log, (void *)src);
|
||||
if ((rv = BAMBULIB(Bambu_Open)(src->tnl)) != Bambu_success) {
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
src->tnl = NULL;
|
||||
gst_bambu_last_error = rv;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
int n = 0;
|
||||
while ((rv = BAMBULIB(Bambu_StartStream)(src->tnl, 1 /* video */)) == Bambu_would_block) {
|
||||
usleep(100000);
|
||||
}
|
||||
if (rv != Bambu_success) {
|
||||
BAMBULIB(Bambu_Close)(src->tnl);
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
src->tnl = NULL;
|
||||
gst_bambu_last_error = rv;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
src->video_type = AVC1;
|
||||
n = BAMBULIB(Bambu_GetStreamCount)(src->tnl);
|
||||
GST_INFO_OBJECT (src, "Bambu_GetStreamCount returned stream count=%d",n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
Bambu_StreamInfo info;
|
||||
BAMBULIB(Bambu_GetStreamInfo)(src->tnl, i, &info);
|
||||
|
||||
GST_INFO_OBJECT (src, "stream %d type=%d, sub_type=%d", i, info.type, info.sub_type);
|
||||
if (info.type == VIDE) {
|
||||
src->video_type = info.sub_type;
|
||||
src->frame_rate = info.format.video.frame_rate;
|
||||
GST_INFO_OBJECT (src, " width %d height=%d, frame_rate=%d",
|
||||
info.format.video.width, info.format.video.height, info.format.video.frame_rate);
|
||||
}
|
||||
}
|
||||
|
||||
src->sttime = 0;
|
||||
src->frame_count = 0;
|
||||
src->last_arrival = 0;
|
||||
src->avg_period = 0;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_stop (GstBaseSrc * bsrc)
|
||||
{
|
||||
GstBambuSrc *src;
|
||||
|
||||
src = GST_BAMBUSRC (bsrc);
|
||||
GST_DEBUG_OBJECT (src, "stop()");
|
||||
if (src->tnl) {
|
||||
BAMBULIB(Bambu_Close)(src->tnl);
|
||||
BAMBULIB(Bambu_Destroy)(src->tnl);
|
||||
src->tnl = NULL;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstStateChangeReturn
|
||||
gst_bambusrc_change_state (GstElement * element, GstStateChange transition)
|
||||
{
|
||||
GstStateChangeReturn ret;
|
||||
GstBambuSrc *src;
|
||||
|
||||
src = GST_BAMBUSRC (element);
|
||||
|
||||
(void) src;
|
||||
|
||||
switch (transition) {
|
||||
case GST_STATE_CHANGE_READY_TO_NULL:
|
||||
//gst_bambusrc_session_close (src);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Interrupt a blocking request. */
|
||||
static gboolean
|
||||
gst_bambusrc_unlock (GstBaseSrc * bsrc)
|
||||
{
|
||||
GstBambuSrc *src;
|
||||
|
||||
src = GST_BAMBUSRC (bsrc);
|
||||
GST_DEBUG_OBJECT (src, "unlock()");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Interrupt interrupt. */
|
||||
static gboolean
|
||||
gst_bambusrc_unlock_stop (GstBaseSrc * bsrc)
|
||||
{
|
||||
GstBambuSrc *src;
|
||||
|
||||
src = GST_BAMBUSRC (bsrc);
|
||||
GST_DEBUG_OBJECT (src, "unlock_stop()");
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_is_seekable (GstBaseSrc * bsrc)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
|
||||
|
||||
(void) src;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (bsrc);
|
||||
gboolean ret;
|
||||
GstSchedulingFlags flags;
|
||||
gint minsize, maxsize, align;
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_URI:
|
||||
gst_query_set_uri (query, src->location);
|
||||
ret = TRUE;
|
||||
break;
|
||||
default:
|
||||
ret = FALSE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
|
||||
|
||||
switch (GST_QUERY_TYPE (query)) {
|
||||
case GST_QUERY_SCHEDULING:
|
||||
gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
|
||||
flags = (GstSchedulingFlags)((int)flags | (int)GST_SCHEDULING_FLAG_SEQUENTIAL);
|
||||
gst_query_set_scheduling (query, flags, minsize, maxsize, align);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_set_location (GstBambuSrc * src, const gchar * uri,
|
||||
GError ** error)
|
||||
{
|
||||
if (src->location) {
|
||||
g_free (src->location);
|
||||
src->location = NULL;
|
||||
}
|
||||
|
||||
if (uri == NULL)
|
||||
return FALSE;
|
||||
|
||||
src->location = g_strdup (uri);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GstURIType
|
||||
gst_bambusrc_uri_get_type (GType type)
|
||||
{
|
||||
return GST_URI_SRC;
|
||||
}
|
||||
|
||||
static const gchar *const *
|
||||
gst_bambusrc_uri_get_protocols (GType type)
|
||||
{
|
||||
static const gchar *protocols[] = { "bambu", NULL };
|
||||
|
||||
return protocols;
|
||||
}
|
||||
|
||||
static gchar *
|
||||
gst_bambusrc_uri_get_uri (GstURIHandler * handler)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (handler);
|
||||
|
||||
/* FIXME: make thread-safe */
|
||||
return g_strdup (src->location);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_bambusrc_uri_set_uri (GstURIHandler * handler, const gchar * uri,
|
||||
GError ** error)
|
||||
{
|
||||
GstBambuSrc *src = GST_BAMBUSRC (handler);
|
||||
|
||||
return gst_bambusrc_set_location (src, uri, error);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_bambusrc_uri_handler_init (gpointer g_iface, gpointer iface_data)
|
||||
{
|
||||
GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
|
||||
|
||||
iface->get_type = gst_bambusrc_uri_get_type;
|
||||
iface->get_protocols = gst_bambusrc_uri_get_protocols;
|
||||
iface->get_uri = gst_bambusrc_uri_get_uri;
|
||||
iface->set_uri = gst_bambusrc_uri_set_uri;
|
||||
}
|
||||
|
||||
static gboolean gstbambusrc_init(GstPlugin *plugin)
|
||||
{
|
||||
return gst_element_register(plugin, "bambusrc", GST_RANK_PRIMARY, GST_TYPE_BAMBUSRC);
|
||||
}
|
||||
|
||||
#ifndef EXTERNAL_GST_PLUGIN
|
||||
|
||||
// for use inside of Bambu Slicer
|
||||
void gstbambusrc_register()
|
||||
{
|
||||
static int did_register = 0;
|
||||
if (did_register)
|
||||
return;
|
||||
did_register = 1;
|
||||
|
||||
gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "bambusrc", "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "BambuStudio", "https://github.com/bambulab/BambuStudio");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifndef PACKAGE
|
||||
#define PACKAGE "bambusrc"
|
||||
#endif
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, bambusrc, "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "https://github.com/bambulab/BambuStudio")
|
||||
|
||||
#endif
|
||||
@@ -1,78 +0,0 @@
|
||||
/* bambusrc for gstreamer
|
||||
* integration with proprietary Bambu Lab blob for getting raw h.264 video
|
||||
*
|
||||
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the
|
||||
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
|
||||
* which case the following provisions apply instead of the ones
|
||||
* mentioned above:
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Library General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Library General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Library General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_BAMBUSRC_H__
|
||||
#define __GST_BAMBUSRC_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_BAMBUSRC (gst_bambusrc_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstBambuSrc, gst_bambusrc,
|
||||
GST, BAMBUSRC, GstPushSrc)
|
||||
|
||||
typedef void *Bambu_Tunnel;
|
||||
|
||||
struct _GstBambuSrc
|
||||
{
|
||||
GstPushSrc element;
|
||||
GstCaps *src_caps;
|
||||
gchar *location;
|
||||
Bambu_Tunnel tnl;
|
||||
GstClockTime sttime;
|
||||
int video_type;
|
||||
int frame_rate;
|
||||
guint64 frame_count;
|
||||
GstClockTime last_arrival;
|
||||
GstClockTime avg_period;
|
||||
};
|
||||
|
||||
extern void gstbambusrc_register();
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_BAMBUSRC_H__ */
|
||||
@@ -63,7 +63,6 @@ class ProjectPanel : public wxPanel
|
||||
{
|
||||
private:
|
||||
std::atomic<bool> m_web_init_completed{false};
|
||||
bool m_reload_already = {false};
|
||||
|
||||
std::shared_ptr<std::atomic<bool>> m_reload_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||
std::unique_ptr<boost::thread> m_reload_task;
|
||||
|
||||
@@ -10,8 +10,6 @@ namespace Slic3r { namespace GUI {
|
||||
class RecenterDialog : public DPIDialog
|
||||
{
|
||||
private:
|
||||
wxStaticText* m_staticText_hint;
|
||||
wxStaticBitmap* m_bitmap_home;
|
||||
ScalableBitmap m_home_bmp;
|
||||
wxString hint1;
|
||||
wxString hint2;
|
||||
|
||||
@@ -681,7 +681,7 @@ SearchDialog::SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindo
|
||||
|
||||
SearchDialog::~SearchDialog() {}
|
||||
|
||||
void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
void SearchDialog::Popup(wxWindow *focus /*= nullptr*/)
|
||||
{
|
||||
/* const std::string& line = searcher->search_string();
|
||||
search_line->SetValue(line.empty() ? default_string : from_u8(line));
|
||||
@@ -696,17 +696,19 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
search_line2->SetValue(wxString(""));
|
||||
//const std::string &line = searcher->search_string();
|
||||
//searcher->search(into_u8(line), true);
|
||||
PopupWindow::Popup();
|
||||
PopupWindow::Popup(focus);
|
||||
search_line2->SetFocus();
|
||||
update_list();
|
||||
}
|
||||
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void SearchDialog::MSWDismissUnfocusedPopup()
|
||||
{
|
||||
Dismiss();
|
||||
OnDismiss();
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
void SearchDialog::OnDismiss() { }
|
||||
|
||||
@@ -926,7 +928,7 @@ SearchObjectDialog::SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* p
|
||||
|
||||
SearchObjectDialog::~SearchObjectDialog() {}
|
||||
|
||||
void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
void SearchObjectDialog::Popup(wxWindow *focus /*= nullptr*/)
|
||||
{
|
||||
if (m_is_dismissing || this->IsShown()) {
|
||||
return;
|
||||
@@ -937,7 +939,7 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
// dropdown list, otherwise the text input won't be usable
|
||||
m_object_list->SetFocus();
|
||||
#endif
|
||||
PopupWindow::Popup();
|
||||
PopupWindow::Popup(focus);
|
||||
search_line2->SetFocus();
|
||||
|
||||
m_object_list->assembly_plate_object_name();
|
||||
@@ -945,11 +947,13 @@ void SearchObjectDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
|
||||
update_list();
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void SearchObjectDialog::MSWDismissUnfocusedPopup()
|
||||
{
|
||||
Dismiss();
|
||||
OnDismiss();
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
void SearchObjectDialog::OnDismiss() {}
|
||||
|
||||
|
||||
@@ -216,10 +216,12 @@ public:
|
||||
SearchDialog(OptionsSearcher *searcher, Preset::Type type, wxWindow *parent, TextInput *input, wxWindow *search_btn);
|
||||
~SearchDialog();
|
||||
|
||||
void MSWDismissUnfocusedPopup();
|
||||
void Popup(wxPoint position = wxDefaultPosition);
|
||||
void OnDismiss();
|
||||
void Dismiss();
|
||||
#ifdef __WXMSW__
|
||||
void MSWDismissUnfocusedPopup() override;
|
||||
#endif // __WXMSW__
|
||||
void Popup(wxWindow *focus = nullptr) override;
|
||||
void OnDismiss() override;
|
||||
void Dismiss() override;
|
||||
void Die();
|
||||
void msw_rescale();
|
||||
|
||||
@@ -260,10 +262,12 @@ public:
|
||||
SearchObjectDialog(GUI::ObjectList* object_list, wxWindow* parent, TextInput* input);
|
||||
~SearchObjectDialog();
|
||||
|
||||
void MSWDismissUnfocusedPopup();
|
||||
void Popup(wxPoint position = wxDefaultPosition);
|
||||
void OnDismiss();
|
||||
void Dismiss();
|
||||
#ifdef __WXMSW__
|
||||
void MSWDismissUnfocusedPopup() override;
|
||||
#endif // __WXMSW__
|
||||
void Popup(wxWindow *focus = nullptr) override;
|
||||
void OnDismiss() override;
|
||||
void Dismiss() override;
|
||||
void Die();
|
||||
|
||||
void OnInputText(wxCommandEvent& event);
|
||||
|
||||
@@ -483,7 +483,7 @@ SelectMachineDialog::SelectMachineDialog(Plater *plater)
|
||||
|
||||
m_link_edit_nozzle->Bind(wxEVT_LEFT_DOWN, [this](auto &e) {
|
||||
|
||||
if (this && this->m_is_in_sending_mode) {
|
||||
if (m_is_in_sending_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -181,8 +181,6 @@ private:
|
||||
PinCodePanel* m_panel_direct_connection{nullptr};
|
||||
wxWindow* m_placeholder_panel{nullptr};
|
||||
HyperLink* m_hyperlink{nullptr}; // ORCA
|
||||
Label* m_ping_code_text{nullptr};
|
||||
wxStaticBitmap* m_img_ping_code{nullptr};
|
||||
wxBoxSizer * m_sizer_body{nullptr};
|
||||
wxBoxSizer * m_sizer_my_devices{nullptr};
|
||||
wxBoxSizer * m_sizer_other_devices{nullptr};
|
||||
|
||||
@@ -149,7 +149,6 @@ private:
|
||||
ScalableBitmap* print_time{ nullptr };
|
||||
wxStaticBitmap* weightimg{ nullptr };
|
||||
ScalableBitmap* print_weight{ nullptr };
|
||||
wxBoxSizer* m_thumbnail_sizer{ nullptr };
|
||||
ThumbnailPanel* m_thumbnail_panel{nullptr};
|
||||
wxPanel* m_panel_image{ nullptr };
|
||||
wxBoxSizer* m_image_sizer{ nullptr };
|
||||
|
||||
@@ -994,18 +994,16 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
|
||||
m_send_job->on_check_ip_address_fail([this, token = std::weak_ptr(m_token)](int result) {
|
||||
CallAfter([token, this] {
|
||||
if (token.expired()) { return; }
|
||||
if (this) {
|
||||
SendFailedConfirm sfcDlg;
|
||||
auto res = sfcDlg.ShowModal();
|
||||
m_status_bar->cancel();
|
||||
SendFailedConfirm sfcDlg;
|
||||
auto res = sfcDlg.ShowModal();
|
||||
m_status_bar->cancel();
|
||||
|
||||
if (res == wxYES) {
|
||||
wxQueueEvent(m_button_ensure, new wxCommandEvent(wxEVT_BUTTON));
|
||||
} else if (res == wxAPPLY) {
|
||||
wxCommandEvent *evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
|
||||
wxQueueEvent(this, evt);
|
||||
wxGetApp().show_ip_address_enter_dialog();
|
||||
}
|
||||
if (res == wxYES) {
|
||||
wxQueueEvent(m_button_ensure, new wxCommandEvent(wxEVT_BUTTON));
|
||||
} else if (res == wxAPPLY) {
|
||||
wxCommandEvent *evt = new wxCommandEvent(EVT_CLEAR_IPADDRESS);
|
||||
wxQueueEvent(this, evt);
|
||||
wxGetApp().show_ip_address_enter_dialog();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -62,7 +62,6 @@ private:
|
||||
bool m_is_in_sending_mode{ false };
|
||||
bool m_is_rename_mode{ false };
|
||||
bool enable_prepare_mode{ true };
|
||||
bool m_need_adaptation_screen{ false };
|
||||
bool m_export_3mf_cancel{ false };
|
||||
bool m_is_canceled{ false };
|
||||
bool m_tcp_try_connect{true};
|
||||
@@ -81,7 +80,6 @@ private:
|
||||
wxStaticBitmap* m_staticbitmap{ nullptr };
|
||||
ThumbnailPanel* m_thumbnailPanel{ nullptr };
|
||||
ComboBox* m_comboBox_printer{ nullptr };
|
||||
ComboBox* m_comboBox_bed{ nullptr };
|
||||
Button* m_rename_button{ nullptr };
|
||||
Button* m_button_refresh{ nullptr };
|
||||
Button* m_button_ensure{ nullptr };
|
||||
|
||||
@@ -87,7 +87,6 @@ private:
|
||||
std::unordered_map < uint32_t, std::vector<std::vector<FloatPoint>>> parts_triangles_;
|
||||
std::unordered_map < uint32_t, std::vector<std::vector<cv::Point>>> pick_parts_;
|
||||
std::unordered_map<uint32_t, PartState> parts_state_;
|
||||
bool gl_inited_{false};
|
||||
int zoom_percent_{100};
|
||||
wxPoint offset_{0,0};
|
||||
wxPoint drag_start_offset_{0,0};
|
||||
|
||||
@@ -984,7 +984,7 @@ void PrintingTaskPanel::paint(wxPaintEvent&)
|
||||
dc.DrawBitmap(m_thumbnail_bmp_display, wxPoint(0, 0));
|
||||
}
|
||||
dc.SetFont(Label::Body_12);
|
||||
|
||||
|
||||
if (m_plate_index >= 0) {
|
||||
wxString plate_id_str = wxString::Format("%d", m_plate_index);
|
||||
dc.DrawText(plate_id_str, wxPoint(4, 4));
|
||||
@@ -1272,7 +1272,7 @@ void PrintingTaskPanel::set_plate_index(int plate_idx)
|
||||
}
|
||||
|
||||
void PrintingTaskPanel::market_scoring_show()
|
||||
{
|
||||
{
|
||||
m_score_staticline->Show();
|
||||
m_score_subtask_info->Show();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " show market scoring page";
|
||||
@@ -1403,7 +1403,7 @@ void StatusBasePanel::init_bitmaps()
|
||||
m_bitmap_fan_off = ScalableBitmap(this, "monitor_fan_off", 22);
|
||||
m_bitmap_speed = ScalableBitmap(this, "monitor_speed", 24);
|
||||
m_bitmap_speed_active = ScalableBitmap(this, "monitor_speed_active", 24);
|
||||
|
||||
|
||||
m_thumbnail_brokenimg = ScalableBitmap(this, "monitor_brokenimg", 120);
|
||||
m_thumbnail_sdcard = ScalableBitmap(this, "monitor_sdcard_thumbnail", 120);
|
||||
//m_bitmap_camera = create_scaled_bitmap("monitor_camera", nullptr, 18);
|
||||
@@ -1531,7 +1531,7 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
||||
// media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
// media_ctrl_panel->SetBackgroundColour(*wxBLACK);
|
||||
// wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL);
|
||||
m_media_ctrl = new wxMediaCtrl2(this);
|
||||
m_media_ctrl = new wxMediaCtrl3(this);
|
||||
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
|
||||
|
||||
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
||||
@@ -2487,7 +2487,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
|
||||
m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
|
||||
m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
|
||||
m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
|
||||
m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
|
||||
m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
|
||||
m_project_task_panel->get_clean_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
|
||||
|
||||
m_setting_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
|
||||
@@ -2549,7 +2549,7 @@ StatusPanel::~StatusPanel()
|
||||
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
|
||||
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
|
||||
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
|
||||
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
|
||||
m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this);
|
||||
m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this);
|
||||
|
||||
m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this);
|
||||
@@ -2595,7 +2595,7 @@ StatusPanel::~StatusPanel()
|
||||
if (sdcard_hint_dlg != nullptr)
|
||||
delete sdcard_hint_dlg;
|
||||
|
||||
if (m_score_data != nullptr) {
|
||||
if (m_score_data != nullptr) {
|
||||
delete m_score_data;
|
||||
}
|
||||
}
|
||||
@@ -2619,7 +2619,7 @@ void StatusPanel::init_scaled_buttons()
|
||||
m_bpButton_e_down_10->SetCornerRadius(FromDIP(12));
|
||||
}
|
||||
|
||||
void StatusPanel::on_market_scoring(wxCommandEvent &event) {
|
||||
void StatusPanel::on_market_scoring(wxCommandEvent &event) {
|
||||
if (obj && obj->is_makeworld_subtask() && obj->rating_info && obj->rating_info->request_successful) { // model is mall model and has rating_id
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_market_scoring" ;
|
||||
if (m_score_data && m_score_data->rating_id == obj->rating_info->rating_id) { // current score data for model is same as mall model
|
||||
@@ -2628,7 +2628,7 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) {
|
||||
int ret = m_score_dlg.ShowModal();
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data";
|
||||
|
||||
if (ret == wxID_OK) {
|
||||
if (ret == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data is upload";
|
||||
m_score_data->rating_id = -1;
|
||||
m_project_task_panel->set_star_count_dirty(false);
|
||||
@@ -2650,11 +2650,11 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) {
|
||||
|
||||
std::string comment = obj->rating_info->content;
|
||||
if (!comment.empty()) { m_score_dlg.set_comment(comment); }
|
||||
|
||||
|
||||
std::vector<std::string> images_json_array;
|
||||
images_json_array = obj->rating_info->image_url_paths;
|
||||
if (!images_json_array.empty()) m_score_dlg.set_cloud_bitmap(images_json_array);
|
||||
|
||||
|
||||
int ret = m_score_dlg.ShowModal();
|
||||
|
||||
if (ret == wxID_OK) {
|
||||
@@ -3694,14 +3694,14 @@ void StatusPanel::update_basic_print_data(bool def)
|
||||
void StatusPanel::update_model_info()
|
||||
{
|
||||
auto get_subtask_fn = [this](BBLModelTask* subtask) {
|
||||
CallAfter([this, subtask]() {
|
||||
CallAfter([this, subtask]() {
|
||||
if (obj && obj->subtask_id_ == subtask->task_id) {
|
||||
obj->set_modeltask(subtask);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (wxGetApp().getAgent() && obj) {
|
||||
BBLSubTask* curr_task = obj->get_subtask();
|
||||
if (curr_task) {
|
||||
@@ -4073,7 +4073,7 @@ void StatusPanel::reset_printing_values()
|
||||
m_project_task_panel->update_left_time(NA_STR);
|
||||
m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR));
|
||||
update_calib_bitmap();
|
||||
|
||||
|
||||
task_thumbnail_state = ThumbnailState::PLACE_HOLDER;
|
||||
m_start_loading_thumbnail = false;
|
||||
m_load_sdcard_thumbnail = false;
|
||||
@@ -4134,7 +4134,7 @@ bool StatusPanel::check_axis_z_at_home(MachineObject* obj)
|
||||
}
|
||||
|
||||
void StatusPanel::on_axis_ctrl_z_up_10(wxCommandEvent &event)
|
||||
{
|
||||
{
|
||||
if (obj) {
|
||||
obj->command_axis_control("Z", 1.0, -10.0f, 900);
|
||||
if (!check_axis_z_at_home(obj))
|
||||
@@ -5497,7 +5497,7 @@ void StatusPanel::msw_rescale()
|
||||
m_calibration_btn->Rescale();
|
||||
|
||||
m_options_btn->SetMinSize(wxSize(-1, FromDIP(26)));
|
||||
m_options_btn->Rescale();
|
||||
m_options_btn->Rescale();
|
||||
|
||||
m_safety_btn->SetMinSize(wxSize(-1, FromDIP(26)));
|
||||
m_safety_btn->Rescale();
|
||||
@@ -5679,11 +5679,11 @@ ScoreDialog::ScoreDialog(wxWindow *parent, ScoreData *score_data)
|
||||
, m_upload_status_code(StatusCode::CODE_NUMBER)
|
||||
{
|
||||
m_tocken.reset(new int(0));
|
||||
|
||||
|
||||
wxBoxSizer *m_main_sizer = get_main_sizer(score_data->local_to_url_image, score_data->comment_text);
|
||||
|
||||
m_image_url_paths = score_data->image_url_paths;
|
||||
|
||||
|
||||
|
||||
this->SetSizer(m_main_sizer);
|
||||
Fit();
|
||||
@@ -5699,16 +5699,16 @@ void ScoreDialog::on_dpi_changed(const wxRect &suggested_rect) {}
|
||||
void ScoreDialog::OnBitmapClicked(wxMouseEvent &event)
|
||||
{
|
||||
wxStaticBitmap *clickedBitmap = dynamic_cast<wxStaticBitmap *>(event.GetEventObject());
|
||||
if (m_image.find(clickedBitmap) != m_image.end()) {
|
||||
if (m_image.find(clickedBitmap) != m_image.end()) {
|
||||
if (!m_image[clickedBitmap].is_selected) {
|
||||
for (auto panel : m_image[clickedBitmap].image_broad) {
|
||||
for (auto panel : m_image[clickedBitmap].image_broad) {
|
||||
panel->Show();
|
||||
}
|
||||
m_image[clickedBitmap].is_selected = true;
|
||||
m_selected_image_list.insert(clickedBitmap);
|
||||
} else {
|
||||
for (auto panel : m_image[clickedBitmap].image_broad) {
|
||||
panel->Hide();
|
||||
for (auto panel : m_image[clickedBitmap].image_broad) {
|
||||
panel->Hide();
|
||||
}
|
||||
m_image[clickedBitmap].is_selected = false;
|
||||
m_selected_image_list.erase(clickedBitmap);
|
||||
@@ -5725,9 +5725,9 @@ void ScoreDialog::OnBitmapClicked(wxMouseEvent &event)
|
||||
}
|
||||
|
||||
std::set <std::pair<wxStaticBitmap * ,wxString>> ScoreDialog::add_need_upload_imgs()
|
||||
{
|
||||
{
|
||||
std::set<std::pair<wxStaticBitmap *, wxString>> need_upload_images;
|
||||
for (auto bitmap : m_image) {
|
||||
for (auto bitmap : m_image) {
|
||||
if (!bitmap.second.is_uploaded) {
|
||||
wxString &local_image_path = bitmap.second.local_image_url;
|
||||
if (!local_image_path.empty()) { need_upload_images.insert(std::make_pair(bitmap.first, local_image_path)); }
|
||||
@@ -5747,7 +5747,7 @@ std::pair<wxStaticBitmap *, ScoreDialog::ImageMsg> ScoreDialog::create_local_thu
|
||||
cur_image_msg.local_image_url = local_path;
|
||||
cur_image_msg.img_url_paths = "";
|
||||
cur_image_msg.is_uploaded = false;
|
||||
|
||||
|
||||
wxStaticBitmap *imageCtrl = new wxStaticBitmap(this, wxID_ANY, wxBitmap(wxImage(local_path, wxBITMAP_TYPE_ANY).Rescale(FromDIP(80), FromDIP(60))), wxDefaultPosition,
|
||||
wxDefaultSize, 0);
|
||||
imageCtrl->Bind(wxEVT_LEFT_DOWN, &ScoreDialog::OnBitmapClicked, this);
|
||||
@@ -5812,7 +5812,7 @@ void ScoreDialog::update_static_bitmap(wxStaticBitmap* static_bitmap, wxImage im
|
||||
}
|
||||
|
||||
wxBoxSizer *ScoreDialog::create_broad_sizer(wxStaticBitmap *bitmap, ImageMsg& cur_image_msg)
|
||||
{
|
||||
{
|
||||
// tb: top and bottom lr: left and right
|
||||
auto m_image_tb_broad = new wxBoxSizer(wxVERTICAL);
|
||||
auto line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
|
||||
@@ -5856,7 +5856,7 @@ void ScoreDialog::init() {
|
||||
fail_image = wxImage(Slic3r::resources_dir() + "/images/oss_picture_load_failed.png", wxBITMAP_TYPE_ANY);
|
||||
}
|
||||
|
||||
wxBoxSizer *ScoreDialog::get_score_sizer() {
|
||||
wxBoxSizer *ScoreDialog::get_score_sizer() {
|
||||
wxBoxSizer *score_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxStaticText *static_score_text = new wxStaticText(this, wxID_ANY, _L("Rate"), wxDefaultPosition, wxDefaultSize, 0);
|
||||
static_score_text->Wrap(-1);
|
||||
@@ -5979,18 +5979,18 @@ wxBoxSizer *ScoreDialog::get_photo_btn_sizer() {
|
||||
for (int i = 0; i < filePaths.GetCount(); i++) { //It's ugly, but useful
|
||||
bool is_repeat = false;
|
||||
for (auto image : m_image) {
|
||||
if (filePaths[i] == image.second.local_image_url) {
|
||||
if (filePaths[i] == image.second.local_image_url) {
|
||||
is_repeat = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!is_repeat) {
|
||||
local_path.push_back(std::make_pair(filePaths[i], ""));
|
||||
if (local_path.size() + m_image.size() > m_photo_nums) {
|
||||
break;
|
||||
if (local_path.size() + m_image.size() > m_photo_nums) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
load_photo(local_path);
|
||||
@@ -6108,7 +6108,7 @@ wxBoxSizer *ScoreDialog::get_button_sizer()
|
||||
}
|
||||
}
|
||||
progress_dialog->Hide();
|
||||
if (progress_dialog) {
|
||||
if (progress_dialog) {
|
||||
delete progress_dialog;
|
||||
progress_dialog = nullptr;
|
||||
}
|
||||
@@ -6242,7 +6242,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vector<std::pair<wxString, st
|
||||
m_main_sizer->Add(m_photo_sizer, 0, wxEXPAND | wxTOP, FromDIP(8));
|
||||
|
||||
m_image_sizer = new wxGridSizer(5, FromDIP(5), FromDIP(5));
|
||||
if (!images.empty()) {
|
||||
if (!images.empty()) {
|
||||
load_photo(images);
|
||||
}
|
||||
m_main_sizer->Add(m_image_sizer, 0, wxEXPAND | wxLEFT, FromDIP(24));
|
||||
@@ -6254,7 +6254,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vector<std::pair<wxString, st
|
||||
return m_main_sizer;
|
||||
}
|
||||
|
||||
ScoreData ScoreDialog::get_score_data() {
|
||||
ScoreData ScoreDialog::get_score_data() {
|
||||
ScoreData score_data;
|
||||
score_data.rating_id = m_rating_id;
|
||||
score_data.design_id = m_design_id;
|
||||
@@ -6265,20 +6265,20 @@ ScoreData ScoreDialog::get_score_data() {
|
||||
score_data.comment_text = m_comment_text->GetValue();
|
||||
score_data.image_url_paths = m_image_url_paths;
|
||||
for (auto img : m_image) { score_data.local_to_url_image.push_back(std::make_pair(img.second.local_image_url, img.second.img_url_paths)); }
|
||||
|
||||
|
||||
return score_data;
|
||||
}
|
||||
|
||||
void ScoreDialog::set_comment(std::string comment)
|
||||
{
|
||||
if (m_comment_text) {
|
||||
if (m_comment_text) {
|
||||
|
||||
m_comment_text->SetValue(wxString::FromUTF8(comment));
|
||||
}
|
||||
}
|
||||
|
||||
void ScoreDialog::set_cloud_bitmap(std::vector<std::string> cloud_bitmaps)
|
||||
{
|
||||
{
|
||||
m_image_url_paths = cloud_bitmaps;
|
||||
for (std::string &url : cloud_bitmaps) {
|
||||
if (std::string::npos == url.find(m_model_id)) continue;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include <chrono>
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "MediaPlayCtrl.h"
|
||||
#include "AMSSetting.hpp"
|
||||
#include "Calibration.hpp"
|
||||
@@ -196,11 +195,11 @@ public:
|
||||
void set_cloud_bitmap(std::vector<std::string> cloud_bitmaps);
|
||||
|
||||
protected:
|
||||
enum StatusCode {
|
||||
UPLOAD_PROGRESS = 0,
|
||||
UPLOAD_EXIST_ISSUE,
|
||||
enum StatusCode {
|
||||
UPLOAD_PROGRESS = 0,
|
||||
UPLOAD_EXIST_ISSUE,
|
||||
UPLOAD_IMG_FAILED,
|
||||
CODE_NUMBER
|
||||
CODE_NUMBER
|
||||
};
|
||||
|
||||
std::shared_ptr<int> m_tocken;
|
||||
@@ -218,7 +217,7 @@ protected:
|
||||
{
|
||||
wxString local_image_url; //local image path
|
||||
std::string img_url_paths; // oss url path
|
||||
vector<wxPanel *> image_broad;
|
||||
vector<wxPanel *> image_broad;
|
||||
bool is_selected;
|
||||
bool is_uploaded; // load
|
||||
wxBoxSizer * image_tb_broad = nullptr;
|
||||
@@ -253,7 +252,7 @@ protected:
|
||||
std::set<std::pair<wxStaticBitmap *, wxString>> add_need_upload_imgs();
|
||||
std::pair<wxStaticBitmap *, ImageMsg> create_local_thumbnail(wxString &local_path);
|
||||
std::pair<wxStaticBitmap *, ImageMsg> create_oss_thumbnail(std::string &oss_path);
|
||||
|
||||
|
||||
};
|
||||
|
||||
class PrintingTaskPanel : public wxPanel
|
||||
@@ -262,7 +261,7 @@ public:
|
||||
PrintingTaskPanel(wxWindow* parent, PrintingTaskType type);
|
||||
~PrintingTaskPanel();
|
||||
void create_panel(wxWindow* parent);
|
||||
|
||||
|
||||
|
||||
private:
|
||||
MachineObject* m_obj{nullptr};
|
||||
@@ -354,7 +353,7 @@ public:
|
||||
void set_plate_index(int plate_idx = -1);
|
||||
void market_scoring_show();
|
||||
void market_scoring_hide();
|
||||
|
||||
|
||||
public:
|
||||
ScalableButton* get_abort_button() {return m_button_abort;};
|
||||
ScalableButton* get_pause_resume_button() {return m_button_pause_resume;};
|
||||
@@ -444,7 +443,7 @@ protected:
|
||||
wxStaticBitmap* m_camera_switch_button;
|
||||
|
||||
|
||||
wxMediaCtrl2 * m_media_ctrl;
|
||||
wxMediaCtrl3 * m_media_ctrl;
|
||||
MediaPlayCtrl * m_media_play_ctrl;
|
||||
|
||||
Label * m_staticText_printing;
|
||||
@@ -568,7 +567,7 @@ protected:
|
||||
virtual void on_bed_temp_kill_focus(wxFocusEvent &event) { event.Skip(); }
|
||||
virtual void on_bed_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
|
||||
virtual void on_nozzle_temp_kill_focus(wxFocusEvent &event) { event.Skip(); }
|
||||
virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
|
||||
virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); }
|
||||
virtual void on_nozzle_fan_switch(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_printing_fan_switch(wxCommandEvent &event) { event.Skip(); }
|
||||
virtual void on_axis_ctrl_z_up_10(wxCommandEvent &event) { event.Skip(); }
|
||||
|
||||
@@ -307,8 +307,6 @@ private:
|
||||
// wxBoxSizer * m_plate_combox_sizer = nullptr;
|
||||
wxBoxSizer * m_mode_combox_sizer = nullptr;
|
||||
//wxStaticText * m_printer_title = nullptr;
|
||||
wxStaticText * m_printer_device_name = nullptr;
|
||||
wxStaticText * m_printer_is_map_title = nullptr;
|
||||
|
||||
CapsuleButton * m_colormap_btn = nullptr;
|
||||
CapsuleButton * m_override_btn = nullptr;
|
||||
@@ -326,7 +324,6 @@ private:
|
||||
|
||||
bool m_check_dirty_fialment = true;
|
||||
bool m_expand_more_settings = true;
|
||||
bool m_image_is_top = false;
|
||||
|
||||
const int LEFT_THUMBNAIL_SIZE_WIDTH = 100;
|
||||
const int RIGHT_THUMBNAIL_SIZE_WIDTH = 300;
|
||||
|
||||
@@ -7530,7 +7530,7 @@ void Tab::delete_preset()
|
||||
for (auto &preset2 : *m_presets)
|
||||
if (preset2.inherits() == current_preset.name) {
|
||||
++count;
|
||||
presets += "\n - " + preset2.name;
|
||||
presets += "\n - " + from_u8(preset2.name);
|
||||
}
|
||||
if (count > 0) {
|
||||
msg = _L("Presets inherited by other presets cannot be deleted!");
|
||||
|
||||
@@ -626,8 +626,6 @@ private:
|
||||
bool m_rebuild_kinematics_page = false;
|
||||
void update_input_shaper_menu(GCodeFlavor flavor);
|
||||
|
||||
ogStaticText* m_fff_print_host_upload_description_line {nullptr};
|
||||
ogStaticText* m_sla_print_host_upload_description_line {nullptr};
|
||||
|
||||
std::vector<PageShp> m_pages_fff;
|
||||
std::vector<PageShp> m_pages_sla;
|
||||
|
||||
@@ -31,7 +31,6 @@ private:
|
||||
wxStaticText *m_remaining_time_label;
|
||||
wxStaticText *m_explanation_label;
|
||||
wxButton *m_ok_button;
|
||||
wxStaticBitmap *m_title_bitmap;
|
||||
|
||||
DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
@@ -862,7 +862,6 @@ void MachineInfoPanel::update_ams_ext(MachineObject *obj)
|
||||
if (new_extra_ams_ver != obj->new_ver_list.end())
|
||||
has_new_version = true;
|
||||
|
||||
extra_ams_it->second.sw_new_ver;
|
||||
if (has_new_version) {
|
||||
m_extra_ams_panel->m_ams_new_version_img->Show();
|
||||
ver_text = new_extra_ams_ver->second.sw_ver;
|
||||
|
||||
@@ -112,7 +112,6 @@ private:
|
||||
AppConfig m_appconfig_new;
|
||||
|
||||
wxWebView *m_browser;
|
||||
wxButton * m_TestBtn;
|
||||
|
||||
wxString m_SectionName;
|
||||
|
||||
|
||||
@@ -125,10 +125,6 @@ private:
|
||||
wxMenuItem* m_edit_undo;
|
||||
wxMenuItem* m_edit_redo;
|
||||
wxMenuItem* m_edit_mode;
|
||||
wxMenuItem* m_scroll_line_up;
|
||||
wxMenuItem* m_scroll_line_down;
|
||||
wxMenuItem* m_scroll_page_up;
|
||||
wxMenuItem* m_scroll_page_down;
|
||||
wxMenuItem* m_script_string;
|
||||
wxMenuItem* m_script_integer;
|
||||
wxMenuItem* m_script_double;
|
||||
|
||||
@@ -114,7 +114,6 @@ bool AMSinfo::parse_ams_info(MachineObject *obj, DevAms *ams, bool remain_flag,
|
||||
info.ctype = 0;
|
||||
info.material_colour = AMS_TRAY_DEFAULT_COL;
|
||||
info.material_state = AMSCanType::AMS_CAN_TYPE_THIRDBRAND;
|
||||
wxColour(255, 255, 255);
|
||||
}
|
||||
|
||||
if (it->second->is_tray_info_ready() && obj->cali_version >= 0) {
|
||||
@@ -171,7 +170,6 @@ void AMSinfo::parse_ext_info(MachineObject* obj, DevAmsTray tray) {
|
||||
info.filament_id = "";
|
||||
info.ctype = 0;
|
||||
info.material_colour = AMS_TRAY_DEFAULT_COL;
|
||||
wxColour(255, 255, 255);
|
||||
}
|
||||
info.material_state = AMSCanType::AMS_CAN_TYPE_VIRTUAL;
|
||||
if (tray.is_tray_info_ready() && obj->cali_version >= 0) {
|
||||
|
||||
@@ -684,7 +684,6 @@ public:
|
||||
|
||||
private:
|
||||
int m_nozzle_num = {1};
|
||||
AMSRoadShowMode m_single_ext_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
||||
AMSRoadShowMode m_left_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
||||
AMSRoadShowMode m_right_rode_mode = {AMSRoadShowMode::AMS_ROAD_MODE_FOUR};
|
||||
bool m_selected = {false};
|
||||
@@ -693,7 +692,6 @@ private:
|
||||
int m_right_road_length = {-1};
|
||||
int m_passroad_width = {6};
|
||||
double m_radius = {4};
|
||||
AMSPassRoadType m_pass_road_type = {AMSPassRoadType::AMS_ROAD_TYPE_NONE};
|
||||
AMSPassRoadSTEP m_pass_road_left_step = {AMSPassRoadSTEP::AMS_ROAD_STEP_NONE};
|
||||
AMSPassRoadSTEP m_pass_road_right_step = {AMSPassRoadSTEP::AMS_ROAD_STEP_NONE};
|
||||
|
||||
@@ -866,11 +864,7 @@ private:
|
||||
AMSinfo m_info;
|
||||
wxBoxSizer * sizer_can = {nullptr};
|
||||
wxGridSizer* sizer_can_extra = { nullptr };
|
||||
wxBoxSizer * sizer_humidity = { nullptr };
|
||||
wxBoxSizer * sizer_item = { nullptr };
|
||||
wxBoxSizer * sizer_can_middle = {nullptr};
|
||||
wxBoxSizer * sizer_can_left = {nullptr};
|
||||
wxBoxSizer * sizer_can_right = {nullptr};
|
||||
AMSExtImage* m_ext_image = { nullptr }; //the ext image upon the ext ams
|
||||
AMSExtText* m_ext_text = { nullptr }; //the ext text upon the ext ams
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ void AxisCtrlButton::SetInnerBackgroundColor(StateColor const& color)
|
||||
|
||||
void AxisCtrlButton::SetBitmap(ScalableBitmap &bmp)
|
||||
{
|
||||
if (&bmp && (& bmp.bmp()) && (bmp.bmp().IsOk())) {
|
||||
if (bmp.bmp().IsOk()) {
|
||||
m_icon = bmp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ bool ComboBox::SetFont(wxFont const& font)
|
||||
|
||||
int ComboBox::Append(const wxString &item, const wxBitmap &bitmap, int style)
|
||||
{
|
||||
if (&bitmap && bitmap.IsOk()) {
|
||||
if (bitmap.IsOk()) {
|
||||
return Append(item, bitmap, nullptr, style);
|
||||
}
|
||||
return Append(item, wxNullBitmap, nullptr, style);
|
||||
@@ -219,7 +219,7 @@ int ComboBox::Append(const wxString &text,
|
||||
void * clientData,
|
||||
int style)
|
||||
{
|
||||
if (&bitmap && bitmap.IsOk()) {
|
||||
if (bitmap.IsOk()) {
|
||||
return Append(text, bitmap, wxString{}, clientData, style);
|
||||
}
|
||||
return Append(text, wxNullBitmap, wxString{}, clientData, style);
|
||||
@@ -237,7 +237,7 @@ int ComboBox::Append(const wxString &text,
|
||||
void *clientData,
|
||||
int style)
|
||||
{
|
||||
auto valid_bit_map = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
|
||||
auto valid_bit_map = bitmap.IsOk() ? bitmap : wxNullBitmap;
|
||||
Item item{text, wxEmptyString, valid_bit_map, valid_bit_map, clientData, group_key, group_label};
|
||||
item.style = style;
|
||||
items.push_back(item);
|
||||
@@ -333,7 +333,7 @@ wxBitmap ComboBox::GetItemBitmap(unsigned int n) { return items[n].icon; }
|
||||
void ComboBox::SetItemBitmap(unsigned int n, wxBitmap const &bitmap)
|
||||
{
|
||||
if (n >= items.size()) return;
|
||||
items[n].icon = (&bitmap && bitmap.IsOk()) ? bitmap : wxNullBitmap;
|
||||
items[n].icon = bitmap.IsOk() ? bitmap : wxNullBitmap;
|
||||
drop.Invalidate();
|
||||
}
|
||||
|
||||
|
||||
@@ -1014,8 +1014,12 @@ void FanControlPopupNew::init_names(MachineObject* obj) {
|
||||
if (obj) {
|
||||
const std::string& special_cooling_text = DevPrinterConfigUtil::get_fan_text(obj->printer_type, "special_cooling_text");
|
||||
if (!special_cooling_text.empty()) {
|
||||
L("Cooling mode is suitable for printing PLA/PETG/TPU materials."); //some potential text, add i18n flags
|
||||
L("Cooling mode is suitable for printing PLA/PETG/TPU materials and filters the chamber air.");
|
||||
// Possible runtime values of special_cooling_text, marked for extraction.
|
||||
static const char *const markers[] = {
|
||||
L("Cooling mode is suitable for printing PLA/PETG/TPU materials."),
|
||||
L("Cooling mode is suitable for printing PLA/PETG/TPU materials and filters the chamber air."),
|
||||
};
|
||||
(void) markers;
|
||||
label_text[AIR_DUCT::AIR_DUCT_COOLING_FILT] = _L(special_cooling_text);
|
||||
}
|
||||
}
|
||||
@@ -1028,9 +1032,13 @@ wxString FanControlPopupNew::get_fan_func_name(int mode, int submode, AIR_FUN fu
|
||||
const std::string& func_text = DevPrinterConfigUtil::get_fan_text(m_obj->printer_type, mode, (int)func, submode);
|
||||
if (!func_text.empty())
|
||||
{
|
||||
L_CONTEXT("Right(Aux)", "air_duct");
|
||||
L_CONTEXT("Right(Filter)", "air_duct");
|
||||
L_CONTEXT("Left(Aux)", "air_duct");
|
||||
// Possible runtime values of func_text, marked for extraction.
|
||||
static const char *const markers[] = {
|
||||
L_CONTEXT("Right(Aux)", "air_duct"),
|
||||
L_CONTEXT("Right(Filter)", "air_duct"),
|
||||
L_CONTEXT("Left(Aux)", "air_duct"),
|
||||
};
|
||||
(void) markers;
|
||||
return _L_CONTEXT(func_text, "air_duct");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,6 @@ public:
|
||||
void decrease_fan_speeds();
|
||||
private:
|
||||
int m_current_speeds;
|
||||
int m_target_speed;
|
||||
int m_min_speeds;
|
||||
int m_max_speeds;
|
||||
ScalableBitmap m_bitmap_add;
|
||||
|
||||
@@ -98,7 +98,7 @@ void LabeledStaticBox::SetBorderColor(StateColor const &color)
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void LabeledStaticBox::SetFont(wxFont set_font)
|
||||
bool LabeledStaticBox::SetFont(const wxFont &set_font)
|
||||
{
|
||||
m_font = set_font;
|
||||
|
||||
@@ -109,6 +109,7 @@ void LabeledStaticBox::SetFont(wxFont set_font)
|
||||
m_label_width = tW;
|
||||
|
||||
Refresh();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LabeledStaticBox::Enable(bool enable)
|
||||
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
|
||||
void SetBorderColor(StateColor const &color);
|
||||
|
||||
void SetFont(wxFont set_font);
|
||||
bool SetFont(const wxFont &set_font) override;
|
||||
|
||||
bool Enable(bool enable) override;
|
||||
|
||||
|
||||
@@ -204,7 +204,6 @@ private:
|
||||
Label* m_caution;
|
||||
|
||||
wxTimer* m_refresh_timer {nullptr};
|
||||
size_t m_rack_event_token;
|
||||
Button* m_cancel_btn;
|
||||
Button* m_confirm_btn;
|
||||
};
|
||||
|
||||
@@ -21,6 +21,8 @@ ScrolledWindow::ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position
|
||||
m_bottomScrollbar = NULL;
|
||||
m_verticalSplitter = NULL;
|
||||
m_horizontalSplitter = NULL;
|
||||
m_userPanel = NULL;
|
||||
m_scroll_win = NULL;
|
||||
|
||||
m_marginWidth = marginWidth;
|
||||
|
||||
@@ -110,12 +112,13 @@ void ScrolledWindow::SetTipColor(wxColour color)
|
||||
if (m_bottomScrollbar) m_bottomScrollbar->SetTipColor(color);
|
||||
}
|
||||
|
||||
void ScrolledWindow::SetBackgroundColour(wxColour color)
|
||||
bool ScrolledWindow::SetBackgroundColour(const wxColour &color)
|
||||
{
|
||||
wxWindow::SetBackgroundColour(color);
|
||||
const bool result = wxWindow::SetBackgroundColour(color);
|
||||
m_verticalSplitter->SetBackgroundColour(color);
|
||||
m_userPanel->SetBackgroundColour(color);
|
||||
m_scroll_win->SetBackgroundColour(color);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ScrolledWindow::SetMarginColor(wxColour color)
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
ScrolledWindow(wxWindow *parent, wxWindowID id, wxPoint position, wxSize size, long style, int marginWidth = 0, int scrollbarWidth = 4, int tipLength = 0);
|
||||
void OnMouseWheel(wxMouseEvent &event);
|
||||
void SetTipColor(wxColour color);
|
||||
void SetBackgroundColour(wxColour color);
|
||||
bool SetBackgroundColour(const wxColour &color) override;
|
||||
|
||||
void SetMarginColor(wxColour color);
|
||||
void SetScrollbarColor(wxColour color);
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
// wxSplitterWindow* GetVerticalSplitter() { return m_verticalSplitter; }
|
||||
// wxSplitterWindow* GetHorizontalSplitter() { return m_horizontalSplitter; }
|
||||
bool IsBothDirections() { return m_bothDirections; }
|
||||
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false);
|
||||
virtual void SetScrollbars(int pixelsPerUnitX, int pixelsPerUnitY, int noUnitsX, int noUnitsY, int xPos = 0, int yPos = 0, bool noRefresh = false) override;
|
||||
|
||||
private:
|
||||
wxPanel * m_userPanel; // the panel targeted by the scrolled window
|
||||
|
||||
@@ -111,7 +111,7 @@ int TabCtrl::AppendItem(const wxString &item,
|
||||
btns.push_back(btn);
|
||||
if (btns.size() > 1)
|
||||
sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0});
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE * 2);
|
||||
sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE);
|
||||
sizer->AddStretchSpacer(1);
|
||||
relayout();
|
||||
return btns.size() - 1;
|
||||
@@ -225,8 +225,9 @@ bool TabCtrl::IsVisible(unsigned int item) const
|
||||
|
||||
void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
auto size = GetSize();
|
||||
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
||||
if (sizeFlags & wxSIZE_USE_EXISTING) return;
|
||||
if (size == GetSize()) return;
|
||||
relayout();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,633 +0,0 @@
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "LinuxDisplayBackend.hpp"
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <string>
|
||||
#ifdef __WIN32__
|
||||
#include <winuser.h>
|
||||
#include <versionhelpers.h>
|
||||
#include <wx/msw/registry.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#ifdef __LINUX__
|
||||
#include "Printer/gstbambusrc.h"
|
||||
#include <gst/gst.h> // main gstreamer header
|
||||
#endif
|
||||
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
#include <gtk/gtk.h>
|
||||
#include <wx/nativewin.h>
|
||||
|
||||
namespace {
|
||||
bool ensure_gstreamer_initialized_for_liveview()
|
||||
{
|
||||
GError* error = nullptr;
|
||||
if (!gst_init_check(nullptr, nullptr, &error)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "wxMediaCtrl2: gst_init_check failed before native Wayland liveview setup"
|
||||
<< (error ? std::string(": ") + error->message : std::string());
|
||||
if (error)
|
||||
g_error_free(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_gstreamer_feature_available(const char* feature)
|
||||
{
|
||||
if (!ensure_gstreamer_initialized_for_liveview())
|
||||
return false;
|
||||
|
||||
GstElementFactory* factory = gst_element_factory_find(feature);
|
||||
if (!factory)
|
||||
return false;
|
||||
|
||||
gst_object_unref(factory);
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_gstreamer_feature_rank(const char* feature, guint rank)
|
||||
{
|
||||
GstElementFactory* factory = gst_element_factory_find(feature);
|
||||
if (!factory)
|
||||
return;
|
||||
|
||||
gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE(factory), rank);
|
||||
gst_object_unref(factory);
|
||||
}
|
||||
|
||||
void configure_wayland_gstreamer_liveview_path()
|
||||
{
|
||||
static bool configured = false;
|
||||
if (configured)
|
||||
return;
|
||||
configured = true;
|
||||
|
||||
if (!ensure_gstreamer_initialized_for_liveview())
|
||||
return;
|
||||
|
||||
// Prefer software decode for Bambu liveview on Wayland/NVIDIA, where
|
||||
// zero-copy GL/DMABUF display paths can be fragile. Keep hardware
|
||||
// decoders available as lower-ranked fallbacks for VAAPI/NVDEC/V4L2-only
|
||||
// installations instead of passing preflight and then blocking autoplug.
|
||||
set_gstreamer_feature_rank("avdec_h264", GST_RANK_PRIMARY + 300);
|
||||
set_gstreamer_feature_rank("openh264dec", GST_RANK_PRIMARY + 100);
|
||||
set_gstreamer_feature_rank("nvh264dec", GST_RANK_MARGINAL);
|
||||
set_gstreamer_feature_rank("vaapih264dec", GST_RANK_MARGINAL);
|
||||
set_gstreamer_feature_rank("vah264dec", GST_RANK_MARGINAL);
|
||||
set_gstreamer_feature_rank("v4l2h264dec", GST_RANK_MARGINAL);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // defined(__LINUX__) && defined(__WXGTK__)
|
||||
|
||||
#ifdef __LINUX__
|
||||
extern "C" int gst_bambu_last_error;
|
||||
|
||||
class WXDLLIMPEXP_MEDIA
|
||||
wxGStreamerMediaBackend : public wxMediaBackendCommonBase
|
||||
{
|
||||
public:
|
||||
GstElement *m_playbin; // GStreamer media element
|
||||
};
|
||||
#endif
|
||||
|
||||
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
wxMediaCtrl2::wxMediaCtrl2(wxWindow *parent)
|
||||
{
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
m_native_wayland = Slic3r::GUI::is_running_on_wayland();
|
||||
if (m_native_wayland && is_gstreamer_feature_available("gtksink"))
|
||||
configure_wayland_gstreamer_liveview_path();
|
||||
else if (m_native_wayland) {
|
||||
m_gtk_sink_error = _L("Native Wayland liveview requires the GStreamer GTK video sink. Please install the gtksink plugin for GStreamer, then restart OrcaSlicer.");
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: native Wayland liveview disabled because GStreamer gtksink is unavailable";
|
||||
}
|
||||
#endif
|
||||
#ifdef __WIN32__
|
||||
auto hModExe = GetModuleHandle(NULL);
|
||||
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: GetModuleHandle " << hModExe;
|
||||
auto NvOptimusEnablement = (DWORD *) GetProcAddress(hModExe, "NvOptimusEnablement");
|
||||
auto AmdPowerXpressRequestHighPerformance = (int *) GetProcAddress(hModExe, "AmdPowerXpressRequestHighPerformance");
|
||||
if (NvOptimusEnablement) {
|
||||
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: NvOptimusEnablement " << *NvOptimusEnablement;
|
||||
*NvOptimusEnablement = 0;
|
||||
}
|
||||
if (AmdPowerXpressRequestHighPerformance) {
|
||||
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: AmdPowerXpressRequestHighPerformance " << *AmdPowerXpressRequestHighPerformance;
|
||||
*AmdPowerXpressRequestHighPerformance = 0;
|
||||
}
|
||||
#endif
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_native_wayland)
|
||||
wxControl::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||
else
|
||||
#endif
|
||||
wxMediaCtrl::Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxMEDIACTRLPLAYERCONTROLS_NONE);
|
||||
#ifdef __LINUX__
|
||||
gstbambusrc_register();
|
||||
#ifdef __WXGTK__
|
||||
if (m_native_wayland && m_gtk_sink_error.empty())
|
||||
m_use_gtk_sink = CreateGtkSinkPlayer();
|
||||
if (m_native_wayland && !m_use_gtk_sink && m_gtk_sink_error.empty())
|
||||
m_gtk_sink_error = _L("Failed to initialize the native Wayland GStreamer video sink. Please check your GStreamer GTK plugin installation.");
|
||||
#endif
|
||||
if (!m_use_gtk_sink && m_imp) {
|
||||
auto playbin = reinterpret_cast<wxGStreamerMediaBackend *>(m_imp)->m_playbin;
|
||||
g_object_set(G_OBJECT(playbin),
|
||||
"audio-sink", nullptr,
|
||||
nullptr);
|
||||
} else if (!m_use_gtk_sink) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: wxMediaCtrl backend is unavailable";
|
||||
}
|
||||
Bind(wxEVT_MEDIA_LOADED, [this](auto & e) {
|
||||
m_loaded = true;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(0);
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
wxMediaCtrl2::~wxMediaCtrl2()
|
||||
{
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
DestroyGtkSinkPlayer();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
bool wxMediaCtrl2::CreateGtkSinkPlayer()
|
||||
{
|
||||
GstElement *playbin = gst_element_factory_make("playbin", "orca-wayland-gtk-playbin");
|
||||
if (!playbin)
|
||||
return false;
|
||||
|
||||
GError *error = nullptr;
|
||||
GstElement *video_sink = gst_parse_bin_from_description(
|
||||
"videoconvert ! videoscale ! video/x-raw,format=BGRx ! gtksink name=orca_wayland_gtksink sync=false",
|
||||
TRUE,
|
||||
&error);
|
||||
if (!video_sink) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to create gtksink video bin"
|
||||
<< (error ? std::string(": ") + error->message : std::string());
|
||||
if (error)
|
||||
g_error_free(error);
|
||||
gst_object_unref(playbin);
|
||||
return false;
|
||||
}
|
||||
|
||||
GstElement *gtk_sink = gst_bin_get_by_name(GST_BIN(video_sink), "orca_wayland_gtksink");
|
||||
if (!gtk_sink) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to find gtksink in video bin";
|
||||
gst_object_unref(video_sink);
|
||||
gst_object_unref(playbin);
|
||||
return false;
|
||||
}
|
||||
|
||||
GtkWidget *gtk_widget = nullptr;
|
||||
g_object_get(G_OBJECT(gtk_sink), "widget", >k_widget, nullptr);
|
||||
if (!gtk_widget) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink did not expose a GtkWidget";
|
||||
gst_object_unref(gtk_sink);
|
||||
gst_object_unref(video_sink);
|
||||
gst_object_unref(playbin);
|
||||
return false;
|
||||
}
|
||||
|
||||
gtk_widget_show(gtk_widget);
|
||||
m_gtk_video_window = new wxNativeWindow(this, wxID_ANY, gtk_widget);
|
||||
m_gtk_video_window->Show();
|
||||
g_object_unref(gtk_widget);
|
||||
|
||||
g_object_set(G_OBJECT(playbin),
|
||||
"video-sink", video_sink,
|
||||
"audio-sink", nullptr,
|
||||
nullptr);
|
||||
gst_object_unref(video_sink);
|
||||
|
||||
m_gtk_playbin = playbin;
|
||||
m_gtk_sink = gtk_sink;
|
||||
|
||||
GstBus *bus = gst_element_get_bus(playbin);
|
||||
m_gtk_bus_watch_id = gst_bus_add_watch(bus, [](GstBus *, GstMessage *message, gpointer data) -> gboolean {
|
||||
auto *self = static_cast<wxMediaCtrl2 *>(data);
|
||||
if (!self || !self->m_gtk_playbin)
|
||||
return G_SOURCE_REMOVE;
|
||||
|
||||
switch (GST_MESSAGE_TYPE(message)) {
|
||||
case GST_MESSAGE_ERROR:
|
||||
{
|
||||
GError *error = nullptr;
|
||||
gchar *debug = nullptr;
|
||||
gst_message_parse_error(message, &error, &debug);
|
||||
BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink pipeline error"
|
||||
<< (error ? std::string(": ") + error->message : std::string())
|
||||
<< (debug ? std::string(" debug: ") + debug : std::string());
|
||||
if (error)
|
||||
g_error_free(error);
|
||||
if (debug)
|
||||
g_free(debug);
|
||||
|
||||
self->m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
|
||||
self->m_loaded = false;
|
||||
self->m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
self->PostGtkSinkStateEvent(self->GetId());
|
||||
break;
|
||||
}
|
||||
case GST_MESSAGE_EOS:
|
||||
self->m_loaded = false;
|
||||
self->m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
self->PostGtkSinkStateEvent(self->GetId());
|
||||
break;
|
||||
case GST_MESSAGE_STATE_CHANGED:
|
||||
if (GST_MESSAGE_SRC(message) == GST_OBJECT(self->m_gtk_playbin)) {
|
||||
GstState old_state;
|
||||
GstState new_state;
|
||||
GstState pending_state;
|
||||
gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state);
|
||||
|
||||
if (new_state == GST_STATE_PLAYING) {
|
||||
self->m_loaded = true;
|
||||
self->m_gtk_state = wxMEDIASTATE_PLAYING;
|
||||
self->PostGtkSinkStateEvent();
|
||||
} else if (new_state == GST_STATE_PAUSED && old_state < GST_STATE_PAUSED) {
|
||||
// Treat only upward READY/NULL -> PAUSED as load completion.
|
||||
// PLAYING -> PAUSED is a normal teardown step before NULL.
|
||||
self->m_loaded = true;
|
||||
self->m_gtk_state = wxMEDIASTATE_PAUSED;
|
||||
self->PostGtkSinkStateEvent();
|
||||
} else if (new_state <= GST_STATE_READY && old_state >= GST_STATE_PAUSED) {
|
||||
self->m_loaded = false;
|
||||
self->m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
self->PostGtkSinkStateEvent();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}, this);
|
||||
gst_object_unref(bus);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: using GTK native Wayland video sink";
|
||||
return true;
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::DestroyGtkSinkPlayer()
|
||||
{
|
||||
if (m_gtk_bus_watch_id) {
|
||||
g_source_remove(m_gtk_bus_watch_id);
|
||||
m_gtk_bus_watch_id = 0;
|
||||
}
|
||||
|
||||
if (m_gtk_playbin) {
|
||||
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
|
||||
}
|
||||
|
||||
if (m_gtk_video_window) {
|
||||
m_gtk_video_window->Destroy();
|
||||
m_gtk_video_window = nullptr;
|
||||
}
|
||||
|
||||
if (m_gtk_playbin) {
|
||||
gst_object_unref(m_gtk_playbin);
|
||||
m_gtk_playbin = nullptr;
|
||||
}
|
||||
|
||||
if (m_gtk_sink) {
|
||||
gst_object_unref(m_gtk_sink);
|
||||
m_gtk_sink = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::PostGtkSinkStateEvent(int id)
|
||||
{
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(id);
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
#endif // defined(__LINUX__) && defined(__WXGTK__)
|
||||
|
||||
#define CLSID_BAMBU_SOURCE L"{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
|
||||
|
||||
void wxMediaCtrl2::Load(wxURI url)
|
||||
{
|
||||
#ifdef __WIN32__
|
||||
InvalidateBestSize();
|
||||
if (m_imp == nullptr) {
|
||||
static bool notified = false;
|
||||
if (!notified) CallAfter([] {
|
||||
auto res = wxMessageBox(_L("Windows Media Player is required for this task! Do you want to enable 'Windows Media Player' for your operation system?"), _L("Error"), wxOK | wxCANCEL);
|
||||
if (res == wxOK) {
|
||||
wxString url = IsWindows10OrGreater()
|
||||
? "ms-settings:optionalfeatures?activationSource=SMC-Article-14209"
|
||||
: "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287";
|
||||
wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE);
|
||||
}
|
||||
});
|
||||
m_error = 100;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
return;
|
||||
}
|
||||
{
|
||||
wxRegKey key11(wxRegKey::HKCU, L"SOFTWARE\\Classes\\CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
|
||||
wxRegKey key12(wxRegKey::HKCR, L"CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32");
|
||||
wxString path = key11.Exists() ? key11.QueryDefaultValue()
|
||||
: key12.Exists() ? key12.QueryDefaultValue() : wxString{};
|
||||
wxRegKey key2(wxRegKey::HKCR, "bambu");
|
||||
wxString clsid;
|
||||
if (key2.Exists())
|
||||
key2.QueryRawValue("Source Filter", clsid);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": clsid %1% path %2%") % clsid % path;
|
||||
|
||||
std::string data_dir_str = Slic3r::data_dir();
|
||||
boost::filesystem::path data_dir_path(data_dir_str);
|
||||
auto dll_path = data_dir_path / "plugins" / "BambuSource.dll";
|
||||
if (path.empty() || !wxFile::Exists(path) || clsid != CLSID_BAMBU_SOURCE) {
|
||||
if (boost::filesystem::exists(dll_path)) {
|
||||
CallAfter(
|
||||
[dll_path] {
|
||||
int res = wxMessageBox(_L("BambuSource has not correctly been registered for media playing! Press Yes to re-register it. You will be promoted twice"), _L("Error"), wxYES_NO);
|
||||
if (res == wxYES) {
|
||||
std::string regContent = R"(Windows Registry Editor Version 5.00
|
||||
[HKEY_CLASSES_ROOT\bambu]
|
||||
"Source Filter"="{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}"
|
||||
)";
|
||||
|
||||
auto reg_path = (fs::temp_directory_path() / fs::unique_path()).replace_extension(".reg");
|
||||
std::ofstream temp_reg_file(reg_path.c_str());
|
||||
if (!temp_reg_file) {
|
||||
return false;
|
||||
}
|
||||
temp_reg_file << regContent;
|
||||
temp_reg_file.close();
|
||||
auto sei_params = L"/q /s " + reg_path.wstring();
|
||||
SHELLEXECUTEINFO sei{sizeof(sei), SEE_MASK_NOCLOSEPROCESS, NULL, L"open",
|
||||
L"regedit", sei_params.c_str(),SW_HIDE,SW_HIDE};
|
||||
::ShellExecuteEx(&sei);
|
||||
|
||||
wstring quoted_dll_path = L"\"" + dll_path.wstring() + L"\"";
|
||||
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"runas", L"regsvr32", quoted_dll_path.c_str(), SW_HIDE };
|
||||
::ShellExecuteEx(&info);
|
||||
fs::remove(reg_path);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
CallAfter([] {
|
||||
wxMessageBox(_L("Missing BambuSource component registered for media playing! Please re-install OrcaSlicer or seek community help."), _L("Error"), wxOK);
|
||||
});
|
||||
}
|
||||
m_error = clsid != CLSID_BAMBU_SOURCE ? 101 : path.empty() ? 102 : 103;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
return;
|
||||
}
|
||||
if (path != dll_path) {
|
||||
static bool notified = false;
|
||||
if (!notified) CallAfter([dll_path] {
|
||||
int res = wxMessageBox(_L("Using a BambuSource from a different install, video play may not work correctly! Press Yes to fix it."), _L("Warning"), wxYES_NO | wxICON_WARNING);
|
||||
if (res == wxYES) {
|
||||
auto path = dll_path.wstring();
|
||||
if (path.find(L' ') != std::wstring::npos)
|
||||
path = L"\"" + path + L"\"";
|
||||
SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"open", L"regsvr32", path.c_str(), SW_HIDE};
|
||||
::ShellExecuteEx(&info);
|
||||
}
|
||||
});
|
||||
notified = true;
|
||||
}
|
||||
wxRegKey keyWmp(wxRegKey::HKCU, "SOFTWARE\\Microsoft\\MediaPlayer\\Player\\Extensions\\.");
|
||||
keyWmp.Create();
|
||||
long permissions = 0;
|
||||
if (keyWmp.HasValue("Permissions"))
|
||||
keyWmp.QueryValue("Permissions", &permissions);
|
||||
if ((permissions & 32) == 0) {
|
||||
permissions |= 32;
|
||||
keyWmp.SetValue("Permissions", permissions);
|
||||
}
|
||||
}
|
||||
url = wxURI(url.BuildURI().append("&hwnd=").append(boost::lexical_cast<std::string>(GetHandle())).append("&tid=").append(
|
||||
boost::lexical_cast<std::string>(GetCurrentThreadId())));
|
||||
#endif
|
||||
#ifdef __WXGTK3__
|
||||
GstElementFactory *factory;
|
||||
int hasplugins = 1;
|
||||
|
||||
factory = gst_element_factory_find("h264parse");
|
||||
if (!factory) {
|
||||
hasplugins = 0;
|
||||
} else {
|
||||
gst_object_unref(factory);
|
||||
}
|
||||
|
||||
factory = gst_element_factory_find("openh264dec");
|
||||
if (!factory) {
|
||||
factory = gst_element_factory_find("avdec_h264");
|
||||
}
|
||||
if (!factory) {
|
||||
factory = gst_element_factory_find("vaapih264dec");
|
||||
}
|
||||
if (!factory) {
|
||||
factory = gst_element_factory_find("vah264dec");
|
||||
}
|
||||
if (!factory) {
|
||||
factory = gst_element_factory_find("nvh264dec");
|
||||
}
|
||||
if (!factory) {
|
||||
factory = gst_element_factory_find("v4l2h264dec");
|
||||
}
|
||||
if (!factory) {
|
||||
hasplugins = 0;
|
||||
} else {
|
||||
gst_object_unref(factory);
|
||||
}
|
||||
|
||||
if (!hasplugins) {
|
||||
CallAfter([] {
|
||||
wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video. (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Orca Slicer?)"), _L("Error"), wxOK);
|
||||
});
|
||||
m_error = 101;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
return;
|
||||
}
|
||||
wxLog::EnableLogging(false);
|
||||
#endif
|
||||
m_error = 0;
|
||||
m_loaded = false;
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_use_gtk_sink && m_gtk_playbin) {
|
||||
const std::string uri = std::string(url.BuildURI().ToUTF8().data());
|
||||
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
|
||||
g_object_set(G_OBJECT(m_gtk_playbin), "uri", uri.c_str(), nullptr);
|
||||
m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PAUSED);
|
||||
if (state == GST_STATE_CHANGE_FAILURE) {
|
||||
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
|
||||
PostGtkSinkStateEvent(GetId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!m_imp) {
|
||||
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
|
||||
m_loaded = false;
|
||||
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
|
||||
m_gtk_sink_error_notified = true;
|
||||
const wxString message = m_gtk_sink_error;
|
||||
CallAfter([message] {
|
||||
wxMessageBox(message, _L("Error"), wxOK);
|
||||
});
|
||||
}
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
wxMediaCtrl::Load(url);
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::Play()
|
||||
{
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_use_gtk_sink && m_gtk_playbin) {
|
||||
GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PLAYING);
|
||||
if (state == GST_STATE_CHANGE_FAILURE) {
|
||||
m_error = gst_bambu_last_error ? gst_bambu_last_error : 2;
|
||||
m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
PostGtkSinkStateEvent(GetId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!m_imp) {
|
||||
m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100;
|
||||
if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) {
|
||||
m_gtk_sink_error_notified = true;
|
||||
const wxString message = m_gtk_sink_error;
|
||||
CallAfter([message] {
|
||||
wxMessageBox(message, _L("Error"), wxOK);
|
||||
});
|
||||
}
|
||||
PostGtkSinkStateEvent(GetId());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
wxMediaCtrl::Play();
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::Stop()
|
||||
{
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_use_gtk_sink && m_gtk_playbin) {
|
||||
gst_element_set_state(m_gtk_playbin, GST_STATE_NULL);
|
||||
m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
m_loaded = false;
|
||||
PostGtkSinkStateEvent(0);
|
||||
return;
|
||||
}
|
||||
if (!m_imp)
|
||||
return;
|
||||
#endif
|
||||
wxMediaCtrl::Stop();
|
||||
}
|
||||
|
||||
wxMediaState wxMediaCtrl2::GetState()
|
||||
{
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
if (m_use_gtk_sink && m_gtk_playbin)
|
||||
return m_gtk_state;
|
||||
if (!m_imp)
|
||||
return wxMEDIASTATE_STOPPED;
|
||||
#endif
|
||||
return wxMediaCtrl::GetState();
|
||||
}
|
||||
|
||||
int wxMediaCtrl2::GetLastError() const
|
||||
{
|
||||
#ifdef __LINUX__
|
||||
#ifdef __WXGTK__
|
||||
if (m_use_gtk_sink && m_error)
|
||||
return m_error;
|
||||
#endif
|
||||
if (m_error)
|
||||
return m_error;
|
||||
return gst_bambu_last_error;
|
||||
#else
|
||||
return m_error;
|
||||
#endif
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl2::GetVideoSize() const
|
||||
{
|
||||
#ifdef __LINUX__
|
||||
// Gstreamer doesn't give us a VideoSize until we're playing, which
|
||||
// confuses the MediaPlayCtrl into claiming that it is stuck
|
||||
// "Loading...". Fake it out for now.
|
||||
return m_loaded ? wxSize(1280, 720) : wxSize{};
|
||||
#else
|
||||
wxSize size = m_imp ? m_imp->GetVideoSize() : wxSize(0, 0);
|
||||
if (size.GetWidth() > 0)
|
||||
const_cast<wxSize&>(m_video_size) = size;
|
||||
return size;
|
||||
#endif
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl2::DoGetBestSize() const
|
||||
{
|
||||
return {-1, -1};
|
||||
}
|
||||
|
||||
#ifdef __WIN32__
|
||||
|
||||
WXLRESULT wxMediaCtrl2::MSWWindowProc(WXUINT nMsg,
|
||||
WXWPARAM wParam,
|
||||
WXLPARAM lParam)
|
||||
{
|
||||
// The stream source sends WM_USER+1000 with a synchronous SendMessage from its own threads,
|
||||
// so this runs re-entrantly on the UI thread at whatever message-retrieval point the player
|
||||
// happens to be in - often nested inside an Orca log statement. Never BOOST_LOG_TRIVIAL here:
|
||||
// boost::log is not re-entrant on one thread, and doing so corrupted its per-thread record
|
||||
// state, crashing later in unrelated places (the player, the log filter, a plug-in heap free).
|
||||
// Post the string out (as the stat branch does) and log it on a clean stack instead.
|
||||
if (nMsg == WM_USER + 1000) {
|
||||
wxString msg((wchar_t const *) lParam);
|
||||
if (wParam == 1) {
|
||||
if (msg.EndsWith("]")) {
|
||||
int n = msg.find_last_of('[');
|
||||
if (n != wxString::npos) {
|
||||
long val = 0;
|
||||
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val))
|
||||
m_error = (int) val;
|
||||
}
|
||||
} else if (msg.Contains("stat_log")) {
|
||||
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
|
||||
evt.SetEventObject(this);
|
||||
evt.SetString(msg.Mid(msg.Find(' ') + 1));
|
||||
wxPostEvent(this, evt);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return wxMediaCtrl::MSWWindowProc(nMsg, wParam, lParam);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,117 +0,0 @@
|
||||
//
|
||||
// wxMediaCtrl2.h
|
||||
// libslic3r_gui
|
||||
//
|
||||
// Created by cmguo on 2021/12/7.
|
||||
//
|
||||
|
||||
#ifndef wxMediaCtrl2_h
|
||||
#define wxMediaCtrl2_h
|
||||
|
||||
#include "wx/uri.h"
|
||||
#include "wx/mediactrl.h"
|
||||
|
||||
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
typedef struct _GstElement GstElement;
|
||||
#endif
|
||||
|
||||
#ifdef __WXMAC__
|
||||
|
||||
class wxMediaCtrl2 : public wxWindow
|
||||
{
|
||||
public:
|
||||
wxMediaCtrl2(wxWindow * parent);
|
||||
|
||||
~wxMediaCtrl2();
|
||||
|
||||
void Load(wxURI url);
|
||||
|
||||
void Play();
|
||||
|
||||
void Stop();
|
||||
|
||||
void SetIdleImage(wxString const & image);
|
||||
|
||||
wxMediaState GetState() const;
|
||||
|
||||
wxSize GetVideoSize() const;
|
||||
|
||||
int GetLastError() const { return m_error; }
|
||||
|
||||
static inline const wxMediaState MEDIASTATE_BUFFERING = static_cast<wxMediaState>(6);
|
||||
|
||||
protected:
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
|
||||
static void bambu_log(void const * ctx, int level, char const * msg);
|
||||
|
||||
void NotifyStopped();
|
||||
|
||||
private:
|
||||
void create_player();
|
||||
void * m_player = nullptr;
|
||||
wxMediaState m_state = wxMEDIASTATE_STOPPED;
|
||||
int m_error = 0;
|
||||
wxSize m_video_size{16, 9};
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class wxMediaCtrl2 : public wxMediaCtrl
|
||||
{
|
||||
public:
|
||||
wxMediaCtrl2(wxWindow *parent);
|
||||
~wxMediaCtrl2();
|
||||
|
||||
void Load(wxURI url);
|
||||
|
||||
void Play();
|
||||
|
||||
void Stop();
|
||||
|
||||
void SetIdleImage(wxString const & image);
|
||||
|
||||
wxMediaState GetState();
|
||||
|
||||
int GetLastError() const;
|
||||
|
||||
wxSize GetVideoSize() const;
|
||||
|
||||
protected:
|
||||
wxSize DoGetBestSize() const override;
|
||||
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
|
||||
#ifdef __WIN32__
|
||||
WXLRESULT MSWWindowProc(WXUINT nMsg,
|
||||
WXWPARAM wParam,
|
||||
WXLPARAM lParam) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
bool CreateGtkSinkPlayer();
|
||||
void DestroyGtkSinkPlayer();
|
||||
void PostGtkSinkStateEvent(int id = 0);
|
||||
|
||||
bool m_native_wayland = false;
|
||||
bool m_use_gtk_sink = false;
|
||||
wxString m_gtk_sink_error;
|
||||
bool m_gtk_sink_error_notified = false;
|
||||
GstElement *m_gtk_playbin = nullptr;
|
||||
GstElement *m_gtk_sink = nullptr;
|
||||
unsigned int m_gtk_bus_watch_id = 0;
|
||||
wxWindow *m_gtk_video_window = nullptr;
|
||||
wxMediaState m_gtk_state = wxMEDIASTATE_STOPPED;
|
||||
#endif
|
||||
wxString m_idle_image;
|
||||
int m_error = 0;
|
||||
bool m_loaded = false;
|
||||
wxSize m_video_size{16, 9};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* wxMediaCtrl2_h */
|
||||
@@ -1,170 +0,0 @@
|
||||
//
|
||||
// wxMediaCtrl2.m
|
||||
// OrcaSlicer
|
||||
//
|
||||
// Created by cmguo on 2021/12/7.
|
||||
//
|
||||
|
||||
#import "wxMediaCtrl2.h"
|
||||
#import "wx/mediactrl.h"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "BambuPlayer/BambuPlayer.h"
|
||||
#import "../Utils/NetworkAgent.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
#define BAMBU_DYNAMIC
|
||||
|
||||
void wxMediaCtrl2::bambu_log(void const * ctx, int level, char const * msg)
|
||||
{
|
||||
if (level == 1) {
|
||||
wxString msg2(msg);
|
||||
if (msg2.EndsWith("]")) {
|
||||
int n = msg2.find_last_of('[');
|
||||
if (n != wxString::npos) {
|
||||
long val = 0;
|
||||
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
|
||||
if (msg2.SubString(n + 1, msg2.Length() - 2).ToLong(&val))
|
||||
ctrl->m_error = (int) val;
|
||||
}
|
||||
} else if (strstr(msg, "stat_log")) {
|
||||
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
|
||||
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
|
||||
evt.SetEventObject(ctrl);
|
||||
evt.SetString(strchr(msg, ' ') + 1);
|
||||
wxPostEvent(ctrl, evt);
|
||||
}
|
||||
} else if (level < 0) {
|
||||
wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx;
|
||||
ctrl->NotifyStopped();
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << msg;
|
||||
}
|
||||
|
||||
wxMediaCtrl2::wxMediaCtrl2(wxWindow * parent)
|
||||
: wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
NSView * imageView = (NSView *) GetHandle();
|
||||
imageView.layer = [[CALayer alloc] init];
|
||||
CGColorRef color = CGColorCreateGenericRGB(0, 0, 0, 1.0f);
|
||||
imageView.layer.backgroundColor = color;
|
||||
CGColorRelease(color);
|
||||
imageView.wantsLayer = YES;
|
||||
create_player();
|
||||
}
|
||||
|
||||
wxMediaCtrl2::~wxMediaCtrl2()
|
||||
{
|
||||
BambuPlayer * player = (BambuPlayer *) m_player;
|
||||
[player dealloc];
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::create_player()
|
||||
{
|
||||
auto module = Slic3r::NetworkAgent::get_bambu_source_entry();
|
||||
if (!module) {
|
||||
//not ready yet
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Network plugin not ready currently!";
|
||||
return;
|
||||
}
|
||||
Class cls = (__bridge Class) dlsym(module, "OBJC_CLASS_$_BambuPlayer");
|
||||
if (cls == nullptr) {
|
||||
m_error = -2;
|
||||
return;
|
||||
}
|
||||
NSView * imageView = (NSView *) GetHandle();
|
||||
BambuPlayer * player = [cls alloc];
|
||||
[player initWithImageView: imageView];
|
||||
[player setLogger: bambu_log withContext: this];
|
||||
m_player = player;
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::Load(wxURI url)
|
||||
{
|
||||
if (!m_player) {
|
||||
create_player();
|
||||
if (!m_player) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
BambuPlayer * player = (BambuPlayer *) m_player;
|
||||
if (player) {
|
||||
[player close];
|
||||
m_error = 0;
|
||||
m_error = [player open: url.BuildURI().ToUTF8()];
|
||||
}
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::Play()
|
||||
{
|
||||
if (!m_player) {
|
||||
create_player();
|
||||
if (!m_player) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
BambuPlayer * player2 = (BambuPlayer *) m_player;
|
||||
[player2 play];
|
||||
if (m_state != wxMEDIASTATE_PLAYING) {
|
||||
m_state = wxMEDIASTATE_PLAYING;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::Stop()
|
||||
{
|
||||
if (!m_player) {
|
||||
create_player();
|
||||
if (!m_player) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!";
|
||||
return;
|
||||
}
|
||||
}
|
||||
BambuPlayer * player2 = (BambuPlayer *) m_player;
|
||||
[player2 close];
|
||||
NotifyStopped();
|
||||
}
|
||||
|
||||
void wxMediaCtrl2::NotifyStopped()
|
||||
{
|
||||
if (m_state != wxMEDIASTATE_STOPPED) {
|
||||
m_state = wxMEDIASTATE_STOPPED;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
wxMediaState wxMediaCtrl2::GetState() const
|
||||
{
|
||||
return m_state;
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl2::GetVideoSize() const
|
||||
{
|
||||
BambuPlayer * player2 = (BambuPlayer *) m_player;
|
||||
if (player2) {
|
||||
NSSize size = [player2 videoSize];
|
||||
if (size.width > 0)
|
||||
const_cast<wxSize&>(m_video_size) = {(int) size.width, (int) size.height};
|
||||
return {(int) size.width, (int) size.height};
|
||||
} else {
|
||||
return {0, 0};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
#include "wxMediaCtrl3.h"
|
||||
#include "AVVideoDecoder.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/dcclient.h>
|
||||
#ifdef __WIN32__
|
||||
#include <versionhelpers.h>
|
||||
#include <wx/msw/registry.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
BEGIN_EVENT_TABLE(wxMediaCtrl3, wxWindow)
|
||||
|
||||
// catch paint events
|
||||
EVT_PAINT(wxMediaCtrl3::paintEvent)
|
||||
|
||||
END_EVENT_TABLE()
|
||||
|
||||
struct StaticBambuLib : BambuLib
|
||||
{
|
||||
static StaticBambuLib &get(BambuLib *);
|
||||
};
|
||||
|
||||
wxMediaCtrl3::wxMediaCtrl3(wxWindow *parent)
|
||||
: wxWindow(parent, wxID_ANY)
|
||||
, BambuLib(StaticBambuLib::get(this))
|
||||
, m_thread([this] { PlayThread(); })
|
||||
{
|
||||
SetBackgroundColour("#000001ff");
|
||||
}
|
||||
|
||||
wxMediaCtrl3::~wxMediaCtrl3()
|
||||
{
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_url.reset(new wxURI);
|
||||
m_frame = wxImage(m_idle_image);
|
||||
m_cond.notify_all();
|
||||
}
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::Load(wxURI url)
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_video_size = wxDefaultSize;
|
||||
m_error = 0;
|
||||
m_url.reset(new wxURI(url));
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::Play()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (m_state != wxMEDIASTATE_PLAYING) {
|
||||
m_state = wxMEDIASTATE_PLAYING;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::Stop()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_url.reset();
|
||||
m_frame = wxImage(m_idle_image);
|
||||
NotifyStopped();
|
||||
m_cond.notify_all();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::SetIdleImage(wxString const &image)
|
||||
{
|
||||
if (m_idle_image == image)
|
||||
return;
|
||||
m_idle_image = image;
|
||||
if (m_url == nullptr) {
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
m_frame = wxImage(m_idle_image);
|
||||
assert(m_frame.IsOk());
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
wxMediaState wxMediaCtrl3::GetState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_state;
|
||||
}
|
||||
|
||||
int wxMediaCtrl3::GetLastError()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_error;
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl3::GetVideoSize()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_video_size;
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl3::DoGetBestSize() const
|
||||
{
|
||||
return {-1, -1};
|
||||
}
|
||||
|
||||
static void adjust_frame_size(wxSize & frame, wxSize const & video, wxSize const & window)
|
||||
{
|
||||
if (video.x * window.y < video.y * window.x)
|
||||
frame = { video.x * window.y / video.y, window.y };
|
||||
else
|
||||
frame = { window.x, video.y * window.x / video.x };
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::paintEvent(wxPaintEvent &evt)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
auto size = GetSize();
|
||||
if (size.x <= 0 || size.y <= 0)
|
||||
return;
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
if (!m_frame.IsOk())
|
||||
return;
|
||||
auto size2 = m_frame.GetSize();
|
||||
if (size2.x != m_frame_size.x && size2.y == m_frame_size.y)
|
||||
size2.x = m_frame_size.x;
|
||||
auto size3 = (size - size2) / 2;
|
||||
if (size2.x != size.x && size2.y != size.y) {
|
||||
double scale = 1.;
|
||||
if (size.x * size2.y > size.y * size2.x) {
|
||||
size3 = {size.x * size2.y / size.y, size2.y};
|
||||
scale = double(size.y) / size2.y;
|
||||
} else {
|
||||
size3 = {size2.x, size.y * size2.x / size.x};
|
||||
scale = double(size.x) / size2.x;
|
||||
}
|
||||
dc.SetUserScale(scale, scale);
|
||||
size3 = (size3 - size2) / 2;
|
||||
}
|
||||
dc.DrawBitmap(m_frame, size3.x, size3.y);
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
||||
{
|
||||
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
||||
if (sizeFlags == wxSIZE_USE_EXISTING) return;
|
||||
wxMediaCtrl_OnSize(this, m_video_size, width, height);
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxString msg(msg2);
|
||||
#else
|
||||
wxString msg = wxString::FromUTF8(msg2);
|
||||
#endif
|
||||
if (level == 1) {
|
||||
if (msg.EndsWith("]")) {
|
||||
int n = msg.find_last_of('[');
|
||||
if (n != wxString::npos) {
|
||||
long val = 0;
|
||||
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
|
||||
if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) {
|
||||
std::unique_lock<std::mutex> lk(ctrl->m_mutex);
|
||||
ctrl->m_error = (int) val;
|
||||
}
|
||||
}
|
||||
} else if (msg.Contains("stat_log")) {
|
||||
wxCommandEvent evt(EVT_MEDIA_CTRL_STAT);
|
||||
wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx;
|
||||
evt.SetEventObject(ctrl);
|
||||
evt.SetString(msg.Mid(msg.Find(' ') + 1));
|
||||
wxPostEvent(ctrl, evt);
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::PlayThread()
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::shared_ptr<wxURI> url;
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
while (true) {
|
||||
m_cond.wait(lk, [this, &url] { return m_url != url; });
|
||||
url = m_url;
|
||||
if (url == nullptr)
|
||||
continue;
|
||||
if (!url->HasScheme())
|
||||
break;
|
||||
lk.unlock();
|
||||
Bambu_Tunnel tunnel = nullptr;
|
||||
int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8());
|
||||
if (error == 0) {
|
||||
Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this);
|
||||
error = Bambu_Open(tunnel);
|
||||
if (error == 0)
|
||||
error = Bambu_would_block;
|
||||
}
|
||||
lk.lock();
|
||||
while (error == int(Bambu_would_block)) {
|
||||
m_cond.wait_for(lk, 100ms);
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
lk.unlock();
|
||||
error = Bambu_StartStream(tunnel, true);
|
||||
lk.lock();
|
||||
}
|
||||
Bambu_StreamInfo info;
|
||||
if (error == 0)
|
||||
error = Bambu_GetStreamInfo(tunnel, 0, &info);
|
||||
AVVideoDecoder decoder;
|
||||
int minFrameDuration = 0;
|
||||
if (error == 0) {
|
||||
decoder.open(info);
|
||||
m_video_size = { info.format.video.width, info.format.video.height };
|
||||
adjust_frame_size(m_frame_size, m_video_size, GetSize());
|
||||
minFrameDuration = 800 / info.format.video.frame_rate; // 80%
|
||||
NotifyStopped();
|
||||
}
|
||||
Bambu_Sample sample;
|
||||
while (error == 0) {
|
||||
lk.unlock();
|
||||
error = Bambu_ReadSample(tunnel, &sample);
|
||||
lk.lock();
|
||||
while (error == int(Bambu_would_block)) {
|
||||
m_cond.wait_for(lk, 100ms);
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
lk.unlock();
|
||||
error = Bambu_ReadSample(tunnel, &sample);
|
||||
lk.lock();
|
||||
}
|
||||
if (error == 0) {
|
||||
auto frame_size = m_frame_size;
|
||||
lk.unlock();
|
||||
decoder.decode(sample);
|
||||
#ifdef _WIN32
|
||||
wxBitmap bm;
|
||||
decoder.toWxBitmap(bm, frame_size);
|
||||
#else
|
||||
wxImage bm;
|
||||
decoder.toWxImage(bm, frame_size);
|
||||
#endif
|
||||
lk.lock();
|
||||
if (m_url != url) {
|
||||
error = 1;
|
||||
break;
|
||||
}
|
||||
if (bm.IsOk()) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s
|
||||
auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL);
|
||||
// The frame is late, catch up a little
|
||||
auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration);
|
||||
auto next_PTS = std::max(next_PTS_expected, next_PTS_practical);
|
||||
if(now < next_PTS)
|
||||
std::this_thread::sleep_until(next_PTS);
|
||||
else
|
||||
next_PTS = now;
|
||||
//auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast<std::chrono::milliseconds>(next_PTS - next_PTS_expected).count());
|
||||
//OutputDebugString(text);
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = next_PTS_expected;
|
||||
m_last_PTS_practical = next_PTS;
|
||||
} else {
|
||||
// Resync
|
||||
m_last_PTS = sample.decode_time;
|
||||
m_last_PTS_expected = now;
|
||||
m_last_PTS_practical = now;
|
||||
}
|
||||
m_frame = bm;
|
||||
}
|
||||
CallAfter([this] { Refresh(); });
|
||||
}
|
||||
}
|
||||
if (tunnel) {
|
||||
lk.unlock();
|
||||
Bambu_Close(tunnel);
|
||||
Bambu_Destroy(tunnel);
|
||||
tunnel = nullptr;
|
||||
lk.lock();
|
||||
}
|
||||
if (m_url == url)
|
||||
m_error = error;
|
||||
m_frame_size = wxDefaultSize;
|
||||
m_video_size = wxDefaultSize;
|
||||
NotifyStopped();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void wxMediaCtrl3::NotifyStopped()
|
||||
{
|
||||
m_state = wxMEDIASTATE_STOPPED;
|
||||
wxMediaEvent event(wxEVT_MEDIA_STATECHANGED);
|
||||
event.SetId(GetId());
|
||||
event.SetEventObject(this);
|
||||
wxPostEvent(this, event);
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// wxMediaCtrl3.h
|
||||
// libslic3r_gui
|
||||
//
|
||||
// Created by cmguo on 2024/6/22.
|
||||
//
|
||||
|
||||
#ifndef wxMediaCtrl3_h
|
||||
#define wxMediaCtrl3_h
|
||||
|
||||
#include "wx/uri.h"
|
||||
#include "wx/mediactrl.h"
|
||||
|
||||
wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);
|
||||
|
||||
void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);
|
||||
|
||||
#define BAMBU_DYNAMIC
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#ifndef _WIN32
|
||||
#include <wx/image.h>
|
||||
#endif
|
||||
#include "Printer/BambuTunnel.h"
|
||||
|
||||
class AVVideoDecoder;
|
||||
|
||||
class wxMediaCtrl3 : public wxWindow, BambuLib
|
||||
{
|
||||
public:
|
||||
wxMediaCtrl3(wxWindow *parent);
|
||||
|
||||
~wxMediaCtrl3();
|
||||
|
||||
void Load(wxURI url);
|
||||
|
||||
void Play();
|
||||
|
||||
void Stop();
|
||||
|
||||
void SetIdleImage(wxString const & image);
|
||||
|
||||
wxMediaState GetState();
|
||||
|
||||
int GetLastError();
|
||||
|
||||
wxSize GetVideoSize();
|
||||
|
||||
protected:
|
||||
DECLARE_EVENT_TABLE()
|
||||
|
||||
void paintEvent(wxPaintEvent &evt);
|
||||
|
||||
wxSize DoGetBestSize() const override;
|
||||
|
||||
void DoSetSize(int x, int y, int width, int height, int sizeFlags) override;
|
||||
|
||||
static void bambu_log(void *ctx, int level, tchar const *msg);
|
||||
|
||||
void PlayThread();
|
||||
|
||||
void NotifyStopped();
|
||||
|
||||
private:
|
||||
wxString m_idle_image;
|
||||
wxMediaState m_state = wxMEDIASTATE_STOPPED;
|
||||
int m_error = 0;
|
||||
wxSize m_video_size = wxDefaultSize;
|
||||
wxSize m_frame_size = wxDefaultSize;
|
||||
#ifdef _WIN32
|
||||
wxBitmap m_frame;
|
||||
#else
|
||||
wxImage m_frame;
|
||||
#endif
|
||||
|
||||
std::shared_ptr<wxURI> m_url;
|
||||
std::uint64_t m_last_PTS{0};
|
||||
std::chrono::system_clock::time_point m_last_PTS_expected;
|
||||
std::chrono::system_clock::time_point m_last_PTS_practical;
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cond;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
#endif /* wxMediaCtrl3_h */
|
||||
@@ -1198,7 +1198,7 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
||||
version.config_version = cache_ver;
|
||||
version.comment = description;
|
||||
// Orca: update vendor.json
|
||||
updates.updates.emplace_back(std::move(file_path), std::move(path_in_vendor.string()), std::move(version), vendor_name, changelog, "", force_update, false);
|
||||
updates.updates.emplace_back(std::move(file_path), path_in_vendor.string(), std::move(version), vendor_name, changelog, "", force_update, false);
|
||||
//Orca: update vendor folder
|
||||
updates.updates.emplace_back(cache_profile_path / vendor_name, vendor_path / vendor_name, Version(), vendor_name, "", "", force_update, true);
|
||||
} else {
|
||||
|
||||
@@ -7,11 +7,127 @@
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <future>
|
||||
#include <slic3r/GUI/BindDialog.hpp>
|
||||
#include <slic3r/GUI/GUI_App.hpp>
|
||||
#include <slic3r/plugin/PluginFsUtils.hpp>
|
||||
#include <slic3r/plugin/PluginManager.hpp>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <wx/event.h>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// extensive list of audit events can be found at https://docs.python.org/3/library/audit_events.html
|
||||
static const std::unordered_map<std::string, AuditEventCategory> audit_event_categories{
|
||||
// fsread
|
||||
{"glob.glob", AuditEventCategory::FsRead},
|
||||
{"glob.glob/2", AuditEventCategory::FsRead},
|
||||
{"os.fwalk", AuditEventCategory::FsRead},
|
||||
{"os.getxattr", AuditEventCategory::FsRead},
|
||||
{"os.listdir", AuditEventCategory::FsRead},
|
||||
{"os.listdrives", AuditEventCategory::FsRead},
|
||||
{"os.listmounts", AuditEventCategory::FsRead},
|
||||
{"os.listvolumes", AuditEventCategory::FsRead},
|
||||
{"os.listxattr", AuditEventCategory::FsRead},
|
||||
{"os.scandir", AuditEventCategory::FsRead},
|
||||
{"os.walk", AuditEventCategory::FsRead},
|
||||
{"pathlib.Path.glob", AuditEventCategory::FsRead},
|
||||
{"pathlib.Path.rglob", AuditEventCategory::FsRead},
|
||||
|
||||
// fsreadwrite
|
||||
{"os.chflags", AuditEventCategory::FsReadWrite},
|
||||
{"os.chmod", AuditEventCategory::FsReadWrite},
|
||||
{"os.chown", AuditEventCategory::FsReadWrite},
|
||||
{"os.removexattr", AuditEventCategory::FsReadWrite},
|
||||
{"os.rename", AuditEventCategory::FsReadWrite},
|
||||
{"os.setxattr", AuditEventCategory::FsReadWrite},
|
||||
{"os.truncate", AuditEventCategory::FsReadWrite},
|
||||
{"os.utime", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.chown", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.copymode", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.copystat", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.copyfile", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.copytree", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.make_archive", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.move", AuditEventCategory::FsReadWrite},
|
||||
{"shutil.unpack_archive", AuditEventCategory::FsReadWrite},
|
||||
|
||||
// fscreate
|
||||
{"os.link", AuditEventCategory::FsCreate},
|
||||
{"os.mkdir", AuditEventCategory::FsCreate},
|
||||
{"os.symlink", AuditEventCategory::FsCreate},
|
||||
{"tempfile.mkdtemp", AuditEventCategory::FsCreate},
|
||||
{"tempfile.mkstemp", AuditEventCategory::FsCreate},
|
||||
{"_winapi.CreateJunction", AuditEventCategory::FsCreate},
|
||||
|
||||
// fsdelete
|
||||
{"os.remove", AuditEventCategory::FsDelete},
|
||||
{"os.rmdir", AuditEventCategory::FsDelete},
|
||||
{"shutil.rmtree", AuditEventCategory::FsDelete},
|
||||
|
||||
// http
|
||||
{"http.client.connect", AuditEventCategory::Http},
|
||||
{"http.client.send", AuditEventCategory::Http},
|
||||
{"urllib.Request", AuditEventCategory::Http},
|
||||
|
||||
// socket
|
||||
{"socket.__new__", AuditEventCategory::Socket},
|
||||
{"socket.bind", AuditEventCategory::Socket},
|
||||
{"socket.connect", AuditEventCategory::Socket},
|
||||
{"socket.getaddrinfo", AuditEventCategory::Socket},
|
||||
{"socket.gethostbyaddr", AuditEventCategory::Socket},
|
||||
{"socket.gethostbyname", AuditEventCategory::Socket},
|
||||
{"socket.gethostname", AuditEventCategory::Socket},
|
||||
{"socket.getnameinfo", AuditEventCategory::Socket},
|
||||
{"socket.getservbyname", AuditEventCategory::Socket},
|
||||
{"socket.getservbyport", AuditEventCategory::Socket},
|
||||
{"socket.sendmsg", AuditEventCategory::Socket},
|
||||
{"socket.sendto", AuditEventCategory::Socket},
|
||||
|
||||
// processcreate
|
||||
{"os.fork", AuditEventCategory::ProcessCreate},
|
||||
{"os.forkpty", AuditEventCategory::ProcessCreate},
|
||||
{"os.posix_spawn", AuditEventCategory::ProcessCreate},
|
||||
{"os.spawn", AuditEventCategory::ProcessCreate},
|
||||
{"os.system", AuditEventCategory::ProcessCreate},
|
||||
{"os.startfile", AuditEventCategory::ProcessCreate},
|
||||
{"os.startfile/2", AuditEventCategory::ProcessCreate},
|
||||
{"pty.spawn", AuditEventCategory::ProcessCreate},
|
||||
{"subprocess.Popen", AuditEventCategory::ProcessCreate},
|
||||
{"_winapi.CreateProcess", AuditEventCategory::ProcessCreate},
|
||||
{"_posixsubprocess.fork_exec", AuditEventCategory::ProcessCreate},
|
||||
};
|
||||
|
||||
// Returns the category event_name belongs to, or AuditEventCategory::None when it isn't audited.
|
||||
static AuditEventCategory event_category(const std::string& event_name)
|
||||
{
|
||||
const auto it = audit_event_categories.find(event_name);
|
||||
return it == audit_event_categories.end() ? AuditEventCategory::None : it->second;
|
||||
}
|
||||
|
||||
// True for the categories whose targets are filesystem paths, as opposed to a network
|
||||
// address or a process command line -- the deny-path and allowed-root checks only make sense
|
||||
// against a path.
|
||||
static bool is_fs_category(AuditEventCategory category)
|
||||
{
|
||||
switch (category) {
|
||||
case AuditEventCategory::FsRead:
|
||||
case AuditEventCategory::FsReadWrite:
|
||||
case AuditEventCategory::FsCreate:
|
||||
case AuditEventCategory::FsDelete:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Path safety
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -78,24 +194,21 @@ bool is_inside_allowed_root(const boost::filesystem::path& candidate, const boos
|
||||
// ScopedPluginAuditContext
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
thread_local std::string PluginAuditManager::m_current_plugin_key = "";
|
||||
thread_local std::string PluginAuditManager::m_current_capability_name = "";
|
||||
thread_local PluginAuditManager::AuditMode PluginAuditManager::m_audit_mode = PluginAuditManager::AuditMode::Loading;
|
||||
thread_local std::vector<boost::filesystem::path> PluginAuditManager::m_scoped_allowed_roots;
|
||||
thread_local std::string PluginAuditManager::m_current_plugin_key = "";
|
||||
thread_local std::string PluginAuditManager::m_current_capability_name = "";
|
||||
thread_local std::vector<AllowedRoot> PluginAuditManager::m_scoped_allowed_roots;
|
||||
thread_local bool PluginAuditManager::m_audit_denial_pending = false;
|
||||
thread_local bool PluginAuditManager::m_has_last_violation = false;
|
||||
thread_local AuditViolation PluginAuditManager::m_last_violation;
|
||||
|
||||
ScopedPluginAuditContext::ScopedPluginAuditContext(const std::string& plugin_key,
|
||||
const std::string& capability_name,
|
||||
PluginAuditManager::AuditMode mode)
|
||||
const std::string& capability_name)
|
||||
: m_previous_id(PluginAuditManager::instance().current_plugin())
|
||||
, m_previous_capability(PluginAuditManager::instance().current_capability())
|
||||
, m_previous_mode(PluginAuditManager::instance().audit_mode())
|
||||
, m_previous_scoped_roots(PluginAuditManager::m_scoped_allowed_roots)
|
||||
{
|
||||
PluginAuditManager::instance().set_current_plugin(plugin_key);
|
||||
PluginAuditManager::instance().set_current_capability(capability_name);
|
||||
PluginAuditManager::instance().set_audit_mode(mode);
|
||||
PluginAuditManager::m_scoped_allowed_roots.clear();
|
||||
}
|
||||
|
||||
@@ -103,7 +216,6 @@ ScopedPluginAuditContext::~ScopedPluginAuditContext()
|
||||
{
|
||||
PluginAuditManager::instance().set_current_plugin(m_previous_id);
|
||||
PluginAuditManager::instance().set_current_capability(m_previous_capability);
|
||||
PluginAuditManager::instance().set_audit_mode(m_previous_mode);
|
||||
PluginAuditManager::m_scoped_allowed_roots = std::move(m_previous_scoped_roots);
|
||||
}
|
||||
|
||||
@@ -129,23 +241,24 @@ std::string PluginAuditManager::current_capability() const { return m_current_ca
|
||||
|
||||
void PluginAuditManager::clear_current_capability() { m_current_capability_name.clear(); }
|
||||
|
||||
void PluginAuditManager::add_global_allowed_root(const boost::filesystem::path& root)
|
||||
void PluginAuditManager::add_global_allowed_root(const boost::filesystem::path& root, bool allow_write)
|
||||
{
|
||||
if (root.empty())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_global_allowed_roots.push_back(root);
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Global allowed root: " << root.string();
|
||||
m_global_allowed_roots.push_back({root, allow_write});
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Global allowed root: " << root.string() << " allow_write=" << allow_write;
|
||||
}
|
||||
|
||||
void PluginAuditManager::add_scoped_allowed_root(const boost::filesystem::path& root)
|
||||
void PluginAuditManager::add_scoped_allowed_root(const boost::filesystem::path& root, bool allow_write)
|
||||
{
|
||||
if (root.empty())
|
||||
return;
|
||||
|
||||
m_scoped_allowed_roots.push_back(root);
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Scoped allowed root for plugin " << current_plugin() << ": " << root.string();
|
||||
m_scoped_allowed_roots.push_back({root, allow_write});
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Scoped allowed root for plugin " << current_plugin() << ": " << root.string()
|
||||
<< " allow_write=" << allow_write;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -199,12 +312,65 @@ bool PluginAuditManager::is_denied_filename(const boost::filesystem::path& candi
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Audit mode
|
||||
// Denied path keywords
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void PluginAuditManager::set_audit_mode(AuditMode mode) { m_audit_mode = mode; }
|
||||
void PluginAuditManager::add_denied_path_keyword(const std::string& keyword)
|
||||
{
|
||||
if (keyword.empty())
|
||||
return;
|
||||
|
||||
PluginAuditManager::AuditMode PluginAuditManager::audit_mode() const { return m_audit_mode; }
|
||||
std::string lower = keyword;
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_denied_path_keywords.push_back(lower);
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Denied path keyword: " << lower;
|
||||
}
|
||||
|
||||
std::vector<std::string> PluginAuditManager::default_denied_path_keywords()
|
||||
{
|
||||
// Broad, categorical rules on top of the exact-name is_denied_filename registry: a plugin
|
||||
// must never be able to reach a secret, a certificate, or a configuration file just because
|
||||
// it happens to live inside an otherwise-allowed root (e.g. the bundled TLS client cert at
|
||||
// resources_dir()/cert/..., which would become reachable the moment resources_dir() is
|
||||
// granted as a read-only allowed root).
|
||||
return {"secret", "cert", "conf"};
|
||||
}
|
||||
|
||||
bool PluginAuditManager::is_denied_path_keyword(const boost::filesystem::path& candidate) const
|
||||
{
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
boost::system::error_code ec;
|
||||
fs::path canon = fs::weakly_canonical(candidate, ec);
|
||||
if (ec) {
|
||||
canon = fs::absolute(candidate, ec).lexically_normal();
|
||||
if (ec)
|
||||
canon = candidate;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_denied_path_keywords.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& component : canon) {
|
||||
std::string name = component.string();
|
||||
if (name.empty())
|
||||
continue;
|
||||
std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); });
|
||||
for (const auto& keyword : m_denied_path_keywords) {
|
||||
if (name.find(keyword) != std::string::npos)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PluginAuditManager::is_denied_path(const boost::filesystem::path& candidate) const
|
||||
{
|
||||
return is_denied_filename(candidate) || is_denied_path_keyword(candidate);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Policy checks
|
||||
@@ -219,20 +385,20 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
||||
if (plugin_key.empty())
|
||||
return {true, ""}; // not running inside a plugin context
|
||||
|
||||
// Denied filenames are checked first, above both the Loading exemption below and the
|
||||
// allowed roots. The app config and the cloud refresh token live directly inside
|
||||
// data_dir(), which is a global allowed root, and no scope ever sets Enforcing — so a
|
||||
// deny placed any lower would be unreachable for reads.
|
||||
// Denied filenames/keywords are checked before the allowed roots. The app config and the
|
||||
// cloud refresh token live directly inside data_dir(), which is a global allowed root, and
|
||||
// the bundled TLS client cert lives inside resources_dir(), a read-only global allowed
|
||||
// root, so a deny placed any lower would be unreachable.
|
||||
if (is_denied_filename(path)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << path.string() << " is_write=" << is_write
|
||||
<< " plugin=" << plugin_key << " reason=denied filename";
|
||||
return {false, "denied filename"};
|
||||
}
|
||||
|
||||
// During import/loading, only block writes. Python must be able to read
|
||||
// stdlib modules and the plugin file itself during import.
|
||||
if (m_audit_mode == AuditMode::Loading && !is_write)
|
||||
return {true, ""};
|
||||
if (is_denied_path_keyword(path)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << path.string() << " is_write=" << is_write
|
||||
<< " plugin=" << plugin_key << " reason=denied path keyword";
|
||||
return {false, "denied path keyword"};
|
||||
}
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
fs::path candidate = path;
|
||||
@@ -245,8 +411,11 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
||||
candidate = absolute_candidate;
|
||||
}
|
||||
|
||||
// A root that doesn't allow writes only matches a read-shaped request; a write/create/
|
||||
// delete-shaped one falls through to "outside allowed root" for that root even though the
|
||||
// path is physically inside it.
|
||||
for (const auto& root : m_scoped_allowed_roots) {
|
||||
if (is_inside_allowed_root(candidate, root)) {
|
||||
if ((!is_write || root.allow_write) && is_inside_allowed_root(candidate, root.path)) {
|
||||
return {true, ""};
|
||||
}
|
||||
}
|
||||
@@ -254,14 +423,13 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
for (const auto& root : m_global_allowed_roots) {
|
||||
if (is_inside_allowed_root(candidate, root)) {
|
||||
if ((!is_write || root.allow_write) && is_inside_allowed_root(candidate, root.path)) {
|
||||
return {true, ""};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] block path=" << candidate.string() << " is_write=" << is_write
|
||||
<< " audit_mode=" << (m_audit_mode == AuditMode::Loading ? "Loading" : "Enforcing")
|
||||
<< " plugin=" << plugin_key;
|
||||
return {false, "outside allowed root"};
|
||||
}
|
||||
@@ -269,19 +437,99 @@ AuditDecision PluginAuditManager::check_path_access(const boost::filesystem::pat
|
||||
AuditDecision PluginAuditManager::check_open(const std::string& path_str, const std::string& mode)
|
||||
{
|
||||
const bool is_write = mode.find('w') != std::string::npos || mode.find('a') != std::string::npos ||
|
||||
mode.find('+') != std::string::npos;
|
||||
mode.find('+') != std::string::npos || mode.find('x') != std::string::npos;
|
||||
return check_path_access(boost::filesystem::path(path_str), is_write);
|
||||
}
|
||||
|
||||
bool PluginAuditManager::request_filesystem_read_permissions(const std::string& plugin_key,
|
||||
const std::vector<std::string>& paths)
|
||||
{
|
||||
if (plugin_key.empty() || paths.empty())
|
||||
return true;
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!PluginManager::instance().try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
||||
return false;
|
||||
|
||||
PluginInstallState state;
|
||||
read_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
||||
|
||||
std::vector<std::string> missing;
|
||||
for (const std::string& path : paths) {
|
||||
if (std::find(state.permissions.fs_read.begin(), state.permissions.fs_read.end(), path) == state.permissions.fs_read.end())
|
||||
missing.push_back(path);
|
||||
}
|
||||
if (missing.empty())
|
||||
return true;
|
||||
|
||||
if (wxTheApp == nullptr || GUI::wxGetApp().is_closing())
|
||||
return false;
|
||||
|
||||
auto show_dialog = [&descriptor, &missing]() {
|
||||
wxString requested_paths;
|
||||
for (const std::string& path : missing)
|
||||
requested_paths += wxString::FromUTF8(path.c_str()) + "\n";
|
||||
|
||||
wxMessageDialog dialog(
|
||||
nullptr,
|
||||
wxString::Format("Plugin \"%s\" requests filesystem read access to:\n%s",
|
||||
wxString::FromUTF8(descriptor.name.c_str()), requested_paths),
|
||||
"Plugin permissions",
|
||||
wxYES_NO | wxICON_WARNING);
|
||||
return dialog.ShowModal() == wxID_YES;
|
||||
};
|
||||
|
||||
bool granted = false;
|
||||
if (wxIsMainThread()) {
|
||||
granted = show_dialog();
|
||||
} else {
|
||||
auto result = std::make_shared<std::promise<bool>>();
|
||||
auto future = result->get_future();
|
||||
GUI::wxGetApp().CallAfter([result, descriptor_name = descriptor.name, missing]() {
|
||||
wxString requested_paths;
|
||||
for (const std::string& path : missing)
|
||||
requested_paths += wxString::FromUTF8(path.c_str()) + "\n";
|
||||
|
||||
wxMessageDialog dialog(
|
||||
nullptr,
|
||||
wxString::Format("Plugin \"%s\" requests filesystem read access to:\n%s",
|
||||
wxString::FromUTF8(descriptor_name.c_str()), requested_paths),
|
||||
"Plugin permissions",
|
||||
wxYES_NO | wxICON_WARNING);
|
||||
result->set_value(dialog.ShowModal() == wxID_YES);
|
||||
});
|
||||
granted = future.get();
|
||||
}
|
||||
|
||||
if (!granted)
|
||||
return false;
|
||||
|
||||
if (state.plugin_name.empty()) {
|
||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
||||
state.plugin_name = descriptor.name;
|
||||
state.cloud_uuid = descriptor.cloud_uuid();
|
||||
state.enabled = true;
|
||||
}
|
||||
|
||||
state.permissions.fs_read.insert(state.permissions.fs_read.end(), missing.begin(), missing.end());
|
||||
return write_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
||||
}
|
||||
|
||||
void PluginAuditManager::report_violation(const AuditViolation& violation)
|
||||
{
|
||||
m_last_violation = violation;
|
||||
m_has_last_violation = true;
|
||||
m_audit_denial_pending = true;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "[AUDIT BLOCKED] plugin=" << violation.plugin_key << " event=" << violation.event_name
|
||||
<< " path=" << violation.path.string() << " reason=" << violation.reason;
|
||||
<< " reason=" << violation.reason;
|
||||
}
|
||||
|
||||
bool PluginAuditManager::audit_denial_pending() const { return m_audit_denial_pending; }
|
||||
|
||||
void PluginAuditManager::clear_audit_denial() { m_audit_denial_pending = false; }
|
||||
|
||||
void PluginAuditManager::clear_last_violation()
|
||||
{
|
||||
m_has_last_violation = false;
|
||||
@@ -297,31 +545,326 @@ bool PluginAuditManager::last_violation(AuditViolation& violation) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PluginAuditManager::has_approved_ancestor(const std::string& plugin_key,
|
||||
const std::vector<std::string>& call_site_ids) const
|
||||
{
|
||||
if (plugin_key.empty() || call_site_ids.empty())
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto it = m_approved_call_sites.find(plugin_key);
|
||||
if (it == m_approved_call_sites.end())
|
||||
return false;
|
||||
|
||||
for (const auto& id : call_site_ids)
|
||||
if (it->second.count(id))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PluginAuditManager::record_approved_call_sites(const std::string& plugin_key,
|
||||
const std::vector<std::string>& call_site_ids)
|
||||
{
|
||||
if (plugin_key.empty() || call_site_ids.empty())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
auto& approved = m_approved_call_sites[plugin_key];
|
||||
approved.insert(call_site_ids.begin(), call_site_ids.end());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// The C-level audit hook
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
namespace PluginAuditDetail {
|
||||
|
||||
PyObject* tuple_item(PyObject* args, Py_ssize_t index)
|
||||
{
|
||||
if (!args || !PyTuple_Check(args) || index < 0 || index >= PyTuple_GET_SIZE(args))
|
||||
return nullptr;
|
||||
return PyTuple_GET_ITEM(args, index);
|
||||
}
|
||||
|
||||
std::string python_path(PyObject* object)
|
||||
{
|
||||
if (!object)
|
||||
return {};
|
||||
|
||||
PyObject* path_object = PyOS_FSPath(object);
|
||||
if (!path_object) {
|
||||
PyErr_Clear();
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* path = PyUnicode_Check(path_object) ? PyUnicode_AsUTF8(path_object) : PyBytes_AsString(path_object);
|
||||
std::string result = path ? path : "";
|
||||
Py_DECREF(path_object);
|
||||
if (!path)
|
||||
PyErr_Clear();
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string python_unicode(PyObject* object)
|
||||
{
|
||||
if (!object || !PyUnicode_Check(object))
|
||||
return {};
|
||||
const char* text = PyUnicode_AsUTF8(object);
|
||||
if (!text) {
|
||||
PyErr_Clear();
|
||||
return {};
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
std::string python_str(PyObject* object)
|
||||
{
|
||||
if (!object)
|
||||
return {};
|
||||
|
||||
PyObject* str_object = PyObject_Str(object);
|
||||
if (!str_object) {
|
||||
PyErr_Clear();
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string result = python_unicode(str_object);
|
||||
Py_DECREF(str_object);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> call_site_identities(const std::string& plugin_root)
|
||||
{
|
||||
std::vector<std::string> ids;
|
||||
if (plugin_root.empty())
|
||||
return ids;
|
||||
|
||||
PyFrameObject* frame = PyEval_GetFrame(); // borrowed reference
|
||||
Py_XINCREF(frame); // normalize to an owned reference for the loop below
|
||||
|
||||
while (frame) {
|
||||
PyCodeObject* code = PyFrame_GetCode(frame); // new reference
|
||||
const std::string filename = python_unicode(reinterpret_cast<PyObject*>(code->co_filename));
|
||||
const bool is_plugin_frame = !filename.empty() && boost::algorithm::starts_with(filename, plugin_root);
|
||||
|
||||
std::string id;
|
||||
if (!is_plugin_frame && !filename.empty()) {
|
||||
const std::string funcname = python_unicode(reinterpret_cast<PyObject*>(code->co_name));
|
||||
id = filename + ":" + funcname + ":" + std::to_string(code->co_firstlineno);
|
||||
}
|
||||
Py_DECREF(code);
|
||||
|
||||
PyFrameObject* back = PyFrame_GetBack(frame); // new reference, or nullptr at the top of the stack
|
||||
Py_DECREF(frame);
|
||||
frame = back;
|
||||
|
||||
if (is_plugin_frame)
|
||||
break;
|
||||
if (!id.empty())
|
||||
ids.push_back(std::move(id));
|
||||
}
|
||||
|
||||
Py_XDECREF(frame); // only holds a reference here if the loop exited via break
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
static const std::unordered_set<std::string> two_path_fs_events{
|
||||
"os.rename", "os.link", "os.symlink", "shutil.copyfile",
|
||||
"shutil.copytree", "shutil.copymode", "shutil.copystat", "shutil.move",
|
||||
"shutil.unpack_archive", "_winapi.CreateJunction",
|
||||
};
|
||||
|
||||
static const std::unordered_map<std::string, std::vector<Py_ssize_t>> audit_target_arg_indices{
|
||||
{"http.client.connect", {1}},
|
||||
{"urllib.Request", {0}},
|
||||
|
||||
{"socket.connect", {1}},
|
||||
{"socket.bind", {1}},
|
||||
{"socket.getaddrinfo", {0}},
|
||||
{"socket.gethostbyname", {0}},
|
||||
{"socket.gethostbyaddr", {0}},
|
||||
{"socket.getnameinfo", {0}},
|
||||
{"socket.getservbyname", {0}},
|
||||
{"socket.getservbyport", {0}},
|
||||
|
||||
{"os.system", {0}},
|
||||
{"subprocess.Popen", {1, 0}},
|
||||
{"os.posix_spawn", {1, 0}},
|
||||
{"os.spawn", {2, 1}},
|
||||
{"os.startfile", {0}},
|
||||
{"pty.spawn", {0}},
|
||||
{"_winapi.CreateProcess", {1, 0}},
|
||||
{"_posixsubprocess.fork_exec", {0}},
|
||||
};
|
||||
|
||||
AuditEventCategory open_category(PyObject* args)
|
||||
{
|
||||
const std::string mode = python_unicode(tuple_item(args, 1));
|
||||
if (!mode.empty() && mode.find_first_of("wax+") == std::string::npos)
|
||||
return AuditEventCategory::FsRead;
|
||||
return AuditEventCategory::FsReadWrite;
|
||||
}
|
||||
|
||||
std::vector<std::string> audit_targets(const std::string& event_name, AuditEventCategory category, PyObject* args)
|
||||
{
|
||||
std::vector<std::string> targets;
|
||||
|
||||
switch (category) {
|
||||
case AuditEventCategory::FsRead:
|
||||
case AuditEventCategory::FsReadWrite:
|
||||
case AuditEventCategory::FsCreate:
|
||||
case AuditEventCategory::FsDelete: {
|
||||
const Py_ssize_t path_count = two_path_fs_events.count(event_name) ? 2 : 1;
|
||||
for (Py_ssize_t index = 0; index < path_count; ++index) {
|
||||
const std::string path = python_path(tuple_item(args, index));
|
||||
if (!path.empty())
|
||||
targets.push_back(path);
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const auto it = audit_target_arg_indices.find(event_name);
|
||||
if (it != audit_target_arg_indices.end()) {
|
||||
for (const Py_ssize_t index : it->second) {
|
||||
std::string value = python_str(tuple_item(args, index));
|
||||
if (!value.empty()) {
|
||||
targets.push_back(std::move(value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return targets;
|
||||
}
|
||||
|
||||
std::vector<std::string>* permission_list_for(AuditEventCategory category, PluginPermissions& permissions)
|
||||
{
|
||||
switch (category) {
|
||||
case AuditEventCategory::FsRead: return &permissions.fs_read;
|
||||
case AuditEventCategory::FsReadWrite: return &permissions.fs_readwrite;
|
||||
case AuditEventCategory::Http: return &permissions.network_http;
|
||||
case AuditEventCategory::Socket: return &permissions.network_socket;
|
||||
case AuditEventCategory::ProcessCreate: return &permissions.process;
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_permission(const std::vector<std::string>& granted, const std::string& target)
|
||||
{
|
||||
return std::find(granted.begin(), granted.end(), target) != granted.end();
|
||||
}
|
||||
|
||||
bool persist_permission(const std::string& plugin_key,
|
||||
PluginInstallState& state,
|
||||
std::vector<std::string>& permission_list,
|
||||
const std::string& target)
|
||||
{
|
||||
PluginDescriptor descriptor;
|
||||
if (!PluginManager::instance().try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
||||
return false;
|
||||
|
||||
// A sandbox plugin may not have a sidecar yet. Seed the small amount of install metadata
|
||||
// needed by the writer so clicking Yes still creates the JSON file.
|
||||
if (state.plugin_name.empty()) {
|
||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
||||
state.plugin_name = descriptor.name;
|
||||
state.cloud_uuid = descriptor.cloud_uuid();
|
||||
state.enabled = true;
|
||||
}
|
||||
|
||||
if (!has_permission(permission_list, target))
|
||||
permission_list.push_back(target);
|
||||
return write_install_state(boost::filesystem::path(descriptor.plugin_root), state);
|
||||
}
|
||||
|
||||
// Records a blocked event and raises PermissionError in the calling interpreter. Returns -1
|
||||
// so an event branch can `return report_denied(...)` directly.
|
||||
int report_denied(PluginAuditManager& mgr,
|
||||
const std::string& event_name,
|
||||
const boost::filesystem::path& path,
|
||||
const AuditDecision& decision)
|
||||
{
|
||||
AuditViolation violation;
|
||||
violation.plugin_key = mgr.current_plugin();
|
||||
violation.event_name = event_name;
|
||||
violation.path = path;
|
||||
violation.reason = decision.reason;
|
||||
mgr.report_violation(violation);
|
||||
|
||||
PyErr_SetString(PyExc_PermissionError, "Plugin attempted to access a blocked file path");
|
||||
PyErr_SetString(PyExc_PermissionError, "Plugin attempted an audited operation without permission");
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
wxString audit_message(AuditEventCategory category, const wxString& plugin_name, const wxString& event_name,
|
||||
const wxString& target_list)
|
||||
{
|
||||
if (target_list.IsEmpty())
|
||||
return wxString::Format(
|
||||
_L("Plugin \"%s\" is requesting permission for the Python audit event \"%s\".\n\n"
|
||||
"This operation does not expose a target the audit hook can display."),
|
||||
plugin_name, event_name);
|
||||
|
||||
switch (category) {
|
||||
case AuditEventCategory::FsRead:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to read the following file(s):\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::FsReadWrite:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to read/write the following file(s):\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::FsCreate:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to create the following file(s):\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::FsDelete:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to delete the following file(s):\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::Http:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to make an HTTP request to:\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::Socket:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to open a network connection to:\n%s"), plugin_name, target_list);
|
||||
case AuditEventCategory::ProcessCreate:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting to run the following command(s):\n%s"), plugin_name, target_list);
|
||||
default:
|
||||
return wxString::Format(_L("Plugin \"%s\" is requesting permission for the Python audit event \"%s\"."), plugin_name, event_name);
|
||||
}
|
||||
}
|
||||
|
||||
int decide_audited_event(PluginAuditManager& mgr,
|
||||
PluginInstallState& state,
|
||||
const std::string& plugin_key,
|
||||
const std::string& plugin_name,
|
||||
const std::string& event_name,
|
||||
AuditEventCategory category,
|
||||
const std::vector<std::string>& targets,
|
||||
std::vector<std::string>* permission_list,
|
||||
const std::vector<std::string>& call_site_ids)
|
||||
{
|
||||
std::vector<std::string> unresolved = targets;
|
||||
if (permission_list) {
|
||||
unresolved.clear();
|
||||
for (const auto& target : targets)
|
||||
if (!has_permission(*permission_list, target))
|
||||
unresolved.push_back(target);
|
||||
if (!targets.empty() && unresolved.empty())
|
||||
return 0;
|
||||
}
|
||||
|
||||
wxString target_list;
|
||||
for (const auto& target : unresolved)
|
||||
target_list += wxString::FromUTF8(target.c_str()) + "\n";
|
||||
|
||||
wxMessageDialog dialog(nullptr,
|
||||
audit_message(category, wxString::FromUTF8(plugin_name.c_str()),
|
||||
wxString::FromUTF8(event_name.c_str()), target_list),
|
||||
_L("Plugin permission request"), wxYES_NO | wxICON_WARNING);
|
||||
if (dialog.ShowModal() != wxID_YES)
|
||||
return report_denied(mgr, event_name, {false, "audit permission required"});
|
||||
|
||||
if (permission_list)
|
||||
for (const auto& target : unresolved)
|
||||
persist_permission(plugin_key, state, *permission_list, target);
|
||||
|
||||
mgr.record_approved_call_sites(plugin_key, call_site_ids);
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace PluginAuditDetail
|
||||
|
||||
int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user_data)
|
||||
{
|
||||
@@ -331,94 +874,95 @@ int PluginAuditManager::audit_hook(const char* event, PyObject* args, void* user
|
||||
|
||||
std::string event_name(event ? event : "");
|
||||
|
||||
if (event_name.empty())
|
||||
return 0;
|
||||
|
||||
// Verbose logging of every audit event (can be noisy)
|
||||
if (mgr->verbose_events) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "[AUDIT EVENT] " << event_name;
|
||||
}
|
||||
|
||||
// extensive list of audit events can be found at https://docs.python.org/3/library/audit_events.html
|
||||
|
||||
// --- open event ---
|
||||
if (event_name == "open") {
|
||||
const char* path_cstr = nullptr;
|
||||
const char* mode_cstr = nullptr;
|
||||
int flags = 0;
|
||||
|
||||
// open(path, mode, flags) — path may be str, bytes, or int fd
|
||||
if (!PyArg_ParseTuple(args, "s|si", &path_cstr, &mode_cstr, &flags)) {
|
||||
PyErr_Clear(); // couldn't parse; allow
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string path_str(path_cstr ? path_cstr : "");
|
||||
std::string mode_str(mode_cstr ? mode_cstr : "r");
|
||||
|
||||
AuditDecision decision = mgr->check_open(path_str, mode_str);
|
||||
if (!decision.allowed)
|
||||
return report_denied(*mgr, event_name, path_str, decision);
|
||||
if (mgr->current_plugin().empty()) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "[AUDIT] event=" << event_name << " bypassed (no plugin context)";
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --- os.rename event (raised by os.rename and os.replace) ---
|
||||
if (event_name == "os.rename") {
|
||||
const char* src_cstr = nullptr;
|
||||
const char* dst_cstr = nullptr;
|
||||
PyObject* src_dir_fd = nullptr;
|
||||
PyObject* dst_dir_fd = nullptr;
|
||||
|
||||
// os.rename(src, dst, src_dir_fd, dst_dir_fd) — paths may be str, bytes, or int fd.
|
||||
// The dir_fd arguments are unused, but must be accepted for the tuple to parse.
|
||||
if (!PyArg_ParseTuple(args, "ss|OO", &src_cstr, &dst_cstr, &src_dir_fd, &dst_dir_fd)) {
|
||||
PyErr_Clear(); // couldn't parse; allow
|
||||
return 0;
|
||||
}
|
||||
|
||||
// A rename writes at both ends, so either end being denied blocks the call.
|
||||
for (const char* path_cstr : {src_cstr, dst_cstr}) {
|
||||
std::string path_str(path_cstr ? path_cstr : "");
|
||||
AuditDecision decision = mgr->check_path_access(path_str, /* is_write */ true);
|
||||
if (!decision.allowed)
|
||||
return report_denied(*mgr, event_name, path_str, decision);
|
||||
}
|
||||
// the open function can take in different flags that determine if it is a read or readwrite.
|
||||
const AuditEventCategory event_type =
|
||||
event_name == "open" ? PluginAuditDetail::open_category(args) : event_category(event_name);
|
||||
if (event_type == AuditEventCategory::None)
|
||||
return 0;
|
||||
|
||||
const bool fs_category = is_fs_category(event_type);
|
||||
const std::vector<std::string> targets = PluginAuditDetail::audit_targets(event_name, event_type, args);
|
||||
|
||||
// A denied path (secrets, certificates, config files -- see is_denied_path) is an
|
||||
// unconditional block: checked before the ancestor-cascade check below, so a cascade
|
||||
// approval recorded for an unrelated action can never launder access to one, and before
|
||||
// the allowed-root shortcut further down, so an allowed root (e.g. the read-only resources
|
||||
// folder) cannot make a denied path underneath it reachable.
|
||||
if (fs_category) {
|
||||
for (const auto& target : targets) {
|
||||
if (mgr->is_denied_path(boost::filesystem::path(target)))
|
||||
return PluginAuditDetail::report_denied(*mgr, event_name, {false, "denied path"});
|
||||
}
|
||||
}
|
||||
|
||||
// --- os.remove event (raised by os.remove and os.unlink) ---
|
||||
if (event_name == "os.remove") {
|
||||
const char* path_cstr = nullptr;
|
||||
PyObject* dir_fd = nullptr;
|
||||
PluginDescriptor plugin_descriptor;
|
||||
PluginManager::instance().try_get_plugin_descriptor(mgr->current_plugin(), plugin_descriptor);
|
||||
|
||||
// os.remove(path, dir_fd) — path may be str, bytes, or int fd
|
||||
if (!PyArg_ParseTuple(args, "s|O", &path_cstr, &dir_fd)) {
|
||||
PyErr_Clear(); // couldn't parse; allow
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string path_str(path_cstr ? path_cstr : "");
|
||||
AuditDecision decision = mgr->check_path_access(path_str, /* is_write */ true);
|
||||
if (!decision.allowed)
|
||||
return report_denied(*mgr, event_name, path_str, decision);
|
||||
// Some plugins call other audited events, e.g. urlib.request will call socket.connect. So this is so that if urlib.request
|
||||
// was already approved, socket.connect won't trigger another permission dialog.
|
||||
const std::vector<std::string> call_site_ids =
|
||||
PluginAuditDetail::call_site_identities(plugin_descriptor.plugin_root);
|
||||
if (mgr->has_approved_ancestor(mgr->current_plugin(), call_site_ids))
|
||||
return 0;
|
||||
|
||||
// A filesystem target that resolves entirely inside a pre-determined allowed root -- the
|
||||
// plugin system's own data_dir() tree (which holds each plugin's storage folder and the
|
||||
// installed/system profile cache), the read-only bundled resources folder, or a per-call
|
||||
// scoped root such as the current G-code folder -- is part of the plugin system's normal
|
||||
// workflow and does not need a prompt.
|
||||
if (fs_category && !targets.empty()) {
|
||||
const bool is_write = event_type != AuditEventCategory::FsRead;
|
||||
const bool all_inside_allowed_root =
|
||||
std::all_of(targets.begin(), targets.end(), [&](const std::string& target) {
|
||||
return mgr->check_path_access(boost::filesystem::path(target), is_write).allowed;
|
||||
});
|
||||
if (all_inside_allowed_root)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Unknown event — allow by default
|
||||
return 0;
|
||||
PluginInstallState state;
|
||||
const bool have_install_state = PluginManager::instance().get_install_state(mgr->current_plugin(), state);
|
||||
if (!have_install_state) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " Failed to get install state for " << mgr->current_plugin();
|
||||
}
|
||||
|
||||
const std::string plugin_name = state.plugin_name.empty() ? mgr->current_plugin() : state.plugin_name;
|
||||
|
||||
std::vector<std::string>* permission_list = PluginAuditDetail::permission_list_for(event_type, state.permissions);
|
||||
|
||||
return PluginAuditDetail::decide_audited_event(*mgr, state, mgr->current_plugin(), plugin_name, event_name,
|
||||
event_type, targets, permission_list, call_site_ids);
|
||||
}
|
||||
|
||||
void PluginAuditManager::install_hook()
|
||||
{
|
||||
if (PySys_AddAuditHook(audit_hook, this) < 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << "[AUDIT] Failed to install CPython audit hook";
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] CPython audit hook installed successfully";
|
||||
|
||||
// data_dir() is the only globally-allowed root during enforced plugin execution.
|
||||
// The executable directory and resources directory are intentionally NOT allowed
|
||||
// here: plugins must not write outside data_dir() (G-code plugins additionally get
|
||||
// the temp G-code folder via a scoped root). Reads remain permissive in Loading mode.
|
||||
// data_dir() is the primary globally-allowed root during plugin execution: read+write. It
|
||||
// covers the plugin system's own workflow needs -- each plugin's storage folder
|
||||
// (data_dir()/orca_plugins) and the installed/system profile cache (data_dir()/system) --
|
||||
// without a separate, narrower grant for either (G-code plugins additionally get the temp
|
||||
// G-code folder via a scoped root, see SlicingPipelinePluginCapabilityTrampoline).
|
||||
add_global_allowed_root(data_dir());
|
||||
|
||||
// resources_dir() holds the app's bundled, shared assets (installed system profiles, the
|
||||
// bundled TLS client cert, web assets). Plugins may read from it -- e.g. inspecting bundled
|
||||
// profiles -- but must never write into the shared, potentially multi-user app install, so
|
||||
// it is granted read-only. The bundled cert itself stays unreachable regardless, via the
|
||||
// "cert" denied-path keyword seeded below.
|
||||
add_global_allowed_root(resources_dir(), /*allow_write=*/false);
|
||||
|
||||
// The user's app config and cloud credentials live directly inside data_dir(), so the
|
||||
// root just granted would otherwise expose them to any plugin. Deny them by name.
|
||||
//
|
||||
@@ -430,6 +974,19 @@ void PluginAuditManager::install_hook()
|
||||
// (see its comment for why all four config names are denied); the tests seed from it too.
|
||||
for (const auto& name : default_denied_filenames())
|
||||
add_denied_filename(name);
|
||||
|
||||
// Categorical denies on top of the exact-name list above: no path a plugin can reach may
|
||||
// contain a "secret", "cert"(ificate), or "conf"(ig) path component, regardless of which
|
||||
// allowed root it happens to sit inside. default_denied_path_keywords() is the single
|
||||
// source of this list; the tests seed from it too.
|
||||
for (const auto& keyword : default_denied_path_keywords())
|
||||
add_denied_path_keyword(keyword);
|
||||
|
||||
if (PySys_AddAuditHook(audit_hook, this) < 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << "[AUDIT] Failed to install CPython audit hook";
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "[AUDIT] CPython audit hook installed successfully";
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -2,8 +2,11 @@
|
||||
#define slic3r_PluginAuditManager_hpp_
|
||||
|
||||
#include <Python.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -18,10 +21,31 @@ struct AuditDecision {
|
||||
struct AuditViolation {
|
||||
std::string plugin_key;
|
||||
std::string event_name;
|
||||
boost::filesystem::path path;
|
||||
std::string reason;
|
||||
};
|
||||
|
||||
// A filesystem root a plugin may access while an audit context is active. allow_write is
|
||||
// false for a root that only grants reads (e.g. the bundled, shared resources folder) --
|
||||
// a write-shaped event never matches such a root, even though a read-shaped one does.
|
||||
struct AllowedRoot {
|
||||
boost::filesystem::path path;
|
||||
bool allow_write = true;
|
||||
};
|
||||
|
||||
// The set of CPython audit events PluginAuditManager recognizes, grouped by the kind of
|
||||
// operation they represent. None means the event isn't one audit_hook() acts on at all.
|
||||
enum class AuditEventCategory {
|
||||
None,
|
||||
FsRead,
|
||||
FsReadWrite,
|
||||
FsCreate,
|
||||
FsDelete,
|
||||
Http,
|
||||
Socket,
|
||||
ProcessCreate,
|
||||
Threading,
|
||||
};
|
||||
|
||||
// Returns true if candidate resolves to a path inside allowed_root.
|
||||
// Uses weakly_canonical and component-wise comparison to reject traversal attacks.
|
||||
bool is_inside_allowed_root(const boost::filesystem::path& candidate,
|
||||
@@ -49,12 +73,15 @@ public:
|
||||
void clear_current_capability();
|
||||
|
||||
// --- allowed-roots registry ---
|
||||
void add_global_allowed_root(const boost::filesystem::path& root);
|
||||
void add_scoped_allowed_root(const boost::filesystem::path& root);
|
||||
// allow_write = false registers a read-only root: a read-shaped event inside it is allowed,
|
||||
// but a write/create/delete-shaped event is not, so it falls through to the normal
|
||||
// prompt-or-deny path instead.
|
||||
void add_global_allowed_root(const boost::filesystem::path& root, bool allow_write = true);
|
||||
void add_scoped_allowed_root(const boost::filesystem::path& root, bool allow_write = true);
|
||||
|
||||
// --- denied-filenames registry ---
|
||||
// Filenames a plugin may never touch, in any directory, regardless of audit mode or
|
||||
// enclosing allowed root. A candidate is denied when its filename starts with a
|
||||
// Filenames a plugin may never touch, in any directory, regardless of the enclosing allowed
|
||||
// root. A candidate is denied when its filename starts with a
|
||||
// registered name, so .bak/.tmp companions are covered by the same entry.
|
||||
//
|
||||
// The comparison is case-insensitive on every platform, unlike the _WIN32-only iequals
|
||||
@@ -73,33 +100,60 @@ public:
|
||||
// 8.3 short name is out of scope (see the design doc). This blocks direct access only.
|
||||
bool is_denied_filename(const boost::filesystem::path& candidate) const;
|
||||
|
||||
// --- enforcement mode ---
|
||||
enum class AuditMode {
|
||||
// Import/loading phase: allow reads anywhere, only block writes
|
||||
// outside allowed roots. Python needs to read stdlib modules
|
||||
// during import and those are not inside plugin directories.
|
||||
Loading,
|
||||
// --- denied-path-keyword registry ---
|
||||
// Keywords that categorically deny a path if ANY of its components (directory or file
|
||||
// name), not just the base name, contains one case-insensitively -- e.g. a "secrets"
|
||||
// subfolder, a "certificates" folder, or a "conf"/"config" file anywhere the plugin can
|
||||
// otherwise reach, including inside an allowed root. This is intentionally broader and
|
||||
// fuzzier than the exact-name is_denied_filename registry: it exists to categorically rule
|
||||
// out whole classes of sensitive paths (secrets, certificates, config) rather than name
|
||||
// specific known files, at the cost of over-blocking an unrelated name that happens to
|
||||
// contain the keyword -- the fail-safe direction, same rationale as is_denied_filename.
|
||||
void add_denied_path_keyword(const std::string& keyword);
|
||||
|
||||
// Execution phase: block both reads and writes outside allowed
|
||||
// roots, plus subprocess/socket/ctypes.
|
||||
Enforcing,
|
||||
};
|
||||
// The list install_hook() seeds into the keyword registry. Exposed so tests seed the exact
|
||||
// same set without a live interpreter.
|
||||
static std::vector<std::string> default_denied_path_keywords();
|
||||
|
||||
void set_audit_mode(AuditMode mode);
|
||||
AuditMode audit_mode() const;
|
||||
// True when any component of candidate's (canonicalized) path contains a registered
|
||||
// keyword, case-insensitively.
|
||||
bool is_denied_path_keyword(const boost::filesystem::path& candidate) const;
|
||||
|
||||
// is_denied_filename(candidate) || is_denied_path_keyword(candidate). Convenience for
|
||||
// call sites that only need to know whether a path is categorically off-limits, not which
|
||||
// specific rule fired.
|
||||
bool is_denied_path(const boost::filesystem::path& candidate) const;
|
||||
|
||||
// --- policy checks ---
|
||||
// Shared core for every audited filesystem event. The deny list is consulted above the
|
||||
// Loading-mode read exemption and above the allowed roots, so a denied filename is
|
||||
// blocked even though every scope currently runs in Loading and the files in question
|
||||
// sit inside data_dir(), which is itself a global allowed root.
|
||||
// Shared core for every audited filesystem event. The deny checks are consulted above the
|
||||
// allowed roots, so a denied path is blocked even when it sits inside an allowed root (e.g.
|
||||
// data_dir(), which is a global allowed root).
|
||||
AuditDecision check_path_access(const boost::filesystem::path& candidate, bool is_write);
|
||||
AuditDecision check_open(const std::string& path, const std::string& mode);
|
||||
|
||||
// Ask the user to grant the requested filesystem-read paths. The request may originate on a
|
||||
// plugin load worker, so the implementation marshals the modal dialog to the wx main thread.
|
||||
// Returns true only when every missing path was granted and persisted; denial aborts the plugin
|
||||
// load without adding a permission.
|
||||
bool request_filesystem_read_permissions(const std::string& plugin_key,
|
||||
const std::vector<std::string>& paths);
|
||||
|
||||
void report_violation(const AuditViolation& violation);
|
||||
bool audit_denial_pending() const;
|
||||
void clear_audit_denial();
|
||||
void clear_last_violation();
|
||||
bool last_violation(AuditViolation& violation) const;
|
||||
|
||||
// --- call-site cascade cache ---
|
||||
// A single plugin action often fires several nested CPython audit events as it passes
|
||||
// through stdlib layers (urllib.request calling http.client calling socket, for example).
|
||||
// Once the user approves one event, every stdlib frame still on the stack for that call
|
||||
// is recorded here by (filename, function, first line) identity. A later event whose own
|
||||
// ancestor chain still contains one of those frames is the same logical action seen from
|
||||
// a deeper layer, so it is auto-approved instead of prompting again.
|
||||
bool has_approved_ancestor(const std::string& plugin_key, const std::vector<std::string>& call_site_ids) const;
|
||||
void record_approved_call_sites(const std::string& plugin_key, const std::vector<std::string>& call_site_ids);
|
||||
|
||||
bool verbose_events = true;
|
||||
|
||||
private:
|
||||
@@ -109,17 +163,19 @@ private:
|
||||
|
||||
static int audit_hook(const char* event, PyObject* args, void* user_data);
|
||||
|
||||
static thread_local std::string m_current_plugin_key;
|
||||
static thread_local std::string m_current_capability_name;
|
||||
static thread_local AuditMode m_audit_mode;
|
||||
static thread_local std::vector<boost::filesystem::path> m_scoped_allowed_roots;
|
||||
static thread_local bool m_has_last_violation;
|
||||
static thread_local AuditViolation m_last_violation;
|
||||
static thread_local std::string m_current_plugin_key;
|
||||
static thread_local std::string m_current_capability_name;
|
||||
static thread_local std::vector<AllowedRoot> m_scoped_allowed_roots;
|
||||
static thread_local bool m_audit_denial_pending;
|
||||
static thread_local bool m_has_last_violation;
|
||||
static thread_local AuditViolation m_last_violation;
|
||||
|
||||
// mutable: is_denied_filename() is a const query that must lock.
|
||||
// mutable: is_denied_filename() and has_approved_ancestor() are const queries that must lock.
|
||||
mutable std::mutex m_mutex;
|
||||
std::vector<boost::filesystem::path> m_global_allowed_roots;
|
||||
std::vector<AllowedRoot> m_global_allowed_roots;
|
||||
std::vector<std::string> m_denied_filenames;
|
||||
std::vector<std::string> m_denied_path_keywords;
|
||||
std::unordered_map<std::string, std::unordered_set<std::string>> m_approved_call_sites; // plugin_key -> call-site ids
|
||||
};
|
||||
|
||||
// RAII guard that sets the current plugin key and capability name, restoring the previous
|
||||
@@ -130,8 +186,7 @@ class ScopedPluginAuditContext
|
||||
public:
|
||||
explicit ScopedPluginAuditContext(
|
||||
const std::string& plugin_key,
|
||||
const std::string& capability_name = {},
|
||||
PluginAuditManager::AuditMode mode = PluginAuditManager::AuditMode::Loading);
|
||||
const std::string& capability_name = {});
|
||||
|
||||
~ScopedPluginAuditContext();
|
||||
|
||||
@@ -141,8 +196,7 @@ public:
|
||||
private:
|
||||
std::string m_previous_id;
|
||||
std::string m_previous_capability;
|
||||
PluginAuditManager::AuditMode m_previous_mode;
|
||||
std::vector<boost::filesystem::path> m_previous_scoped_roots;
|
||||
std::vector<AllowedRoot> m_previous_scoped_roots;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
@@ -784,6 +784,23 @@ bool read_install_state(const boost::filesystem::path& plugin_dir, PluginInstall
|
||||
parsed.plugin_name = state["plugin_name"].get<std::string>();
|
||||
if (state.contains("cloud_uuid") && state["cloud_uuid"].is_string())
|
||||
parsed.cloud_uuid = state["cloud_uuid"].get<std::string>();
|
||||
|
||||
if (state.contains("permissions") && state["permissions"].is_object()) {
|
||||
const auto& permissions = state["permissions"];
|
||||
auto read_string_list = [&permissions](const char* key, std::vector<std::string>& out) {
|
||||
if (!permissions.contains(key) || !permissions[key].is_array())
|
||||
return;
|
||||
for (const auto& entry : permissions[key])
|
||||
if (entry.is_string())
|
||||
out.push_back(entry.get<std::string>());
|
||||
};
|
||||
read_string_list("fs_read", parsed.permissions.fs_read);
|
||||
read_string_list("fs_readwrite", parsed.permissions.fs_readwrite);
|
||||
read_string_list("network_http", parsed.permissions.network_http);
|
||||
read_string_list("network_socket", parsed.permissions.network_socket);
|
||||
read_string_list("process", parsed.permissions.process);
|
||||
}
|
||||
|
||||
if (state.contains("enabled") && state["enabled"].is_boolean())
|
||||
parsed.enabled = state["enabled"].get<bool>();
|
||||
|
||||
@@ -819,6 +836,14 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
||||
if (!state.cloud_uuid.empty())
|
||||
json["cloud_uuid"] = state.cloud_uuid;
|
||||
|
||||
json["permissions"] = {
|
||||
{"fs_read", state.permissions.fs_read},
|
||||
{"fs_readwrite", state.permissions.fs_readwrite},
|
||||
{"network_http", state.permissions.network_http},
|
||||
{"network_socket", state.permissions.network_socket},
|
||||
{"process", state.permissions.process},
|
||||
};
|
||||
|
||||
nlohmann::json capabilities = nlohmann::json::array();
|
||||
for (const auto& [name, enabled] : state.capabilities)
|
||||
capabilities.push_back(nlohmann::json{{name, enabled}});
|
||||
@@ -836,6 +861,9 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
||||
const std::vector<std::pair<std::string, bool>>& capabilities)
|
||||
{
|
||||
PluginInstallState state;
|
||||
// Loading a plugin updates its lifecycle/capability state, but must retain permissions granted
|
||||
// during register_capabilities() or by a previous runtime audit prompt.
|
||||
read_install_state(plugin_dir, state);
|
||||
state.installed_from = entry.is_cloud_plugin() ? "cloud" : "local";
|
||||
// Prefer the descriptor's recorded installed_version (the version fetched from the cloud
|
||||
// at install time, preserved across sidecar re-writes) so a stale manifest/PEP723 header
|
||||
@@ -852,10 +880,16 @@ bool write_install_state(const boost::filesystem::path& plugin_dir, const Plugin
|
||||
bool write_install_state(const boost::filesystem::path& plugin_dir, const PluginDescriptor& entry)
|
||||
{
|
||||
// Install-time writer: the package is not loaded, so its capabilities are not known yet and the
|
||||
// sidecar is (re)initialized to "auto-load, nothing disabled". PluginManager writes the real
|
||||
// per-capability flags once the package is loaded, via the (dir, entry, enabled, capabilities)
|
||||
// overload.
|
||||
return write_install_state(plugin_dir, entry, true, {});
|
||||
// sidecar is (re)initialized to "auto-load, nothing disabled". This intentionally resets
|
||||
// permissions on a fresh install/reinstall. PluginManager writes the real per-capability flags
|
||||
// while preserving permissions once the package is loaded, via the overload above.
|
||||
PluginInstallState state;
|
||||
state.installed_from = entry.is_cloud_plugin() ? "cloud" : "local";
|
||||
state.installed_version = !entry.installed_version.empty() ? entry.installed_version : entry.version;
|
||||
state.plugin_name = entry.name;
|
||||
state.cloud_uuid = entry.cloud_uuid();
|
||||
state.enabled = true;
|
||||
return write_install_state(plugin_dir, state);
|
||||
}
|
||||
|
||||
bool read_python_plugin_metadata(const boost::filesystem::path& py_path, PluginDescriptor& descriptor, std::string& error)
|
||||
|
||||
@@ -82,11 +82,23 @@ inline nlohmann::json py_to_json(const pybind11::handle& o)
|
||||
return py::str(o).cast<std::string>(); // fallback: str()
|
||||
}
|
||||
|
||||
struct PluginPermissions
|
||||
{
|
||||
std::vector<std::string> fs_read;
|
||||
std::vector<std::string> fs_readwrite;
|
||||
std::vector<std::string> network_http;
|
||||
std::vector<std::string> network_socket;
|
||||
std::vector<std::string> process;
|
||||
};
|
||||
|
||||
struct PluginInstallState {
|
||||
std::string installed_from; // "local" | "cloud"
|
||||
std::string installed_version;
|
||||
std::string plugin_name;
|
||||
std::string cloud_uuid; // empty for local
|
||||
|
||||
PluginPermissions permissions;
|
||||
|
||||
bool enabled = true;
|
||||
std::vector<std::pair<std::string, bool>> capabilities; // name -> enabled, ordered
|
||||
};
|
||||
|
||||
@@ -280,7 +280,7 @@ bool load(const PluginDescriptor& descriptor,
|
||||
// (while the active plugin key is set), then instantiates each registered capability and caches
|
||||
// its get_name(). Returns one entry per capability.
|
||||
std::string bridge_error;
|
||||
auto capabilities_found = bridge.finalize_plugin_capture(descriptor.entry_path, bridge_error);
|
||||
auto capabilities_found = bridge.finalize_plugin_capture(descriptor.entry_path, descriptor.plugin_key, bridge_error);
|
||||
if (!bridge_error.empty()) {
|
||||
capabilities_found.clear();
|
||||
error = "Plugin registration failed: " + bridge_error;
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <slic3r/plugin/PluginConfig.hpp>
|
||||
#include <slic3r/plugin/PluginDescriptor.hpp>
|
||||
#include <slic3r/plugin/PluginLoader.hpp>
|
||||
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||
#include <slic3r/plugin/pluginTypes/script/ScriptPluginCapability.hpp>
|
||||
@@ -622,6 +623,20 @@ std::shared_ptr<PluginCapabilityInterface> PluginManager::get_plugin_capability(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool PluginManager::get_install_state(const std::string& plugin_key, PluginInstallState& install_state)
|
||||
{
|
||||
PluginDescriptor descriptor;
|
||||
if (!try_get_plugin_descriptor(plugin_key, descriptor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!read_install_state(boost::filesystem::path(descriptor.plugin_root), install_state)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ── Lifecycle ───────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
bool PluginManager::is_plugin_loaded(const std::string& plugin_key) const
|
||||
@@ -910,6 +925,8 @@ void PluginManager::load_plugin_impl(const std::string& plugin_key, bool skip_de
|
||||
if (!plugin_loader::load(descriptor, skip_deps, capabilities_to_enable, registry_precheck, plugin, error)) {
|
||||
if (error == LOAD_CANCELLED)
|
||||
return; // cancelled: nothing materialized survives, and no error is recorded
|
||||
if (error.rfind("Plugin registration failed:", 0) == 0)
|
||||
mark_plugin_install_state_disabled(plugin_key);
|
||||
fail(std::move(error));
|
||||
return;
|
||||
}
|
||||
@@ -1135,6 +1152,51 @@ void PluginManager::write_loaded_plugin_install_state(const std::string& plugin_
|
||||
write_install_state(boost::filesystem::path(descriptor.plugin_root), descriptor, /*enabled=*/true, capabilities);
|
||||
}
|
||||
|
||||
void PluginManager::mark_plugin_install_state_disabled(const std::string& plugin_key)
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(m_install_state_mutex);
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
||||
return;
|
||||
|
||||
const boost::filesystem::path root(descriptor.plugin_root);
|
||||
PluginInstallState state;
|
||||
if (!read_install_state(root, state)) {
|
||||
state.installed_from = descriptor.is_cloud_plugin() ? "cloud" : "local";
|
||||
state.installed_version = !descriptor.installed_version.empty() ? descriptor.installed_version : descriptor.version;
|
||||
state.plugin_name = descriptor.name;
|
||||
state.cloud_uuid = descriptor.cloud_uuid();
|
||||
}
|
||||
state.enabled = false;
|
||||
if (!write_install_state(root, state))
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (Plugin* plugin = find_plugin_locked(plugin_key))
|
||||
plugin->descriptor.enabled = false;
|
||||
}
|
||||
|
||||
void PluginManager::revoke_plugin_permissions(const std::string& plugin_key)
|
||||
{
|
||||
std::lock_guard<std::mutex> state_lock(m_install_state_mutex);
|
||||
|
||||
PluginDescriptor descriptor;
|
||||
if (!try_get_plugin_descriptor(plugin_key, descriptor) || descriptor.plugin_root.empty())
|
||||
return;
|
||||
|
||||
const boost::filesystem::path root(descriptor.plugin_root);
|
||||
PluginInstallState state;
|
||||
if (!read_install_state(root, state)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": Failed to read install state for " << plugin_key;
|
||||
return;
|
||||
}
|
||||
|
||||
state.permissions = {};
|
||||
if (!write_install_state(root, state))
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": Failed to revoke permissions for " << plugin_key;
|
||||
}
|
||||
|
||||
// ── Callbacks ───────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
void PluginManager::subscribe_on_load_callback(PluginLifecycleCompleteFn fn)
|
||||
@@ -1354,6 +1416,11 @@ bool PluginManager::install_plugin(const boost::filesystem::path& filepath, Plug
|
||||
return false;
|
||||
}
|
||||
|
||||
// Every successful install may have replaced executable plugin code. Revoke any permissions
|
||||
// associated with the previous package so the newly installed version must request them again.
|
||||
if (!plugin_descriptor.plugin_key.empty())
|
||||
revoke_plugin_permissions(plugin_descriptor.plugin_key);
|
||||
|
||||
if (!plugin_descriptor.plugin_key.empty())
|
||||
clear_plugin_error(plugin_descriptor.plugin_key);
|
||||
|
||||
@@ -1741,6 +1808,7 @@ bool PluginManager::update_cloud_plugin(const std::string& plugin_key, std::stri
|
||||
}
|
||||
|
||||
clear_plugin_error(plugin_key);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +159,8 @@ public:
|
||||
PluginCapabilityType type = PluginCapabilityType::Unknown,
|
||||
bool only_enabled = true) const;
|
||||
|
||||
bool get_install_state(const std::string& plugin_key, PluginInstallState& install_state);
|
||||
|
||||
void load_plugin(const std::string& plugin_key, bool skip_deps = false, std::vector<std::string> capabilities_to_enable = {});
|
||||
bool unload_plugin(const std::string& plugin_key);
|
||||
void unload_all_plugins();
|
||||
@@ -255,6 +257,9 @@ private:
|
||||
|
||||
// Writes the sidecar for a loaded plugin (enabled=true plus the current per-capability flags).
|
||||
void write_loaded_plugin_install_state(const std::string& plugin_key);
|
||||
void mark_plugin_install_state_disabled(const std::string& plugin_key);
|
||||
// Revoke permissions after a package replacement so the new package must request them again.
|
||||
void revoke_plugin_permissions(const std::string& plugin_key);
|
||||
|
||||
bool finalize_cloud_plugin_removal(const PluginDescriptor& plugin, bool keep_local, std::string& error);
|
||||
bool delete_installed_plugin_package(const PluginDescriptor& plugin, std::string& error);
|
||||
|
||||
@@ -26,25 +26,30 @@
|
||||
try { \
|
||||
override_call; \
|
||||
} catch (pybind11::error_already_set & err) { \
|
||||
const bool _orca_audit_denial = ::Slic3r::PluginAuditManager::instance().audit_denial_pending(); \
|
||||
if (_orca_audit_denial) \
|
||||
::Slic3r::PluginAuditManager::instance().clear_current_plugin(); \
|
||||
::Slic3r::log_python_exception_keep(err); \
|
||||
if (_orca_audit_denial) \
|
||||
::Slic3r::PluginAuditManager::instance().clear_audit_denial(); \
|
||||
throw; \
|
||||
}
|
||||
|
||||
// Opens the plugin's filesystem audit scope for the duration of a C++ -> Python call, and publishes
|
||||
// the calling capability's cached name so host APIs invoked from Python can tell which capability
|
||||
// they are serving. No-op without an audit plugin key. Declares a local `_orca_audit_scope`.
|
||||
#define ORCA_PY_AUDIT_SCOPE(mode) \
|
||||
#define ORCA_PY_AUDIT_SCOPE() \
|
||||
std::optional<::Slic3r::ScopedPluginAuditContext> _orca_audit_scope; \
|
||||
if (const std::string& _orca_audit_key = this->audit_plugin_key(); !_orca_audit_key.empty()) \
|
||||
_orca_audit_scope.emplace(_orca_audit_key, this->name(), mode)
|
||||
_orca_audit_scope.emplace(_orca_audit_key, this->name())
|
||||
|
||||
#define ORCA_PY_OVERRIDE_AUDITED(mode, audit_setup, override_macro, ret, base, name, ...) \
|
||||
#define ORCA_PY_OVERRIDE_AUDITED(audit_setup, override_macro, ret, base, name, ...) \
|
||||
do { \
|
||||
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this); \
|
||||
::Slic3r::PythonGILState _orca_python_gil; \
|
||||
if (!_orca_python_gil) \
|
||||
throw std::runtime_error("Python interpreter is shutting down"); \
|
||||
ORCA_PY_AUDIT_SCOPE(mode); \
|
||||
ORCA_PY_AUDIT_SCOPE(); \
|
||||
if (_orca_audit_scope) \
|
||||
audit_setup(); \
|
||||
ORCA_PY_LOGGED_OVERRIDE_BODY(override_macro(ret, base, name, ##__VA_ARGS__)); \
|
||||
@@ -58,7 +63,7 @@ public:
|
||||
|
||||
std::string get_name() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, std::string, Base, get_name);
|
||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE_PURE, std::string, Base, get_name);
|
||||
}
|
||||
|
||||
// Config UI hooks. Available on every capability type, so they live here rather than in
|
||||
@@ -66,7 +71,6 @@ public:
|
||||
bool has_config_ui() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE,
|
||||
bool,
|
||||
@@ -77,7 +81,6 @@ public:
|
||||
std::string get_config_ui() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE,
|
||||
std::string,
|
||||
@@ -94,7 +97,7 @@ public:
|
||||
// mistake), both fall back to the base's empty object rather than writing `"cap_config": null`.
|
||||
nlohmann::json get_default_config() const override
|
||||
{
|
||||
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||
ORCA_PY_AUDIT_SCOPE();
|
||||
try {
|
||||
pybind11::gil_scoped_acquire gil;
|
||||
pybind11::function override = pybind11::get_override(static_cast<const Base*>(this), "get_default_config");
|
||||
@@ -117,17 +120,17 @@ public:
|
||||
|
||||
void on_load() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_load);
|
||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_load);
|
||||
}
|
||||
|
||||
void on_unload() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_unload);
|
||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_unload);
|
||||
}
|
||||
|
||||
void on_cancelled() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
|
||||
ORCA_PY_OVERRIDE_AUDITED([] {}, PYBIND11_OVERRIDE, void, Base, on_cancelled);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -139,7 +142,7 @@ public:
|
||||
PluginCapabilityType get_type() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE, PluginCapabilityType, PluginCapabilityInterface,
|
||||
[] {}, PYBIND11_OVERRIDE, PluginCapabilityType, PluginCapabilityInterface,
|
||||
get_type);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,6 +87,12 @@ void log_python_exception_keep(pybind11::error_already_set& err)
|
||||
if (!gil)
|
||||
return;
|
||||
|
||||
// Traceback output is host-owned work. In particular, the stderr tee opens the Python log
|
||||
// file on every write. Keep that open outside the plugin audit context, otherwise an ordinary
|
||||
// exception raised by a plugin can recursively trigger the filesystem permission dialog while
|
||||
// its original exception is being reported.
|
||||
ScopedPluginAuditContext audit_suppression("");
|
||||
|
||||
// Non-destructive: print the traceback to sys.stderr (tee'd to the session log)
|
||||
// WITHOUT consuming err, so the caller can rethrow it intact. For example, downstream C++
|
||||
// catchers can still read err.what() for the user-facing dialog. We must NOT use
|
||||
@@ -622,10 +628,6 @@ bool PythonInterpreter::initialize()
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(info) << "Bundled uv executable not found";
|
||||
|
||||
// Install the CPython audit hook for plugin policy enforcement.
|
||||
// This is defense-in-depth: today it only inspects the `open` audit event
|
||||
// and blocks writes outside the allowed roots; subprocess/socket/ctypes and
|
||||
// other events are not yet handled. It is NOT a full security sandbox.
|
||||
PluginAuditManager::instance().install_hook();
|
||||
|
||||
// Persist Python stderr (plugin tracebacks, including uncaught
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "PythonPluginBridge.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <exception>
|
||||
#include <memory>
|
||||
@@ -40,6 +41,7 @@ thread_local std::string g_active_plugin_key;
|
||||
std::mutex g_registry_mutex;
|
||||
std::unordered_map<std::string, std::vector<py::object>> g_pending_capabilities;
|
||||
std::unordered_map<std::string, py::object> g_pending_package;
|
||||
std::unordered_map<std::string, std::vector<std::string>> g_pending_fs_read_permissions;
|
||||
struct PluginInstanceHandle
|
||||
{
|
||||
// The C++ plugin interface points into a Python object. Keep both alive through one
|
||||
@@ -77,6 +79,8 @@ void discard_pending_capture_without_python(const std::string& plugin_key)
|
||||
(void) package->second.release();
|
||||
g_pending_package.erase(package);
|
||||
}
|
||||
|
||||
g_pending_fs_read_permissions.erase(plugin_key);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -101,34 +105,38 @@ void PythonPluginBridge::begin_plugin_capture(const std::string& plugin_key)
|
||||
// for this same entry path.
|
||||
g_pending_capabilities.erase(plugin_key);
|
||||
g_pending_package.erase(plugin_key);
|
||||
g_pending_fs_read_permissions.erase(plugin_key);
|
||||
}
|
||||
// From now until finalize/cancel, @orca.plugin and register_capability() calls made by
|
||||
// Python code on this thread are attributed to this plugin.
|
||||
g_active_plugin_key = plugin_key;
|
||||
}
|
||||
|
||||
std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(const std::string& plugin_key, std::string& error)
|
||||
std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(const std::string& capture_key,
|
||||
const std::string& plugin_key,
|
||||
std::string& error)
|
||||
{
|
||||
PythonGILState gil;
|
||||
if (!gil) {
|
||||
error = "Python interpreter is shutting down";
|
||||
return {};
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Finalizing Python plugin capture for key " << plugin_key;
|
||||
BOOST_LOG_TRIVIAL(info) << "Finalizing Python plugin capture for key " << capture_key;
|
||||
|
||||
// Phase 1: run the package class's register_capabilities() while the active key is
|
||||
// still set. That method is expected to call orca.register_capability() once per
|
||||
// capability class, and register_capability() needs g_active_plugin_key to know which
|
||||
// pending bucket to append to.
|
||||
{
|
||||
auto clear_active_key = [&plugin_key]() {
|
||||
if (g_active_plugin_key == plugin_key)
|
||||
auto clear_active_key = [&capture_key]() {
|
||||
if (g_active_plugin_key == capture_key)
|
||||
g_active_plugin_key.clear();
|
||||
};
|
||||
auto discard_pending_for_key = [&plugin_key]() {
|
||||
auto discard_pending_for_key = [&capture_key]() {
|
||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||
g_pending_capabilities.erase(plugin_key);
|
||||
g_pending_package.erase(plugin_key);
|
||||
g_pending_capabilities.erase(capture_key);
|
||||
g_pending_package.erase(capture_key);
|
||||
g_pending_fs_read_permissions.erase(capture_key);
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -138,7 +146,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
py::object package_cls;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||
auto it = g_pending_package.find(plugin_key);
|
||||
auto it = g_pending_package.find(capture_key);
|
||||
if (it != g_pending_package.end()) {
|
||||
package_cls = it->second;
|
||||
g_pending_package.erase(it);
|
||||
@@ -157,6 +165,33 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
// are kept.
|
||||
py::object package = package_cls();
|
||||
package.attr("register_capabilities")();
|
||||
|
||||
// Permission declarations are collected while register_capabilities() runs so the
|
||||
// plugin can describe its needs without touching wx from the Python load worker. Ask
|
||||
// only after registration returns, before capability instances are materialized.
|
||||
std::vector<std::string> fs_read_permissions;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||
auto it = g_pending_fs_read_permissions.find(capture_key);
|
||||
if (it != g_pending_fs_read_permissions.end()) {
|
||||
fs_read_permissions = std::move(it->second);
|
||||
g_pending_fs_read_permissions.erase(it);
|
||||
}
|
||||
}
|
||||
if (!fs_read_permissions.empty()) {
|
||||
bool granted = false;
|
||||
{
|
||||
py::gil_scoped_release release;
|
||||
granted = PluginAuditManager::instance().request_filesystem_read_permissions(plugin_key, fs_read_permissions);
|
||||
}
|
||||
if (!granted) {
|
||||
error = "Plugin filesystem read permission request denied";
|
||||
BOOST_LOG_TRIVIAL(warning) << error << " for key " << plugin_key;
|
||||
discard_pending_for_key();
|
||||
clear_active_key();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
} catch (py::error_already_set& err) {
|
||||
log_python_exception_keep(err);
|
||||
error = err.what();
|
||||
@@ -180,7 +215,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
std::vector<py::object> classes;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||
auto it = g_pending_capabilities.find(plugin_key);
|
||||
auto it = g_pending_capabilities.find(capture_key);
|
||||
if (it != g_pending_capabilities.end()) {
|
||||
classes = std::move(it->second);
|
||||
g_pending_capabilities.erase(it);
|
||||
@@ -189,10 +224,10 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
|
||||
// Registration is complete. Later register_capability() calls should fail instead of
|
||||
// accidentally attaching themselves to this plugin.
|
||||
if (g_active_plugin_key == plugin_key)
|
||||
if (g_active_plugin_key == capture_key)
|
||||
g_active_plugin_key.clear();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Collected " << classes.size() << " registered capability class(es) for key " << plugin_key;
|
||||
BOOST_LOG_TRIVIAL(info) << "Collected " << classes.size() << " registered capability class(es) for key " << capture_key;
|
||||
|
||||
std::vector<CapturedCapability> capabilities;
|
||||
capabilities.reserve(classes.size());
|
||||
@@ -204,7 +239,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
py::object instance = cls();
|
||||
if (!py::isinstance<PluginCapabilityInterface>(instance)) {
|
||||
error = "Registered capability must inherit from a PluginCapability base";
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed type check for key " << plugin_key
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed type check for key " << capture_key
|
||||
<< " error=" << error;
|
||||
return {};
|
||||
}
|
||||
@@ -212,7 +247,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
auto capability_iface = instance.cast<std::shared_ptr<PluginCapabilityInterface>>();
|
||||
if (!capability_iface) {
|
||||
error = "Failed to cast Python capability to PluginCapabilityInterface";
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed cast for key " << plugin_key
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture failed cast for key " << capture_key
|
||||
<< " error=" << error;
|
||||
return {};
|
||||
}
|
||||
@@ -226,7 +261,7 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
// here is a hard error that rejects the whole plugin capture.
|
||||
if (name.find(';') != std::string::npos) {
|
||||
error = "Capability name must not contain ';': " + name;
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture rejected capability for key " << plugin_key
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture rejected capability for key " << capture_key
|
||||
<< " error=" << error;
|
||||
return {};
|
||||
}
|
||||
@@ -246,12 +281,12 @@ std::vector<CapturedCapability> PythonPluginBridge::finalize_plugin_capture(cons
|
||||
// log the traceback here. GIL is held for the duration of finalize_plugin_capture.
|
||||
log_python_exception_keep(err);
|
||||
error = err.what();
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised Python exception for key " << plugin_key
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised Python exception for key " << capture_key
|
||||
<< " error=" << error;
|
||||
return {};
|
||||
} catch (const std::exception& ex) {
|
||||
error = ex.what();
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised exception for key " << plugin_key
|
||||
BOOST_LOG_TRIVIAL(error) << "Python plugin capture raised exception for key " << capture_key
|
||||
<< " error=" << error;
|
||||
return {};
|
||||
}
|
||||
@@ -279,6 +314,7 @@ void PythonPluginBridge::cancel_plugin_capture(const std::string& plugin_key)
|
||||
// may already have registered under this key.
|
||||
g_pending_capabilities.erase(plugin_key);
|
||||
g_pending_package.erase(plugin_key);
|
||||
g_pending_fs_read_permissions.erase(plugin_key);
|
||||
}
|
||||
|
||||
if (g_active_plugin_key == plugin_key)
|
||||
@@ -305,6 +341,7 @@ void PythonPluginBridge::clear_pending_captures()
|
||||
(void) pkg.release();
|
||||
}
|
||||
g_pending_package.clear();
|
||||
g_pending_fs_read_permissions.clear();
|
||||
g_active_plugin_key.clear();
|
||||
return;
|
||||
}
|
||||
@@ -313,6 +350,7 @@ void PythonPluginBridge::clear_pending_captures()
|
||||
BOOST_LOG_TRIVIAL(info) << "Clearing " << g_pending_capabilities.size() << " pending Python plugin capture(s)";
|
||||
g_pending_capabilities.clear();
|
||||
g_pending_package.clear();
|
||||
g_pending_fs_read_permissions.clear();
|
||||
g_active_plugin_key.clear();
|
||||
}
|
||||
|
||||
@@ -450,6 +488,27 @@ void bind_python_api(pybind11::module_& m)
|
||||
},
|
||||
R"pbdoc(Register a PluginCapability subclass while OrcaSlicer loads your module.)pbdoc");
|
||||
|
||||
m.def(
|
||||
"request_permissions",
|
||||
[](const std::vector<std::string>& fs_read) {
|
||||
if (g_active_plugin_key.empty())
|
||||
throw py::value_error("request_permissions() called outside plugin discovery context");
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_registry_mutex);
|
||||
auto& requested = g_pending_fs_read_permissions[g_active_plugin_key];
|
||||
for (const std::string& path : fs_read) {
|
||||
if (path.empty())
|
||||
throw py::value_error("request_permissions(fs_read=...) does not accept empty paths");
|
||||
if (std::find(requested.begin(), requested.end(), path) == requested.end())
|
||||
requested.push_back(path);
|
||||
}
|
||||
},
|
||||
py::arg("fs_read") = std::vector<std::string>{},
|
||||
R"pbdoc(Request filesystem read permissions while OrcaSlicer loads your module.
|
||||
|
||||
The host presents the request to the user after register_capabilities() returns. Denying the
|
||||
request aborts the plugin load.)pbdoc");
|
||||
|
||||
m.def("plugin", [](py::object cls) {
|
||||
if (g_active_plugin_key.empty())
|
||||
throw py::value_error("@orca.plugin used outside plugin discovery context");
|
||||
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
// Returns one CapturedCapability per capability, or an empty vector on failure
|
||||
// (error message populated).
|
||||
std::vector<CapturedCapability> finalize_plugin_capture(
|
||||
const std::string& plugin_key, std::string& error);
|
||||
const std::string& capture_key, const std::string& plugin_key, std::string& error);
|
||||
|
||||
// Clear any pending registrations for the key. Safe to call when import fails.
|
||||
void cancel_plugin_capture(const std::string& plugin_key);
|
||||
|
||||
@@ -16,7 +16,6 @@ public:
|
||||
std::string get_icon() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE,
|
||||
std::string,
|
||||
@@ -27,7 +26,6 @@ public:
|
||||
std::string get_ui() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||
[] {},
|
||||
PYBIND11_OVERRIDE_PURE,
|
||||
std::string,
|
||||
@@ -42,7 +40,7 @@ public:
|
||||
if (!gil)
|
||||
throw std::runtime_error("Python interpreter is shutting down");
|
||||
|
||||
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||
ORCA_PY_AUDIT_SCOPE();
|
||||
|
||||
pybind11::function override = pybind11::get_override(static_cast<PagesPluginCapability*>(this), "on_message");
|
||||
if (!override)
|
||||
|
||||
+32
-32
@@ -16,206 +16,206 @@ public:
|
||||
AgentInfo get_agent_info() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, AgentInfo, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, AgentInfo, PrinterAgentPluginCapability,
|
||||
get_agent_info);
|
||||
}
|
||||
|
||||
int connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, connect_printer, dev_id,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, connect_printer, dev_id,
|
||||
dev_ip, username, password, use_ssl);
|
||||
}
|
||||
|
||||
int disconnect_printer() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, disconnect_printer);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, disconnect_printer);
|
||||
}
|
||||
|
||||
int send_message(std::string dev_id, std::string json_str, int qos, int flag) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message, dev_id,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message, dev_id,
|
||||
json_str, qos, flag);
|
||||
}
|
||||
|
||||
int send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message_to_printer,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, send_message_to_printer,
|
||||
dev_id, json_str, qos, flag);
|
||||
}
|
||||
|
||||
bool start_discovery(bool start, bool sending) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, start_discovery, start,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, start_discovery, start,
|
||||
sending);
|
||||
}
|
||||
|
||||
int bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind_detect, dev_ip,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind_detect, dev_ip,
|
||||
sec_link, detect);
|
||||
}
|
||||
|
||||
std::string get_user_selected_machine() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, std::string, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, std::string, PrinterAgentPluginCapability,
|
||||
get_user_selected_machine);
|
||||
}
|
||||
|
||||
int set_user_selected_machine(std::string dev_id) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
set_user_selected_machine, dev_id);
|
||||
}
|
||||
|
||||
int start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
start_send_gcode_to_sdcard, params, update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
|
||||
int start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_local_print,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_local_print,
|
||||
params, update_fn, cancel_fn);
|
||||
}
|
||||
|
||||
FilamentSyncMode get_filament_sync_mode() const override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, FilamentSyncMode, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, FilamentSyncMode, PrinterAgentPluginCapability,
|
||||
get_filament_sync_mode);
|
||||
}
|
||||
|
||||
bool fetch_filament_info(std::string dev_id, FilamentSyncMode sync_mode = FilamentSyncMode::pull) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, fetch_filament_info, dev_id);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, bool, PrinterAgentPluginCapability, fetch_filament_info, dev_id);
|
||||
}
|
||||
|
||||
int check_cert() override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, check_cert);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, check_cert);
|
||||
}
|
||||
|
||||
void install_device_cert(std::string dev_id, bool lan_only) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, void, PrinterAgentPluginCapability, install_device_cert, dev_id,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, void, PrinterAgentPluginCapability, install_device_cert, dev_id,
|
||||
lan_only);
|
||||
}
|
||||
|
||||
int ping_bind(std::string ping_code) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, ping_bind, ping_code);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, ping_bind, ping_code);
|
||||
}
|
||||
|
||||
int bind(std::string dev_ip, std::string dev_id, std::string dev_model, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind, dev_ip, dev_id,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, bind, dev_ip, dev_id,
|
||||
dev_model, sec_link, timezone, improved, update_fn);
|
||||
}
|
||||
|
||||
int unbind(std::string dev_id) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, unbind, dev_id);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, unbind, dev_id);
|
||||
}
|
||||
|
||||
int start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_print, params,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_print, params,
|
||||
update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
|
||||
int start_local_print_with_record(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability,
|
||||
start_local_print_with_record, params, update_fn, cancel_fn, wait_fn);
|
||||
}
|
||||
|
||||
int start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_sdcard_print, params,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, start_sdcard_print, params,
|
||||
update_fn, cancel_fn);
|
||||
}
|
||||
|
||||
int get_hms_snapshot(std::string dev_id, std::string file_name, std::function<void(std::string, int)> callback) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, get_hms_snapshot, dev_id,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, get_hms_snapshot, dev_id,
|
||||
file_name, callback);
|
||||
}
|
||||
|
||||
int set_server_callback(OnServerErrFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_server_callback, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_server_callback, fn);
|
||||
}
|
||||
|
||||
int set_on_ssdp_msg_fn(OnMsgArrivedFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_ssdp_msg_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_ssdp_msg_fn, fn);
|
||||
}
|
||||
|
||||
int set_on_printer_connected_fn(OnPrinterConnectedFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_printer_connected_fn,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_printer_connected_fn,
|
||||
fn);
|
||||
}
|
||||
|
||||
int set_on_subscribe_failure_fn(GetSubscribeFailureFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_subscribe_failure_fn,
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_subscribe_failure_fn,
|
||||
fn);
|
||||
}
|
||||
|
||||
int set_on_message_fn(OnMessageFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_message_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_message_fn, fn);
|
||||
}
|
||||
|
||||
int set_on_user_message_fn(OnMessageFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_user_message_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_user_message_fn, fn);
|
||||
}
|
||||
|
||||
int set_on_local_connect_fn(OnLocalConnectedFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_connect_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_connect_fn, fn);
|
||||
}
|
||||
|
||||
int set_on_local_message_fn(OnMessageFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_message_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_on_local_message_fn, fn);
|
||||
}
|
||||
|
||||
int set_queue_on_main_fn(QueueOnMainFn fn) override
|
||||
{
|
||||
ORCA_PY_OVERRIDE_AUDITED(
|
||||
::Slic3r::PluginAuditManager::AuditMode::Loading, [] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_queue_on_main_fn, fn);
|
||||
[] {}, PYBIND11_OVERRIDE_PURE, int, PrinterAgentPluginCapability, set_queue_on_main_fn, fn);
|
||||
}
|
||||
|
||||
// request_bind_ticket returns its ticket through a std::string* out-param, which pybind11
|
||||
@@ -223,7 +223,7 @@ public:
|
||||
// returns a (result, ticket) tuple, which we unpack into the int result and the out-param.
|
||||
int request_bind_ticket(std::string* ticket) override
|
||||
{
|
||||
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||
ORCA_PY_AUDIT_SCOPE();
|
||||
::Slic3r::PluginCapabilityInterface::RefCounter _orca_ref_counter(*this);
|
||||
::Slic3r::PythonGILState gil;
|
||||
if (!gil)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user