Implement a command-line interface.

This commit adds a basic command-line interface. This interface
allows rendering thumbnails and exporting data in batch mode.
pull/36/merge
whitequark 2016-11-29 02:57:41 +00:00
parent dbc567ed89
commit 47244c5e89
11 changed files with 422 additions and 87 deletions

View File

@ -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,

View File

@ -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

View File

@ -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_PROPERTY:resources,EXTRA_SOURCES>)
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)

View File

@ -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}
$<TARGET_PROPERTY:resources,EXTRA_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}
$<TARGET_FILE:solvespace>
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 $<TARGET_FILE:${bundle}>
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_PROPERTY:resources,EXTRA_SOURCES>)
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 $<TARGET_FILE:solvespace-cli> ${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}
$<TARGET_FILE:${bundle}>
COMMAND install_name_tool -change ${canonical_lib} @executable_path/${name}
$<TARGET_FILE:solvespace-cli>
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 $<TARGET_FILE:${bundle}>
COMMENT "Building ${bundle}.dmg"
VERBATIM)
add_custom_target(${bundle}-dmg ALL
DEPENDS ${EXECUTABLE_OUTPUT_PATH}/${bundle}.dmg)
endif()

317
src/platform/climain.cpp Normal file
View File

@ -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<Pixmap> framebuffer;
}
static void ShowUsage(const char *argv0) {
fprintf(stderr, "Usage: %s <command> <options> <filename> [filename...]", argv0);
//-----------------------------------------------------------------------------> 80 col */
fprintf(stderr, R"(
When run, performs an action specified by <command> on every <filename>.
Common options:
-o, --output <pattern>
For an input file <basename>.slvs, replaces the '%%' symbol in <pattern>
with <basename> 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 <direction>
Selects the camera direction. <direction> can be one of "top", "bottom",
"left", "right", "front", "back", or "isometric".
-t, --chord-tol <tolerance>
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 <pattern> --size <size> --view <direction>
[--chord-tol <tolerance>]
Outputs a rendered view of the sketch, like the SolveSpace GUI would.
<size> is <width>x<height>, in pixels. Graphics acceleration is
not used, and the output may look slightly different from the GUI.
export-view --output <pattern> --view <direction> [--chord-tol <tolerance>]
Exports a view of the sketch, in a 2d vector format.
export-wireframe --output <pattern> [--chord-tol <tolerance>]
Exports a wireframe of the sketch, in a 3d vector format.
export-mesh --output <pattern> [--chord-tol <tolerance>]
Exports a triangle mesh of solids in the sketch, with exact surfaces
being triangulated first.
export-surfaces --output <pattern>
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<void(const std::string &)> runner;
std::vector<std::string> 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;
}
}

View File

@ -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
//-----------------------------------------------------------------------------

View File

@ -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;

View File

@ -182,7 +182,7 @@ const FileFilter SlvsFileFilter[] = {
};
// PNG format bitmap
const FileFilter PngFileFilter[] = {
{ "PNG", { "png" } },
{ "PNG file", { "png" } },
{ NULL, {} }
};
// Triangle mesh

View File

@ -57,19 +57,19 @@ set(testsuite_SOURCES
group/translate_nd/test.cpp
)
add_executable(solvespace_testsuite
add_executable(solvespace-testsuite
${testsuite_SOURCES}
$<TARGET_PROPERTY:resources,EXTRA_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 $<TARGET_FILE:solvespace_testsuite>
add_custom_target(test_solvespace ALL
COMMAND $<TARGET_FILE:solvespace-testsuite>
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()

View File

@ -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;

View File

@ -228,7 +228,7 @@ bool Test::Helper::CheckRender(const char *file, int line, const char *reference
std::shared_ptr<Pixmap> 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");