PRUSA 2.7.0

This commit is contained in:
sunsets
2023-12-27 18:02:35 +08:00
parent b33112327f
commit 0a3c63dcb1
488 changed files with 92371 additions and 29443 deletions

View File

@@ -1,23 +1,17 @@
# TODO Add individual tests as executables in separate directories
# add_subirectory(<testcase>)
find_package(Catch2 2.9 REQUIRED)
include(Catch)
set(TEST_DATA_DIR ${CMAKE_CURRENT_SOURCE_DIR}/data)
file(TO_NATIVE_PATH "${TEST_DATA_DIR}" TEST_DATA_DIR)
add_library(Catch2 INTERFACE)
list (APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/Catch2)
target_include_directories(Catch2 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
add_library(Catch2::Catch2 ALIAS Catch2)
if (APPLE)
# OSX builds targeting OSX 10.9 do not support new std::uncought_exception()
# see https://github.com/catchorg/Catch2/issues/1218
target_compile_definitions(Catch2 INTERFACE -DCATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS)
endif()
include(Catch)
set(CATCH_EXTRA_ARGS "" CACHE STRING "Extra arguments for catch2 test suites.")
add_library(test_common INTERFACE)
target_include_directories(test_common INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_compile_definitions(test_common INTERFACE TEST_DATA_DIR=R"\(${TEST_DATA_DIR}\)" CATCH_CONFIG_FAST_COMPILE)
target_link_libraries(test_common INTERFACE Catch2::Catch2)
@@ -28,6 +22,7 @@ endif()
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
add_subdirectory(arrange)
add_subdirectory(thumbnails)
add_subdirectory(libslic3r)
add_subdirectory(slic3rutils)
add_subdirectory(fff_print)

View File

@@ -202,10 +202,10 @@ static void check_nfp(const std::string & outfile_prefix,
ExPolygons bed_negative = diff_ex(bedrect, bedpoly);
ExPolygons orb_ex_r = to_expolygons(orbiter);
ExPolygons orb_ex_r_ch = {ExPolygon(Geometry::convex_hull(orb_ex_r))};
auto orb_ex_offs_pos_r = offset_ex(orb_ex_r, SCALED_EPSILON);
auto orb_ex_offs_neg_r = offset_ex(orb_ex_r, -SCALED_EPSILON);
auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, SCALED_EPSILON);
auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -SCALED_EPSILON);
auto orb_ex_offs_pos_r = offset_ex(orb_ex_r, scaled<float>(EPSILON));
auto orb_ex_offs_neg_r = offset_ex(orb_ex_r, -scaled<float>(EPSILON));
auto orb_ex_offs_pos_r_ch = offset_ex(orb_ex_r_ch, scaled<float>(EPSILON));
auto orb_ex_offs_neg_r_ch = offset_ex(orb_ex_r_ch, -scaled<float>(EPSILON));
auto bedpoly_offs = offset_ex(bedpoly, SCALED_EPSILON);

View File

@@ -738,8 +738,8 @@ bool is_collision_free(const Slic3r::Range<It> &item_range)
bool collision_free = true;
foreach_combo(item_range, [&collision_free](auto &itm1, auto &itm2) {
auto outline1 = offset(arr2::fixed_outline(itm1), -SCALED_EPSILON);
auto outline2 = offset(arr2::fixed_outline(itm2), -SCALED_EPSILON);
auto outline1 = offset(arr2::fixed_outline(itm1), -scaled<float>(EPSILON));
auto outline2 = offset(arr2::fixed_outline(itm2), -scaled<float>(EPSILON));
auto inters = intersection(outline1, outline2);
collision_free = collision_free && inters.empty();

View File

@@ -1,23 +0,0 @@
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

View File

@@ -1,2 +0,0 @@
2.9.2 g2c869e1

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +0,0 @@
/*
* Created by Justin R. Wilson on 2/19/2017.
* Copyright 2017 Justin R. Wilson. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED
// Don't #include any Catch headers here - we can assume they are already
// included before this header.
// This is not good practice in general but is necessary in this case so this
// file can be distributed as a single header that works with the main
// Catch single header.
namespace Catch {
struct AutomakeReporter : StreamingReporterBase<AutomakeReporter> {
AutomakeReporter( ReporterConfig const& _config )
: StreamingReporterBase( _config )
{}
~AutomakeReporter() override;
static std::string getDescription() {
return "Reports test results in the format of Automake .trs files";
}
void assertionStarting( AssertionInfo const& ) override {}
bool assertionEnded( AssertionStats const& /*_assertionStats*/ ) override { return true; }
void testCaseEnded( TestCaseStats const& _testCaseStats ) override {
// Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR.
stream << ":test-result: ";
if (_testCaseStats.totals.assertions.allPassed()) {
stream << "PASS";
} else if (_testCaseStats.totals.assertions.allOk()) {
stream << "XFAIL";
} else {
stream << "FAIL";
}
stream << ' ' << _testCaseStats.testInfo.name << '\n';
StreamingReporterBase::testCaseEnded( _testCaseStats );
}
void skipTest( TestCaseInfo const& testInfo ) override {
stream << ":test-result: SKIP " << testInfo.name << '\n';
}
};
#ifdef CATCH_IMPL
AutomakeReporter::~AutomakeReporter() {}
#endif
CATCH_REGISTER_REPORTER( "automake", AutomakeReporter)
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED

View File

@@ -1,253 +0,0 @@
/*
* Created by Colton Wolkins on 2015-08-15.
* Copyright 2015 Martin Moene. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED
// Don't #include any Catch headers here - we can assume they are already
// included before this header.
// This is not good practice in general but is necessary in this case so this
// file can be distributed as a single header that works with the main
// Catch single header.
#include <algorithm>
namespace Catch {
struct TAPReporter : StreamingReporterBase<TAPReporter> {
using StreamingReporterBase::StreamingReporterBase;
~TAPReporter() override;
static std::string getDescription() {
return "Reports test results in TAP format, suitable for test harnesses";
}
ReporterPreferences getPreferences() const override {
return m_reporterPrefs;
}
void noMatchingTestCases( std::string const& spec ) override {
stream << "# No test cases matched '" << spec << "'" << std::endl;
}
void assertionStarting( AssertionInfo const& ) override {}
bool assertionEnded( AssertionStats const& _assertionStats ) override {
++counter;
stream << "# " << currentTestCaseInfo->name << std::endl;
AssertionPrinter printer( stream, _assertionStats, counter );
printer.print();
stream << std::endl;
return true;
}
void testRunEnded( TestRunStats const& _testRunStats ) override {
printTotals( _testRunStats.totals );
stream << "\n" << std::endl;
StreamingReporterBase::testRunEnded( _testRunStats );
}
private:
std::size_t counter = 0;
class AssertionPrinter {
public:
AssertionPrinter& operator= ( AssertionPrinter const& ) = delete;
AssertionPrinter( AssertionPrinter const& ) = delete;
AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter )
: stream( _stream )
, result( _stats.assertionResult )
, messages( _stats.infoMessages )
, itMessage( _stats.infoMessages.begin() )
, printInfoMessages( true )
, counter(_counter)
{}
void print() {
itMessage = messages.begin();
switch( result.getResultType() ) {
case ResultWas::Ok:
printResultType( passedString() );
printOriginalExpression();
printReconstructedExpression();
if ( ! result.hasExpression() )
printRemainingMessages( Colour::None );
else
printRemainingMessages();
break;
case ResultWas::ExpressionFailed:
if (result.isOk()) {
printResultType(passedString());
} else {
printResultType(failedString());
}
printOriginalExpression();
printReconstructedExpression();
if (result.isOk()) {
printIssue(" # TODO");
}
printRemainingMessages();
break;
case ResultWas::ThrewException:
printResultType( failedString() );
printIssue( "unexpected exception with message:" );
printMessage();
printExpressionWas();
printRemainingMessages();
break;
case ResultWas::FatalErrorCondition:
printResultType( failedString() );
printIssue( "fatal error condition with message:" );
printMessage();
printExpressionWas();
printRemainingMessages();
break;
case ResultWas::DidntThrowException:
printResultType( failedString() );
printIssue( "expected exception, got none" );
printExpressionWas();
printRemainingMessages();
break;
case ResultWas::Info:
printResultType( "info" );
printMessage();
printRemainingMessages();
break;
case ResultWas::Warning:
printResultType( "warning" );
printMessage();
printRemainingMessages();
break;
case ResultWas::ExplicitFailure:
printResultType( failedString() );
printIssue( "explicitly" );
printRemainingMessages( Colour::None );
break;
// These cases are here to prevent compiler warnings
case ResultWas::Unknown:
case ResultWas::FailureBit:
case ResultWas::Exception:
printResultType( "** internal error **" );
break;
}
}
private:
static Colour::Code dimColour() { return Colour::FileName; }
static const char* failedString() { return "not ok"; }
static const char* passedString() { return "ok"; }
void printSourceInfo() const {
Colour colourGuard( dimColour() );
stream << result.getSourceInfo() << ":";
}
void printResultType( std::string const& passOrFail ) const {
if( !passOrFail.empty() ) {
stream << passOrFail << ' ' << counter << " -";
}
}
void printIssue( std::string const& issue ) const {
stream << " " << issue;
}
void printExpressionWas() {
if( result.hasExpression() ) {
stream << ";";
{
Colour colour( dimColour() );
stream << " expression was:";
}
printOriginalExpression();
}
}
void printOriginalExpression() const {
if( result.hasExpression() ) {
stream << " " << result.getExpression();
}
}
void printReconstructedExpression() const {
if( result.hasExpandedExpression() ) {
{
Colour colour( dimColour() );
stream << " for: ";
}
std::string expr = result.getExpandedExpression();
std::replace( expr.begin(), expr.end(), '\n', ' ');
stream << expr;
}
}
void printMessage() {
if ( itMessage != messages.end() ) {
stream << " '" << itMessage->message << "'";
++itMessage;
}
}
void printRemainingMessages( Colour::Code colour = dimColour() ) {
if (itMessage == messages.end()) {
return;
}
// using messages.end() directly (or auto) yields compilation error:
std::vector<MessageInfo>::const_iterator itEnd = messages.end();
const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) );
{
Colour colourGuard( colour );
stream << " with " << pluralise( N, "message" ) << ":";
}
for(; itMessage != itEnd; ) {
// If this assertion is a warning ignore any INFO messages
if( printInfoMessages || itMessage->type != ResultWas::Info ) {
stream << " '" << itMessage->message << "'";
if ( ++itMessage != itEnd ) {
Colour colourGuard( dimColour() );
stream << " and";
}
}
}
}
private:
std::ostream& stream;
AssertionResult const& result;
std::vector<MessageInfo> messages;
std::vector<MessageInfo>::const_iterator itMessage;
bool printInfoMessages;
std::size_t counter;
};
void printTotals( const Totals& totals ) const {
if( totals.testCases.total() == 0 ) {
stream << "1..0 # Skipped: No tests ran.";
} else {
stream << "1.." << counter;
}
}
};
#ifdef CATCH_IMPL
TAPReporter::~TAPReporter() {}
#endif
CATCH_REGISTER_REPORTER( "tap", TAPReporter )
} // end namespace Catch
#endif // TWOBLUECUBES_CATCH_REPORTER_TAP_HPP_INCLUDED

View File

@@ -1,220 +0,0 @@
/*
* Created by Phil Nash on 19th December 2014
* Copyright 2014 Two Blue Cubes Ltd. All rights reserved.
*
* Distributed under the Boost Software License, Version 1.0. (See accompanying
* file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
#define TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED
// Don't #include any Catch headers here - we can assume they are already
// included before this header.
// This is not good practice in general but is necessary in this case so this
// file can be distributed as a single header that works with the main
// Catch single header.
#include <cstring>
#ifdef __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wpadded"
#endif
namespace Catch {
struct TeamCityReporter : StreamingReporterBase<TeamCityReporter> {
TeamCityReporter( ReporterConfig const& _config )
: StreamingReporterBase( _config )
{
m_reporterPrefs.shouldRedirectStdOut = true;
}
static std::string escape( std::string const& str ) {
std::string escaped = str;
replaceInPlace( escaped, "|", "||" );
replaceInPlace( escaped, "'", "|'" );
replaceInPlace( escaped, "\n", "|n" );
replaceInPlace( escaped, "\r", "|r" );
replaceInPlace( escaped, "[", "|[" );
replaceInPlace( escaped, "]", "|]" );
return escaped;
}
~TeamCityReporter() override;
static std::string getDescription() {
return "Reports test results as TeamCity service messages";
}
void skipTest( TestCaseInfo const& /* testInfo */ ) override {
}
void noMatchingTestCases( std::string const& /* spec */ ) override {}
void testGroupStarting( GroupInfo const& groupInfo ) override {
StreamingReporterBase::testGroupStarting( groupInfo );
stream << "##teamcity[testSuiteStarted name='"
<< escape( groupInfo.name ) << "']\n";
}
void testGroupEnded( TestGroupStats const& testGroupStats ) override {
StreamingReporterBase::testGroupEnded( testGroupStats );
stream << "##teamcity[testSuiteFinished name='"
<< escape( testGroupStats.groupInfo.name ) << "']\n";
}
void assertionStarting( AssertionInfo const& ) override {}
bool assertionEnded( AssertionStats const& assertionStats ) override {
AssertionResult const& result = assertionStats.assertionResult;
if( !result.isOk() ) {
ReusableStringStream msg;
if( !m_headerPrintedForThisSection )
printSectionHeader( msg.get() );
m_headerPrintedForThisSection = true;
msg << result.getSourceInfo() << "\n";
switch( result.getResultType() ) {
case ResultWas::ExpressionFailed:
msg << "expression failed";
break;
case ResultWas::ThrewException:
msg << "unexpected exception";
break;
case ResultWas::FatalErrorCondition:
msg << "fatal error condition";
break;
case ResultWas::DidntThrowException:
msg << "no exception was thrown where one was expected";
break;
case ResultWas::ExplicitFailure:
msg << "explicit failure";
break;
// We shouldn't get here because of the isOk() test
case ResultWas::Ok:
case ResultWas::Info:
case ResultWas::Warning:
CATCH_ERROR( "Internal error in TeamCity reporter" );
// These cases are here to prevent compiler warnings
case ResultWas::Unknown:
case ResultWas::FailureBit:
case ResultWas::Exception:
CATCH_ERROR( "Not implemented" );
}
if( assertionStats.infoMessages.size() == 1 )
msg << " with message:";
if( assertionStats.infoMessages.size() > 1 )
msg << " with messages:";
for( auto const& messageInfo : assertionStats.infoMessages )
msg << "\n \"" << messageInfo.message << "\"";
if( result.hasExpression() ) {
msg <<
"\n " << result.getExpressionInMacro() << "\n"
"with expansion:\n" <<
" " << result.getExpandedExpression() << "\n";
}
if( currentTestCaseInfo->okToFail() ) {
msg << "- failure ignore as test marked as 'ok to fail'\n";
stream << "##teamcity[testIgnored"
<< " name='" << escape( currentTestCaseInfo->name )<< "'"
<< " message='" << escape( msg.str() ) << "'"
<< "]\n";
}
else {
stream << "##teamcity[testFailed"
<< " name='" << escape( currentTestCaseInfo->name )<< "'"
<< " message='" << escape( msg.str() ) << "'"
<< "]\n";
}
}
stream.flush();
return true;
}
void sectionStarting( SectionInfo const& sectionInfo ) override {
m_headerPrintedForThisSection = false;
StreamingReporterBase::sectionStarting( sectionInfo );
}
void testCaseStarting( TestCaseInfo const& testInfo ) override {
m_testTimer.start();
StreamingReporterBase::testCaseStarting( testInfo );
stream << "##teamcity[testStarted name='"
<< escape( testInfo.name ) << "']\n";
stream.flush();
}
void testCaseEnded( TestCaseStats const& testCaseStats ) override {
StreamingReporterBase::testCaseEnded( testCaseStats );
if( !testCaseStats.stdOut.empty() )
stream << "##teamcity[testStdOut name='"
<< escape( testCaseStats.testInfo.name )
<< "' out='" << escape( testCaseStats.stdOut ) << "']\n";
if( !testCaseStats.stdErr.empty() )
stream << "##teamcity[testStdErr name='"
<< escape( testCaseStats.testInfo.name )
<< "' out='" << escape( testCaseStats.stdErr ) << "']\n";
stream << "##teamcity[testFinished name='"
<< escape( testCaseStats.testInfo.name ) << "' duration='"
<< m_testTimer.getElapsedMilliseconds() << "']\n";
stream.flush();
}
private:
void printSectionHeader( std::ostream& os ) {
assert( !m_sectionStack.empty() );
if( m_sectionStack.size() > 1 ) {
os << getLineOfChars<'-'>() << "\n";
std::vector<SectionInfo>::const_iterator
it = m_sectionStack.begin()+1, // Skip first section (test case)
itEnd = m_sectionStack.end();
for( ; it != itEnd; ++it )
printHeaderString( os, it->name );
os << getLineOfChars<'-'>() << "\n";
}
SourceLineInfo lineInfo = m_sectionStack.front().lineInfo;
if( !lineInfo.empty() )
os << lineInfo << "\n";
os << getLineOfChars<'.'>() << "\n\n";
}
// if string has a : in first line will set indent to follow it on
// subsequent lines
static void printHeaderString( std::ostream& os, std::string const& _string, std::size_t indent = 0 ) {
std::size_t i = _string.find( ": " );
if( i != std::string::npos )
i+=2;
else
i = 0;
os << Column( _string )
.indent( indent+i)
.initialIndent( indent ) << "\n";
}
private:
bool m_headerPrintedForThisSection = false;
Timer m_testTimer;
};
#ifdef CATCH_IMPL
TeamCityReporter::~TeamCityReporter() {}
#endif
CATCH_REGISTER_REPORTER( "teamcity", TeamCityReporter )
} // end namespace Catch
#ifdef __clang__
# pragma clang diagnostic pop
#endif
#endif // TWOBLUECUBES_CATCH_REPORTER_TEAMCITY_HPP_INCLUDED

