diff --git a/CHANGELOG.md b/CHANGELOG.md index 70d58733..65ea7704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ New rendering features: * The "Show/hide outlines" button is now independent from "Show/hide edges". Other new features: + * New command-line interface, for batch exporting and more. * New command for measuring total length of selected entities, "Analyze → Measure Perimeter". * New link to match the on-screen size of the sketch with its actual size, diff --git a/appveyor.yml b/appveyor.yml index 5533b176..38a090a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,28 +9,22 @@ before_build: - cmake -G"Visual Studio 12" -T v120 .. build_script: - msbuild "src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "test\solvespace_testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild "src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild "test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: - - bin\%BUILD_TYPE%\solvespace_testsuite.exe + - bin\%BUILD_TYPE%\solvespace-testsuite.exe artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe name: solvespace.exe + - path: build\bin\%BUILD_TYPE%\solvespace-cli.exe + name: solvespace-cli.exe - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb deploy: - # Releases to solvespace/solvespace - provider: GitHub auth_token: secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC description: "" - artifact: solvespace.exe - on: - appveyor_repo_tag: true - # Releases to whitequark/solvespace (to be removed) - - provider: GitHub - auth_token: - secure: Flqxu1cz6PyxVT1wzTP4bSrQOY8wFrO7pJxYxvjEkLqIUU4dsDQrs2rac/A9deet - description: "" - artifact: solvespace.exe + artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb on: appveyor_repo_tag: true diff --git a/bench/CMakeLists.txt b/bench/CMakeLists.txt index 18e57364..26172f16 100644 --- a/bench/CMakeLists.txt +++ b/bench/CMakeLists.txt @@ -5,12 +5,13 @@ foreach(pkg_config_lib CAIRO) link_directories(${${pkg_config_lib}_LIBRARY_DIRS}) endforeach() -add_executable(solvespace_benchmark +add_executable(solvespace-benchmark harness.cpp $) -target_link_libraries(solvespace_benchmark - solvespace_headless) +target_link_libraries(solvespace-benchmark + solvespace-core + solvespace-headless) -add_dependencies(solvespace_benchmark +add_dependencies(solvespace-benchmark resources) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91cfe792..de95913a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -127,7 +127,7 @@ endif() # solvespace library -set(solvespace_cad_HEADERS +set(solvespace_core_HEADERS config.h dsc.h expr.h @@ -139,7 +139,7 @@ set(solvespace_cad_HEADERS render/gl2shader.h srf/surface.h) -set(solvespace_cad_SOURCES +set(solvespace_core_SOURCES bsp.cpp clipboard.cpp confscreen.cpp @@ -186,16 +186,16 @@ set(solvespace_cad_SOURCES srf/surfinter.cpp srf/triangulate.cpp) -set(solvespace_cad_gl_SOURCES +set(solvespace_core_gl_SOURCES export.cpp solvespace.cpp) -add_library(solvespace_cad STATIC +add_library(solvespace-core STATIC ${util_SOURCES} - ${solvespace_cad_HEADERS} - ${solvespace_cad_SOURCES}) + ${solvespace_core_HEADERS} + ${solvespace_core_SOURCES}) -target_link_libraries(solvespace_cad +target_link_libraries(solvespace-core dxfrw ${util_LIBRARIES} ${ZLIB_LIBRARY} @@ -203,13 +203,13 @@ target_link_libraries(solvespace_cad ${FREETYPE_LIBRARY} ${Backtrace_LIBRARIES}) -target_compile_options(solvespace_cad +target_compile_options(solvespace-core PRIVATE ${COVERAGE_FLAGS}) -# solvespace gui executable +# solvespace graphical executable add_executable(solvespace WIN32 MACOSX_BUNDLE - ${solvespace_cad_gl_SOURCES} + ${solvespace_core_gl_SOURCES} ${platform_SOURCES} $) @@ -217,69 +217,93 @@ add_dependencies(solvespace resources) target_link_libraries(solvespace - solvespace_cad + solvespace-core ${OPENGL_LIBRARIES} ${platform_LIBRARIES} ${COVERAGE_LIBRARY}) -if(WIN32 AND NOT MINGW) +if(MSVC) set_target_properties(solvespace PROPERTIES LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO") endif() -if(APPLE) - foreach(lib ${platform_BUNDLED_LIBS}) - get_filename_component(name ${lib} NAME) - set(target ${CMAKE_CURRENT_BINARY_DIR}/solvespace.app/Contents/MacOS/${name}) - - execute_process(COMMAND otool -D ${lib} - OUTPUT_VARIABLE canonical_lib OUTPUT_STRIP_TRAILING_WHITESPACE) - string(REGEX REPLACE "^.+:\n" "" canonical_lib ${canonical_lib}) - add_custom_command(TARGET solvespace POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy ${lib} ${target} - COMMAND install_name_tool -change ${canonical_lib} @executable_path/${name} - $ - COMMENT "Bundling shared library ${lib}" - VERBATIM) - endforeach() - - set(bundle solvespace) - add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${bundle}.dmg - COMMAND ${CMAKE_COMMAND} -E remove ${CMAKE_BINARY_DIR}/${bundle}.dmg - COMMAND hdiutil create -srcfolder ${CMAKE_CURRENT_BINARY_DIR}/${bundle}.app - ${CMAKE_BINARY_DIR}/${bundle}.dmg - DEPENDS $ - COMMENT "Building ${bundle}.dmg" - VERBATIM) - add_custom_target(${bundle}-dmg ALL - DEPENDS ${CMAKE_BINARY_DIR}/${bundle}.dmg) -endif() - -if(NOT WIN32) - install(TARGETS solvespace - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - BUNDLE DESTINATION .) -endif() - # solvespace headless library set(headless_SOURCES platform/headless.cpp render/rendercairo.cpp) -add_library(solvespace_headless STATIC EXCLUDE_FROM_ALL - ${solvespace_cad_gl_SOURCES} +add_library(solvespace-headless STATIC EXCLUDE_FROM_ALL + ${solvespace_core_gl_SOURCES} ${headless_SOURCES}) -target_compile_definitions(solvespace_headless +target_compile_definitions(solvespace-headless PRIVATE -DHEADLESS) -target_include_directories(solvespace_headless +target_include_directories(solvespace-headless INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(solvespace_headless - solvespace_cad +target_link_libraries(solvespace-headless + solvespace-core ${CAIRO_LIBRARIES}) -target_compile_options(solvespace_headless +target_compile_options(solvespace-headless PRIVATE ${COVERAGE_FLAGS}) + +# solvespace command-line executable + +add_executable(solvespace-cli + platform/climain.cpp + $) + +target_link_libraries(solvespace-cli + solvespace-core + solvespace-headless) + +add_dependencies(solvespace-cli + resources) + +# solvespace unix package + +if(NOT (WIN32 OR APPLE)) + install(TARGETS solvespace solvespace-cli + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() + +# solvespace macOS package + +if(APPLE) + set(bundle solvespace) + set(bundle_bin ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app/Contents/MacOS) + + add_custom_command(TARGET solvespace POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy $ ${bundle_bin} + COMMENT "Bundling executable solvespace-cli" + VERBATIM) + + foreach(lib ${platform_BUNDLED_LIBS}) + get_filename_component(name ${lib} NAME) + + execute_process(COMMAND otool -D ${lib} + OUTPUT_VARIABLE canonical_lib OUTPUT_STRIP_TRAILING_WHITESPACE) + string(REGEX REPLACE "^.+:\n" "" canonical_lib ${canonical_lib}) + add_custom_command(TARGET ${bundle} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${lib} ${bundle_bin}/${name} + COMMAND install_name_tool -change ${canonical_lib} @executable_path/${name} + $ + COMMAND install_name_tool -change ${canonical_lib} @executable_path/${name} + $ + COMMENT "Bundling shared library ${lib}" + VERBATIM) + endforeach() + + add_custom_command(OUTPUT ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg + COMMAND ${CMAKE_COMMAND} -E remove ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg + COMMAND hdiutil create -srcfolder ${EXECUTABLE_OUTPUT_PATH}/${bundle}.app + ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg + DEPENDS $ + COMMENT "Building ${bundle}.dmg" + VERBATIM) + add_custom_target(${bundle}-dmg ALL + DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg) +endif() diff --git a/src/platform/climain.cpp b/src/platform/climain.cpp new file mode 100644 index 00000000..79b16a62 --- /dev/null +++ b/src/platform/climain.cpp @@ -0,0 +1,317 @@ +//----------------------------------------------------------------------------- +// Our main() function for the command-line interface. +// +// Copyright 2016 whitequark +//----------------------------------------------------------------------------- +#include "solvespace.h" + +namespace SolveSpace { + // These are defined in headless.cpp, and aren't exposed in solvespace.h. + extern std::shared_ptr framebuffer; +} + +static void ShowUsage(const char *argv0) { + fprintf(stderr, "Usage: %s [filename...]", argv0); +//-----------------------------------------------------------------------------> 80 col */ + fprintf(stderr, R"( + When run, performs an action specified by on every . + + Common options: + -o, --output + For an input file .slvs, replaces the '%%' symbol in + with and uses it as output file. For example, when using + --output %%-2d.png for input files a.slvs and b.slvs, output files + a-2d.png and b-2d.png will be written. + -v, --view + Selects the camera direction. can be one of "top", "bottom", + "left", "right", "front", "back", or "isometric". + -t, --chord-tol + Selects the chord tolerance, used for converting exact curves to + piecewise linear, and exact surfaces into triangle meshes. + For export commands, the unit is mm, and the default is 1.0 mm. + For non-export commands, the unit is %%, and the default is 1.0 %%. + + Commands: + thumbnail --output --size --view + [--chord-tol ] + Outputs a rendered view of the sketch, like the SolveSpace GUI would. + is x, in pixels. Graphics acceleration is + not used, and the output may look slightly different from the GUI. + export-view --output --view [--chord-tol ] + Exports a view of the sketch, in a 2d vector format. + export-wireframe --output [--chord-tol ] + Exports a wireframe of the sketch, in a 3d vector format. + export-mesh --output [--chord-tol ] + Exports a triangle mesh of solids in the sketch, with exact surfaces + being triangulated first. + export-surfaces --output + Exports exact surfaces of solids in the sketch, if any. +)"); + + auto FormatListFromFileFilter = [](const FileFilter *filter) { + std::string descr; + while(filter->name) { + descr += "\n "; + descr += filter->name; + descr += " ("; + const char *const *patterns = filter->patterns; + while(*patterns) { + descr += *patterns; + if(*++patterns) { + descr += ", "; + } + } + descr += ")"; + filter++; + } + return descr; + }; + + fprintf(stderr, R"( + File formats: + thumbnail:%s + export-view:%s + export-wireframe:%s + export-mesh:%s + export-surfaces:%s +)", FormatListFromFileFilter(PngFileFilter).c_str(), + FormatListFromFileFilter(VectorFileFilter).c_str(), + FormatListFromFileFilter(Vector3dFileFilter).c_str(), + FormatListFromFileFilter(MeshFileFilter).c_str(), + FormatListFromFileFilter(SurfaceFileFilter).c_str()); +} + +static bool RunCommand(size_t argc, char **argv) { + if(argc < 2) return false; + + std::function runner; + + std::vector inputFiles; + auto ParseInputFile = [&](size_t &argn) { + std::string arg = argv[argn]; + if(arg[0] != '-') { + inputFiles.push_back(arg); + return true; + } else return false; + }; + + std::string outputPattern; + auto ParseOutputPattern = [&](size_t &argn) { + if(argn + 1 < argc && (!strcmp(argv[argn], "--output") || + !strcmp(argv[argn], "-o"))) { + argn++; + outputPattern = argv[argn]; + return true; + } else return false; + }; + + Vector projUp, projRight; + auto ParseViewDirection = [&](size_t &argn) { + if(argn + 1 < argc && (!strcmp(argv[argn], "--view") || + !strcmp(argv[argn], "-v"))) { + argn++; + if(!strcmp(argv[argn], "top")) { + projRight = Vector::From(1, 0, 0); + projUp = Vector::From(0, 1, 0); + } else if(!strcmp(argv[argn], "bottom")) { + projRight = Vector::From(-1, 0, 0); + projUp = Vector::From(0, 1, 0); + } else if(!strcmp(argv[argn], "left")) { + projRight = Vector::From(0, 1, 0); + projUp = Vector::From(0, 0, 1); + } else if(!strcmp(argv[argn], "right")) { + projRight = Vector::From(0, -1, 0); + projUp = Vector::From(0, 0, 1); + } else if(!strcmp(argv[argn], "front")) { + projRight = Vector::From(-1, 0, 0); + projUp = Vector::From(0, 0, 1); + } else if(!strcmp(argv[argn], "back")) { + projRight = Vector::From(1, 0, 0); + projUp = Vector::From(0, 0, 1); + } else if(!strcmp(argv[argn], "isometric")) { + projRight = Vector::From(0.707, 0.000, -0.707); + projUp = Vector::From(-0.408, 0.816, -0.408); + } else { + fprintf(stderr, "Unrecognized view direction '%s'\n", argv[argn]); + } + return true; + } else return false; + }; + + double chordTol = 1.0; + auto ParseChordTolerance = [&](size_t &argn) { + if(argn + 1 < argc && (!strcmp(argv[argn], "--chord-tol") || + !strcmp(argv[argn], "-t"))) { + argn++; + if(sscanf(argv[argn], "%lf", &chordTol) == 1) { + return true; + } else return false; + } else return false; + }; + + if(!strcmp(argv[1], "thumbnail")) { + unsigned width, height; + auto ParseSize = [&](size_t &argn) { + if(argn + 1 < argc && !strcmp(argv[argn], "--size")) { + argn++; + if(sscanf(argv[argn], "%ux%u", &width, &height) == 2) { + return true; + } else return false; + } else return false; + }; + + for(size_t argn = 2; argn < argc; argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseViewDirection(argn) || + ParseChordTolerance(argn) || + ParseSize(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", argv[argn]); + return false; + } + } + + if(width == 0 || height == 0) { + fprintf(stderr, "Non-zero viewport size must be specified.\n"); + return false; + } + + if(EXACT(projUp.Magnitude() == 0 || projRight.Magnitude() == 0)) { + fprintf(stderr, "View direction must be specified.\n"); + return false; + } + + runner = [&](const std::string &output) { + SS.GW.width = width; + SS.GW.height = height; + SS.GW.projRight = projRight; + SS.GW.projUp = projUp; + SS.chordTol = chordTol; + + SS.GW.ZoomToFit(/*includingInvisibles=*/false); + SS.GenerateAll(); + PaintGraphics(); + framebuffer->WritePng(output, /*flip=*/true); + }; + } else if(!strcmp(argv[1], "export-view")) { + for(size_t argn = 2; argn < argc; argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseViewDirection(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", argv[argn]); + return false; + } + } + + if(EXACT(projUp.Magnitude() == 0 || projRight.Magnitude() == 0)) { + fprintf(stderr, "View direction must be specified.\n"); + return false; + } + + runner = [&](const std::string &output) { + SS.GW.projRight = projRight; + SS.GW.projUp = projUp; + SS.exportChordTol = chordTol; + + SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/false); + }; + } else if(!strcmp(argv[1], "export-wireframe")) { + for(size_t argn = 2; argn < argc; argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", argv[argn]); + return false; + } + } + + runner = [&](const std::string &output) { + SS.exportChordTol = chordTol; + + SS.ExportViewOrWireframeTo(output, /*exportWireframe=*/true); + }; + } else if(!strcmp(argv[1], "export-mesh")) { + for(size_t argn = 2; argn < argc; argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn) || + ParseChordTolerance(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", argv[argn]); + return false; + } + } + + runner = [&](const std::string &output) { + SS.exportChordTol = chordTol; + + SS.ExportMeshTo(output); + }; + } else if(!strcmp(argv[1], "export-surfaces")) { + for(size_t argn = 2; argn < argc; argn++) { + if(!(ParseInputFile(argn) || + ParseOutputPattern(argn))) { + fprintf(stderr, "Unrecognized option '%s'.\n", argv[argn]); + return false; + } + } + + runner = [&](const std::string &output) { + StepFileWriter sfw = {}; + sfw.ExportSurfacesTo(output); + }; + } else { + fprintf(stderr, "Unrecognized command '%s'.\n", argv[1]); + return false; + } + + if(outputPattern.empty()) { + fprintf(stderr, "An output pattern must be specified.\n"); + return false; + } else if(outputPattern.find('%') == std::string::npos && inputFiles.size() > 1) { + fprintf(stderr, + "Output pattern must include a %% symbol when using multiple inputs!\n"); + return false; + } + + if(inputFiles.size() == 0) { + fprintf(stderr, "At least one input file must be specified.\n"); + return false; + } + + for(const std::string &inputFile : inputFiles) { + std::string outputFile = outputPattern; + size_t replaceAt = outputFile.find('%'); + if(replaceAt != std::string::npos) { + outputFile.replace(replaceAt, 1, Basename(inputFile, /*stripExtension=*/true)); + } + + SS.Init(); + if(!SS.LoadFromFile(inputFile)) { + fprintf(stderr, "Cannot load '%s'!\n", inputFile.c_str()); + return false; + } + SS.AfterNewFile(); + runner(outputFile); + SK.Clear(); + SS.Clear(); + + fprintf(stderr, "Written '%s'.\n", outputFile.c_str()); + } + + return true; +} + +int main(int argc, char **argv) { + InitPlatform(); + + if(argc == 1) { + ShowUsage(argv[0]); + return 0; + } + + if(!RunCommand(argc, argv)) { + return 1; + } else { + return 0; + } +} diff --git a/src/platform/headless.cpp b/src/platform/headless.cpp index 9be35d08..845eeb0a 100644 --- a/src/platform/headless.cpp +++ b/src/platform/headless.cpp @@ -1,5 +1,5 @@ //----------------------------------------------------------------------------- -// Our main() function for the headless (no OpenGL) test runner. +// Our platform support functions for the headless (no OpenGL) test runner. // // Copyright 2016 whitequark //----------------------------------------------------------------------------- diff --git a/src/platform/unixutil.cpp b/src/platform/unixutil.cpp index 9594bf07..193694e4 100644 --- a/src/platform/unixutil.cpp +++ b/src/platform/unixutil.cpp @@ -96,10 +96,8 @@ void ssremove(const std::string &filename) static std::string ExpandPath(std::string path) { char *expanded_c_path = realpath(path.c_str(), NULL); - if(expanded_c_path == NULL) { - fprintf(stderr, "realpath(%s): %s\n", path.c_str(), strerror(errno)); - return ""; - } + if(expanded_c_path == NULL) return ""; + std::string expanded_path = expanded_c_path; free(expanded_c_path); return expanded_path; diff --git a/src/solvespace.h b/src/solvespace.h index c6bff08f..d7deda96 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -182,7 +182,7 @@ const FileFilter SlvsFileFilter[] = { }; // PNG format bitmap const FileFilter PngFileFilter[] = { - { "PNG", { "png" } }, + { "PNG file", { "png" } }, { NULL, {} } }; // Triangle mesh diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa18451e..ca91188c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -57,19 +57,19 @@ set(testsuite_SOURCES group/translate_nd/test.cpp ) -add_executable(solvespace_testsuite +add_executable(solvespace-testsuite ${testsuite_SOURCES} $) -target_link_libraries(solvespace_testsuite - solvespace_headless +target_link_libraries(solvespace-testsuite + solvespace-headless ${COVERAGE_LIBRARY}) -add_dependencies(solvespace_testsuite +add_dependencies(solvespace-testsuite resources) -add_custom_target(solvespace-test ALL - COMMAND $ +add_custom_target(test_solvespace ALL + COMMAND $ COMMENT "Testing SolveSpace" VERBATIM) @@ -86,11 +86,11 @@ if(ENABLE_COVERAGE) OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/coverage_base.info COMMAND ${LCOV} ${LCOV_FLAGS} ${LCOV_COLLECT} -o ${CMAKE_BINARY_DIR}/coverage_base.info -i - DEPENDS solvespace_testsuite + DEPENDS solvespace-testsuite COMMENT "Importing baseline coverage data" VERBATIM) - add_custom_target(solvespace-coverage ALL + add_custom_target(coverage_solvespace ALL COMMAND ${LCOV} ${LCOV_FLAGS} ${LCOV_COLLECT} -o ${CMAKE_BINARY_DIR}/coverage_test.info COMMAND ${LCOV} ${LCOV_FLAGS} @@ -104,7 +104,7 @@ if(ENABLE_COVERAGE) -o ${CMAKE_BINARY_DIR}/coverage/ -t "SolveSpace testbench" DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/coverage_base.info - DEPENDS solvespace-test + DEPENDS test_solvespace COMMENT "Generating coverage report" VERBATIM) endif() diff --git a/test/commit.sh b/test/commit.sh index bd95b5d2..d49708e6 100755 --- a/test/commit.sh +++ b/test/commit.sh @@ -1,7 +1,7 @@ #!/bin/sh -ex -make -C build solvespace_testsuite -./build/test/solvespace_testsuite $* || true +make -C build solvespace-testsuite +./build/test/solvespace-testsuite $* || true for e in slvs png; do for i in `find . -name *.out.$e`; do mv $i `dirname $i`/`basename $i .out.$e`.$e; diff --git a/test/harness.cpp b/test/harness.cpp index e281659c..5a14b76b 100644 --- a/test/harness.cpp +++ b/test/harness.cpp @@ -228,7 +228,7 @@ bool Test::Helper::CheckRender(const char *file, int line, const char *reference std::shared_ptr refPixmap = Pixmap::ReadPng(refPath.c_str(), /*flip=*/true); if(!RecordCheck(refPixmap && refPixmap->Equals(*framebuffer))) { - framebuffer->WritePng(outPath.c_str(), /*flip=*/true); + framebuffer->WritePng(outPath, /*flip=*/true); if(!refPixmap) { PrintFailure(file, line, "reference render not present");