mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-14 00:52:04 +00:00
Tree support "on build plate only" no interface layers fix (#13192)
This commit is contained in:
@@ -44,6 +44,18 @@ if (SLIC3R_GUI)
|
|||||||
include(${wxWidgets_USE_FILE})
|
include(${wxWidgets_USE_FILE})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(APPLE AND TARGET wx::wxgl)
|
||||||
|
# Newer wxWidgets CMake exports may inject the macOS OpenGL framework
|
||||||
|
# as a quoted file path inside wx::wxgl, which Ninja then treats as a
|
||||||
|
# missing input file. Orca links OpenGL explicitly elsewhere, so strip
|
||||||
|
# these broken pseudo-paths from the imported target.
|
||||||
|
get_target_property(_wx_gl_libs wx::wxgl INTERFACE_LINK_LIBRARIES)
|
||||||
|
if(_wx_gl_libs)
|
||||||
|
list(FILTER _wx_gl_libs EXCLUDE REGEX "framework OpenGL")
|
||||||
|
set_target_properties(wx::wxgl PROPERTIES INTERFACE_LINK_LIBRARIES "${_wx_gl_libs}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
find_package(JPEG QUIET)
|
find_package(JPEG QUIET)
|
||||||
|
|
||||||
string(REGEX MATCH "wxpng" WX_PNG_BUILTIN ${wxWidgets_LIBRARIES})
|
string(REGEX MATCH "wxpng" WX_PNG_BUILTIN ${wxWidgets_LIBRARIES})
|
||||||
|
|||||||
@@ -2076,9 +2076,10 @@ void TreeSupport::draw_circles()
|
|||||||
if (!area.empty()) has_circle_node = true;
|
if (!area.empty()) has_circle_node = true;
|
||||||
if (node.need_extra_wall) need_extra_wall = true;
|
if (node.need_extra_wall) need_extra_wall = true;
|
||||||
|
|
||||||
// merge overhang to get a smoother interface surface
|
// Merge the overhang into the roof area so tree tips can still produce
|
||||||
// Do not merge when buildplate_only is on, because some underneath nodes may have been deleted.
|
// a continuous support interface. Suppressing this for build-plate-only
|
||||||
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !on_buildplate_only && !node.is_sharp_tail) {
|
// support drops the roof polygons entirely in valid tree branches.
|
||||||
|
if (top_interface_layers > 0 && node.support_roof_layers_below > 0 && !node.is_sharp_tail) {
|
||||||
ExPolygons overhang_expanded;
|
ExPolygons overhang_expanded;
|
||||||
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
|
if (node.overhang.contour.size() > 100 || node.overhang.holes.size()>1)
|
||||||
overhang_expanded.emplace_back(node.overhang);
|
overhang_expanded.emplace_back(node.overhang);
|
||||||
@@ -2121,6 +2122,17 @@ void TreeSupport::draw_circles()
|
|||||||
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
roof_1st_layer = diff_ex(roof_1st_layer, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roof_areas,get_extents(roof_1st_layer)));
|
||||||
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
|
roof_1st_layer = intersection_ex(roof_1st_layer, m_machine_border);
|
||||||
|
|
||||||
|
// Build-plate-only pruning can collapse the roof stack down to a single
|
||||||
|
// printable layer. In that case we still need to emit an interface layer
|
||||||
|
// instead of downgrading the last roof-adjacent layer to base support.
|
||||||
|
if (on_buildplate_only && top_interface_layers > 0 && roof_areas.empty() && !roof_1st_layer.empty()) {
|
||||||
|
append(roof_areas, roof_1st_layer);
|
||||||
|
roof_1st_layer.clear();
|
||||||
|
max_layers_above_roof = std::max(max_layers_above_roof, max_layers_above_roof1);
|
||||||
|
max_layers_above_roof1 = 0;
|
||||||
|
interface_id = obj_layer_nr % top_interface_layers;
|
||||||
|
}
|
||||||
|
|
||||||
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
|
ExPolygons roofs; append(roofs, roof_1st_layer); append(roofs, roof_areas);append(roofs, roof_gap_areas);
|
||||||
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
|
base_areas = diff_ex(base_areas, ClipperUtils::clip_clipper_polygons_with_subject_bbox(roofs, get_extents(base_areas)));
|
||||||
base_areas = intersection_ex(base_areas, m_machine_border);
|
base_areas = intersection_ex(base_areas, m_machine_border);
|
||||||
|
|||||||
@@ -898,6 +898,7 @@ public:
|
|||||||
size_t dtt_roof_tip;
|
size_t dtt_roof_tip;
|
||||||
for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) {
|
for (dtt_roof_tip = 0; dtt_roof_tip < roof_tip_layers && insert_layer_idx - dtt_roof_tip >= 1; ++ dtt_roof_tip) {
|
||||||
size_t this_layer_idx = insert_layer_idx - dtt_roof_tip;
|
size_t this_layer_idx = insert_layer_idx - dtt_roof_tip;
|
||||||
|
const size_t roof_recovery_depth = dtt_roof_tip + supports_roof_layers;
|
||||||
auto evaluateRoofWillGenerate = [&](const std::pair<Point, LineStatus> &p) {
|
auto evaluateRoofWillGenerate = [&](const std::pair<Point, LineStatus> &p) {
|
||||||
//FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time!
|
//FIXME Vojtech: The circle is just shifted, it has a known size, the infill should fit all the time!
|
||||||
#if 0
|
#if 0
|
||||||
@@ -927,7 +928,9 @@ public:
|
|||||||
// don't move until
|
// don't move until
|
||||||
roof_tip_layers - dtt_roof_tip,
|
roof_tip_layers - dtt_roof_tip,
|
||||||
// supports roof
|
// supports roof
|
||||||
dtt_roof_tip + supports_roof_layers > 0,
|
roof_recovery_depth > 0,
|
||||||
|
// recovered roof/contact depth for this slice
|
||||||
|
roof_recovery_depth,
|
||||||
// disable ovalization
|
// disable ovalization
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
@@ -942,9 +945,10 @@ public:
|
|||||||
roof_circle.translate(p.first);
|
roof_circle.translate(p.first);
|
||||||
new_roofs.emplace_back(std::move(roof_circle));
|
new_roofs.emplace_back(std::move(roof_circle));
|
||||||
}
|
}
|
||||||
this->add_roof(std::move(new_roofs), this_layer_idx, dtt_roof_tip + supports_roof_layers);
|
this->add_roof(std::move(new_roofs), this_layer_idx, roof_recovery_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const size_t roof_recovery_depth = dtt_roof_tip + supports_roof_layers;
|
||||||
for (const LineInformation &line : lines) {
|
for (const LineInformation &line : lines) {
|
||||||
// If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern.
|
// If a line consists of enough tips, the assumption is that it is not a single tip, but part of a simulated support pattern.
|
||||||
// Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width
|
// Ovalisation should be disabled for these to improve the quality of the lines when tip_diameter=line_width
|
||||||
@@ -954,14 +958,16 @@ public:
|
|||||||
// don't move until
|
// don't move until
|
||||||
dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0,
|
dont_move_until > dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0,
|
||||||
// supports roof
|
// supports roof
|
||||||
dtt_roof_tip + supports_roof_layers > 0,
|
roof_recovery_depth > 0,
|
||||||
|
// recovered roof/contact depth for this slice
|
||||||
|
roof_recovery_depth,
|
||||||
disable_ovalistation);
|
disable_ovalistation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// called by this->add_points_along_lines()
|
// called by this->add_points_along_lines()
|
||||||
void add_point_as_influence_area(std::pair<Point, LineStatus> p, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation)
|
void add_point_as_influence_area(std::pair<Point, LineStatus> p, LayerIndex insert_layer, size_t dont_move_until, bool roof, size_t roof_recovery_dtt, bool skip_ovalisation)
|
||||||
{
|
{
|
||||||
bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE;
|
bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE;
|
||||||
bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE;
|
bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE;
|
||||||
@@ -997,7 +1003,7 @@ private:
|
|||||||
state.supports_roof = roof;
|
state.supports_roof = roof;
|
||||||
state.dont_move_until = dont_move_until;
|
state.dont_move_until = dont_move_until;
|
||||||
state.can_use_safe_radius = safe_radius;
|
state.can_use_safe_radius = safe_radius;
|
||||||
state.missing_roof_layers = force_tip_to_roof ? dont_move_until : 0;
|
state.set_pending_roof_recovery(force_tip_to_roof ? dont_move_until : 0, roof_recovery_dtt);
|
||||||
state.skip_ovalisation = skip_ovalisation;
|
state.skip_ovalisation = skip_ovalisation;
|
||||||
move_bounds[insert_layer].emplace_back(state, std::move(circle));
|
move_bounds[insert_layer].emplace_back(state, std::move(circle));
|
||||||
}
|
}
|
||||||
@@ -1095,10 +1101,9 @@ void finalize_raft_contact(
|
|||||||
// 1) Maximum num_support_roof_layers roof (top interface & contact) layers.
|
// 1) Maximum num_support_roof_layers roof (top interface & contact) layers.
|
||||||
// 2) Tree tips supporting either the roof layers or the object itself.
|
// 2) Tree tips supporting either the roof layers or the object itself.
|
||||||
// num_support_roof_layers should always be respected:
|
// num_support_roof_layers should always be respected:
|
||||||
// If num_support_roof_layers contact layers could not be produced, then the tree tip
|
// If the requested roof/contact stack cannot be generated directly, the affected tree tips
|
||||||
// is augmented with SupportElementState::missing_roof_layers
|
// carry explicit pending roof recovery metadata so the sliced branch geometry can later be
|
||||||
// and the top "missing_roof_layers" of such particular tree tips are supposed to be coverted to
|
// promoted back to top contacts / interfaces at the correct contact depth.
|
||||||
// roofs aka interface layers by the tool path generator.
|
|
||||||
void sample_overhang_area(
|
void sample_overhang_area(
|
||||||
// Area to support
|
// Area to support
|
||||||
Polygons &&overhang_area,
|
Polygons &&overhang_area,
|
||||||
@@ -1606,7 +1611,6 @@ static Point move_inside_if_outside(const Polygons &polygons, Point from, int di
|
|||||||
if (settings.increase_radius)
|
if (settings.increase_radius)
|
||||||
current_elem.effective_radius_height += 1;
|
current_elem.effective_radius_height += 1;
|
||||||
coord_t radius = support_element_collision_radius(config, current_elem);
|
coord_t radius = support_element_collision_radius(config, current_elem);
|
||||||
|
|
||||||
const auto _tiny_area_threshold = tiny_area_threshold();
|
const auto _tiny_area_threshold = tiny_area_threshold();
|
||||||
if (settings.move) {
|
if (settings.move) {
|
||||||
increased = relevant_offset;
|
increased = relevant_offset;
|
||||||
@@ -2059,7 +2063,10 @@ static void increase_areas_one_layer(
|
|||||||
out.supports_roof = first.supports_roof || second.supports_roof;
|
out.supports_roof = first.supports_roof || second.supports_roof;
|
||||||
out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until);
|
out.dont_move_until = std::max(first.dont_move_until, second.dont_move_until);
|
||||||
out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius;
|
out.can_use_safe_radius = first.can_use_safe_radius || second.can_use_safe_radius;
|
||||||
out.missing_roof_layers = std::min(first.missing_roof_layers, second.missing_roof_layers);
|
// Preserve the deepest outstanding roof recovery request across merged sub-branches.
|
||||||
|
out.set_pending_roof_recovery(
|
||||||
|
std::max(first.missing_roof_layers, second.missing_roof_layers),
|
||||||
|
std::max(first.roof_recovery_dtt, second.roof_recovery_dtt));
|
||||||
out.skip_ovalisation = false;
|
out.skip_ovalisation = false;
|
||||||
if (first.target_height > second.target_height) {
|
if (first.target_height > second.target_height) {
|
||||||
out.target_height = first.target_height;
|
out.target_height = first.target_height;
|
||||||
@@ -3473,7 +3480,6 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
|||||||
|
|
||||||
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
|
// value is the area where support may be placed. As this is calculated in CreateLayerPathing it is saved and reused in draw_areas
|
||||||
std::vector<SupportElements> move_bounds(num_support_layers);
|
std::vector<SupportElements> move_bounds(num_support_layers);
|
||||||
|
|
||||||
// ### Place tips of the support tree
|
// ### Place tips of the support tree
|
||||||
for (size_t mesh_idx : processing.second)
|
for (size_t mesh_idx : processing.second)
|
||||||
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
|
generate_initial_areas(*print.get_object(mesh_idx), volumes, config, overhangs,
|
||||||
@@ -3591,7 +3597,33 @@ static void generate_support_areas(Print &print, TreeSupport* tree_support, cons
|
|||||||
// storage.support.generated = true;
|
// storage.support.generated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Organic specific: Smooth branches and produce one cummulative mesh to be sliced.
|
static void recover_pending_branch_roofs(
|
||||||
|
InterfacePlacer &interface_placer,
|
||||||
|
const std::vector<const SupportElement*> &branch_path,
|
||||||
|
const LayerIndex layer_begin,
|
||||||
|
std::vector<Polygons> &slices)
|
||||||
|
{
|
||||||
|
if (! interface_placer.support_parameters.has_top_contacts)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto it = branch_path.rbegin(); it != branch_path.rend(); ++ it) {
|
||||||
|
const SupportElement &el = **it;
|
||||||
|
if (! el.state.has_pending_roof_recovery())
|
||||||
|
break;
|
||||||
|
|
||||||
|
const LayerIndex slice_idx = el.state.layer_idx - layer_begin;
|
||||||
|
if (slice_idx < 0 || slice_idx >= LayerIndex(slices.size()))
|
||||||
|
continue;
|
||||||
|
if (slices[size_t(slice_idx)].empty())
|
||||||
|
continue;
|
||||||
|
if (el.state.roof_recovery_dtt > interface_placer.support_parameters.num_top_interface_layers)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
interface_placer.add_roof(std::move(slices[size_t(slice_idx)]), el.state.layer_idx, el.state.roof_recovery_dtt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organic specific: Smooth branches and produce one cumulative mesh to be sliced.
|
||||||
void organic_draw_branches(
|
void organic_draw_branches(
|
||||||
PrintObject &print_object,
|
PrintObject &print_object,
|
||||||
TreeModelVolumes &volumes,
|
TreeModelVolumes &volumes,
|
||||||
@@ -3770,13 +3802,12 @@ void organic_draw_branches(
|
|||||||
// ++ ielement;
|
// ++ ielement;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const SlicingParameters &slicing_params = print_object.slicing_parameters();
|
const SlicingParameters &slicing_params = print_object.slicing_parameters();
|
||||||
MeshSlicingParams mesh_slicing_params;
|
MeshSlicingParams mesh_slicing_params;
|
||||||
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
|
mesh_slicing_params.mode = MeshSlicingParams::SlicingMode::Positive;
|
||||||
|
|
||||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, trees.size(), 1),
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, trees.size(), 1),
|
||||||
[&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
[&trees, &volumes, &config, &slicing_params, &move_bounds, &mesh_slicing_params, &interface_placer, &throw_on_cancel](const tbb::blocked_range<size_t> &range) {
|
||||||
indexed_triangle_set partial_mesh;
|
indexed_triangle_set partial_mesh;
|
||||||
std::vector<float> slice_z;
|
std::vector<float> slice_z;
|
||||||
std::vector<Polygons> bottom_contacts;
|
std::vector<Polygons> bottom_contacts;
|
||||||
@@ -3811,7 +3842,7 @@ void organic_draw_branches(
|
|||||||
num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin();
|
num_empty = std::find_if(slices.begin(), slices.end(), [](auto &s) { return !s.empty(); }) - slices.begin();
|
||||||
} else {
|
} else {
|
||||||
if (branch.has_root) {
|
if (branch.has_root) {
|
||||||
if (branch.path.front()->state.to_model_gracious) {
|
if (config.support_rests_on_model && branch.path.front()->state.to_model_gracious) {
|
||||||
if (config.settings.support_floor_layers > 0)
|
if (config.settings.support_floor_layers > 0)
|
||||||
//FIXME one may just take the whole tree slice as bottom interface.
|
//FIXME one may just take the whole tree slice as bottom interface.
|
||||||
bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {})));
|
bottom_contacts.emplace_back(intersection_clipped(slices.front(), volumes.getPlaceableAreas(0, layer_begin, [] {})));
|
||||||
@@ -3860,7 +3891,7 @@ void organic_draw_branches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (config.settings.support_floor_layers > 0)
|
if (config.support_rests_on_model && config.settings.support_floor_layers > 0)
|
||||||
for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i)
|
for (int i = int(bottom_extra_slices.size()) - 2; i >= 0; -- i)
|
||||||
bottom_contacts.emplace_back(
|
bottom_contacts.emplace_back(
|
||||||
intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {})));
|
intersection_clipped(bottom_extra_slices[i].polygons, volumes.getPlaceableAreas(0, layer_begin - i - 1, [] {})));
|
||||||
@@ -3872,19 +3903,7 @@ void organic_draw_branches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
recover_pending_branch_roofs(interface_placer, branch.path, layer_begin, slices);
|
||||||
//FIXME branch.has_tip seems to not be reliable.
|
|
||||||
if (branch.has_tip && interface_placer.support_parameters.has_top_contacts)
|
|
||||||
// Add top slices to top contacts / interfaces / base interfaces.
|
|
||||||
for (int i = int(branch.path.size()) - 1; i >= 0; -- i) {
|
|
||||||
const SupportElement &el = *branch.path[i];
|
|
||||||
if (el.state.missing_roof_layers == 0)
|
|
||||||
break;
|
|
||||||
//FIXME Move or not?
|
|
||||||
interface_placer.add_roof(std::move(slices[int(slices.size()) - i - 1]), el.state.layer_idx,
|
|
||||||
interface_placer.support_parameters.num_top_interface_layers + 1 - el.state.missing_roof_layers);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
layer_begin += LayerIndex(num_empty);
|
layer_begin += LayerIndex(num_empty);
|
||||||
|
|||||||
@@ -199,9 +199,20 @@ struct SupportElementState : public SupportElementStateBits
|
|||||||
AreaIncreaseSettings last_area_increase;
|
AreaIncreaseSettings last_area_increase;
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Amount of roof layers that were not yet added, because the branch needed to move.
|
* \brief Number of pending roof/contact recovery slices from this node downward, including this node.
|
||||||
*/
|
*/
|
||||||
uint32_t missing_roof_layers;
|
uint32_t missing_roof_layers = 0;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Contact/interface depth that this node should recover when missing_roof_layers > 0.
|
||||||
|
*/
|
||||||
|
uint32_t roof_recovery_dtt = 0;
|
||||||
|
|
||||||
|
void set_pending_roof_recovery(uint32_t pending_layers, uint32_t recovery_depth)
|
||||||
|
{
|
||||||
|
this->missing_roof_layers = pending_layers;
|
||||||
|
this->roof_recovery_dtt = pending_layers > 0 ? recovery_depth : 0;
|
||||||
|
}
|
||||||
|
|
||||||
// called by increase_single_area() and increaseAreas()
|
// called by increase_single_area() and increaseAreas()
|
||||||
[[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src)
|
[[nodiscard]] static SupportElementState propagate_down(const SupportElementState &src)
|
||||||
@@ -209,6 +220,10 @@ struct SupportElementState : public SupportElementStateBits
|
|||||||
SupportElementState dst{ src };
|
SupportElementState dst{ src };
|
||||||
++ dst.distance_to_top;
|
++ dst.distance_to_top;
|
||||||
-- dst.layer_idx;
|
-- dst.layer_idx;
|
||||||
|
if (dst.has_pending_roof_recovery()) {
|
||||||
|
-- dst.missing_roof_layers;
|
||||||
|
++ dst.roof_recovery_dtt;
|
||||||
|
}
|
||||||
// set to invalid as we are a new node on a new layer
|
// set to invalid as we are a new node on a new layer
|
||||||
dst.result_on_layer_reset();
|
dst.result_on_layer_reset();
|
||||||
dst.skip_ovalisation = false;
|
dst.skip_ovalisation = false;
|
||||||
@@ -216,6 +231,7 @@ struct SupportElementState : public SupportElementStateBits
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; }
|
[[nodiscard]] bool locked() const { return this->distance_to_top < this->dont_move_until; }
|
||||||
|
[[nodiscard]] bool has_pending_roof_recovery() const { return this->missing_roof_layers > 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|||||||
Reference in New Issue
Block a user