20
tests/data/20mm_cube.obj Normal file
View File

@@ -0,0 +1,20 @@
v 20.000000 20.000000 0.000000
v 20.000000 0.000000 0.000000
v 0.000000 0.000000 0.000000
v 0.000000 20.000000 0.000000
v 20.000000 20.000000 20.000000
v 0.000000 20.000000 20.000000
v 0.000000 0.000000 20.000000
v 20.000000 0.000000 20.000000
f 1 2 3
f 1 3 4
f 5 6 7
f 5 7 8
f 1 5 8
f 1 8 2
f 2 8 7
f 2 7 3
f 3 7 6
f 3 6 4
f 5 1 4
f 5 4 6

20
tests/data/2x20x10.obj Normal file
View File

@@ -0,0 +1,20 @@
v 2.000000 20.000000 0.000000
v 2.000000 0.000000 0.000000
v 0.000000 0.000000 0.000000
v 0.000000 20.000000 0.000000
v 2.000000 20.000000 10.000000
v 0.000000 20.000000 10.000000
v 0.000000 0.000000 10.000000
v 2.000000 0.000000 10.000000
f 1 2 3
f 1 3 4
f 5 6 7
f 5 7 8
f 1 5 8
f 1 8 2
f 2 8 7
f 2 7 3
f 3 7 6
f 3 6 4
f 5 1 4
f 5 4 6

1867
tests/data/A.obj Normal file

File diff suppressed because it is too large Load Diff

2504
tests/data/A_upsidedown.obj Normal file

File diff suppressed because it is too large Load Diff

76
tests/data/U_overhang.obj Normal file
View File

@@ -0,0 +1,76 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object U_overhang.obj
#
# Vertices: 16
# Faces: 28
#
####
vn 1.570797 1.570796 1.570796
v 10.000000 10.000000 11.000000
vn 4.712389 1.570796 -1.570796
v 10.000000 1.000000 10.000000
vn 1.570796 1.570796 -1.570796
v 10.000000 10.000000 10.000000
vn 1.570796 -1.570796 1.570796
v 10.000000 0.000000 11.000000
vn 4.712389 1.570796 1.570796
v 10.000000 1.000000 1.000000
vn 1.570797 1.570796 -1.570796
v 10.000000 10.000000 0.000000
vn 1.570796 1.570796 1.570796
v 10.000000 10.000000 1.000000
vn 1.570796 -1.570796 -1.570796
v 10.000000 0.000000 0.000000
vn -1.570796 1.570796 1.570796
v 0.000000 10.000000 1.000000
vn -4.712389 1.570796 1.570796
v 0.000000 1.000000 1.000000
vn -1.570796 -1.570796 -1.570796
v 0.000000 0.000000 0.000000
vn -1.570797 1.570796 -1.570796
v 0.000000 10.000000 0.000000
vn -4.712389 1.570796 -1.570796
v 0.000000 1.000000 10.000000
vn -1.570797 1.570796 1.570796
v 0.000000 10.000000 11.000000
vn -1.570796 1.570796 -1.570796
v 0.000000 10.000000 10.000000
vn -1.570796 -1.570796 1.570796
v 0.000000 0.000000 11.000000
# 16 vertices, 0 vertices normals
f 1//1 2//2 3//3
f 2//2 4//4 5//5
f 4//4 2//2 1//1
f 5//5 6//6 7//7
f 5//5 8//8 6//6
f 8//8 5//5 4//4
f 9//9 5//5 7//7
f 5//5 9//9 10//10
f 11//11 6//6 8//8
f 6//6 11//11 12//12
f 12//12 10//10 9//9
f 10//10 11//11 13//13
f 11//11 10//10 12//12
f 13//13 14//14 15//15
f 13//13 16//16 14//14
f 16//16 13//13 11//11
f 6//6 9//9 7//7
f 9//9 6//6 12//12
f 11//11 4//4 16//16
f 4//4 11//11 8//8
f 13//13 3//3 2//2
f 3//3 13//13 15//15
f 5//5 13//13 2//2
f 13//13 5//5 10//10
f 14//14 4//4 1//1
f 4//4 14//14 16//16
f 3//3 14//14 1//1
f 14//14 3//3 15//15
# 28 faces, 0 coords texture
# End of File

68
tests/data/V.obj Normal file
View File

@@ -0,0 +1,68 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object V.obj
#
# Vertices: 14
# Faces: 24
#
####
vn -0.835484 -0.584839 0.000000
v 41.480000 44.020000 0.000000
vn 0.000000 0.000000 1.768192
v 51.480000 44.020000 15.000000
vn 0.000000 0.000000 0.960070
v 41.480000 44.020000 15.000000
vn 0.000000 0.000000 0.413330
v 55.480000 24.020000 15.000000
vn 0.000000 -0.982794 0.000000
v 65.480003 24.020000 15.000000
vn -0.000000 -0.000000 -2.776433
v 60.480000 31.162861 0.000000
vn -0.000000 -0.000000 -0.099828
v 79.480003 44.020000 0.000000
vn -0.000000 -0.000000 -0.265332
v 55.480000 24.020000 0.000000
vn 0.661947 0.463363 0.000000
v 51.480000 44.020000 0.000000
vn 0.624900 0.437430 0.000000
v 60.480000 31.162861 15.000000
vn -0.661947 0.463363 0.000000
v 69.480003 44.020000 15.000000
vn -1.286846 0.900793 0.000000
v 69.480003 44.020000 0.000000
vn 0.000000 0.982794 0.000000
v 79.480003 44.020000 15.000000
vn 0.451362 -0.315954 0.000000
v 65.480003 24.020000 0.000000
# 14 vertices, 0 vertices normals
f 3//3 1//1 4//4
f 4//4 1//1 8//8
f 1//1 3//3 2//2
f 9//9 1//1 2//2
f 2//2 3//3 4//4
f 10//10 2//2 4//4
f 5//5 10//10 4//4
f 13//13 11//11 5//5
f 11//11 10//10 5//5
f 4//4 8//8 5//5
f 5//5 8//8 14//14
f 1//1 9//9 8//8
f 8//8 9//9 6//6
f 6//6 12//12 7//7
f 6//6 7//7 8//8
f 8//8 7//7 14//14
f 9//9 2//2 10//10
f 6//6 9//9 10//10
f 10//10 11//11 6//6
f 6//6 11//11 12//12
f 12//12 11//11 13//13
f 7//7 12//12 13//13
f 13//13 5//5 14//14
f 7//7 13//13 14//14
# 24 faces, 0 coords texture
# End of File

38
tests/data/V_standing.obj Normal file
View File

@@ -0,0 +1,38 @@
v -14.000000 0.000000 20.000000
v -14.000000 15.000000 20.000000
v 0.000000 0.000000 0.000000
v 0.000000 15.000000 0.000000
v -4.000000 0.000000 20.000000
v -4.000000 15.000000 20.000000
v 5.000000 0.000000 7.142860
v 10.000000 0.000000 0.000000
v 24.000000 0.000000 20.000000
v 14.000000 0.000000 20.000000
v 10.000000 15.000000 0.000000
v 5.000000 15.000000 7.142860
v 14.000000 15.000000 20.000000
v 24.000000 15.000000 20.000000
f 1 2 3
f 3 2 4
f 2 1 5
f 6 2 5
f 5 1 3
f 7 5 3
f 8 7 3
f 9 10 8
f 10 7 8
f 3 4 8
f 8 4 11
f 2 6 4
f 4 6 12
f 12 13 14
f 12 14 4
f 4 14 11
f 6 5 7
f 12 6 7
f 7 10 12
f 12 10 13
f 13 10 9
f 14 13 9
f 9 8 11
f 14 9 11

44
tests/data/bridge.obj Normal file
View File

@@ -0,0 +1,44 @@
v 75.000000 84.500000 8.000000
v 125.000000 84.500000 8.000000
v 75.000000 94.500000 8.000000
v 120.000000 84.500000 5.000000
v 125.000000 94.500000 8.000000
v 75.000000 84.500000 0.000000
v 80.000000 84.500000 5.000000
v 125.000000 84.500000 0.000000
v 125.000000 94.500000 0.000000
v 80.000000 94.500000 5.000000
v 75.000000 94.500000 0.000000
v 120.000000 94.500000 5.000000
v 120.000000 84.500000 0.000000
v 80.000000 94.500000 0.000000
v 80.000000 84.500000 0.000000
v 120.000000 94.500000 0.000000
f 1 2 3
f 2 1 4
f 3 2 5
f 3 6 1
f 1 7 4
f 2 4 8
f 2 9 5
f 5 10 3
f 11 6 3
f 6 7 1
f 7 12 4
f 4 13 8
f 8 9 2
f 5 9 12
f 5 12 10
f 10 11 3
f 11 14 6
f 15 7 6
f 10 12 7
f 12 13 4
f 13 9 8
f 12 9 16
f 14 11 10
f 6 14 15
f 15 14 7
f 7 14 10
f 16 13 12
f 16 9 13

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg">
<path fill="#ed6b21" d="m 68.34,199.06 c 0,6.2 -5.04,11.24 -11.24,11.24 l 0.03507,-11.27073 z" />
<path fill="#808080" d="m 68.34,199.06 5.608583,1.97096 -14.648722,13.67 L 57.1,210.3 c 6.2,0 11.24,-5.04 11.24,-11.24 z" />
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@@ -0,0 +1,72 @@
v -10.000000 -10.000000 -5.000000
v -10.000000 -10.000000 5.000000
v -10.000000 10.000000 -5.000000
v -10.000000 10.000000 5.000000
v 10.000000 -10.000000 -5.000000
v 10.000000 -10.000000 5.000000
v -5.000000 -5.000000 -5.000000
v 5.000000 -5.000000 -5.000000
v 5.000000 5.000000 -5.000000
v 5.000000 10.000000 -5.000000
v -5.000000 5.000000 -5.000000
v 0.000000 5.000000 -5.000000
v 5.000000 0.000000 -5.000000
v 0.000000 0.000000 -5.000000
v 10.000000 5.000000 -5.000000
v 5.000000 10.000000 5.000000
v -5.000000 -5.000000 5.000000
v 5.000000 0.000000 5.000000
v 5.000000 -5.000000 5.000000
v -5.000000 5.000000 5.000000
v 10.000000 5.000000 5.000000
v 5.000000 5.000000 5.000000
v 0.000000 5.000000 5.000000
v 0.000000 0.000000 5.000000
f 1 2 3
f 3 2 4
f 2 1 5
f 6 2 5
f 7 8 5
f 9 3 10
f 11 3 12
f 12 13 14
f 1 3 11
f 1 11 7
f 1 7 5
f 12 3 9
f 5 8 13
f 5 13 9
f 13 12 9
f 15 5 9
f 3 4 10
f 10 4 16
f 17 2 6
f 18 19 6
f 20 4 17
f 21 22 6
f 19 17 6
f 4 2 17
f 23 4 20
f 22 4 23
f 22 18 6
f 22 23 18
f 22 16 4
f 24 18 23
f 6 5 15
f 21 6 15
f 21 15 22
f 22 15 9
f 10 16 22
f 9 10 22
f 11 20 17
f 7 11 17
f 12 23 20
f 11 12 20
f 14 24 12
f 12 24 23
f 24 14 13
f 18 24 13
f 18 13 19
f 19 13 8
f 19 8 17
f 17 8 7

View File

@@ -0,0 +1,112 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object cube_with_concave_hole_enlarged.obj
#
# Vertices: 24
# Faces: 48
#
####
vn 1.107149 0.000000 0.000000
v 76.634163 17.865837 0.000000
vn 0.000000 0.000000 0.321750
v 68.557083 25.942917 16.154167
vn 0.000000 0.000000 2.356194
v 52.402920 25.942917 16.154167
vn 0.000000 0.000000 0.321751
v 76.634163 50.174164 16.154167
vn 0.000000 0.000000 2.034444
v 68.557083 42.097084 16.154167
vn 0.000000 0.000000 0.463648
v 76.634163 17.865837 16.154167
vn 0.000000 0.000000 0.785398
v 52.402920 34.020000 16.154167
vn 0.000000 0.000000 0.321750
v 44.325836 17.865837 16.154167
vn 0.000000 0.982794 0.000000
v 52.402920 50.174164 16.154167
vn 0.000000 1.570796 0.000000
v 52.402920 50.174164 0.000000
vn 0.000000 0.000000 -0.463648
v 52.402920 34.020000 0.000000
vn 0.000000 0.000000 -0.321751
v 44.325836 17.865837 0.000000
vn 0.000000 0.000000 -0.463648
v 76.634163 50.174164 0.000000
vn 0.000000 0.000000 -1.570796
v 44.325836 42.097084 0.000000
vn -0.588003 0.000000 0.000000
v 44.325836 42.097084 16.154167
vn 0.000000 1.570796 0.000000
v 52.402920 42.097084 16.154167
vn -1.107149 0.000000 0.000000
v 52.402920 42.097084 0.000000
vn 0.000000 -0.463648 0.000000
v 60.480000 42.097084 16.154167
vn 0.000000 -1.107149 0.000000
v 68.557083 42.097084 0.000000
vn 1.570796 0.000000 0.000000
v 60.480000 42.097084 0.000000
vn 0.000000 -0.463647 0.000000
v 60.480000 34.020000 0.000000
vn 0.000000 -1.570796 0.000000
v 60.480000 34.020000 16.154167
vn 1.107149 0.000000 0.000000
v 52.402920 25.942917 0.000000
vn 0.000000 0.785398 0.000000
v 68.557083 25.942917 0.000000
# 24 vertices, 0 vertices normals
f 6//6 1//1 4//4
f 4//4 1//1 13//13
f 1//1 6//6 8//8
f 12//12 1//1 8//8
f 2//2 3//3 8//8
f 16//16 4//4 9//9
f 5//5 4//4 18//18
f 18//18 7//7 22//22
f 6//6 4//4 5//5
f 6//6 5//5 2//2
f 6//6 2//2 8//8
f 18//18 4//4 16//16
f 8//8 3//3 7//7
f 8//8 7//7 16//16
f 7//7 18//18 16//16
f 15//15 8//8 16//16
f 4//4 13//13 9//9
f 9//9 13//13 10//10
f 24//24 1//1 12//12
f 11//11 23//23 12//12
f 19//19 13//13 24//24
f 14//14 17//17 12//12
f 23//23 24//24 12//12
f 13//13 1//1 24//24
f 20//20 13//13 19//19
f 17//17 13//13 20//20
f 17//17 11//11 12//12
f 17//17 20//20 11//11
f 17//17 10//10 13//13
f 21//21 11//11 20//20
f 12//12 8//8 15//15
f 14//14 12//12 15//15
f 14//14 15//15 17//17
f 17//17 15//15 16//16
f 9//9 10//10 17//17
f 16//16 9//9 17//17
f 5//5 19//19 24//24
f 2//2 5//5 24//24
f 18//18 20//20 19//19
f 5//5 18//18 19//19
f 22//22 21//21 18//18
f 18//18 21//21 20//20
f 21//21 22//22 7//7
f 11//11 21//21 7//7
f 11//11 7//7 23//23
f 23//23 7//7 3//3
f 23//23 3//3 24//24
f 24//24 3//3 2//2
# 48 faces, 0 coords texture
# End of File

View File

@@ -0,0 +1,112 @@
####
#
# OBJ File Generated by Meshlab
#
####
# Object cube_with_concave_hole_enlarged.obj
#
# Vertices: 24
# Faces: 48
#
####
vn 0.000000 0.000000 -1.570796
v 68.557083 17.865835 0.000000
vn 0.000000 0.000000 -1.107149
v 52.402916 17.865835 0.000000
vn 0.000000 -0.463648 0.000000
v 68.557083 17.865835 32.308331
vn 0.000000 -1.570796 0.000000
v 52.402916 17.865835 32.308331
vn 2.356194 0.000000 0.000000
v 68.557083 25.942917 24.231247
vn 1.570796 0.000000 0.000000
v 68.557083 50.174164 24.231247
vn 2.034444 0.000000 0.000000
v 68.557083 42.097084 8.077083
vn 2.677945 0.000000 0.000000
v 68.557083 34.020000 24.231247
vn 0.000000 1.570796 0.000000
v 68.557083 50.174164 0.000000
vn 0.000000 1.570796 0.000000
v 52.402916 50.174164 24.231247
vn -0.321750 0.000000 0.000000
v 52.402916 25.942917 8.077083
vn -1.570796 0.000000 0.000000
v 52.402916 42.097084 32.308331
vn -0.321751 0.000000 0.000000
v 52.402916 50.174164 0.000000
vn -2.356194 0.000000 0.000000
v 52.402916 42.097084 8.077083
vn -0.785398 0.000000 0.000000
v 52.402916 42.097084 16.154165
vn 0.000000 0.000000 0.588003
v 68.557083 42.097084 32.308331
vn 0.000000 1.107149 0.000000
v 52.402916 42.097084 24.231247
vn 0.000000 1.570796 0.000000
v 68.557083 42.097084 24.231247
vn 0.000000 0.000000 1.570796
v 68.557083 25.942917 8.077083
vn 0.000000 -0.463647 0.000000
v 68.557083 42.097084 16.154165
vn 0.000000 0.000000 -1.570796
v 68.557083 34.020000 16.154165
vn 0.000000 0.000000 -0.463648
v 52.402916 34.020000 16.154165
vn 0.000000 0.000000 -1.570796
v 52.402916 34.020000 24.231247
vn 0.000000 0.000000 -0.463648
v 52.402916 25.942917 24.231247
# 24 vertices, 0 vertices normals
f 1//1 2//2 9//9
f 9//9 2//2 13//13
f 2//2 1//1 3//3
f 4//4 2//2 3//3
f 19//19 5//5 3//3
f 18//18 9//9 6//6
f 7//7 9//9 20//20
f 20//20 8//8 21//21
f 1//1 9//9 7//7
f 1//1 7//7 19//19
f 1//1 19//19 3//3
f 20//20 9//9 18//18
f 3//3 5//5 8//8
f 3//3 8//8 18//18
f 8//8 20//20 18//18
f 16//16 3//3 18//18
f 9//9 13//13 6//6
f 6//6 13//13 10//10
f 11//11 2//2 4//4
f 23//23 24//24 4//4
f 14//14 13//13 11//11
f 12//12 17//17 4//4
f 24//24 11//11 4//4
f 13//13 2//2 11//11
f 15//15 13//13 14//14
f 17//17 13//13 15//15
f 17//17 23//23 4//4
f 17//17 15//15 23//23
f 17//17 10//10 13//13
f 22//22 23//23 15//15
f 4//4 3//3 16//16
f 12//12 4//4 16//16
f 12//12 16//16 17//17
f 17//17 16//16 18//18
f 6//6 10//10 17//17
f 18//18 6//6 17//17
f 7//7 14//14 11//11
f 19//19 7//7 11//11
f 20//20 15//15 14//14
f 7//7 20//20 14//14
f 21//21 22//22 20//20
f 20//20 22//22 15//15
f 22//22 21//21 8//8
f 23//23 22//22 8//8
f 23//23 8//8 24//24
f 24//24 8//8 5//5
f 24//24 5//5 11//11
f 11//11 5//5 19//19
# 48 faces, 0 coords texture
# End of File

