Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/Duet3D/RepRapFirmware.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Crocker <dcrocker@eschertech.com>2020-01-13 01:59:32 +0300
committerDavid Crocker <dcrocker@eschertech.com>2020-01-13 01:59:32 +0300
commit13084f6566c536df08430cce6c584f5e5c06b5a8 (patch)
tree44765c911f8a5850ee3d18147658b29b0eba0726
parentd935cc8c828e44430f6a4dc2eacf53c04268fe96 (diff)
Various
Refactored how GCode blocks and indentation are tracked Fixed lack of motor movement on Duet 3 Create path recursively if needed when creating a directory or renaming a file
-rw-r--r--src/GCodes/GCodeBuffer/StringParser.cpp66
-rw-r--r--src/GCodes/GCodeBuffer/StringParser.h2
-rw-r--r--src/GCodes/GCodeMachineState.cpp25
-rw-r--r--src/GCodes/GCodeMachineState.h26
-rw-r--r--src/Hardware/IoPorts.cpp22
-rw-r--r--src/Movement/StepTimer.cpp2
-rw-r--r--src/Movement/StepperDrivers/TMC51xx.cpp2
-rw-r--r--src/RepRap.cpp2
-rw-r--r--src/Storage/FileStore.cpp24
-rw-r--r--src/Storage/MassStorage.cpp48
-rw-r--r--src/Storage/MassStorage.h2
-rw-r--r--src/Version.h4
12 files changed, 111 insertions, 114 deletions
diff --git a/src/GCodes/GCodeBuffer/StringParser.cpp b/src/GCodes/GCodeBuffer/StringParser.cpp
index 8c5dcb3e..cefaab89 100644
--- a/src/GCodes/GCodeBuffer/StringParser.cpp
+++ b/src/GCodes/GCodeBuffer/StringParser.cpp
@@ -297,18 +297,27 @@ bool StringParser::CheckMetaCommand(const StringRef& reply)
indentToSkipTo = NoIndentSkip; // no longer skipping
}
- if (commandIndent > gb.machineState->indentLevel)
+ while (commandIndent < gb.machineState->CurrentBlockIndent())
{
- CreateBlocks(); // indentation has increased so start new block(s)
- }
- else if (commandIndent < gb.machineState->indentLevel)
- {
- if (EndBlocks())
+ gb.machineState->EndBlock();
+ if (gb.machineState->CurrentBlockState().GetType() == BlockType::loop)
{
+ // Go back to the start of the loop and re-evaluate the while-part
+ gb.machineState->lineNumber = gb.machineState->CurrentBlockState().GetLineNumber();
+ gb.RestartFrom(gb.machineState->CurrentBlockState().GetFilePosition());
Init();
return true;
}
}
+
+ if (commandIndent > gb.machineState->CurrentBlockIndent())
+ {
+ // indentation has increased so start new block(s)
+ if (!gb.machineState->CreateBlock(commandIndent))
+ {
+ throw ConstructParseException("blocks nested too deeply");
+ }
+ }
}
const bool b = ProcessConditionalGCode(reply, previousBlockType, doingFile); // this may throw a ParseException
@@ -419,35 +428,6 @@ bool StringParser::ProcessConditionalGCode(const StringRef& reply, BlockType pre
return false;
}
-// Create new code blocks
-void StringParser::CreateBlocks()
-{
- while (gb.machineState->indentLevel < commandIndent)
- {
- if (!gb.machineState->CreateBlock())
- {
- throw ConstructParseException("blocks nested too deeply");
- }
- }
-}
-
-// End blocks returning true if nothing more to process on this line
-bool StringParser::EndBlocks() noexcept
-{
- while (gb.machineState->indentLevel > commandIndent)
- {
- gb.machineState->EndBlock();
- if (gb.machineState->CurrentBlockState().GetType() == BlockType::loop)
- {
- // Go back to the start of the loop and re-evaluate the while-part
- gb.machineState->lineNumber = gb.machineState->CurrentBlockState().GetLineNumber();
- gb.RestartFrom(gb.machineState->CurrentBlockState().GetFilePosition());
- return true;
- }
- }
- return false;
-}
-
void StringParser::ProcessIfCommand()
{
if (EvaluateCondition())
@@ -457,7 +437,7 @@ void StringParser::ProcessIfCommand()
else
{
gb.machineState->CurrentBlockState().SetIfFalseNoneTrueBlock();
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the block
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the block
}
}
@@ -469,7 +449,7 @@ void StringParser::ProcessElseCommand(BlockType previousBlockType)
}
else if (gb.machineState->CurrentBlockState().GetType() == BlockType::ifTrue || gb.machineState->CurrentBlockState().GetType() == BlockType::ifFalseHadTrue)
{
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the if-block
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the if-block
gb.machineState->CurrentBlockState().SetPlainBlock(); // so that we get an error if there is another 'else' part
}
else
@@ -488,13 +468,13 @@ void StringParser::ProcessElifCommand(BlockType previousBlockType)
}
else
{
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the elif-block
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the elif-block
gb.machineState->CurrentBlockState().SetIfFalseNoneTrueBlock();
}
}
else if (gb.machineState->CurrentBlockState().GetType() == BlockType::ifTrue || gb.machineState->CurrentBlockState().GetType() == BlockType::ifFalseHadTrue)
{
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the if-block
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the if-block
gb.machineState->CurrentBlockState().SetIfFalseHadTrueBlock();
}
else
@@ -518,7 +498,7 @@ void StringParser::ProcessWhileCommand()
if (!EvaluateCondition())
{
gb.machineState->CurrentBlockState().SetPlainBlock();
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the block
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the block
}
}
@@ -526,21 +506,21 @@ void StringParser::ProcessBreakCommand()
{
do
{
- if (gb.machineState->indentLevel == 0)
+ if (gb.machineState->CurrentBlockIndent() == 0)
{
throw ConstructParseException("'break' was not inside a loop");
}
gb.machineState->EndBlock();
} while (gb.machineState->CurrentBlockState().GetType() != BlockType::loop);
gb.machineState->CurrentBlockState().SetPlainBlock();
- indentToSkipTo = gb.machineState->indentLevel; // skip forwards to the end of the loop
+ indentToSkipTo = gb.machineState->CurrentBlockIndent(); // skip forwards to the end of the loop
}
void StringParser::ProcessContinueCommand()
{
do
{
- if (gb.machineState->indentLevel == 0)
+ if (gb.machineState->CurrentBlockIndent() == 0)
{
throw ConstructParseException("'continue' was not inside a loop");
}
diff --git a/src/GCodes/GCodeBuffer/StringParser.h b/src/GCodes/GCodeBuffer/StringParser.h
index bc5236f5..c767baba 100644
--- a/src/GCodes/GCodeBuffer/StringParser.h
+++ b/src/GCodes/GCodeBuffer/StringParser.h
@@ -100,8 +100,6 @@ private:
bool ProcessConditionalGCode(const StringRef& reply, BlockType previousBlockType, bool doingFile) THROWS_GCODE_EXCEPTION;
// Check for and process a conditional GCode language command returning true if we found one
- void CreateBlocks() THROWS_GCODE_EXCEPTION; // Create new code blocks
- bool EndBlocks() noexcept; // End blocks returning true if nothing more to process on this line
void ProcessIfCommand() THROWS_GCODE_EXCEPTION;
void ProcessElseCommand(BlockType previousBlockType) THROWS_GCODE_EXCEPTION;
void ProcessElifCommand(BlockType previousBlockType) THROWS_GCODE_EXCEPTION;
diff --git a/src/GCodes/GCodeMachineState.cpp b/src/GCodes/GCodeMachineState.cpp
index 36aa4e14..f243de2f 100644
--- a/src/GCodes/GCodeMachineState.cpp
+++ b/src/GCodes/GCodeMachineState.cpp
@@ -22,13 +22,13 @@ GCodeMachineState::GCodeMachineState() noexcept
: previous(nullptr), feedRate(DefaultFeedRate * SecondsToMinutes), lockedResources(0), errorMessage(nullptr),
lineNumber(0), compatibility(Compatibility::reprapFirmware), drivesRelative(false), axesRelative(false), doingFileMacro(false), runningM501(false),
runningM502(false), volumetricExtrusion(false), g53Active(false), runningSystemMacro(false), usingInches(false),
- waitingForAcknowledgement(false), messageAcknowledged(false), indentLevel(0), state(GCodeState::normal)
+ waitingForAcknowledgement(false), messageAcknowledged(false), blockNesting(0), state(GCodeState::normal)
{
#if HAS_LINUX_INTERFACE
fileId = 0;
isFileFinished = fileError = false;
#endif
- blockStates[0].SetPlainBlock();
+ blockStates[0].SetPlainBlock(0);
}
#if HAS_LINUX_INTERFACE
@@ -147,13 +147,18 @@ void GCodeMachineState::CopyStateFrom(const GCodeMachineState& other) noexcept
GCodeMachineState::BlockState& GCodeMachineState::CurrentBlockState() noexcept
{
- return blockStates[indentLevel];
+ return blockStates[blockNesting];
+}
+
+const GCodeMachineState::BlockState& GCodeMachineState::CurrentBlockState() const noexcept
+{
+ return blockStates[blockNesting];
}
// Get the number of iterations of the closest enclosing loop in the current file, or -1 if there is on enclosing loop
int32_t GCodeMachineState::GetIterations() const noexcept
{
- uint8_t i = indentLevel;
+ uint8_t i = blockNesting;
while (true)
{
if (blockStates[i].GetType() == BlockType::loop)
@@ -169,23 +174,23 @@ int32_t GCodeMachineState::GetIterations() const noexcept
}
// Create a new block returning true if successful, false if maximum indent level exceeded
-bool GCodeMachineState::CreateBlock() noexcept
+bool GCodeMachineState::CreateBlock(uint16_t indentLevel) noexcept
{
- if (indentLevel + 1 == ARRAY_SIZE(blockStates))
+ if (blockNesting + 1 == ARRAY_SIZE(blockStates))
{
return false;
}
- ++indentLevel;
- CurrentBlockState().SetPlainBlock();
+ ++blockNesting;
+ CurrentBlockState().SetPlainBlock(indentLevel);
return true;
}
void GCodeMachineState::EndBlock() noexcept
{
- if (indentLevel != 0)
+ if (blockNesting != 0)
{
- --indentLevel;
+ --blockNesting;
}
}
diff --git a/src/GCodes/GCodeMachineState.h b/src/GCodes/GCodeMachineState.h
index 8ace3eff..b63e9b18 100644
--- a/src/GCodes/GCodeMachineState.h
+++ b/src/GCodes/GCodeMachineState.h
@@ -137,24 +137,27 @@ public:
class BlockState
{
public:
- BlockType GetType() const noexcept { return (BlockType) blockType; }
+ BlockType GetType() const noexcept { return blockType; }
uint32_t GetIterations() const noexcept { return iterationsDone; }
uint32_t GetLineNumber() const noexcept { return lineNumber; }
FilePosition GetFilePosition() const noexcept { return fpos; }
+ uint16_t GetIndent() const noexcept { return indentLevel; }
- void SetLoopBlock(FilePosition filePos, uint32_t lineNum) noexcept { blockType = (uint32_t)BlockType::loop; fpos = filePos; lineNumber = lineNum; iterationsDone = 0; }
- void SetPlainBlock() noexcept { blockType = (uint32_t)BlockType::plain; iterationsDone = 0; }
- void SetIfTrueBlock() noexcept { blockType = (uint32_t)BlockType::ifTrue; iterationsDone = 0; }
- void SetIfFalseNoneTrueBlock() noexcept { blockType = (uint32_t)BlockType::ifFalseNoneTrue; iterationsDone = 0; }
- void SetIfFalseHadTrueBlock() noexcept { blockType = (uint32_t)BlockType::ifFalseHadTrue; iterationsDone = 0; }
+ void SetLoopBlock(FilePosition filePos, uint32_t lineNum) noexcept { blockType = BlockType::loop; fpos = filePos; lineNumber = lineNum; iterationsDone = 0; }
+ void SetPlainBlock() noexcept { blockType = BlockType::plain; iterationsDone = 0; }
+ void SetPlainBlock(uint16_t p_indentLevel) noexcept { blockType = BlockType::plain; iterationsDone = 0; indentLevel = p_indentLevel; }
+ void SetIfTrueBlock() noexcept { blockType = BlockType::ifTrue; iterationsDone = 0; }
+ void SetIfFalseNoneTrueBlock() noexcept { blockType = BlockType::ifFalseNoneTrue; iterationsDone = 0; }
+ void SetIfFalseHadTrueBlock() noexcept { blockType = BlockType::ifFalseHadTrue; iterationsDone = 0; }
void IncrementIterations() noexcept { ++iterationsDone; }
private:
FilePosition fpos; // the file offset at which the current block started
- uint32_t lineNumber : 29, // the line number at which the current block started
- blockType : 3; // the type of this block
+ uint32_t lineNumber; // the line number at which the current block started
uint32_t iterationsDone;
+ uint16_t indentLevel; // the indentation of this block
+ BlockType blockType; // the type of this block
};
GCodeMachineState() noexcept;
@@ -192,7 +195,8 @@ public:
waitingForAcknowledgement : 1,
messageAcknowledged : 1,
messageCancelled : 1;
- uint8_t indentLevel;
+
+ uint8_t blockNesting;
GCodeState state;
uint8_t toolChangeParam;
@@ -214,9 +218,11 @@ public:
void CopyStateFrom(const GCodeMachineState& other) noexcept;
BlockState& CurrentBlockState() noexcept;
+ const BlockState& CurrentBlockState() const noexcept;
int32_t GetIterations() const noexcept;
+ uint16_t CurrentBlockIndent() const noexcept { return CurrentBlockState().GetIndent(); }
- bool CreateBlock() noexcept;
+ bool CreateBlock(uint16_t indentLevel) noexcept;
void EndBlock() noexcept;
static void Release(GCodeMachineState *ms) noexcept;
diff --git a/src/Hardware/IoPorts.cpp b/src/Hardware/IoPorts.cpp
index 5f024ed3..7bef2981 100644
--- a/src/Hardware/IoPorts.cpp
+++ b/src/Hardware/IoPorts.cpp
@@ -138,17 +138,17 @@ void IoPort::Release() noexcept
if (IsValid() && !isSharedInput)
{
#ifdef __LPC17xx__
- //Release PWM/Servo from pin if needed
- if(logicalPinModes[logicalPin] == OUTPUT_SERVO_HIGH || logicalPinModes[logicalPin] == OUTPUT_SERVO_LOW)
- {
- ReleaseServoPin(PinTable[logicalPin].pin);
- }
- if(logicalPinModes[logicalPin] == OUTPUT_PWM_HIGH || logicalPinModes[logicalPin] == OUTPUT_PWM_LOW)
- {
- ReleasePWMPin(PinTable[logicalPin].pin);
- }
+ // Release PWM/Servo from pin if needed
+ if (logicalPinModes[logicalPin] == OUTPUT_SERVO_HIGH || logicalPinModes[logicalPin] == OUTPUT_SERVO_LOW)
+ {
+ ReleaseServoPin(PinTable[logicalPin].pin);
+ }
+ if (logicalPinModes[logicalPin] == OUTPUT_PWM_HIGH || logicalPinModes[logicalPin] == OUTPUT_PWM_LOW)
+ {
+ ReleasePWMPin(PinTable[logicalPin].pin);
+ }
#endif
- detachInterrupt(PinTable[logicalPin].pin);
+ detachInterrupt(PinTable[logicalPin].pin);
portUsedBy[logicalPin] = PinUsedBy::unused;
logicalPinModes[logicalPin] = PIN_MODE_NOT_CONFIGURED;
}
@@ -342,7 +342,7 @@ bool IoPort::SetMode(PinAccess access) noexcept
}
}
#endif
- IoPort::SetPinMode(PinTable[logicalPin].pin, desiredMode);
+ IoPort::SetPinMode(PinTable[logicalPin].pin, desiredMode);
logicalPinModes[logicalPin] = (int8_t)desiredMode;
}
return true;
diff --git a/src/Movement/StepTimer.cpp b/src/Movement/StepTimer.cpp
index 1f9e92a4..092900a2 100644
--- a/src/Movement/StepTimer.cpp
+++ b/src/Movement/StepTimer.cpp
@@ -279,10 +279,10 @@ void StepTimer::CancelCallbackFromIsr() noexcept
if (*ppst == this)
{
*ppst = this->next; // unlink this from the pending list
- active = false;
break;
}
}
+ active = false;
}
void StepTimer::CancelCallback() noexcept
diff --git a/src/Movement/StepperDrivers/TMC51xx.cpp b/src/Movement/StepperDrivers/TMC51xx.cpp
index d05bc7a6..798b27ea 100644
--- a/src/Movement/StepperDrivers/TMC51xx.cpp
+++ b/src/Movement/StepperDrivers/TMC51xx.cpp
@@ -1183,7 +1183,7 @@ extern "C" [[noreturn]] void TmcLoop(void *) noexcept
}
// Wait for the end-of-transfer interrupt
- timedOut = (TaskBase::Take(TransferTimeout) == 0);
+ timedOut = TaskBase::Take(TransferTimeout);
DisableEndOfTransferInterrupt();
DisableDma();
diff --git a/src/RepRap.cpp b/src/RepRap.cpp
index a5d0fde7..d40e9561 100644
--- a/src/RepRap.cpp
+++ b/src/RepRap.cpp
@@ -106,7 +106,7 @@ extern "C" void hsmciIdle(uint32_t stBits, uint32_t dmaBits) noexcept
XDMAC->XDMAC_CHID[DmacChanHsmci].XDMAC_CIE = dmaBits;
XDMAC->XDMAC_GIE = 1u << DmacChanHsmci;
#endif
- if (TaskBase::Take(200) == 0)
+ if (TaskBase::Take(200))
{
// We timed out waiting for the HSMCI operation to complete
reprap.GetPlatform().LogError(ErrorCode::HsmciTimeout);
diff --git a/src/Storage/FileStore.cpp b/src/Storage/FileStore.cpp
index 284aa956..c3d2643d 100644
--- a/src/Storage/FileStore.cpp
+++ b/src/Storage/FileStore.cpp
@@ -64,29 +64,9 @@ bool FileStore::Open(const char* filePath, OpenMode mode, uint32_t preAllocSize)
if (writing)
{
- // Try to create the path of this file if we want to write to it
- String<MaxFilenameLength> filePathCopy;
- filePathCopy.copy(filePath);
-
- size_t i = (isdigit(filePathCopy[0]) && filePathCopy[1] == ':') ? 2 : 0;
- if (filePathCopy[i] == '/')
+ if (!MassStorage::EnsurePath(filePath))
{
- ++i;
- }
-
- while (i < filePathCopy.strlen())
- {
- if (filePathCopy[i] == '/')
- {
- filePathCopy[i] = 0;
- if (!MassStorage::DirectoryExists(filePathCopy.GetRef()) && !MassStorage::MakeDirectory(filePathCopy.c_str()))
- {
- reprap.GetPlatform().MessageF(ErrorMessage, "Failed to create folder %s while trying to open file %s\n", filePathCopy.c_str(), filePath);
- return false;
- }
- filePathCopy[i] = '/';
- }
- ++i;
+ return false;
}
// Also try to allocate a write buffer so we can perform faster writes
diff --git a/src/Storage/MassStorage.cpp b/src/Storage/MassStorage.cpp
index e3d8e6b9..52a94e0b 100644
--- a/src/Storage/MassStorage.cpp
+++ b/src/Storage/MassStorage.cpp
@@ -374,27 +374,51 @@ bool MassStorage::Delete(const char* filePath) noexcept
return true;
}
-// Create a new directory
-bool MassStorage::MakeDirectory(const char *parentDir, const char *dirName) noexcept
+// Ensure that the path up to the last '/' (excluding trailing '/' characters) in filePath exists, returning true if successful
+bool MassStorage::EnsurePath(const char* filePath) noexcept
{
- String<MaxFilenameLength> location;
- if (!CombineName(location.GetRef(), parentDir, dirName))
+ // Try to create the path of this file if we want to write to it
+ String<MaxFilenameLength> filePathCopy;
+ filePathCopy.copy(filePath);
+
+ size_t i = (isdigit(filePathCopy[0]) && filePathCopy[1] == ':') ? 2 : 0;
+ if (filePathCopy[i] == '/')
{
- return false;
+ ++i;
}
- if (f_mkdir(location.c_str()) != FR_OK)
+
+ size_t limit = filePathCopy.strlen();
+ while (limit != 0 && filePath[limit - 1] == '/')
{
- reprap.GetPlatform().MessageF(ErrorMessage, "Failed to create directory %s\n", location.c_str());
- return false;
+ --limit;
+ }
+ while (i < limit)
+ {
+ if (filePathCopy[i] == '/')
+ {
+ filePathCopy[i] = 0;
+ if (!MassStorage::DirectoryExists(filePathCopy.GetRef()) && f_mkdir(filePathCopy.c_str()) != FR_OK)
+ {
+ reprap.GetPlatform().MessageF(ErrorMessage, "Failed to create folder %s in path %s\n", filePathCopy.c_str(), filePath);
+ return false;
+ }
+ filePathCopy[i] = '/';
+ }
+ ++i;
}
return true;
}
+// Create a new directory
bool MassStorage::MakeDirectory(const char *directory) noexcept
{
+ if (!EnsurePath(directory))
+ {
+ return false;
+ }
if (f_mkdir(directory) != FR_OK)
{
- reprap.GetPlatform().MessageF(ErrorMessage, "Failed to create directory %s\n", directory);
+ reprap.GetPlatform().MessageF(ErrorMessage, "Failed to create folder %s\n", directory);
return false;
}
return true;
@@ -405,11 +429,15 @@ bool MassStorage::Rename(const char *oldFilename, const char *newFilename) noexc
{
if (newFilename[0] >= '0' && newFilename[0] <= '9' && newFilename[1] == ':')
{
- // Workaround for DWC 1.13 which send a volume specification at the start of the new path.
+ // Workaround for DWC 1.13 which sends a volume specification at the start of the new path.
// f_rename can't handle this, so skip past the volume specification.
// We are assuming that the user isn't really trying to rename across volumes. This is a safe assumption when the client is DWC.
newFilename += 2;
}
+ if (!EnsurePath(newFilename))
+ {
+ return false;
+ }
if (f_rename(oldFilename, newFilename) != FR_OK)
{
reprap.GetPlatform().MessageF(ErrorMessage, "Failed to rename file or directory %s to %s\n", oldFilename, newFilename);
diff --git a/src/Storage/MassStorage.h b/src/Storage/MassStorage.h
index c18438f2..603b871c 100644
--- a/src/Storage/MassStorage.h
+++ b/src/Storage/MassStorage.h
@@ -33,7 +33,7 @@ namespace MassStorage
bool FindNext(FileInfo &file_info) noexcept;
void AbandonFindNext() noexcept;
bool Delete(const char* filePath) noexcept;
- bool MakeDirectory(const char *parentDir, const char *dirName) noexcept;
+ bool EnsurePath(const char* filePath) noexcept;
bool MakeDirectory(const char *directory) noexcept;
bool Rename(const char *oldFilePath, const char *newFilePath) noexcept;
bool FileExists(const char *filePath) noexcept;
diff --git a/src/Version.h b/src/Version.h
index 6028f701..0e8c0874 100644
--- a/src/Version.h
+++ b/src/Version.h
@@ -10,7 +10,7 @@
#ifndef VERSION
-# define MAIN_VERSION "3.1alpha"
+# define MAIN_VERSION "3.1beta1"
# ifdef USE_CAN0
# define VERSION_SUFFIX " (CAN0)"
# else
@@ -20,7 +20,7 @@
#endif
#ifndef DATE
-# define DATE "2020-01-12b2"
+# define DATE "2020-01-12b3"
#endif
#define AUTHORS "reprappro, dc42, chrishamm, t3p3, dnewman, printm3d"