mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-19 08:52:09 +00:00
Merge branch 'main' into feat/plugin-feature
This commit is contained in:
@@ -201,9 +201,12 @@ if (WIN32)
|
||||
if(MSVC)
|
||||
target_link_options(OrcaSlicer_app_gui PUBLIC "$<$<CONFIG:RELEASE>:/DEBUG>")
|
||||
endif()
|
||||
target_compile_definitions(OrcaSlicer_app_gui PRIVATE -DSLIC3R_WRAPPER_NOCONSOLE)
|
||||
target_compile_definitions(OrcaSlicer_app_gui PRIVATE "$<$<NOT:$<CONFIG:RelWithDebInfo>>:SLIC3R_WRAPPER_NOCONSOLE>")
|
||||
add_dependencies(OrcaSlicer_app_gui OrcaSlicer)
|
||||
set_target_properties(OrcaSlicer_app_gui PROPERTIES OUTPUT_NAME "orca-slicer")
|
||||
set_target_properties(OrcaSlicer_app_gui PROPERTIES
|
||||
OUTPUT_NAME "orca-slicer"
|
||||
WIN32_EXECUTABLE "$<NOT:$<CONFIG:RelWithDebInfo>>"
|
||||
)
|
||||
target_link_libraries(OrcaSlicer_app_gui PRIVATE boost_headeronly)
|
||||
endif ()
|
||||
|
||||
|
||||
@@ -532,6 +532,7 @@ endif ()
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_compile_definitions(libslic3r PRIVATE $<$<CONFIG:RelWithDebInfo>:SLIC3R_CONSOLE_LOG>)
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(libslic3r SYSTEM PUBLIC ${EXPAT_INCLUDE_DIRS})
|
||||
|
||||
|
||||
@@ -836,9 +836,11 @@ namespace client
|
||||
static std::map<std::string, std::string> tag_to_error_message;
|
||||
|
||||
size_t get_extruder_id() const {
|
||||
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
|
||||
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
|
||||
return filament_map_opt->values[current_extruder_id];
|
||||
if (external_config != nullptr) {
|
||||
const ConfigOptionInts * filament_map_opt = external_config->option<ConfigOptionInts>("filament_map");
|
||||
if (filament_map_opt && current_extruder_id < filament_map_opt->values.size()) {
|
||||
return filament_map_opt->values[current_extruder_id];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -1096,27 +1098,109 @@ namespace client
|
||||
const ConfigOptionVectorBase* vec = static_cast<const ConfigOptionVectorBase*>(opt.opt);
|
||||
if (vec->empty())
|
||||
ctx->throw_exception("Indexing an empty vector variable", opt.it_range);
|
||||
|
||||
// Helper to resolve a FloatOrPercent value (handles ratio_over chain for percent values).
|
||||
// elem_index: the element index used to access this vector element, so that
|
||||
// parent vectors (via ratio_over) use the same index rather than the current extruder.
|
||||
auto resolve_float_or_percent = [ctx, &opt, &output](const FloatOrPercent &fop, size_t elem_index) {
|
||||
std::string opt_key(opt.it_range.begin(), opt.it_range.end());
|
||||
if (boost::ends_with(opt_key, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast<unsigned int>(ctx->current_extruder_id)));
|
||||
} else if (! fop.percent) {
|
||||
// Not a percent, just return the value.
|
||||
output.set_d(fop.value);
|
||||
} else {
|
||||
// Resolve dependencies using the "ratio_over" link to a parent value.
|
||||
const ConfigOptionDef *opt_def = print_config_def.get(opt_key);
|
||||
assert(opt_def != nullptr);
|
||||
double v = fop.value * 0.01; // percent to ratio
|
||||
for (;;) {
|
||||
const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over);
|
||||
if (opt_parent == nullptr)
|
||||
ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range);
|
||||
if (boost::ends_with(opt_def->ratio_over, "line_width")) {
|
||||
// Line width supports defaults and a complex graph of dependencies.
|
||||
assert(opt_parent->type() == coFloatOrPercent);
|
||||
v *= Flow::extrusion_width(opt_def->ratio_over, static_cast<const ConfigOptionFloatOrPercent*>(opt_parent), *ctx, static_cast<unsigned int>(ctx->current_extruder_id));
|
||||
break;
|
||||
}
|
||||
if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) {
|
||||
v *= opt_parent->getFloat();
|
||||
if (opt_parent->type() == coFloat || ! static_cast<const ConfigOptionFloatOrPercent*>(opt_parent)->percent)
|
||||
break;
|
||||
v *= 0.01; // percent to ratio
|
||||
} else if (opt_parent->type() == coFloats) {
|
||||
// Vector parent: extract the value for the current extruder.
|
||||
const ConfigOptionFloatsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt_parent);
|
||||
if (parent_nullable) {
|
||||
v *= (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
|
||||
} else {
|
||||
const ConfigOptionFloats *parent_vec = static_cast<const ConfigOptionFloats *>(opt_parent);
|
||||
v *= (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
|
||||
}
|
||||
break;
|
||||
} else if (opt_parent->type() == coFloatsOrPercents) {
|
||||
// Vector parent with percent support: extract the FloatOrPercent for the current extruder.
|
||||
const ConfigOptionFloatsOrPercentsNullable *parent_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt_parent);
|
||||
if (parent_nullable) {
|
||||
const FloatOrPercent &parent_fop = (parent_nullable->size() == 1) ? parent_nullable->get_at(0) : parent_nullable->get_at(elem_index);
|
||||
if (! parent_fop.percent) {
|
||||
v *= parent_fop.value;
|
||||
break;
|
||||
}
|
||||
v *= parent_fop.value * 0.01; // percent to ratio
|
||||
} else {
|
||||
const ConfigOptionFloatsOrPercents *parent_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt_parent);
|
||||
const FloatOrPercent &parent_fop = (parent_vec->size() == 1) ? parent_vec->get_at(0) : parent_vec->get_at(elem_index);
|
||||
if (! parent_fop.percent) {
|
||||
v *= parent_fop.value;
|
||||
break;
|
||||
}
|
||||
v *= parent_fop.value * 0.01; // percent to ratio
|
||||
}
|
||||
}
|
||||
// Continue one level up in the "ratio_over" hierarchy.
|
||||
opt_def = print_config_def.get(opt_def->ratio_over);
|
||||
assert(opt_def != nullptr);
|
||||
}
|
||||
output.set_d(v);
|
||||
}
|
||||
};
|
||||
|
||||
if (!opt.has_index()) {
|
||||
// Allow omitting extruder id when referencing vectors
|
||||
switch (opt.opt->type()) {
|
||||
case coFloats: {
|
||||
const ConfigOptionFloatsNullable* opt_floatsnullable = static_cast<const ConfigOptionFloatsNullable *>(opt.opt);
|
||||
const ConfigOptionFloatsNullable* opt_floatsnullable = dynamic_cast<const ConfigOptionFloatsNullable *>(opt.opt);
|
||||
if (opt_floatsnullable) {
|
||||
if (opt_floatsnullable->size() == 1) { // old version
|
||||
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(0));
|
||||
output.set_d(opt_floatsnullable->get_at(0));
|
||||
} else {
|
||||
output.set_d(static_cast<const ConfigOptionFloatsNullable*>(opt.opt)->get_at(ctx->get_extruder_id()));
|
||||
output.set_d(opt_floatsnullable->get_at(ctx->get_extruder_id()));
|
||||
}
|
||||
} else {
|
||||
const ConfigOptionFloats* opt_floats = static_cast<const ConfigOptionFloats*>(opt.opt);
|
||||
if (opt_floats->size() == 1) { // old version
|
||||
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(0));
|
||||
output.set_d(opt_floats->get_at(0));
|
||||
} else {
|
||||
output.set_d(static_cast<const ConfigOptionFloats*>(opt.opt)->get_at(ctx->get_extruder_id()));
|
||||
output.set_d(opt_floats->get_at(ctx->get_extruder_id()));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case coFloatsOrPercents: {
|
||||
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
|
||||
if (opt_vec_nullable) {
|
||||
size_t elem_index = (opt_vec_nullable->size() == 1) ? 0 : ctx->get_extruder_id();
|
||||
resolve_float_or_percent(opt_vec_nullable->get_at(elem_index), elem_index);
|
||||
} else {
|
||||
const ConfigOptionFloatsOrPercents *opt_vec = static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt);
|
||||
size_t elem_index = (opt_vec->size() == 1) ? 0 : ctx->get_extruder_id();
|
||||
resolve_float_or_percent(opt_vec->get_at(elem_index), elem_index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range);
|
||||
}
|
||||
} else {
|
||||
@@ -1131,6 +1215,15 @@ namespace client
|
||||
case coPoints: output.set_s(to_string(static_cast<const ConfigOptionPoints*>(opt.opt)->values[idx])); break;
|
||||
case coBools: output.set_b(static_cast<const ConfigOptionBools*>(opt.opt)->values[idx] != 0); break;
|
||||
case coEnums: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break;
|
||||
case coFloatsOrPercents: {
|
||||
const ConfigOptionFloatsOrPercentsNullable *opt_vec_nullable = dynamic_cast<const ConfigOptionFloatsOrPercentsNullable *>(opt.opt);
|
||||
if (opt_vec_nullable) {
|
||||
resolve_float_or_percent(opt_vec_nullable->values[idx], idx);
|
||||
} else {
|
||||
resolve_float_or_percent(static_cast<const ConfigOptionFloatsOrPercents *>(opt.opt)->values[idx], idx);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ctx->throw_exception("Unsupported vector variable type", opt.it_range);
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ void PrintTryCancel::operator()()
|
||||
|
||||
size_t PrintStateBase::g_last_timestamp = 0;
|
||||
|
||||
// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects.
|
||||
// Update "scale", "input_filename", "input_filename_base", "first_object_name" placeholders from the current m_objects.
|
||||
void PrintBase::update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const
|
||||
{
|
||||
// get the first input file name
|
||||
std::string input_file;
|
||||
std::string first_object_name;
|
||||
std::vector<std::string> v_scale;
|
||||
int num_objects = 0;
|
||||
int num_instances = 0;
|
||||
@@ -38,6 +39,8 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
||||
}
|
||||
if (printable) {
|
||||
++ num_objects;
|
||||
if (num_objects == 1)
|
||||
first_object_name = model_object->name;
|
||||
// CHECK_ME -> Is the following correct ?
|
||||
v_scale.push_back("x:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(X) * 100) +
|
||||
"% y:" + boost::lexical_cast<std::string>(printable->get_scaling_factor(Y) * 100) +
|
||||
@@ -51,6 +54,7 @@ void PrintBase::update_object_placeholders(DynamicConfig &config, const std::str
|
||||
config.set_key_value("num_instances", new ConfigOptionInt(num_instances));
|
||||
|
||||
config.set_key_value("scale", new ConfigOptionStrings(v_scale));
|
||||
config.set_key_value("first_object_name", new ConfigOptionString(first_object_name));
|
||||
if (! input_file.empty()) {
|
||||
// get basename with and without suffix
|
||||
const std::string input_filename = boost::filesystem::path(input_file).filename().string();
|
||||
|
||||
@@ -194,6 +194,7 @@ std::string debug_out_path(const char *name, ...);
|
||||
// smaller level means less log. level=5 means saving all logs.
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level);
|
||||
void flush_logs();
|
||||
void shutdown_console_logging();
|
||||
boost::filesystem::path get_log_file_name();
|
||||
|
||||
// A special type for strings encoded in the local Windows 8-bit code page.
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <locale>
|
||||
#include <ctime>
|
||||
#include <cstdarg>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <filesystem>
|
||||
|
||||
@@ -46,14 +47,19 @@
|
||||
#include <boost/log/core.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/log/expressions.hpp>
|
||||
#include <boost/log/sinks/async_frontend.hpp>
|
||||
#include <boost/log/sinks/text_file_backend.hpp>
|
||||
#include <boost/log/sinks/text_ostream_backend.hpp>
|
||||
#include <boost/log/utility/setup/file.hpp>
|
||||
#include <boost/log/utility/setup/common_attributes.hpp>
|
||||
#include <boost/log/sources/severity_logger.hpp>
|
||||
#include <boost/log/sources/record_ostream.hpp>
|
||||
#include <boost/log/support/date_time.hpp>
|
||||
|
||||
#include <boost/core/null_deleter.hpp>
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/make_shared.hpp>
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
@@ -174,6 +180,7 @@ unsigned get_logging_level()
|
||||
}
|
||||
|
||||
boost::shared_ptr<boost::log::sinks::synchronous_sink<boost::log::sinks::text_file_backend>> g_log_sink;
|
||||
boost::shared_ptr<boost::log::sinks::asynchronous_sink<boost::log::sinks::text_ostream_backend>> g_console_log_sink;
|
||||
|
||||
// Force set_logging_level(<=error) after loading of the DLL.
|
||||
// This is currently only needed if libslic3r is loaded as a shared library into Perl interpreter
|
||||
@@ -346,6 +353,19 @@ namespace src = boost::log::sources;
|
||||
namespace expr = boost::log::expressions;
|
||||
namespace keywords = boost::log::keywords;
|
||||
namespace attrs = boost::log::attributes;
|
||||
namespace sinks = boost::log::sinks;
|
||||
|
||||
void shutdown_console_logging()
|
||||
{
|
||||
if (!g_console_log_sink)
|
||||
return;
|
||||
|
||||
auto console_sink = g_console_log_sink;
|
||||
boost::log::core::get()->remove_sink(console_sink);
|
||||
console_sink->stop();
|
||||
g_console_log_sink.reset();
|
||||
}
|
||||
|
||||
void set_log_path_and_level(const std::string& file, unsigned int level)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
@@ -377,6 +397,24 @@ void set_log_path_and_level(const std::string& file, unsigned int level)
|
||||
keywords::auto_flush = true
|
||||
);
|
||||
|
||||
shutdown_console_logging();
|
||||
|
||||
#ifdef SLIC3R_CONSOLE_LOG
|
||||
auto console_backend = boost::make_shared<sinks::text_ostream_backend>();
|
||||
console_backend->add_stream(boost::shared_ptr<std::ostream>(&std::cout, boost::null_deleter()));
|
||||
console_backend->auto_flush(true);
|
||||
|
||||
g_console_log_sink = boost::make_shared<sinks::asynchronous_sink<sinks::text_ostream_backend>>(console_backend);
|
||||
g_console_log_sink->set_formatter(
|
||||
expr::stream
|
||||
<< "[" << expr::attr< logging::trivial::severity_level >("Severity") << "]\t"
|
||||
<< expr::format_date_time< boost::posix_time::ptime >("TimeStamp", "%Y-%m-%d %H:%M:%S.%f") << " "
|
||||
<<"[Thread " << expr::attr<attrs::current_thread_id::value_type>("ThreadID") << "]"
|
||||
<< ": " << expr::smessage
|
||||
);
|
||||
boost::log::core::get()->add_sink(g_console_log_sink);
|
||||
#endif
|
||||
|
||||
logging::add_common_attributes();
|
||||
|
||||
set_logging_level(level);
|
||||
|
||||
@@ -1472,8 +1472,32 @@ int GUI_App::install_plugin(std::string name, std::string package_name, InstallP
|
||||
boost::filesystem::create_directories(dest_path.parent_path());
|
||||
std::string dest_zip_file = encode_path(dest_path.string().c_str());
|
||||
try {
|
||||
if (fs::exists(dest_path))
|
||||
fs::remove(dest_path);
|
||||
if (fs::exists(dest_path)) {
|
||||
boost::system::error_code ec;
|
||||
fs::remove(dest_path, ec);
|
||||
if (ec) {
|
||||
// On Windows a currently-loaded DLL (e.g. BambuSource.dll, or the
|
||||
// networking library in legacy mode) cannot be deleted or overwritten
|
||||
// in place, which failed the whole install with "The plug-in file may
|
||||
// be in use" (issue #14373). It CAN however be renamed aside: the
|
||||
// running module keeps mapping the renamed file while we write the new
|
||||
// one. The stale ".old" copy is cleared on the next install/launch.
|
||||
boost::filesystem::path aside = dest_path;
|
||||
aside += ".old";
|
||||
boost::system::error_code ec2;
|
||||
fs::remove(aside, ec2);
|
||||
fs::rename(dest_path, aside, ec2);
|
||||
if (ec2) {
|
||||
close_zip_reader(&archive);
|
||||
BOOST_LOG_TRIVIAL(error) << "[install_plugin] cannot replace in-use file "
|
||||
<< dest_path.string() << ": " << ec2.message();
|
||||
if (pro_fn) { pro_fn(InstallStatusUnzipFailed, 0, cancel); }
|
||||
return InstallStatusUnzipFailed;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(warning) << "[install_plugin] " << dest_path.filename().string()
|
||||
<< " was in use, renamed aside to .old";
|
||||
}
|
||||
}
|
||||
mz_bool res = 0;
|
||||
#ifndef WIN32
|
||||
if (S_ISLNK(stat.m_external_attr >> 16)) {
|
||||
@@ -2259,6 +2283,7 @@ GUI_App::~GUI_App()
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": exit");
|
||||
shutdown_console_logging();
|
||||
}
|
||||
|
||||
bool GUI_App::is_blocking_printing(MachineObject *obj_)
|
||||
@@ -3473,6 +3498,26 @@ void GUI_App::copy_network_if_available()
|
||||
|
||||
bool GUI_App::on_init_network(bool try_backup)
|
||||
{
|
||||
// Clean up stale ".old" files left by install_plugin() when it had to rename an in-use
|
||||
// DLL aside (see the rename-aside path in install_plugin). This runs before the plug-in
|
||||
// is (re)loaded - at startup nothing is mapped yet, and on a hot reload the previous
|
||||
// module has already been unloaded - so the previously locked files can now be removed.
|
||||
{
|
||||
boost::filesystem::path plugin_folder = boost::filesystem::path(data_dir()) / "plugins";
|
||||
boost::system::error_code ec;
|
||||
if (boost::filesystem::is_directory(plugin_folder, ec)) {
|
||||
for (boost::filesystem::directory_iterator it(plugin_folder, ec), end; !ec && it != end; it.increment(ec)) {
|
||||
if (it->path().extension() == ".old") {
|
||||
boost::system::error_code rm_ec;
|
||||
boost::filesystem::remove(it->path(), rm_ec);
|
||||
if (rm_ec)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": could not remove stale " << it->path().filename().string()
|
||||
<< " (" << rm_ec.message() << "), will retry next launch";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto should_load_networking_plugin = app_config->get_bool("installed_networking");
|
||||
|
||||
std::string config_version = app_config->get_network_plugin_version();
|
||||
|
||||
@@ -91,9 +91,14 @@ Vec3d GLGizmoMeasure::get_feature_offset(const Measure::SurfaceFeature &feature)
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Edge:
|
||||
{
|
||||
std::optional<Vec3d> p = feature.get_extra_point();
|
||||
assert(p.has_value());
|
||||
ret = *p;
|
||||
// Only polygon edges store an extra point (the polygon centre); plain edges have none.
|
||||
const std::optional<Vec3d> extra = feature.get_extra_point();
|
||||
if (extra.has_value())
|
||||
ret = *extra;
|
||||
else {
|
||||
const auto [pt1, pt2] = feature.get_edge();
|
||||
ret = 0.5 * (pt1 + pt2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Measure::SurfaceFeatureType::Point:
|
||||
@@ -1065,7 +1070,7 @@ void GLGizmoMeasure::on_render()
|
||||
|
||||
if (requires_raycaster_update) {
|
||||
if (m_gripper_id_raycast_map.find(GripperType::SPHERE_2) != m_gripper_id_raycast_map.end()) {
|
||||
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) *
|
||||
m_gripper_id_raycast_map[GripperType::SPHERE_2]->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.second.feature)) *
|
||||
Geometry::scale_transform(inv_zoom));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +535,9 @@ void Selection::drop()
|
||||
|
||||
for (unsigned int i : m_list) {
|
||||
GLVolume& volume = *(*m_volumes)[i];
|
||||
// Skip the wipe tower: its synthetic id (>= 1000) is not an index into m_model->objects.
|
||||
if (volume.object_idx() >= 1000)
|
||||
continue;
|
||||
ModelObject* model_object = m_model->objects[volume.object_idx()];
|
||||
|
||||
if (model_object != nullptr) {
|
||||
@@ -1848,6 +1851,9 @@ void Selection::notify_instance_update(int object_idx, int instance_idx)
|
||||
for (unsigned int i : m_list)
|
||||
{
|
||||
int obj_index = (*m_volumes)[i]->object_idx();
|
||||
// Skip the wipe tower: its synthetic id (>= 1000) is not an index into m_model->objects.
|
||||
if (obj_index >= 1000)
|
||||
continue;
|
||||
//-1 means all the instance in this object
|
||||
if (instance_idx == -1)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user