View File

@@ -0,0 +1,48 @@
v 0.000000 0.000000 0.000000
v 0.000000 0.000000 10.000000
v 0.000000 20.000000 0.000000
v 0.000000 20.000000 10.000000
v 20.000000 0.000000 0.000000
v 20.000000 0.000000 10.000000
v 5.000000 5.000000 0.000000
v 15.000000 5.000000 0.000000
v 5.000000 15.000000 0.000000
v 20.000000 20.000000 0.000000
v 15.000000 15.000000 0.000000
v 20.000000 20.000000 10.000000
v 5.000000 5.000000 10.000000
v 5.000000 15.000000 10.000000
v 15.000000 5.000000 10.000000
v 15.000000 15.000000 10.000000
f 1 2 3
f 3 2 4
f 2 1 5
f 6 2 5
f 7 8 5
f 9 3 10
f 1 3 9
f 11 9 10
f 1 9 7
f 1 7 5
f 5 8 10
f 8 11 10
f 3 4 10
f 10 4 12
f 13 2 6
f 14 4 13
f 15 13 6
f 4 2 13
f 12 4 14
f 12 16 6
f 12 14 16
f 16 15 6
f 6 5 10
f 12 6 10
f 9 14 13
f 7 9 13
f 11 16 14
f 9 11 14
f 16 11 15
f 15 11 8
f 15 8 13
f 13 8 7

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

36702
tests/data/frog_legs.obj Normal file

File diff suppressed because it is too large Load Diff

86
tests/data/ipadstand.obj Normal file
View File

@@ -0,0 +1,86 @@
v 17.434467 -0.000000 9.500000
v 14.281480 10.000000 9.500000
v 0.000000 0.000000 9.500000
v 31.715948 10.000000 9.500000
v 62.234474 0.000000 20.000000
v 31.715948 10.000000 20.000000
v 17.434467 -0.000000 20.000000
v 62.234474 10.000000 20.000000
v 98.207970 10.000000 0.000000
v 98.207970 0.000000 10.000000
v 98.207970 0.000000 0.000000
v 98.207970 10.000000 20.000000
v 98.207970 0.000000 20.000000
v 81.660965 -0.000000 10.000000
v 90.054985 10.000000 10.000000
v 78.507980 10.000000 10.000000
v 93.207970 0.000000 10.000000
v 14.281480 10.000000 20.000000
v 0.000000 0.000000 20.000000
v 87.434471 0.000000 20.000000
v 84.281479 10.000000 20.000000
v 0.000000 10.000000 20.000000
v 0.000000 0.000000 0.000000
v 0.000000 10.000000 0.000000
v 62.234474 0.000000 30.000000
v 66.960976 10.000000 30.000000
v 62.234474 10.000000 30.000000
v 70.113960 0.000000 30.000000
v 67.705338 10.000000 28.710720
v 71.678711 0.000000 27.289770
f 1 2 3
f 2 1 4
f 5 6 7
f 6 5 8
f 9 10 11
f 10 12 13
f 12 10 9
f 14 15 16
f 15 14 17
f 18 3 2
f 3 18 19
f 20 12 21
f 12 20 13
f 18 22 19
f 22 3 19
f 3 22 23
f 23 22 24
f 9 23 24
f 23 9 11
f 25 26 27
f 26 25 28
f 24 2 9
f 2 24 22
f 2 22 18
f 6 16 4
f 16 6 8
f 16 8 29
f 29 8 27
f 29 27 26
f 9 15 12
f 15 9 4
f 4 9 2
f 15 4 16
f 12 15 21
f 27 5 25
f 5 27 8
f 13 17 10
f 17 13 20
f 30 5 14
f 5 30 25
f 25 30 28
f 10 23 11
f 23 10 1
f 1 10 17
f 1 17 14
f 1 14 7
f 7 14 5
f 3 23 1
f 20 15 17
f 15 20 21
f 16 30 14
f 30 26 28
f 26 30 16
f 26 16 29
f 7 4 1
f 4 7 6

179
tests/data/overhang.obj Normal file
View File

@@ -0,0 +1,179 @@
v 1364.685059 614.398010 20.002499
v 1389.685059 614.398010 20.002499
v 1377.185059 589.398987 20.002499
v 1389.685059 589.398987 20.002499
v 1389.685059 564.398987 20.001499
v 1364.685059 589.398987 20.002499
v 1364.685059 564.398987 20.001499
v 1360.935059 589.398987 17.001499
v 1360.935059 585.646973 17.001499
v 1357.185059 564.398987 17.001499
v 1364.685059 589.398987 17.001499
v 1364.685059 571.899963 17.001499
v 1364.685059 564.398987 17.001499
v 1348.436035 564.398987 17.001499
v 1352.809082 589.398987 17.001499
v 1357.184082 589.398987 17.001499
v 1357.183105 614.398010 17.001499
v 1364.685059 606.895996 17.001499
v 1364.685059 614.398010 17.001499
v 1352.186035 564.398987 20.001499
v 1363.654053 589.398987 23.300499
v 1359.467041 589.398987 23.300499
v 1358.371094 564.398987 23.300499
v 1385.561035 564.398987 23.300499
v 1373.063110 589.398987 23.300499
v 1368.808105 564.398987 23.300499
v 1387.623047 589.398987 23.300499
v 1387.623047 585.276001 23.300499
v 1389.685059 589.398987 23.300499
v 1389.685059 572.645996 23.300499
v 1389.685059 564.398987 23.300499
v 1367.777100 589.398987 23.300499
v 1366.747070 564.398987 23.300499
v 1354.312012 589.398987 23.300499
v 1352.186035 564.398987 23.300499
v 1389.685059 614.398010 23.300499
v 1377.317017 614.398010 23.300499
v 1381.439087 589.398987 23.300499
v 1368.807007 614.398010 23.300499
v 1368.808105 589.398987 23.300499
v 1356.439087 614.398010 23.300499
v 1357.405029 589.398987 23.300499
v 1360.562012 614.398010 23.300499
v 1348.705078 614.398010 23.300499
v 1350.445068 589.398987 23.300499
v 1389.685059 606.153015 23.300499
v 1347.352051 589.398987 23.300499
v 1346.560059 589.398987 23.300499
v 1346.560059 594.159912 17.001499
v 1346.560059 589.398987 17.001499
v 1346.560059 605.250427 23.300499
v 1346.560059 614.398010 23.300499
v 1346.560059 614.398010 20.825829
v 1346.560059 614.398010 17.001499
v 1346.560059 564.398987 19.101339
v 1346.560059 567.548584 23.300499
v 1346.560059 564.398987 17.002033
v 1346.560059 564.398987 23.001850
v 1346.560059 564.398987 23.300499
v 1346.560059 575.118958 17.001499
v 1346.560059 574.754028 23.300499
f 1 2 3
f 3 4 5
f 3 6 1
f 5 7 3
f 3 7 6
f 3 2 4
f 8 9 10
f 11 10 9
f 12 10 11
f 13 10 12
f 10 14 15
f 8 16 17
f 11 18 1
f 11 1 6
f 13 12 7
f 19 17 1
f 7 20 14
f 7 14 10
f 10 13 7
f 18 19 1
f 12 11 6
f 12 6 7
f 15 17 16
f 18 8 19
f 17 19 8
f 15 16 10
f 8 10 16
f 8 18 9
f 11 9 18
f 21 22 23
f 24 25 26
f 27 24 28
f 29 28 24
f 30 29 24
f 31 30 24
f 26 32 33
f 23 34 35
f 36 37 38
f 25 39 40
f 22 41 42
f 39 43 21
f 34 44 45
f 7 5 24
f 7 24 26
f 37 36 2
f 2 1 39
f 2 39 37
f 30 31 5
f 26 33 7
f 41 43 1
f 36 46 2
f 5 4 29
f 5 29 30
f 4 2 46
f 4 46 29
f 23 35 20
f 20 7 33
f 20 33 23
f 43 39 1
f 31 24 5
f 1 17 44
f 1 44 41
f 25 38 37
f 39 25 37
f 25 24 38
f 38 24 27
f 23 33 21
f 21 33 32
f 34 42 41
f 44 34 41
f 46 36 27
f 38 27 36
f 34 45 35
f 45 44 47
f 21 43 22
f 41 22 43
f 32 40 39
f 21 32 39
f 34 23 42
f 22 42 23
f 32 26 40
f 25 40 26
f 27 28 46
f 29 46 28
f 48 49 50
f 48 51 49
f 52 49 51
f 53 49 52
f 54 49 53
f 55 56 57
f 58 56 55
f 59 56 58
f 50 60 48
f 61 57 56
f 60 57 61
f 61 48 60
f 49 54 17
f 57 14 20
f 55 57 20
f 57 60 14
f 60 50 15
f 60 15 14
f 50 49 17
f 50 17 15
f 45 47 61
f 45 61 56
f 52 51 44
f 20 35 59
f 20 59 58
f 54 53 17
f 44 17 53
f 44 53 52
f 58 55 20
f 48 61 47
f 56 59 35
f 56 35 45
f 51 48 47
f 51 47 44

5981
tests/data/prusaparts.cpp Normal file

File diff suppressed because it is too large Load Diff

14
tests/data/prusaparts.hpp Normal file
View File

@@ -0,0 +1,14 @@
#ifndef PRUSAPARTS_H
#define PRUSAPARTS_H
#include <vector>
#include <libslic3r/ExPolygon.hpp>
using TestData = std::vector<Slic3r::Polygon>;
using TestDataEx = std::vector<Slic3r::ExPolygons>;
extern const TestData PRUSA_PART_POLYGONS;
extern const TestData PRUSA_STEGOSAUR_POLYGONS;
extern const TestDataEx PRUSA_PART_POLYGONS_EX;
#endif // PRUSAPARTS_H

11
tests/data/pyramid.obj Normal file
View File

@@ -0,0 +1,11 @@
v 10.000000 10.000000 40.000000
v 0.000000 0.000000 0.000000
v 20.000000 0.000000 0.000000
v 20.000000 20.000000 0.000000
v 0.000000 20.000000 0.000000
f 1 2 3
f 1 4 5
f 4 2 5
f 2 4 3
f 4 1 3
f 5 2 1

View File

@@ -0,0 +1,46 @@
v 39.349007 -54.069000 -199.819000
v 39.489006 -54.029007 -199.815002
v 39.419006 -53.993011 -199.769012
v 39.629005 -53.975006 -199.815002
v 39.639008 -53.947006 -199.805023
v 39.651001 -53.919006 -199.795013
v 39.807007 -53.863007 -199.796997
v 39.729004 -53.891006 -199.796997
v 39.727005 -53.935013 -199.813019
v 39.767006 -53.899002 -199.805023
v 39.871002 -53.835007 -199.801025
v 39.443001 -53.829010 -199.878998
v 39.523003 -53.965012 -199.827026
v 39.807007 -53.863007 -199.796997
v 39.833008 -53.723007 -199.723022
v 39.759003 -53.822998 -199.822998
v 39.867004 -53.845001 -199.805023
v 39.937004 -53.805008 -199.805023
f 1 2 3
f 4 5 2
f 2 6 3
f 7 8 4
f 9 10 4
f 10 9 11
f 12 2 1
f 13 6 4
f 13 4 2
f 8 7 9
f 6 9 4
f 6 14 15
f 16 14 6
f 17 18 9
f 3 6 15
f 12 16 6
f 12 6 13
f 12 13 2
f 5 4 8
f 6 8 9
f 5 6 2
f 6 5 8
f 17 9 7
f 7 11 17
f 18 11 9
f 11 18 17
f 10 7 4
f 7 10 11

204
tests/data/sloping_hole.obj Normal file
View File

@@ -0,0 +1,204 @@
v -20.000000 -20.000000 -5.000000
v -20.000000 -20.000000 5.000000
v -20.000000 20.000000 -5.000000
v -20.000000 20.000000 5.000000
v 20.000000 -20.000000 -5.000000
v 20.000000 -20.000000 5.000000
v 4.462940 7.431450 -5.000000
v 20.000000 20.000000 -5.000000
v -19.142099 0.000000 -5.000000
v -18.833099 -2.079120 -5.000000
v -17.919500 -4.067370 -5.000000
v -16.441200 -5.877850 -5.000000
v -14.462900 -7.431450 -5.000000
v -12.071100 -8.660250 -5.000000
v -9.370160 -9.510560 -5.000000
v -3.521740 -9.945220 -5.000000
v -6.478260 -9.945220 -5.000000
v -0.629840 -9.510560 -5.000000
v 2.071070 -8.660250 -5.000000
v 6.441230 -5.877850 -5.000000
v 4.462940 -7.431450 -5.000000
v -12.071100 8.660250 -5.000000
v -9.370160 9.510560 -5.000000
v 7.919480 -4.067370 -5.000000
v 8.833100 -2.079120 -5.000000
v -6.478260 9.945220 -5.000000
v -0.629840 9.510560 -5.000000
v 2.071070 8.660250 -5.000000
v 9.142140 0.000000 -5.000000
v 8.833100 2.079120 -5.000000
v -3.521740 9.945220 -5.000000
v 7.919480 4.067370 -5.000000
v 6.441230 5.877850 -5.000000
v -14.462900 7.431450 -5.000000
v -16.441200 5.877850 -5.000000
v -17.919500 4.067370 -5.000000
v -18.833099 2.079120 -5.000000
v 20.000000 20.000000 5.000000
v 3.521740 -9.945220 5.000000
v -8.833100 -2.079120 5.000000
v -9.142140 0.000000 5.000000
v -8.833100 2.079120 5.000000
v 6.478260 -9.945220 5.000000
v -7.919480 4.067370 5.000000
v -6.441230 5.877850 5.000000
v -4.462940 7.431450 5.000000
v -2.071070 8.660250 5.000000
v 0.629840 9.510560 5.000000
v 12.071100 -8.660250 5.000000
v 9.370160 -9.510560 5.000000
v 3.521740 9.945220 5.000000
v 6.478260 9.945220 5.000000
v 9.370160 9.510560 5.000000
v 12.071100 8.660250 5.000000
v 14.462900 7.431450 5.000000
v 16.441200 -5.877850 5.000000
v 14.462900 -7.431450 5.000000
v 16.441200 5.877850 5.000000
v 17.919500 4.067370 5.000000
v 18.833099 -2.079120 5.000000
v 17.919500 -4.067370 5.000000
v 18.833099 2.079120 5.000000
v 19.142099 0.000000 5.000000
v 0.629840 -9.510560 5.000000
v -2.071070 -8.660250 5.000000
v -4.462940 -7.431450 5.000000
v -6.441230 -5.877850 5.000000
v -7.919480 -4.067370 5.000000
f 1 2 3
f 3 2 4
f 2 1 5
f 6 2 5
f 7 3 8
f 1 3 9
f 1 9 10
f 1 10 11
f 1 11 12
f 1 12 13
f 1 13 14
f 1 14 5
f 14 15 5
f 16 5 17
f 18 5 16
f 19 5 18
f 20 5 21
f 19 21 5
f 22 3 23
f 5 20 24
f 5 24 8
f 24 25 8
f 23 3 26
f 27 3 28
f 29 30 8
f 26 3 31
f 30 32 8
f 31 3 27
f 32 33 8
f 28 3 7
f 33 7 8
f 29 8 25
f 34 3 22
f 35 3 34
f 36 3 35
f 37 3 36
f 9 3 37
f 17 5 15
f 3 4 8
f 8 4 38
f 39 2 6
f 4 2 40
f 4 40 41
f 4 41 42
f 43 39 6
f 4 42 44
f 4 44 45
f 38 4 46
f 38 46 47
f 38 47 48
f 49 50 6
f 38 48 51
f 50 43 6
f 38 51 52
f 38 52 53
f 38 53 54
f 38 54 55
f 56 57 6
f 38 55 58
f 38 58 59
f 60 61 6
f 38 59 62
f 38 63 6
f 38 62 63
f 63 60 6
f 61 56 6
f 64 2 39
f 65 2 64
f 66 2 65
f 67 2 66
f 68 2 67
f 40 2 68
f 45 46 4
f 57 49 6
f 6 5 8
f 38 6 8
f 42 41 37
f 37 41 9
f 40 10 41
f 41 10 9
f 44 42 36
f 36 42 37
f 45 44 35
f 35 44 36
f 34 46 45
f 35 34 45
f 22 47 46
f 34 22 46
f 23 48 47
f 22 23 47
f 26 51 48
f 23 26 48
f 31 52 51
f 26 31 51
f 27 53 52
f 31 27 52
f 28 54 53
f 27 28 53
f 7 55 54
f 28 7 54
f 33 58 55
f 7 33 55
f 32 59 58
f 33 32 58
f 30 62 59
f 32 30 59
f 29 63 62
f 30 29 62
f 60 63 29
f 25 60 29
f 61 60 25
f 24 61 25
f 56 61 24
f 20 56 24
f 56 20 57
f 57 20 21
f 57 21 49
f 49 21 19
f 49 19 50
f 50 19 18
f 50 18 43
f 43 18 16
f 43 16 39
f 39 16 17
f 39 17 64
f 64 17 15
f 64 15 65
f 65 15 14
f 65 14 66
f 66 14 13
f 66 13 67
f 67 13 12
f 67 12 68
f 68 12 11
f 68 11 40
f 40 11 10

View File

@@ -0,0 +1,14 @@
v 6.000589 -22.998209 0.000000
v 22.001024 -49.999874 0.000000
v -9.999578 -49.999870 0.000000
v 6.000714 -32.237164 28.001925
v 11.167055 -37.972702 18.960167
v 6.000602 -26.539246 10.732185
f 1 2 3
f 4 5 6
f 3 2 5
f 3 5 4
f 3 4 6
f 3 6 1
f 6 5 2
f 6 2 1

View File

@@ -0,0 +1,144 @@
v 66.713348 104.286667 0.000000
v 66.713348 95.713333 0.000000
v 65.666687 94.666672 0.000000
v 75.286682 95.713333 0.000000
v 76.333344 105.333336 0.000000
v 76.333344 94.666672 0.000000
v 65.666687 105.333328 0.000000
v 75.286682 104.286667 0.000000
v 71.106682 104.586662 2.800000
v 66.413353 104.586662 2.800000
v 75.586685 104.586662 2.800000
v 66.413353 99.893333 2.800000
v 66.413353 95.413338 2.800000
v 71.106682 95.413338 2.800000
v 75.586685 95.413338 2.800000
v 75.586685 100.106667 2.800000
v 74.540016 103.540001 2.800000
v 70.032013 103.540001 2.800000
v 67.460007 103.540001 2.800000
v 67.460007 100.968002 2.800000
v 67.460007 96.459999 2.800000
v 74.540016 99.031998 2.800000
v 74.540016 96.459999 2.800000
v 70.032013 96.459999 2.800000
v 123.666718 94.666672 0.000000
v 134.333313 94.666672 0.000000
v 124.413361 95.413338 2.800000
v 129.106674 95.413338 2.800000
v 133.586670 95.413338 2.800000
v 123.666718 105.333328 0.000000
v 124.413361 104.586662 2.800000
v 124.413361 99.893333 2.800000
v 134.333313 105.333328 0.000000
v 129.106674 104.586662 2.800000
v 133.586670 104.586662 2.800000
v 133.586670 100.106667 2.800000
v 124.713318 104.286667 0.000000
v 124.713318 95.713333 0.000000
v 133.286713 95.713333 0.000000
v 133.286713 104.286667 0.000000
v 132.540024 103.540001 2.800000
v 128.032028 103.540009 2.800000
v 125.460007 103.540001 2.800000
v 125.460007 100.968002 2.800000
v 125.460007 96.459999 2.800000
v 132.540024 99.031998 2.800000
v 132.540024 96.459999 2.800000
v 128.032028 96.459999 2.800000
f 1 2 3
f 4 5 6
f 7 5 1
f 7 1 3
f 3 2 6
f 8 5 4
f 2 4 6
f 1 5 8
f 5 7 9
f 7 10 9
f 5 9 11
f 7 3 10
f 3 12 10
f 3 13 12
f 3 6 13
f 6 14 13
f 6 15 14
f 5 11 16
f 6 5 15
f 5 16 15
f 8 17 18
f 1 8 19
f 8 18 19
f 2 20 21
f 2 1 20
f 1 19 20
f 8 4 22
f 4 23 22
f 8 22 17
f 4 24 23
f 4 2 24
f 2 21 24
f 25 26 27
f 26 28 27
f 26 29 28
f 30 25 31
f 25 32 31
f 25 27 32
f 33 30 34
f 30 31 34
f 33 34 35
f 33 35 36
f 26 33 29
f 33 36 29
f 37 38 25
f 39 33 26
f 30 33 37
f 30 37 25
f 25 38 26
f 40 33 39
f 38 39 26
f 37 33 40
f 40 41 42
f 37 40 43
f 40 42 43
f 38 44 45
f 38 37 44
f 37 43 44
f 40 39 46
f 39 47 46
f 40 46 41
f 39 48 47
f 39 38 48
f 38 45 48
f 17 9 10
f 17 11 9
f 11 17 16
f 16 17 22
f 23 16 22
f 16 23 15
f 23 24 15
f 24 21 15
f 18 17 10
f 19 18 10
f 20 19 10
f 20 10 12
f 20 12 21
f 14 15 21
f 21 12 13
f 14 21 13
f 42 41 31
f 43 42 31
f 44 43 31
f 44 31 32
f 44 32 45
f 28 29 45
f 45 32 27
f 28 45 27
f 41 34 31
f 41 35 34
f 35 41 36
f 36 41 46
f 47 36 46
f 36 47 29
f 47 48 29
f 48 45 29

View File

@@ -14,7 +14,7 @@
using namespace Slic3r;
std::unique_ptr<CoolingBuffer> make_cooling_buffer(
GCode &gcode,
GCodeGenerator &gcode,
const DynamicPrintConfig &config = DynamicPrintConfig{},
const std::vector<unsigned int> &extruder_ids = { 0 })
{
@@ -65,7 +65,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
const double print_time = 100. / (3000. / 60.);
//FIXME slowdown_below_layer_time is rounded down significantly from 1.8s to 1s.
config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 0.999) } } });
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer("G1 F3000;_EXTRUDE_SET_SPEED\nG1 X100 E1", 0, true);
bool speed_not_altered = gcode.find("F3000") != gcode.npos;
@@ -83,7 +83,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
// Print time of gcode.
const double print_time = 50. / (2500. / 60.) + 100. / (3000. / 60.) + 4. / (400. / 60.);
config.set_deserialize_strict({ { "slowdown_below_layer_time", { int(print_time * 1.001) } } });
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode_src, 0, true);
THEN("speed is altered when elapsed time is lower than slowdown threshold") {
@@ -106,7 +106,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
{ "fan_below_layer_time" , int(print_time1 * 0.88) },
{ "slowdown_below_layer_time" , int(print_time1 * 0.99) }
});
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode1, 0, true);
bool fan_not_activated = gcode.find("M106") == gcode.npos;
@@ -119,7 +119,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
{ "fan_below_layer_time", { int(print_time2 + 1.), int(print_time2 + 1.) } },
{ "slowdown_below_layer_time", { int(print_time2 + 2.), int(print_time2 + 2.) } }
});
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config, { 0, 1 });
std::string gcode = buffer->process_layer(gcode1 + "T1\nG1 X0 E1 F3000\n", 0, true);
THEN("fan is activated for the 1st tool") {
@@ -134,7 +134,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
WHEN("G-code block 2") {
THEN("slowdown is computed on all objects printing at the same Z") {
config.set_deserialize_strict({ { "slowdown_below_layer_time", int(print_time2 * 0.99) } });
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
std::string gcode = buffer->process_layer(gcode2, 0, true);
bool ok = gcode.find("F3000") != gcode.npos;
@@ -145,7 +145,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
{ "fan_below_layer_time", int(print_time2 * 0.65) },
{ "slowdown_below_layer_time", int(print_time2 * 0.7) }
});
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
// use an elapsed time which is < the threshold but greater than it when summed twice
std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);
@@ -158,7 +158,7 @@ SCENARIO("Cooling unit tests", "[Cooling]") {
{ "fan_below_layer_time", int(print_time2 + 1) },
{ "slowdown_below_layer_time", int(print_time2 + 1) }
});
GCode gcodegen;
GCodeGenerator gcodegen;
auto buffer = make_cooling_buffer(gcodegen, config);
// use an elapsed time which is < the threshold but greater than it when summed twice
std::string gcode = buffer->process_layer(gcode2, 0, true) + buffer->process_layer(gcode2, 1, true);

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,7 @@ static inline Slic3r::Point random_point(float LO=-50, float HI=50)
// build a sample extrusion entity collection with random start and end points.
static Slic3r::ExtrusionPath random_path(size_t length = 20, float LO = -50, float HI = 50)
{
ExtrusionPath t { ExtrusionRole::Perimeter, 1.0, 1.0, 1.0 };
ExtrusionPath t{ ExtrusionAttributes{ ExtrusionRole::Perimeter, ExtrusionFlow{ 1.0, 1.0, 1.0 } } };
for (size_t j = 0; j < length; ++ j)
t.polyline.append(random_point(LO, HI));
return t;
@@ -37,9 +37,8 @@ static Slic3r::ExtrusionPaths random_paths(size_t count = 10, size_t length = 20
SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
GIVEN("Simple path") {
Slic3r::ExtrusionPath path{ ExtrusionRole::ExternalPerimeter };
path.polyline = { { 100, 100 }, { 200, 100 }, { 200, 200 } };
path.mm3_per_mm = 1.;
Slic3r::ExtrusionPath path{ { { 100, 100 }, { 200, 100 }, { 200, 200 } },
ExtrusionAttributes{ ExtrusionRole::ExternalPerimeter, ExtrusionFlow{ 1., -1.f, -1.f } } };
THEN("first point") {
REQUIRE(path.first_point() == path.polyline.front());
}
@@ -52,10 +51,7 @@ SCENARIO("ExtrusionPath", "[ExtrusionEntity]") {
static ExtrusionPath new_extrusion_path(const Polyline &polyline, ExtrusionRole role, double mm3_per_mm)
{
ExtrusionPath path(role);
path.polyline = polyline;
path.mm3_per_mm = 1.;
return path;
return { polyline, ExtrusionAttributes{ role, ExtrusionFlow{ mm3_per_mm, -1.f, -1.f } } };
}
SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
@@ -67,6 +63,7 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
loop.paths.emplace_back(new_extrusion_path(square.split_at_first_point(), ExtrusionRole::ExternalPerimeter, 1.));
THEN("polygon area") {
REQUIRE(loop.polygon().area() == Approx(square.area()));
REQUIRE(loop.area() == Approx(square.area()));
}
THEN("loop length") {
REQUIRE(loop.length() == Approx(square.length()));
@@ -110,6 +107,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
loop.paths.emplace_back(new_extrusion_path(polyline1, ExtrusionRole::ExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline2, ExtrusionRole::OverhangPerimeter, 1.));
THEN("area") {
REQUIRE(loop.area() == Approx(loop.polygon().area()));
}
double tot_len = polyline1.length() + polyline2.length();
THEN("length") {
REQUIRE(loop.length() == Approx(tot_len));
@@ -212,6 +212,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
loop.paths.emplace_back(new_extrusion_path(polyline3, ExtrusionRole::ExternalPerimeter, 1.));
loop.paths.emplace_back(new_extrusion_path(polyline4, ExtrusionRole::OverhangPerimeter, 1.));
double len = loop.length();
THEN("area") {
REQUIRE(loop.area() == Approx(loop.polygon().area()));
}
WHEN("splitting at vertex") {
Point point(4821067, 9321068);
if (! loop.split_at_vertex(point))
@@ -234,6 +237,9 @@ SCENARIO("ExtrusionLoop", "[ExtrusionEntity]")
Polyline { { 15896783, 15868739 }, { 24842049, 12117558 }, { 33853238, 15801279 }, { 37591780, 24780128 }, { 37591780, 24844970 },
{ 33853231, 33825297 }, { 24842049, 37509013 }, { 15896798, 33757841 }, { 12211841, 24812544 }, { 15896783, 15868739 } },
ExtrusionRole::ExternalPerimeter, 1.));
THEN("area") {
REQUIRE(loop.area() == Approx(loop.polygon().area()));
}
double len = loop.length();
THEN("split_at() preserves total length") {
loop.split_at({ 15896783, 15868739 }, false, 0);
@@ -378,23 +384,27 @@ TEST_CASE("ExtrusionEntityCollection: Chained path", "[ExtrusionEntity]") {
REQUIRE(chained == test.chained);
ExtrusionEntityCollection unchained_extrusions;
extrusion_entities_append_paths(unchained_extrusions.entities, test.unchained,
ExtrusionRole::InternalInfill, 0., 0.4f, 0.3f);
ExtrusionAttributes{ ExtrusionRole::InternalInfill, ExtrusionFlow{ 0., 0.4f, 0.3f } });
THEN("Chaining works") {
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.chained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
REQUIRE(chained_extrusions.size() == test.chained.size());
for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
const Points &p1 = test.chained[i].points;
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points;
Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
if (chained_extrusions[i].flipped())
std::reverse(p2.begin(), p2.end());
REQUIRE(p1 == p2);
}
}
THEN("Chaining produces no change with no_sort") {
unchained_extrusions.no_sort = true;
ExtrusionEntityCollection chained_extrusions = unchained_extrusions.chained_path_from(test.initial_point);
REQUIRE(chained_extrusions.entities.size() == test.unchained.size());
for (size_t i = 0; i < chained_extrusions.entities.size(); ++ i) {
ExtrusionEntityReferences chained_extrusions = chain_extrusion_references(unchained_extrusions, &test.initial_point);
REQUIRE(chained_extrusions.size() == test.unchained.size());
for (size_t i = 0; i < chained_extrusions.size(); ++ i) {
const Points &p1 = test.unchained[i].points;
const Points &p2 = dynamic_cast<const ExtrusionPath*>(chained_extrusions.entities[i])->polyline.points;
Points p2 = chained_extrusions[i].cast<ExtrusionPath>()->polyline.points;
if (chained_extrusions[i].flipped())
std::reverse(p2.begin(), p2.end());
REQUIRE(p1 == p2);
}
}

View File

@@ -5,9 +5,10 @@
#include "libslic3r/GCode.hpp"
using namespace Slic3r;
using namespace Slic3r::GCode::Impl;
SCENARIO("Origin manipulation", "[GCode]") {
Slic3r::GCode gcodegen;
Slic3r::GCodeGenerator gcodegen;
WHEN("set_origin to (10,0)") {
gcodegen.set_origin(Vec2d(10,0));
REQUIRE(gcodegen.origin() == Vec2d(10, 0));
@@ -20,3 +21,220 @@ SCENARIO("Origin manipulation", "[GCode]") {
}
}
}
struct ApproxEqualsPoints : public Catch::MatcherBase<Points> {
ApproxEqualsPoints(const Points& expected, unsigned tolerance): expected(expected), tolerance(tolerance) {}
bool match(const Points& points) const override {
if (points.size() != expected.size()) {
return false;
}
for (auto i = 0u; i < points.size(); ++i) {
const Point& point = points[i];
const Point& expected_point = this->expected[i];
if (
std::abs(point.x() - expected_point.x()) > this->tolerance
|| std::abs(point.y() - expected_point.y()) > this->tolerance
) {
return false;
}
}
return true;
}
std::string describe() const override {
std::stringstream ss;
ss << std::endl;
for (const Point& point : expected) {
ss << "(" << point.x() << ", " << point.y() << ")" << std::endl;
}
ss << "With tolerance: " << this->tolerance;
return "Equals " + ss.str();
}
private:
Points expected;
unsigned tolerance;
};
Points get_points(const std::vector<DistancedPoint>& result) {
Points result_points;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_points),
[](const DistancedPoint& point){
return point.point;
}
);
return result_points;
}
std::vector<double> get_distances(const std::vector<DistancedPoint>& result) {
std::vector<double> result_distances;
std::transform(
result.begin(),
result.end(),
std::back_inserter(result_distances),
[](const DistancedPoint& point){
return point.distance_from_start;
}
);
return result_distances;
}
TEST_CASE("Place points at distances - expected use", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 2})
};
std::vector<double> distances{0, 0.2, 0.5, 1 + std::sqrt(2)/2, 1 + std::sqrt(2) + 0.5, 100.0};
std::vector<DistancedPoint> result = slice_xy_path(line, distances);
REQUIRE_THAT(get_points(result), ApproxEqualsPoints(Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{0.2, 0}),
scaled(Vec2f{0.5, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0.5}),
scaled(Vec2f{2, 1}),
scaled(Vec2f{2, 1.5}),
scaled(Vec2f{2, 2})
}, 5));
REQUIRE_THAT(get_distances(result), Catch::Matchers::Approx(std::vector<double>{
distances[0], distances[1], distances[2], 1, distances[3], 1 + std::sqrt(2), distances[4], 2 + std::sqrt(2)
}));
}
TEST_CASE("Place points at distances - edge case", "[GCode]") {
std::vector<Point> line{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{2, 0})
};
std::vector<double> distances{0, 1, 1.5, 2};
Points result{get_points(slice_xy_path(line, distances))};
CHECK(result == Points{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
scaled(Vec2f{1.5, 0}),
scaled(Vec2f{2, 0})
});
}
TEST_CASE("Generate elevated travel", "[GCode]") {
std::vector<Point> xy_path{
scaled(Vec2f{0, 0}),
scaled(Vec2f{1, 0}),
};
std::vector<double> ensure_points_at_distances{0.2, 0.5};
Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})};
CHECK(result == Points3{
scaled(Vec3f{0, 0, 3.0}),
scaled(Vec3f{0.2, 0, 3.2}),
scaled(Vec3f{0.5, 0, 3.5}),
scaled(Vec3f{1, 0, 4.0})
});
}
TEST_CASE("Get first crossed line distance", "[GCode]") {
// A 2x2 square at 0, 0, with 1x1 square hole in its center.
ExPolygon square_with_hole{
{
scaled(Vec2f{-1, -1}),
scaled(Vec2f{1, -1}),
scaled(Vec2f{1, 1}),
scaled(Vec2f{-1, 1})
},
{
scaled(Vec2f{-0.5, -0.5}),
scaled(Vec2f{0.5, -0.5}),
scaled(Vec2f{0.5, 0.5}),
scaled(Vec2f{-0.5, 0.5})
}
};
// A 2x2 square above the previous square at (0, 3).
ExPolygon square_above{
{
scaled(Vec2f{-1, 2}),
scaled(Vec2f{1, 2}),
scaled(Vec2f{1, 4}),
scaled(Vec2f{-1, 4})
}
};
// Bottom-up travel intersecting the squares.
Lines travel{Polyline{
scaled(Vec2f{0, -2}),
scaled(Vec2f{0, -0.7}),
scaled(Vec2f{0, 0}),
scaled(Vec2f{0, 1}),
scaled(Vec2f{0, 1.3}),
scaled(Vec2f{0, 2.4}),
scaled(Vec2f{0, 4.5}),
scaled(Vec2f{0, 5}),
}.lines()};
// Try different cases by skipping lines in the travel.
AABBTreeLines::LinesDistancer<Linef> distancer = get_expolygons_distancer({square_with_hole, square_above});
CHECK(*get_first_crossed_line_distance(travel, distancer) == Approx(1));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7));
CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6));
CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer));
}
TEST_CASE("Generate regular polygon", "[GCode]") {
const unsigned points_count{32};
const Point centroid{scaled(Vec2d{5, -2})};
const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)};
const Point oposite_point{centroid * 2};
REQUIRE(result.size() == 32);
CHECK(result[16].x() == Approx(oposite_point.x()));
CHECK(result[16].y() == Approx(oposite_point.y()));
std::vector<double> angles;
angles.reserve(points_count);
for (unsigned index = 0; index < points_count; index++) {
const unsigned previous_index{index == 0 ? points_count - 1 : index - 1};
const unsigned next_index{index == points_count - 1 ? 0 : index + 1};
const Point previous_point = result.points[previous_index];
const Point current_point = result.points[index];
const Point next_point = result.points[next_index];
angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point}));
}
std::vector<double> expected;
angles.reserve(points_count);
std::generate_n(std::back_inserter(expected), points_count, [&](){
return angles.front();
});
CHECK_THAT(angles, Catch::Matchers::Approx(expected));
}
TEST_CASE("Square bed with padding", "[GCode]") {
const Bed bed{
{
Vec2d{0, 0},
Vec2d{100, 0},
Vec2d{100, 100},
Vec2d{0, 100}
},
10.0
};
CHECK(bed.centroid.x() == 50);
CHECK(bed.centroid.y() == 50);
CHECK(bed.contains_within_padding(Vec2d{10, 10}));
CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10}));
}

View File

@@ -2,71 +2,10 @@
#include <memory>
#include "libslic3r/GCodeWriter.hpp"
#include "libslic3r/GCode/GCodeWriter.hpp"
using namespace Slic3r;
SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWriter]") {
GIVEN("A config from a file and a single extruder.") {
GCodeWriter writer;
GCodeConfig &config = writer.config;
config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable);
std::vector<unsigned int> extruder_ids {0};
writer.set_extruders(extruder_ids);
writer.set_extruder(0);
WHEN("Z is set to 203") {
double trouble_Z = 203;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 500003") {
double trouble_Z = 500003;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
WHEN("Z is set to 10.3") {
double trouble_Z = 10.3;
writer.travel_to_z(trouble_Z);
AND_WHEN("GcodeWriter::Lift() is called") {
REQUIRE(writer.lift().size() > 0);
AND_WHEN("Z is moved post-lift to the same delta as the config Z lift") {
REQUIRE(writer.travel_to_z(trouble_Z + config.retract_lift.values[0]).size() == 0);
AND_WHEN("GCodeWriter::Unlift() is called") {
REQUIRE(writer.unlift().size() == 0); // we're the same height so no additional move happens.
THEN("GCodeWriter::Lift() emits gcode.") {
REQUIRE(writer.lift().size() > 0);
}
}
}
}
}
// The test above will fail for trouble_Z == 9007199254740992, where trouble_Z + 1.5 will be rounded to trouble_Z + 2.0 due to double mantisa overflow.
}
}
SCENARIO("set_speed emits values with fixed-point output.", "[GCodeWriter]") {

View File

@@ -6,6 +6,7 @@ add_executable(${_TEST_NAME}_tests
test_aabbindirect.cpp
test_kdtreeindirect.cpp
test_arachne.cpp
test_arc_welder.cpp
test_clipper_offset.cpp
test_clipper_utils.cpp
test_color.cpp
@@ -38,8 +39,10 @@ add_executable(${_TEST_NAME}_tests
test_astar.cpp
test_anyptr.cpp
test_jump_point_search.cpp
../data/qidiparts.cpp
../data/qidiparts.hpp
test_support_spots_generator.cpp
../data/prusaparts.cpp
../data/prusaparts.hpp
test_static_map.cpp
)
if (TARGET OpenVDB::openvdb)

View File

@@ -743,4 +743,23 @@ TEST_CASE("Arachne - #10034 - Degenerated Voronoi diagram - That wasn't fixed by
#ifdef ARACHNE_DEBUG_OUT
export_perimeters_to_svg(debug_out_path("arachne-degenerated-diagram-10034-rotation-not-works.svg"), polygons, perimeters, union_ex(wall_tool_paths.getInnerContour()));
#endif
}
TEST_CASE("Arachne - SPE-1837 - No perimeters generated", "[ArachneNoPerimetersGeneratedSPE1837]") {
Polygon poly_0 = {
Point( 10000000, 10000000),
Point(-10000000, 10000000),
Point(-10000000, -10000000),
Point( 10000000, -10000000)
};
Polygons polygons = {poly_0};
coord_t ext_perimeter_spacing = 300000;
coord_t perimeter_spacing = 700000;
coord_t inset_count = 1;
Arachne::WallToolPaths wall_tool_paths(polygons, ext_perimeter_spacing, perimeter_spacing, inset_count, 0, 0.2, PrintObjectConfig::defaults(), PrintConfig::defaults());
wall_tool_paths.generate();
std::vector<Arachne::VariableWidthLines> perimeters = wall_tool_paths.getToolPaths();
REQUIRE(!perimeters.empty());
}

View File

@@ -0,0 +1,511 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <random>
#include <libslic3r/Geometry/ArcWelder.hpp>
#include <libslic3r/Geometry/Circle.hpp>
#include <libslic3r/SVG.hpp>
#include <libslic3r/libslic3r.h>
using namespace Slic3r;
TEST_CASE("arc basics", "[ArcWelder]") {
using namespace Slic3r::Geometry;
WHEN("arc from { 2000.f, 1000.f } to { 1000.f, 2000.f }") {
Vec2f p1{ 2000.f, 1000.f };
Vec2f p2{ 1000.f, 2000.f };
float r{ 1000.f };
const double s = 1000.f / sqrt(2.);
THEN("90 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, true);
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.5 * M_PI));
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.5 * M_PI).epsilon(0.001));
REQUIRE(is_approx(m, Vec2f{ 1000.f + s, 1000.f + s }, 0.001f));
}
THEN("90 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
Vec2f m = ArcWelder::arc_middle_point(p1, p2, r, false);
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
REQUIRE(is_approx(m, Vec2f{ 2000.f - s, 2000.f - s }, 0.001f));
}
THEN("270 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, true);
REQUIRE(is_approx(c, Vec2f{ 2000.f, 2000.f }));
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx(1.5 * M_PI));
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * 1.5 * M_PI).epsilon(0.001));
REQUIRE(is_approx(m, Vec2f{ 2000.f + s, 2000.f + s }, 0.001f));
}
THEN("270 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
Vec2f m = ArcWelder::arc_middle_point(p1, p2, - r, false);
REQUIRE(is_approx(c, Vec2f{ 1000.f, 1000.f }));
REQUIRE(is_approx(m, Vec2f{ 1000.f - s, 1000.f - s }, 0.001f));
}
}
WHEN("arc from { 1707.11f, 1707.11f } to { 1000.f, 2000.f }") {
Vec2f p1{ 1707.11f, 1707.11f };
Vec2f p2{ 1000.f, 2000.f };
float r{ 1000.f };
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
// Center on the other side of the CCW arch.
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
THEN("45 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
REQUIRE(is_approx(c, center1, 1.f));
REQUIRE(ArcWelder::arc_angle(p1, p2, r) == Approx(0.25 * M_PI));
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * 0.25 * M_PI).epsilon(0.001));
}
THEN("45 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
REQUIRE(is_approx(c, center2, 1.f));
}
THEN("315 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
REQUIRE(is_approx(c, center2, 1.f));
REQUIRE(ArcWelder::arc_angle(p1, p2, - r) == Approx((2. - 0.25) * M_PI));
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 0.25) * M_PI).epsilon(0.001));
}
THEN("315 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
REQUIRE(is_approx(c, center1, 1.f));
}
}
WHEN("arc from { 1866.f, 1500.f } to { 1000.f, 2000.f }") {
Vec2f p1{ 1866.f, 1500.f };
Vec2f p2{ 1000.f, 2000.f };
float r{ 1000.f };
Vec2f center1 = Vec2f{ 1000.f, 1000.f };
// Center on the other side of the CCW arch.
Vec2f center2 = center1 + 2. * (0.5 * (p1 + p2) - center1);
THEN("60 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, true);
REQUIRE(is_approx(c, center1, 1.f));
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, r), float(M_PI / 3.), 0.001f));
REQUIRE(ArcWelder::arc_length(p1, p2, r) == Approx(r * M_PI / 3.).epsilon(0.001));
}
THEN("60 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, r, false);
REQUIRE(is_approx(c, center2, 1.f));
}
THEN("300 degrees arc, CCW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, true);
REQUIRE(is_approx(c, center2, 1.f));
REQUIRE(is_approx(ArcWelder::arc_angle(p1, p2, - r), float((2. - 1./3.) * M_PI), 0.001f));
REQUIRE(ArcWelder::arc_length(p1, p2, - r) == Approx(r * (2. - 1. / 3.) * M_PI).epsilon(0.001));
}
THEN("300 degrees arc, CW") {
Vec2f c = ArcWelder::arc_center(p1, p2, - r, false);
REQUIRE(is_approx(c, center1, 1.f));
}
}
}
TEST_CASE("arc discretization", "[ArcWelder]") {
using namespace Slic3r::Geometry;
WHEN("arc from { 2, 1 } to { 1, 2 }") {
const Point p1 = Point::new_scale(2., 1.);
const Point p2 = Point::new_scale(1., 2.);
const Point center = Point::new_scale(1., 1.);
const float radius = scaled<float>(1.);
const float resolution = scaled<float>(0.002);
auto test = [center, resolution, radius](const Point &p1, const Point &p2, const float r, const bool ccw) {
Vec2f c = ArcWelder::arc_center(p1.cast<float>(), p2.cast<float>(), r, ccw);
REQUIRE((p1.cast<float>() - c).norm() == Approx(radius));
REQUIRE((c - center.cast<float>()).norm() == Approx(0.));
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
REQUIRE(pts.size() >= 2);
REQUIRE(pts.front() == p1);
REQUIRE(pts.back() == p2);
for (const Point &p : pts)
REQUIRE(std::abs((p.cast<double>() - c.cast<double>()).norm() - double(radius)) < double(resolution + SCALED_EPSILON));
};
THEN("90 degrees arc, CCW") {
test(p1, p2, radius, true);
}
THEN("270 degrees arc, CCW") {
test(p2, p1, - radius, true);
}
THEN("90 degrees arc, CW") {
test(p2, p1, radius, false);
}
THEN("270 degrees arc, CW") {
test(p1, p2, - radius, false);
}
}
}
void test_arc_fit_variance(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
{
using namespace Slic3r::Geometry;
double variance = ArcWelder::arc_fit_variance(p1, p2, r, ccw, begin, end);
double variance_fit = ArcWelder::arc_fit_variance(p1, p2, r_fit, ccw, begin, end);
REQUIRE(variance_fit < variance);
};
void test_arc_fit_max_deviation(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
{
using namespace Slic3r::Geometry;
double max_deviation = ArcWelder::arc_fit_max_deviation(p1, p2, r, ccw, begin, end);
double max_deviation_fit = ArcWelder::arc_fit_max_deviation(p1, p2, r_fit, ccw, begin, end);
REQUIRE(std::abs(max_deviation_fit) < std::abs(max_deviation));
};
void test_arc_fit(const Point &p1, const Point &p2, const float r, const float r_fit, const bool ccw, const Points::const_iterator begin, const Points::const_iterator end)
{
test_arc_fit_variance(p1, p2, r, r_fit, ccw, begin, end);
test_arc_fit_max_deviation(p1, p2, r, r_fit, ccw, begin, end);
};
TEST_CASE("arc fitting", "[ArcWelder]") {
using namespace Slic3r::Geometry;
WHEN("arc from { 2, 1 } to { 1, 2 }") {
const Point p1 = Point::new_scale(2., 1.);
const Point p2 = Point::new_scale(1., 2.);
const Point center = Point::new_scale(1., 1.);
const float radius = scaled<float>(1.);
const float resolution = scaled<float>(0.002);
auto test = [center, resolution](const Point &p1, const Point &p2, const float r, const bool ccw) {
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
REQUIRE(path.size() == 2);
REQUIRE(path.front().point == p1);
REQUIRE(path.front().radius == 0.f);
REQUIRE(path.back().point == p2);
REQUIRE(path.back().ccw() == ccw);
test_arc_fit(p1, p2, r, path.back().radius, true, pts.begin(), pts.end());
};
THEN("90 degrees arc, CCW is fitted") {
test(p1, p2, radius, true);
}
THEN("270 degrees arc, CCW is fitted") {
test(p2, p1, - radius, true);
}
THEN("90 degrees arc, CW is fitted") {
test(p2, p1, radius, false);
}
THEN("270 degrees arc, CW is fitted") {
test(p1, p2, - radius, false);
}
}
WHEN("arc from { 2, 1 } to { 1, 2 }, another arc from { 2, 1 } to { 0, 2 }, tangentially connected") {
const Point p1 = Point::new_scale(2., 1.);
const Point p2 = Point::new_scale(1., 2.);
const Point p3 = Point::new_scale(0., 3.);
const Point center1 = Point::new_scale(1., 1.);
const Point center2 = Point::new_scale(1., 3.);
const float radius = scaled<float>(1.);
const float resolution = scaled<float>(0.002);
auto test = [center1, center2, resolution](const Point &p1, const Point &p2, const Point &p3, const float r, const bool ccw) {
Points pts = ArcWelder::arc_discretize(p1, p2, r, ccw, resolution);
size_t num_pts1 = pts.size();
{
Points pts2 = ArcWelder::arc_discretize(p2, p3, - r, ! ccw, resolution);
REQUIRE(pts.back() == pts2.front());
pts.insert(pts.end(), std::next(pts2.begin()), pts2.end());
}
ArcWelder::Path path = ArcWelder::fit_path(pts, resolution + SCALED_EPSILON, ArcWelder::default_scaled_resolution);
REQUIRE(path.size() == 3);
REQUIRE(path.front().point == p1);
REQUIRE(path.front().radius == 0.f);
REQUIRE(path[1].point == p2);
REQUIRE(path[1].ccw() == ccw);
REQUIRE(path.back().point == p3);
REQUIRE(path.back().ccw() == ! ccw);
test_arc_fit(p1, p2, r, path[1].radius, ccw, pts.begin(), pts.begin() + num_pts1);
test_arc_fit(p2, p3, - r, path.back().radius, ! ccw, pts.begin() + num_pts1 - 1, pts.end());
};
THEN("90 degrees arches, CCW are fitted") {
test(p1, p2, p3, radius, true);
}
THEN("270 degrees arc, CCW is fitted") {
test(p3, p2, p1, -radius, true);
}
THEN("90 degrees arc, CW is fitted") {
test(p3, p2, p1, radius, false);
}
THEN("270 degrees arc, CW is fitted") {
test(p1, p2, p3, -radius, false);
}
}
}
TEST_CASE("least squares arc fitting, interpolating end points", "[ArcWelder]") {
using namespace Slic3r::Geometry;
// Generate bunch of random arches.
const coord_t max_coordinate = scaled<coord_t>(sqrt(250. - 1.));
static constexpr const double min_radius = scaled<double>(0.01);
static constexpr const double max_radius = scaled<double>(250.);
// static constexpr const double deviation = scaled<double>(0.5);
static constexpr const double deviation = scaled<double>(0.1);
// Seeded with a fixed seed, to be repeatable.
std::mt19937 rng(867092346);
std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
std::uniform_real_distribution<double> angle_sampler(0.001, 2. * M_PI - 0.001);
std::uniform_real_distribution<double> radius_sampler(min_radius, max_radius);
std::uniform_int_distribution<int> num_samples_sampler(1, 100);
auto test_arc_fitting = [&rng, &coord_sampler, &num_samples_sampler, &angle_sampler, &radius_sampler]() {
auto sample_point = [&rng, &coord_sampler]() {
return Vec2d(coord_sampler(rng), coord_sampler(rng));
};
// Start and end point of the arc:
Vec2d center_pos = sample_point();
double angle0 = angle_sampler(rng);
double angle = angle_sampler(rng);
double radius = radius_sampler(rng);
Vec2d v1 = Eigen::Rotation2D(angle0) * Vec2d(1., 0.);
Vec2d v2 = Eigen::Rotation2D(angle0 + angle) * Vec2d(1., 0.);
Vec2d start_pos = center_pos + radius * v1;
Vec2d end_pos = center_pos + radius * v2;
std::vector<Vec2d> samples;
size_t num_samples = num_samples_sampler(rng);
for (size_t i = 0; i < num_samples; ++ i) {
double sample_r = sqrt(std::uniform_real_distribution<double>(sqr(std::max(0., radius - deviation)), sqr(radius + deviation))(rng));
double sample_a = std::uniform_real_distribution<double>(0., angle)(rng);
Vec2d pt = center_pos + Eigen::Rotation2D(angle0 + sample_a) * Vec2d(sample_r, 0.);
samples.emplace_back(pt);
assert((pt - center_pos).norm() > radius - deviation - SCALED_EPSILON);
assert((pt - center_pos).norm() < radius + deviation + SCALED_EPSILON);
}
// Vec2d new_center = ArcWelder::arc_fit_center_algebraic_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end());
THEN("Center is fitted correctly") {
std::optional<Vec2d> new_center_opt = ArcWelder::arc_fit_center_gauss_newton_ls(start_pos, end_pos, center_pos, samples.begin(), samples.end(), 10);
REQUIRE(new_center_opt);
if (new_center_opt) {
Vec2d new_center = *new_center_opt;
double new_radius = (new_center - start_pos).norm();
double total_deviation = 0;
double new_total_deviation = 0;
for (const Vec2d &s : samples) {
total_deviation += sqr((s - center_pos).norm() - radius);
new_total_deviation += sqr((s - new_center).norm() - radius);
}
total_deviation /= double(num_samples);
new_total_deviation /= double(num_samples);
REQUIRE(new_total_deviation <= total_deviation);
#if 0
double dist = (center_pos - new_center).norm();
printf("Radius: %lf, Angle: %lf deg, Samples: %d, Dist: %lf\n", unscaled<double>(radius), 180. * angle / M_PI, int(num_samples), unscaled<double>(dist));
// REQUIRE(is_approx(center_pos, new_center, deviation));
if (dist > scaled<double>(1.)) {
static int irun = 0;
char path[2048];
sprintf(path, "d:\\temp\\debug\\circle-fit-%d.svg", irun++);
Eigen::AlignedBox<double, 2> bbox(start_pos, end_pos);
for (const Vec2d& sample : samples)
bbox.extend(sample);
bbox.extend(center_pos);
Slic3r::SVG svg(path, BoundingBox(bbox.min().cast<coord_t>(), bbox.max().cast<coord_t>()).inflated(bbox.sizes().maxCoeff() * 0.05));
Points arc_src;
for (size_t i = 0; i <= 1000; ++i)
arc_src.emplace_back((center_pos + Eigen::Rotation2D(angle0 + double(i) * angle / 1000.) * Vec2d(radius, 0.)).cast<coord_t>());
svg.draw(Polyline(arc_src));
Points arc_new;
double new_arc_angle = ArcWelder::arc_angle(start_pos, end_pos, (new_center - start_pos).norm());
for (size_t i = 0; i <= 1000; ++i)
arc_new.emplace_back((new_center + Eigen::Rotation2D(double(i) * new_arc_angle / 1000.) * (start_pos - new_center)).cast<coord_t>());
svg.draw(Polyline(arc_new), "magenta");
svg.draw(Point(start_pos.cast<coord_t>()), "blue");
svg.draw(Point(end_pos.cast<coord_t>()), "blue");
svg.draw(Point(center_pos.cast<coord_t>()), "blue");
for (const Vec2d &sample : samples)
svg.draw(Point(sample.cast<coord_t>()), "red");
svg.draw(Point(new_center.cast<coord_t>()), "magenta");
}
if (!is_approx(center_pos, new_center, scaled<double>(5.))) {
printf("Failed\n");
}
#endif
} else {
printf("Fitting failed\n");
}
}
};
WHEN("Generating a random arc and randomized arc samples") {
for (size_t i = 0; i < 1000; ++ i)
test_arc_fitting();
}
}
TEST_CASE("arc wedge test", "[ArcWelder]") {
using namespace Slic3r::Geometry;
WHEN("test point inside wedge, arc from { 2, 1 } to { 1, 2 }") {
const int64_t s = 1000000;
const Vec2i64 p1{ 2 * s, s };
const Vec2i64 p2{ s, 2 * s };
const Vec2i64 center{ s, s };
const int64_t radius{ s };
auto test = [center](
// Arc data
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
// Test data
const Vec2i64 &ptest, const bool ptest_inside) {
const Vec2d c = ArcWelder::arc_center(p1.cast<double>(), p2.cast<double>(), double(r), ccw);
REQUIRE(is_approx(c, center.cast<double>()));
REQUIRE(ArcWelder::inside_arc_wedge(p1, p2, center, r > 0, ccw, ptest) == ptest_inside);
REQUIRE(ArcWelder::inside_arc_wedge(p1.cast<double>(), p2.cast<double>(), double(r), ccw, ptest.cast<double>()) == ptest_inside);
};
auto test_quadrants = [center, test](
// Arc data
const Vec2i64 &p1, const Vec2i64 &p2, const int64_t r, const bool ccw,
// Test data
const Vec2i64 &ptest1, const bool ptest_inside1,
const Vec2i64 &ptest2, const bool ptest_inside2,
const Vec2i64 &ptest3, const bool ptest_inside3,
const Vec2i64 &ptest4, const bool ptest_inside4) {
test(p1, p2, r, ccw, ptest1 + center, ptest_inside1);
test(p1, p2, r, ccw, ptest2 + center, ptest_inside2);
test(p1, p2, r, ccw, ptest3 + center, ptest_inside3);
test(p1, p2, r, ccw, ptest4 + center, ptest_inside4);
};
THEN("90 degrees arc, CCW") {
test_quadrants(p1, p2, radius, true,
Vec2i64{ s, s }, true,
Vec2i64{ s, - s }, false,
Vec2i64{ - s, s }, false,
Vec2i64{ - s, - s }, false);
}
THEN("270 degrees arc, CCW") {
test_quadrants(p2, p1, -radius, true,
Vec2i64{ s, s }, false,
Vec2i64{ s, - s }, true,
Vec2i64{ - s, s }, true,
Vec2i64{ - s, - s }, true);
}
THEN("90 degrees arc, CW") {
test_quadrants(p2, p1, radius, false,
Vec2i64{ s, s }, true,
Vec2i64{ s, - s }, false,
Vec2i64{ - s, s }, false,
Vec2i64{ - s, - s }, false);
}
THEN("270 degrees arc, CW") {
test_quadrants(p1, p2, -radius, false,
Vec2i64{ s, s }, false,
Vec2i64{ s, - s }, true,
Vec2i64{ - s, s }, true,
Vec2i64{ - s, - s }, true);
}
}
}
#if 0
// For quantization
//#include <libslic3r/GCode/GCodeWriter.hpp>
TEST_CASE("arc quantization", "[ArcWelder]") {
using namespace Slic3r::Geometry;
WHEN("generating a bunch of random arches") {
static constexpr const size_t len = 100000;
static constexpr const coord_t max_coordinate = scaled<coord_t>(250.);
static constexpr const float max_radius = scaled<float>(250.);
ArcWelder::Segments path;
path.reserve(len + 1);
// Seeded with a fixed seed, to be repeatable.
std::mt19937 rng(987432690);
// Generate bunch of random arches.
std::uniform_int_distribution<int32_t> coord_sampler(0, int32_t(max_coordinate));
std::uniform_real_distribution<float> radius_sampler(- max_radius, max_radius);
std::uniform_int_distribution<int> orientation_sampler(0, 1);
path.push_back({ Point{coord_sampler(rng), coord_sampler(rng)}, 0, ArcWelder::Orientation::CCW });
for (size_t i = 0; i < len; ++ i) {
ArcWelder::Segment seg { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
while ((seg.point.cast<double>() - path.back().point.cast<double>()).norm() > 2. * std::abs(seg.radius))
seg = { Point{coord_sampler(rng), coord_sampler(rng)}, radius_sampler(rng), orientation_sampler(rng) ? ArcWelder::Orientation::CCW : ArcWelder::Orientation::CW };
path.push_back(seg);
}
// Run the test, quantize coordinates and radius, find the maximum error of quantization comparing the two arches.
struct ArcEval {
double error;
double radius;
double angle;
};
std::vector<ArcEval> center_errors_R;
center_errors_R.reserve(len);
std::vector<double> center_errors_R_exact;
center_errors_R_exact.reserve(len);
std::vector<double> center_errors_IJ;
center_errors_IJ.reserve(len);
for (size_t i = 0; i < len; ++ i) {
// Source arc:
const Vec2d start_point = unscaled<double>(path[i].point);
const Vec2d end_point = unscaled<double>(path[i + 1].point);
const double radius = unscaled<double>(path[i + 1].radius);
const bool ccw = path[i + 1].ccw();
const Vec2d center = ArcWelder::arc_center(start_point, end_point, radius, ccw);
{
const double d1 = (start_point - center).norm();
const double d2 = (end_point - center).norm();
const double dx = (end_point - start_point).norm();
assert(std::abs(d1 - std::abs(radius)) < EPSILON);
assert(std::abs(d2 - std::abs(radius)) < EPSILON);
}
// Quantized arc:
const Vec2d start_point_quantized { GCodeFormatter::quantize_xyzf(start_point.x()), GCodeFormatter::quantize_xyzf(start_point.y()) };
const Vec2d end_point_quantized { GCodeFormatter::quantize_xyzf(end_point .x()), GCodeFormatter::quantize_xyzf(end_point .y()) };
const double radius_quantized { GCodeFormatter::quantize_xyzf(radius) };
const Vec2d center_quantized { GCodeFormatter::quantize_xyzf(center .x()), GCodeFormatter::quantize_xyzf(center .y()) };
// Evaulate maximum error for the quantized arc given by the end points and radius.
const Vec2d center_from_quantized = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius, ccw);
const Vec2d center_reconstructed = ArcWelder::arc_center(start_point_quantized, end_point_quantized, radius_quantized, ccw);
#if 0
center_errors_R.push_back({
std::abs((center_reconstructed - center).norm()),
radius,
ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
});
if (center_errors_R.back().error > 0.15)
printf("Fuj\n");
#endif
center_errors_R_exact.emplace_back(std::abs((center_from_quantized - center).norm()));
if (center_errors_R_exact.back() > 0.15)
printf("Fuj\n");
center_errors_IJ.emplace_back(std::abs((center_quantized - center).norm()));
if (center_errors_IJ.back() > 0.15)
printf("Fuj\n");
// Adjust center of the arc to the quantized end points.
Vec2d third_point = ArcWelder::arc_middle_point(start_point, end_point, radius, ccw);
double third_point_radius = (third_point - center).norm();
assert(std::abs(third_point_radius - std::abs(radius)) < EPSILON);
std::optional<Vec2d> center_adjusted = try_circle_center(start_point_quantized, end_point_quantized, third_point, EPSILON);
//assert(center_adjusted);
if (center_adjusted) {
const double radius_adjusted = (center_adjusted.value() - start_point_quantized).norm() * (radius > 0 ? 1. : -1.);
const double radius_adjusted_quantized = GCodeFormatter::quantize_xyzf(radius_adjusted);
// Evaulate maximum error for the quantized arc given by the end points and radius.
const Vec2f center_reconstructed = ArcWelder::arc_center(start_point_quantized.cast<float>(), end_point_quantized.cast<float>(), float(radius_adjusted_quantized), ccw);
double rtest = std::abs(radius_adjusted_quantized);
double d1 = std::abs((center_reconstructed - start_point.cast<float>()).norm() - rtest);
double d2 = std::abs((center_reconstructed - end_point.cast<float>()).norm() - rtest);
double d3 = std::abs((center_reconstructed - third_point.cast<float>()).norm() - rtest);
double d = std::max(d1, std::max(d2, d3));
center_errors_R.push_back({
d,
radius,
ArcWelder::arc_angle(start_point, end_point, radius) * 180. / M_PI
});
} else {
printf("Adjusted circle is collinear.\n");
}
}
std::sort(center_errors_R.begin(), center_errors_R.end(), [](auto l, auto r) { return l.error > r.error; });
std::sort(center_errors_R_exact.begin(), center_errors_R_exact.end(), [](auto l, auto r) { return l > r; });
std::sort(center_errors_IJ.begin(), center_errors_IJ.end(), [](auto l, auto r) { return l > r; });
printf("Maximum error R: %lf\n", center_errors_R.back().error);
printf("Maximum error R exact: %lf\n", center_errors_R_exact.back());
printf("Maximum error IJ: %lf\n", center_errors_IJ.back());
}
}
#endif

View File

@@ -23,7 +23,8 @@ TEST_CASE("Cut character from surface", "[]")
Transform3d tr = Transform3d::Identity();
tr.translate(Vec3d(0., 0., -z_depth));
tr.scale(Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
tr.scale(text_shape_scale);
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
@@ -158,7 +159,7 @@ TEST_CASE("CutSurface in 3mf", "[Emboss]")
FontProp fp = tc.style.prop;
ExPolygons shapes = Emboss::text2shapes(ff, tc.text.c_str(), fp);
double shape_scale = Emboss::get_shape_scale(fp, *ff.font_file);
double shape_scale = Emboss::get_text_shape_scale(fp, *ff.font_file);
Emboss::OrthoProject projection = create_projection_for_cut(
cut_projection_tr, shape_scale, get_extents(shapes), z_range);

View File

@@ -195,15 +195,17 @@ TEST_CASE("Visualize glyph from font", "[Emboss]")
#endif // VISUALIZE
#include "test_utils.hpp"
#include "nanosvg/nanosvg.h" // load SVG file
#include "libslic3r/NSVGUtils.hpp"
#include <nanosvg/nanosvg.h> // load SVG file
#include <libslic3r/NSVGUtils.hpp>
#include <libslic3r/IntersectionPoints.hpp>
ExPolygons heal_and_check(const Polygons &polygons)
{
Pointfs intersections_prev = intersection_points(polygons);
IntersectionsLines intersections_prev = get_intersections(polygons);
Points polygons_points = to_points(polygons);
Points duplicits_prev = collect_duplicates(polygons_points);
ExPolygons shape = Emboss::heal_shape(polygons);
auto [shape, success] = Emboss::heal_polygons(polygons);
CHECK(success);
// Is default shape for unhealabled shape?
bool is_default_shape =
@@ -213,7 +215,7 @@ ExPolygons heal_and_check(const Polygons &polygons)
shape.front().holes.front().points.size() == 4 ;
CHECK(!is_default_shape);
Pointfs intersections = intersection_points(shape);
IntersectionsLines intersections = get_intersections(shape);
Points shape_points = to_points(shape);
Points duplicits = collect_duplicates(shape_points);
//{
@@ -244,7 +246,9 @@ void scale(Polygons &polygons, double multiplicator) {
Polygons load_polygons(const std::string &svg_file) {
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + svg_file;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = NSVGUtils::to_polygons(image);
NSVGLineParams param{1000};
param.scale = 10.;
Polygons polygons = to_polygons(*image, param);
nsvgDelete(image);
return polygons;
}
@@ -258,7 +262,8 @@ TEST_CASE("Heal of 'i' in ALIENATO.TTF", "[Emboss]")
auto a = heal_and_check(polygons);
Polygons scaled_shape = polygons; // copy
scale(scaled_shape, 1 / Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
scale(scaled_shape, 1 / text_shape_scale);
auto b = heal_and_check(scaled_shape);
// different scale
@@ -282,12 +287,37 @@ TEST_CASE("Heal of 'm' in Allura_Script.ttf", "[Emboss]")
auto a = heal_and_check(polygons);
}
#include "libslic3r/NSVGUtils.hpp"
TEST_CASE("Heal of svg contour overlap", "[Emboss]") {
std::string svg_file = "contour_neighbor.svg";
auto image = nsvgParseFromFile(TEST_DATA_DIR PATH_SEPARATOR + svg_file, "mm");
NSVGLineParams param(1e10);
ExPolygonsWithIds shapes = create_shape_with_ids(*image, param);
Polygons polygons;
for (ExPolygonsWithId &shape : shapes)
polygons.push_back(shape.expoly.front().contour);
auto a = heal_and_check(polygons);
}
// Input contour is extracted from case above "contour_neighbor.svg" with trouble shooted scale
TEST_CASE("Heal of overlaping contour", "[Emboss]"){
// Extracted from svg:
Points contour{{2228926, 1543620}, {745002, 2065101}, {745002, 2065094}, {744990, 2065094}, {684487, 1466338},
{510999, 908378}, {236555, 403250}, {-126813, -37014}, {-567074, -400382}, {-1072201, -674822},
{-567074, -400378}, {-126813, -37010}, {236555, 403250}, {510999, 908382}, {684487, 1466346},
{744990, 2065105}, {-2219648, 2073234}, {-2228926, -908814}, {-1646879, -2073235}};
ExPolygons shapes = {ExPolygon{contour}};
CHECK(Emboss::heal_expolygons(shapes));
}
TEST_CASE("Heal of points close to line", "[Emboss]")
{
std::string file_name = "points_close_to_line.svg";
std::string file_path = TEST_DATA_DIR PATH_SEPARATOR + file_name;
NSVGimage *image = nsvgParseFromFile(file_path.c_str(), "px", 96.0f);
Polygons polygons = NSVGUtils::to_polygons(image);
NSVGLineParams param{1000};
param.scale = 1.;
Polygons polygons = to_polygons(*image, param);
nsvgDelete(image);
REQUIRE(polygons.size() == 1);
Polygon polygon = polygons.front();
@@ -309,16 +339,19 @@ The other kids at school nicknamed him Ix,\n\
which in the language of Betelgeuse Five translates as\t\n\
\"boy who is not able satisfactorily to explain what a Hrung is,\n\
nor why it should choose to collapse on Betelgeuse Seven\".";
float line_height = 10.f, depth = 2.f;
float line_height = 10.f;
auto font = Emboss::create_font_file(font_path.c_str());
REQUIRE(font != nullptr);
Emboss::FontFileWithCache ffwc(std::move(font));
FontProp fp{line_height, depth};
ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp);
FontProp fp{line_height};
auto was_canceled = []() { return false; };
ExPolygons shapes = Emboss::text2shapes(ffwc, text.c_str(), fp, was_canceled);
REQUIRE(!shapes.empty());
float depth = 2.f;
Emboss::ProjectZ projection(depth);
indexed_triangle_set its = Emboss::polygons2model(shapes, projection);
CHECK(!its.indices.empty());
@@ -468,7 +501,8 @@ TEST_CASE("Cut surface", "[]")
Transform3d tr = Transform3d::Identity();
tr.translate(Vec3d(0., 0., -z_depth));
tr.scale(Emboss::SHAPE_SCALE);
double text_shape_scale = 0.001; // Emboss.cpp --> SHAPE_SCALE
tr.scale(text_shape_scale);
Emboss::OrthoProject cut_projection(tr, Vec3d(0., 0., z_depth));
auto object = its_make_cube(782 - 49 + 50, 724 + 10 + 50, 5);
@@ -491,7 +525,7 @@ TEST_CASE("Cut surface", "[]")
#include <sstream>
#include <cereal/cereal.hpp>
#include <cereal/archives/binary.hpp>
TEST_CASE("UndoRedo serialization", "[Emboss]")
TEST_CASE("UndoRedo TextConfiguration serialization", "[Emboss]")
{
TextConfiguration tc;
tc.text = "Dovede-li se člověk zasmát sám sobě, nevyjde ze smíchu po celý život.";
@@ -500,11 +534,12 @@ TEST_CASE("UndoRedo serialization", "[Emboss]")
es.path = "Simply the best";
es.type = EmbossStyle::Type::file_path;
FontProp &fp = es.prop;
fp.angle = 100.;
fp.distance = 10.;
fp.char_gap = 1;
fp.use_surface = true;
tc.fix_3mf_tr = Transform3d::Identity();
fp.char_gap = 3;
fp.line_gap = 7;
fp.boldness = 2.3f;
fp.skew = 4.5f;
fp.collection_number = 13;
fp.size_in_mm= 6.7f;
std::stringstream ss; // any stream can be used
{
@@ -520,7 +555,45 @@ TEST_CASE("UndoRedo serialization", "[Emboss]")
}
CHECK(tc.style == tc_loaded.style);
CHECK(tc.text == tc_loaded.text);
CHECK(tc.fix_3mf_tr.has_value() == tc_loaded.fix_3mf_tr.has_value());
}
#include "libslic3r/EmbossShape.hpp"
TEST_CASE("UndoRedo EmbossShape serialization", "[Emboss]")
{
EmbossShape emboss;
emboss.shapes_with_ids = {{0, {{{0, 0}, {10, 0}, {10, 10}, {0, 10}}, {{5, 5}, {6, 5}, {6, 6}, {5, 6}}}}};
emboss.scale = 2.;
emboss.projection.depth = 5.;
emboss.projection.use_surface = true;
emboss.fix_3mf_tr = Transform3d::Identity();
emboss.svg_file = EmbossShape::SvgFile{};
emboss.svg_file->path = "Everything starts somewhere, though many physicists disagree.\
But people have always been dimly aware of the problem with the start of things.\
They wonder how the snowplough driver gets to work,\
or how the makers of dictionaries look up the spelling of words.";
emboss.svg_file->path_in_3mf = "Všechno někde začíná, i když mnoho fyziků nesouhlasí.\
Ale lidé si vždy jen matně uvědomovali problém se začátkem věcí.\
Zajímalo je, jak se řidič sněžného pluhu dostane do práce\
nebo jak tvůrci slovníků vyhledávají pravopis slov.";
emboss.svg_file->file_data = std::make_unique<std::string>("cite: Terry Pratchett");
std::stringstream ss; // any stream can be used
{
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
oarchive(emboss);
} // archive goes out of scope, ensuring all contents are flushed
EmbossShape emboss_loaded;
{
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
iarchive(emboss_loaded);
}
CHECK(emboss.shapes_with_ids.front().expoly == emboss_loaded.shapes_with_ids.front().expoly);
CHECK(emboss.scale == emboss_loaded.scale);
CHECK(emboss.projection.depth == emboss_loaded.projection.depth);
CHECK(emboss.projection.use_surface == emboss_loaded.projection.use_surface);
CHECK(emboss.svg_file->path == emboss_loaded.svg_file->path);
CHECK(emboss.svg_file->path_in_3mf == emboss_loaded.svg_file->path_in_3mf);
}
@@ -780,7 +853,7 @@ using MyMesh = Slic3r::MeshBoolean::cgal2::CGALMesh;
// Second Idea
// Store original its inside of text configuration[optional]
// Cause problem with next editation of object -> cut, simplify, Netfabb, Hollow, ...(transform original vertices)
// Cause problem with next editation of object -> cut, simplify, repair by WinSDK, Hollow, ...(transform original vertices)
TEST_CASE("Emboss extrude cut", "[Emboss-Cut]")
{
std::string font_path = get_font_filepath();

View File

@@ -65,3 +65,99 @@ SCENARIO("Basics", "[ExPolygon]") {
}
}
}
#include <sstream>
#include <cereal/cereal.hpp>
#include <cereal/archives/binary.hpp>
#include "libslic3r/ExPolygonSerialize.hpp"
TEST_CASE("Serialization of expolygons", "[ExPolygon, Cereal, serialization]")
{
ExPolygons expolys{{
// expolygon 1 - without holes
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
// expolygon 2 - with rect 1px hole
{{{0,0}, {10,0}, {10,10}, {0,10}},
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
}};
std::stringstream ss; // any stream can be used
{
cereal::BinaryOutputArchive oarchive(ss); // Create an output archive
oarchive(expolys);
} // archive goes out of scope, ensuring all contents are flushed
std::string data = ss.str();
CHECK(!data.empty());
ExPolygons expolys_loaded;
{
cereal::BinaryInputArchive iarchive(ss); // Create an input archive
iarchive(expolys_loaded);
}
CHECK(expolys == expolys_loaded);
}
#include <cereal/archives/json.hpp>
#include <regex>
// It is used to serialize expolygons into 3mf.
TEST_CASE("Serialization of expolygons to string", "[ExPolygon, Cereal, serialization]")
{
ExPolygons expolys{{
// expolygon 1 - without holes
{{0,0}, {10,0}, {10,10}, {0,10}}, // contour
// expolygon 2 - with rect 1px hole
{{{0,0}, {10,0}, {10,10}, {0,10}},
{{5, 5}, {6, 5}, {6, 6}, {5, 6}}}
}};
std::stringstream ss_out; // any stream can be used
{
cereal::JSONOutputArchive oarchive(ss_out); // Create an output archive
oarchive(expolys);
} // archive goes out of scope, ensuring all contents are flushed
//Simplify text representation of expolygons
std::string data = ss_out.str();
// Data contain this JSON string
//{
// "value0": [
// {
// "value0": {
// "value0":
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
// },
// "value1": []
// },
// {
// "value0": {
// "value0":
// [{"value0": 0, "value1": 0}, {"value0": 10, "value1": 0}, {"value0": 10, "value1": 10}, {"value0": 0, "value1": 10}]
// },
// "value1": [{
// "value0":
// [{"value0": 5, "value1": 5}, {"value0": 6, "value1": 5}, {"value0": 6, "value1": 6}, {"value0": 5, "value1": 6}]
// }]
// }
// ]
//}
// Change JSON named object to JSON arrays(without name)
// RegEx for wihitespace = "[ \t\r\n\v\f]"
std::regex r("\"value[0-9]+\":|[ \t\r\n\v\f]");
std::string data_short = std::regex_replace(data, r , "");
std::replace(data_short.begin(), data_short.end(), '{', '[');
std::replace(data_short.begin(), data_short.end(), '}', ']');
CHECK(!data_short.empty());
// Cereal acceptable string
// [[[[[[0,0],[10,0],[10,10],[0,10]]],[]],[[[[0,0],[10,0],[10,10],[0,10]]],[[[[5,5],[6,5],[6,6],[5,6]]]]]]]
std::stringstream ss_in(data_short);
ExPolygons expolys_loaded;
{
cereal::JSONInputArchive iarchive(ss_in); // Create an input archive
iarchive(expolys_loaded);
}
CHECK(expolys == expolys_loaded);
}

View File

@@ -84,16 +84,16 @@ TEST_CASE("Line::perpendicular_to", "[Geometry]") {
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
// this test was failing on Windows (GH #1950)
Slic3r::Polygon polygon(Points({
Point(207802834,-57084522),
Point(196528149,-37556190),
Point(173626821,-25420928),
Point(171285751,-21366123),
Point(118673592,-21366123),
Point(116332562,-25420928),
Point(93431208,-37556191),
Point(82156517,-57084523),
Point(129714478,-84542120),
Point(160244873,-84542120)
{207802834,-57084522},
{196528149,-37556190},
{173626821,-25420928},
{171285751,-21366123},
{118673592,-21366123},
{116332562,-25420928},
{93431208,-37556191},
{82156517,-57084523},
{129714478,-84542120},
{160244873,-84542120}
}));
Point point(95706562, -57294774);
REQUIRE(polygon.contains(point));
@@ -196,6 +196,40 @@ TEST_CASE("Offseting a line generates a polygon correctly", "[Geometry]"){
REQUIRE(area.area() == Slic3r::Polygon(Points({Point(10,5),Point(20,5),Point(20,15),Point(10,15)})).area());
}
SCENARIO("Circle Fit, 3 points", "[Geometry]") {
WHEN("Three points make a circle") {
double s1 = scaled<double>(1.);
THEN("circle_center(): A center point { 0, 0 } is returned") {
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
REQUIRE(is_approx(center, Vec2d(0, 0)));
}
THEN("circle_center(): A center point { 0, 0 } is returned for points in reverse") {
Vec2d center = Geometry::circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
REQUIRE(is_approx(center, Vec2d(0, 0)));
}
THEN("try_circle_center(): A center point { 0, 0 } is returned") {
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ -s1, 0. }, SCALED_EPSILON);
REQUIRE(center);
REQUIRE(is_approx(*center, Vec2d(0, 0)));
}
THEN("try_circle_center(): A center point { 0, 0 } is returned for points in reverse") {
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ -s1, 0. }, Vec2d{ 0, s1 }, Vec2d{ s1, 0. }, SCALED_EPSILON);
REQUIRE(center);
REQUIRE(is_approx(*center, Vec2d(0, 0)));
}
}
WHEN("Three points are collinear") {
double s1 = scaled<double>(1.);
THEN("circle_center(): A center point { 2, 0 } is returned") {
Vec2d center = Geometry::circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
REQUIRE(is_approx(center, Vec2d(2. * s1, 0)));
}
THEN("try_circle_center(): Fails for collinear points") {
std::optional<Vec2d> center = Geometry::try_circle_center(Vec2d{ s1, 0. }, Vec2d{ 2. * s1, 0. }, Vec2d{ 3. * s1, 0. }, SCALED_EPSILON);
REQUIRE(! center);
}
}
}
SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
Vec2d expected_center(-6, 0);
@@ -288,6 +322,53 @@ SCENARIO("Circle Fit, TaubinFit with Newton's method", "[Geometry]") {
}
}
SCENARIO("Circle Fit, least squares by decomposition or by solving normal equation", "[Geometry]") {
auto test_circle_fit = [](const Geometry::Circled &circle, const Vec2d &center, const double radius) {
THEN("A center point matches.") {
REQUIRE(is_approx(circle.center, center));
}
THEN("Radius matches") {
REQUIRE(is_approx(circle.radius, radius));
}
};
GIVEN("A vector of Vec2ds arranged in a half-circle with approximately the same distance R from some point") {
const Vec2d expected_center(-6., 0.);
const double expected_radius = 6.;
Vec2ds sample{Vec2d(6.0, 0), Vec2d(5.1961524, 3), Vec2d(3 ,5.1961524), Vec2d(0, 6.0), Vec2d(3, 5.1961524), Vec2d(-5.1961524, 3), Vec2d(-6.0, 0)};
std::transform(sample.begin(), sample.end(), sample.begin(), [expected_center] (const Vec2d &a) { return a + expected_center; });
WHEN("Circle fit is called on the entire array, least squares SVD") {
test_circle_fit(Geometry::circle_linear_least_squares_svd(sample), expected_center, expected_radius);
}
WHEN("Circle fit is called on the first four points, least squares SVD") {
test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
}
WHEN("Circle fit is called on the middle four points, least squares SVD") {
test_circle_fit(Geometry::circle_linear_least_squares_svd(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
}
WHEN("Circle fit is called on the entire array, least squares QR decomposition") {
test_circle_fit(Geometry::circle_linear_least_squares_qr(sample), expected_center, expected_radius);
}
WHEN("Circle fit is called on the first four points, least squares QR decomposition") {
test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
}
WHEN("Circle fit is called on the middle four points, least squares QR decomposition") {
test_circle_fit(Geometry::circle_linear_least_squares_qr(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
}
WHEN("Circle fit is called on the entire array, least squares by normal equations") {
test_circle_fit(Geometry::circle_linear_least_squares_normal(sample), expected_center, expected_radius);
}
WHEN("Circle fit is called on the first four points, least squares by normal equations") {
test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin(), sample.cbegin() + 4)), expected_center, expected_radius);
}
WHEN("Circle fit is called on the middle four points, least squares by normal equations") {
test_circle_fit(Geometry::circle_linear_least_squares_normal(Vec2ds(sample.cbegin() + 2, sample.cbegin() + 6)), expected_center, expected_radius);
}
}
}
TEST_CASE("smallest_enclosing_circle_welzl", "[Geometry]") {
// Some random points in plane.
Points pts {
@@ -310,6 +391,7 @@ SCENARIO("Path chaining", "[Geometry]") {
GIVEN("A path") {
Points points = { Point(26,26),Point(52,26),Point(0,26),Point(26,52),Point(26,0),Point(0,52),Point(52,52),Point(52,0) };
THEN("Chained with no diagonals (thus 26 units long)") {
// if chain_points() works correctly, these points should be joined with no diagonal paths
std::vector<Points::size_type> indices = chain_points(points);
for (Points::size_type i = 0; i + 1 < indices.size(); ++ i) {
double dist = (points.at(indices.at(i)).cast<double>() - points.at(indices.at(i+1)).cast<double>()).norm();

View File

@@ -5,6 +5,24 @@
using namespace Slic3r;
SCENARIO("Simplify polyne, template", "[Polyline]")
{
Points polyline{ {0,0}, {1000,0}, {2000,0}, {2000,1000}, {2000,2000}, {1000,2000}, {0,2000}, {0,1000}, {0,0} };
WHEN("simplified with Douglas-Peucker with back inserter") {
Points out;
douglas_peucker<int64_t>(polyline.begin(), polyline.end(), std::back_inserter(out), 10, [](const Point &p) { return p; });
THEN("simplified correctly") {
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
}
}
WHEN("simplified with Douglas-Peucker in place") {
Points out{ polyline };
out.erase(douglas_peucker<int64_t>(out.begin(), out.end(), out.begin(), 10, [](const Point &p) { return p; }), out.end());
THEN("simplified correctly") {
REQUIRE(out == Points{ {0,0}, {2000,0}, {2000,2000}, {0,2000}, {0,0} });
}
}
}
SCENARIO("Simplify polyline", "[Polyline]")
{
GIVEN("polyline 1") {

View File

@@ -0,0 +1,98 @@
#include <catch2/catch.hpp>
#include <string_view>
#include "libslic3r/StaticMap.hpp"
TEST_CASE("Empty static map should be possible to create and should be empty", "[StaticMap]")
{
using namespace Slic3r;
static const constexpr StaticSet EmptySet;
static const constexpr auto EmptyMap = make_staticmap<int, int>();
constexpr bool is_map_empty = EmptyMap.empty();
constexpr bool is_set_empty = EmptySet.empty();
REQUIRE(is_map_empty);
REQUIRE(is_set_empty);
}
TEST_CASE("StaticSet should derive it's type from the initializer", "[StaticMap]") {
using namespace Slic3r;
static const constexpr StaticSet iOneSet = { 1 };
static constexpr size_t iOneSetSize = iOneSet.size();
REQUIRE(iOneSetSize == 1);
static const constexpr StaticSet iManySet = { 1, 3, 5, 80, 40 };
static constexpr size_t iManySetSize = iManySet.size();
REQUIRE(iManySetSize == 5);
}
TEST_CASE("StaticMap should derive it's type using make_staticmap", "[StaticMap]") {
using namespace Slic3r;
static const constexpr auto ciOneMap = make_staticmap<char, int>({
{'a', 1},
});
static constexpr size_t ciOneMapSize = ciOneMap.size();
static constexpr bool ciOneMapValid = query(ciOneMap, 'a').value_or(0) == 1;
REQUIRE(ciOneMapSize == 1);
REQUIRE(ciOneMapValid);
static const constexpr auto ciManyMap = make_staticmap<char, int>({
{'a', 1}, {'b', 2}, {'A', 10}
});
static constexpr size_t ciManyMapSize = ciManyMap.size();
static constexpr bool ciManyMapValid =
query(ciManyMap, 'a').value_or(0) == 1 &&
query(ciManyMap, 'b').value_or(0) == 2 &&
query(ciManyMap, 'A').value_or(0) == 10 &&
!contains(ciManyMap, 'B') &&
!query(ciManyMap, 'c').has_value();
REQUIRE(ciManyMapSize == 3);
REQUIRE(ciManyMapValid);
for (auto &[k, v] : ciManyMap) {
auto val = query(ciManyMap, k);
REQUIRE(val.has_value());
REQUIRE(*val == v);
}
}
TEST_CASE("StaticSet should be able to find contained values", "[StaticMap]")
{
using namespace Slic3r;
using namespace std::string_view_literals;
auto cmp = [](const char *a, const char *b) constexpr {
return std::string_view{a} < std::string_view{b};
};
static constexpr StaticSet CStrSet = {cmp, "One", "Two", "Three"};
static constexpr StaticSet StringSet = {"One"sv, "Two"sv, "Three"sv};
static constexpr bool CStrSetValid = query(CStrSet, "One").has_value() &&
contains(CStrSet, "Two") &&
contains(CStrSet, "Three") &&
!contains(CStrSet, "one") &&
!contains(CStrSet, "two") &&
!contains(CStrSet, "three");
static constexpr bool StringSetValid = contains(StringSet, "One"sv) &&
contains(StringSet, "Two"sv) &&
contains(StringSet, "Three"sv) &&
!contains(StringSet, "one"sv) &&
!contains(StringSet, "two"sv) &&
!contains(StringSet, "three"sv);
REQUIRE(CStrSetValid);
REQUIRE(StringSetValid);
REQUIRE(CStrSet.size() == 3);
REQUIRE(StringSet.size() == 3);
}

View File

@@ -0,0 +1,163 @@
#include "libslic3r/Point.hpp"
#include <catch2/catch.hpp>
#include <libslic3r/SupportSpotsGenerator.hpp>
using namespace Slic3r;
using namespace SupportSpotsGenerator;
TEST_CASE("Numerical integral calculation compared with exact solution.", "[SupportSpotsGenerator]") {
const float width = 10;
const float height = 20;
const Polygon polygon = {
scaled(Vec2f{-width / 2, -height / 2}),
scaled(Vec2f{width / 2, -height / 2}),
scaled(Vec2f{width / 2, height / 2}),
scaled(Vec2f{-width / 2, height / 2})
};
const Integrals integrals{{polygon}};
CHECK(integrals.area == Approx(width * height));
CHECK(integrals.x_i.x() == Approx(0));
CHECK(integrals.x_i.y() == Approx(0));
CHECK(integrals.x_i_squared.x() == Approx(std::pow(width, 3) * height / 12));
CHECK(integrals.x_i_squared.y() == Approx(width * std::pow(height, 3) / 12));
}
TEST_CASE("Moment values and ratio check.", "[SupportSpotsGenerator]") {
const float width = 40;
const float height = 2;
// Moments are calculated at centroid.
// Polygon centroid must not be (0, 0).
const Polygon polygon = {
scaled(Vec2f{0, 0}),
scaled(Vec2f{width, 0}),
scaled(Vec2f{width, height}),
scaled(Vec2f{0, height})
};
const Integrals integrals{{polygon}};
const Vec2f x_axis{1, 0};
const float x_axis_moment = compute_second_moment(integrals, x_axis);
const Vec2f y_axis{0, 1};
const float y_axis_moment = compute_second_moment(integrals, y_axis);
const float moment_ratio = std::pow(width / height, 2);
// Ensure the object transaltion has no effect.
CHECK(x_axis_moment == Approx(width * std::pow(height, 3) / 12));
CHECK(y_axis_moment == Approx(std::pow(width, 3) * height / 12));
// If the object is "wide" the y axis moments should be large compared to x axis moment.
CHECK(y_axis_moment / x_axis_moment == Approx(moment_ratio));
}
TEST_CASE("Moments calculation for rotated axis.", "[SupportSpotsGenerator]") {
Polygon polygon = {
scaled(Vec2f{6.362284076172198, 138.9674202217155}),
scaled(Vec2f{97.48779843751677, 106.08136606617076}),
scaled(Vec2f{135.75221821532384, 66.84428834668765}),
scaled(Vec2f{191.5308049852741, 45.77905628725614}),
scaled(Vec2f{182.7525148049201, 74.01799041087513}),
scaled(Vec2f{296.83210979283473, 196.80022572637228}),
scaled(Vec2f{215.16434429179148, 187.45715418834143}),
scaled(Vec2f{64.64574271229334, 284.293883209721}),
scaled(Vec2f{110.76507036894843, 174.35633141113783}),
scaled(Vec2f{77.56229640885199, 189.33057746591336})
};
Integrals integrals{{polygon}};
// Meassured counterclockwise from (1, 0)
const float angle = 1.432f;
Vec2f axis{std::cos(angle), std::sin(angle)};
float moment_calculated_then_rotated = compute_second_moment(
integrals,
axis
);
// We want to rotate the object clockwise by angle to align the axis with (1, 0)
// Method .rotate is counterclockwise for positive angle
polygon.rotate(-angle);
Integrals integrals_rotated{{polygon}};
float moment_rotated_polygon = compute_second_moment(
integrals_rotated,
Vec2f{1, 0}
);
// Up to 0.1% accuracy
CHECK_THAT(moment_calculated_then_rotated, Catch::Matchers::WithinRel(moment_rotated_polygon, 0.001f));
}
struct ObjectPartFixture {
const Polyline polyline{
Point{scaled(Vec2f{0, 0})},
Point{scaled(Vec2f{1, 0})},
};
const float width = 0.1f;
bool connected_to_bed = true;
coordf_t print_head_z = 0.2;
coordf_t layer_height = 0.2;
ExtrusionAttributes attributes;
ExtrusionEntityCollection collection;
std::vector<const ExtrusionEntityCollection*> extrusions{};
Polygon expected_polygon{
Point{scaled(Vec2f{0, -width / 2})},
Point{scaled(Vec2f{1, -width / 2})},
Point{scaled(Vec2f{1, width / 2})},
Point{scaled(Vec2f{0, width / 2})}
};
ObjectPartFixture() {
attributes.width = width;
const ExtrusionPath path{polyline, attributes};
collection.append(path);
extrusions.push_back(&collection);
}
};
TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart using extrusion collections", "[SupportSpotsGenerator]") {
ObjectPart part{
extrusions,
connected_to_bed,
print_head_z,
layer_height,
std::nullopt
};
Integrals expected{{expected_polygon}};
CHECK(part.connected_to_bed == true);
Vec3f volume_centroid{part.volume_centroid_accumulator / part.volume};
CHECK(volume_centroid.x() == Approx(0.5));
CHECK(volume_centroid.y() == Approx(0));
CHECK(volume_centroid.z() == Approx(layer_height / 2));
CHECK(part.sticking_area == Approx(expected.area));
CHECK(part.sticking_centroid_accumulator.x() == Approx(expected.x_i.x()));
CHECK(part.sticking_centroid_accumulator.y() == Approx(expected.x_i.y()));
CHECK(part.sticking_second_moment_of_area_accumulator.x() == Approx(expected.x_i_squared.x()));
CHECK(part.sticking_second_moment_of_area_accumulator.y() == Approx(expected.x_i_squared.y()));
CHECK(part.sticking_second_moment_of_area_covariance_accumulator == Approx(expected.xy).margin(1e-6));
CHECK(part.volume == Approx(layer_height * width));
}
TEST_CASE_METHOD(ObjectPartFixture, "Constructing ObjectPart with brim", "[SupportSpotsGenerator]") {
float brim_width = 1;
Polygons brim = get_brim(ExPolygon{expected_polygon}, BrimType::btOuterOnly, brim_width);
ObjectPart part{
extrusions,
connected_to_bed,
print_head_z,
layer_height,
brim
};
CHECK(part.sticking_area == Approx((1 + 2*brim_width) * (width + 2*brim_width)));
}

View File

@@ -18,6 +18,6 @@ if (WIN32)
endif()
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
set(_catch_args "exclude:[NotWorking]")
set(_catch_args "exclude:[NotWorking];-s")
list(APPEND _catch_args "${CATCH_EXTRA_ARGS}")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${_catch_args})

View File

@@ -20,6 +20,13 @@ struct Progress: Slic3r::ProgressIndicator {
using TestClasses = std::tuple< Slic3r::GUI::UIThreadWorker, Slic3r::GUI::BoostThreadWorker >;
TEMPLATE_LIST_TEST_CASE("Empty worker should not block when queried for idle", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
worker.wait_for_idle();
REQUIRE(worker.is_idle());
}
TEMPLATE_LIST_TEST_CASE("Empty worker should not do anything", "[Jobs]", TestClasses) {
TestType worker{std::make_unique<Progress>()};
@@ -49,6 +56,8 @@ TEMPLATE_LIST_TEST_CASE("State should not be idle while running a job", "[Jobs]"
}).wait();
});
// make sure that the job starts BEFORE the worker.wait_for_idle() is called
std::this_thread::sleep_for(std::chrono::milliseconds(100));
worker.wait_for_idle();
REQUIRE(worker.is_idle());
@@ -85,7 +94,8 @@ TEMPLATE_LIST_TEST_CASE("Cancellation should be recognized be the worker", "[Job
for (int s = 0; s <= 100; ++s) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
ctl.update_status(s, "Running");
if (ctl.was_canceled()) break;
if (ctl.was_canceled())
break;
}
},
[](bool cancelled, std::exception_ptr &) { // finalize

View File

@@ -0,0 +1,13 @@
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
add_executable(${_TEST_NAME}_tests
${_TEST_NAME}_tests_main.cpp
test_thumbnails_input_string.cpp
test_thumbnails_ini_string.cpp
)
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS})

View File

@@ -0,0 +1,235 @@
#include <catch2/catch.hpp>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <libslic3r/GCode/Thumbnails.hpp>
using namespace Slic3r;
using namespace GCodeThumbnails;
static std::string empty_thumbnails()
{
return "thumbnails = \n"
"thumbnails_format = ";
}
static std::string valid_thumbnails()
{
return "thumbnails = 160x120/JPG, 23x78/QOI, 230x780/JPG\n"
"thumbnails_format = JPG";
}
static std::string valid_thumbnails2()
{
return "thumbnails = 160x120/PNG, 23x78/QOi, 320x240/PNg, 230x780/JPG\n"
"thumbnails_format = pnG";
}
static std::string valid_thumbnails3()
{
return "thumbnails = 160x120/JPG, 23x78/QOI, 230x780/JPG";
}
static std::string old_valid_thumbnails()
{
return "thumbnails = 160x120\n"
"thumbnails_format = JPG";
}
static std::string old_valid_thumbnails2()
{
return "thumbnails = 160x120, 23x78, 320x240\n"
"thumbnails_format = PNG";
}
static std::string old_invalid_thumbnails()
{
return "thumbnails = 160x\n"
"thumbnails_format = JPG";
}
static std::string old_invalid_thumbnails2()
{
return "thumbnails = 160x120, 23*78, 320x240\n"
"thumbnails_format = PNG";
}
static std::string out_of_range_thumbnails()
{
return "thumbnails = 1160x1200/PNG, 23x78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = PNG";
}
static std::string out_of_range_thumbnails2()
{
return "thumbnails = 1160x120/PNG, 23x78/QOI, -320x240/PNG, 230x780/JPG\n"
"thumbnails_format = PNG";
}
static std::string invalid_ext_thumbnails()
{
return "thumbnails = 1160x120/PNk, 23x78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = QOI";
}
static std::string invalid_ext_thumbnails2()
{
return "thumbnails = 1160x120/PNG, 23x78/QO, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = PNG";
}
static std::string invalid_val_thumbnails()
{
return "thumbnails = 1160x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = JPG";
}
static std::string invalid_val_thumbnails2()
{
return "thumbnails = x120/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = PNG";
}
static std::string invalid_val_thumbnails3()
{
return "thumbnails = 1x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = qoi";
}
static std::string invalid_val_thumbnails4()
{
return "thumbnails = 123*78/QOI, 320x240/PNG, 230x780/JPG\n"
"thumbnails_format = jpG";
}
static DynamicPrintConfig thumbnails_config()
{
DynamicPrintConfig config;
config.apply_only(FullPrintConfig::defaults() , { "thumbnails", "thumbnails_format" });
return config;
}
TEST_CASE("Validate Empty Thumbnails", "[Thumbnails in Config]") {
DynamicPrintConfig config = thumbnails_config();
auto test_loaded_config = [](DynamicPrintConfig& config) {
REQUIRE(config.opt<ConfigOptionString>("thumbnails")->empty());
REQUIRE(config.option("thumbnails_format")->getInt() == (int)GCodeThumbnailsFormat::PNG);
};
SECTION("Load empty init_data") {
REQUIRE_NOTHROW(config.load_from_ini_string("", Enable));
test_loaded_config(config);
}
SECTION("Load empty format and empty thumbnails") {
REQUIRE_THROWS_AS(config.load_from_ini_string(empty_thumbnails(), Enable), BadOptionValueException);
test_loaded_config(config);
}
}
TEST_CASE("Validate New Thumbnails", "[Thumbnails in Config]") {
DynamicPrintConfig config = thumbnails_config();
auto test_loaded_config = [](DynamicPrintConfig& config, GCodeThumbnailsFormat format) {
REQUIRE(!config.opt<ConfigOptionString>("thumbnails")->empty());
REQUIRE(config.option("thumbnails_format")->getInt() == (int)format);
};
SECTION("Test 1 (valid)") {
REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails(), Enable));
test_loaded_config(config, GCodeThumbnailsFormat::JPG);
}
SECTION("Test 2 (valid)") {
REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails2(), Enable));
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 3 (valid)") {
REQUIRE_NOTHROW(config.load_from_ini_string(valid_thumbnails3(), Enable));
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 1 (out_of_range)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(out_of_range_thumbnails(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 2 (out_of_range)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(out_of_range_thumbnails2(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 1 (invalid_ext)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_ext_thumbnails(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::QOI);
}
SECTION("Test 2 (invalid_ext)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_ext_thumbnails2(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 1 (invalid_val)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::JPG);
}
SECTION("Test 2 (invalid_val)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails2(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 3 (invalid_val)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails3(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 4 (invalid_val)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(invalid_val_thumbnails4(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
}
TEST_CASE("Validate Old Thumbnails", "[Thumbnails in Config]") {
DynamicPrintConfig config = thumbnails_config();
auto test_loaded_config = [](DynamicPrintConfig& config, GCodeThumbnailsFormat format) {
REQUIRE(!config.opt<ConfigOptionString>("thumbnails")->empty());
REQUIRE(config.option("thumbnails_format")->getInt() == (int)format);
};
SECTION("Test 1 (valid)") {
REQUIRE_NOTHROW(config.load_from_ini_string(old_valid_thumbnails(), Enable));
test_loaded_config(config, GCodeThumbnailsFormat::JPG);
}
SECTION("Test 2 (valid)") {
REQUIRE_NOTHROW(config.load_from_ini_string(old_valid_thumbnails2(), Enable));
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
SECTION("Test 1 (invalid)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(old_invalid_thumbnails(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::JPG);
}
SECTION("Test 2 (invalid)") {
REQUIRE_THROWS_AS(config.load_from_ini_string(old_invalid_thumbnails2(), Enable), BadOptionValueException);
test_loaded_config(config, GCodeThumbnailsFormat::PNG);
}
}

View File

@@ -0,0 +1,152 @@
#include <catch2/catch.hpp>
#include <test_utils.hpp>
#include <libslic3r/GCode/Thumbnails.hpp>
using namespace Slic3r;
using namespace GCodeThumbnails;
// Test Thumbnails lines
static std::string empty_thumbnails()
{
return "";
}
static std::string valid_thumbnails()
{
return "160x120/PNG, 23x78/QOI, 230x780/JPG";
}
static std::string valid_thumbnails2()
{
return "160x120/PNG, 23x78/QOi, 320x240/PNg, 230x780/JPG";
}
static std::string out_of_range_thumbnail()
{
return "160x1200/PNG, 23x78/QOI, 320x240/PNG, 230x780/JPG";
}
static std::string out_of_range_thumbnail2()
{
return "160x120/PNG, 23x78/QOI, -320x240/PNG, 230x780/JPG";
}
static std::string invalid_ext_thumbnail()
{
return "160x120/PNk, 23x78/QOI, 320x240/PNG, 230x780/JPG";
}
static std::string invalid_ext_thumbnail2()
{
return "160x120/PNG, 23x78/QO, 320x240/PNG, 230x780/JPG";
}
static std::string invalid_val_thumbnail()
{
return "160x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG";
}
static std::string invalid_val_thumbnail2()
{
return "x120/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG";
}
static std::string invalid_val_thumbnail3()
{
return "x/PNg, 23x78/QOI, 320x240/PNG, 230x780/JPG";
}
static std::string invalid_val_thumbnail4()
{
return "23*78/QOI, 320x240/PNG, 230x780/JPG";
}
TEST_CASE("Empty Thumbnails", "[Thumbnails]") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(empty_thumbnails());
REQUIRE(errors == enum_bitmask<ThumbnailError>());
REQUIRE(thumbnails.empty());
}
TEST_CASE("Valid Thumbnails", "[Thumbnails]") {
SECTION("Test 1") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(valid_thumbnails());
REQUIRE(errors == enum_bitmask<ThumbnailError>());
REQUIRE(thumbnails.size() == 3);
}
SECTION("Test 2") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(valid_thumbnails2());
REQUIRE(errors == enum_bitmask<ThumbnailError>());
REQUIRE(thumbnails.size() == 4);
}
}
TEST_CASE("Out of range Thumbnails", "[Thumbnails]") {
SECTION("Test 1") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(out_of_range_thumbnail());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::OutOfRange));
REQUIRE(thumbnails.size() == 3);
}
SECTION("Test 2") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(out_of_range_thumbnail2());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::OutOfRange));
REQUIRE(thumbnails.size() == 3);
}
}
TEST_CASE("Invalid extention Thumbnails", "[Thumbnails]") {
SECTION("Test 1") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_ext_thumbnail());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidExt));
REQUIRE(thumbnails.size() == 4);
}
SECTION("Test 2") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_ext_thumbnail2());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidExt));
REQUIRE(thumbnails.size() == 4);
}
}
TEST_CASE("Invalid value Thumbnails", "[Thumbnails]") {
SECTION("Test 1") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidVal));
REQUIRE(thumbnails.size() == 3);
}
SECTION("Test 2") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail2());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidVal));
REQUIRE(thumbnails.size() == 3);
}
SECTION("Test 3") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail3());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidVal));
REQUIRE(thumbnails.size() == 3);
}
SECTION("Test 4") {
auto [thumbnails, errors] = make_and_check_thumbnail_list(invalid_val_thumbnail4());
REQUIRE(errors != enum_bitmask<ThumbnailError>());
REQUIRE(errors.has(ThumbnailError::InvalidVal));
REQUIRE(thumbnails.size() == 2);
}
}

View File

@@ -0,0 +1 @@
#include <catch_main.hpp>