Просмотр исходного кода

Merged in marius_stanciu/flatcam_beta/Beta (pull request #185)

Beta
Marius Stanciu 6 лет назад
Родитель
Сommit
5b493c2450

+ 222 - 214
FlatCAMApp.py

@@ -98,8 +98,8 @@ class App(QtCore.QObject):
     # ####################################
     # Version and VERSION DATE ###########
     # ####################################
-    version = 8.96
-    version_date = "2019/08/23"
+    version = 8.97
+    version_date = "2019/08/31"
     beta = True
 
     # current date now
@@ -1755,212 +1755,219 @@ class App(QtCore.QObject):
         # #######Auto-complete KEYWORDS #############
         # ###########################################
         self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
-                                  'aligndrill', 'clear',
-                                  'aligndrillgrid', 'cncjob', 'cutout', 'delete', 'drillcncjob',
-                                  'export_gcode',
-                                  'export_svg', 'ext', 'exteriors', 'follow', 'geo_union', 'geocutout', 'get_names',
-                                  'get_sys', 'getsys', 'help', 'import_svg', 'interiors', 'isolate', 'join_excellon',
-                                  'join_excellons', 'join_geometries', 'join_geometry', 'list_sys', 'listsys', 'mill',
-                                  'millholes', 'mirror', 'new', 'new_geometry', 'offset', 'open_excellon', 'open_gcode',
-                                  'open_gerber', 'open_project', 'options', 'paint', 'pan', 'panel', 'panelize', 'plot',
-                                  'save', 'save_project', 'save_sys', 'scale', 'set_active', 'set_sys', 'setsys',
-                                  'skew', 'subtract_poly', 'subtract_rectangle', 'version', 'write_gcode'
+                                  'aligndrill', 'aligndrillgrid', 'bbox', 'bounding_box', 'clear', 'cncjob', 'cutout',
+                                  'delete', 'drillcncjob', 'export_gcode', 'export_svg', 'ext', 'exteriors', 'follow',
+                                  'geo_union', 'geocutout', 'get_names', 'get_sys', 'getsys', 'help', 'import_svg',
+                                  'interiors', 'isolate', 'join_excellon', 'join_excellons', 'join_geometries',
+                                  'join_geometry', 'list_sys', 'listsys', 'mill', 'millholes', 'mirror', 'ncc',
+                                  'ncc_clear', 'ncr', 'new', 'new_geometry', 'non_copper_regions', 'offset',
+                                  'open_excellon', 'open_gcode', 'open_gerber', 'open_project', 'options', 'paint',
+                                  'pan', 'panel', 'panelize', 'plot', 'save', 'save_project', 'save_sys', 'scale',
+                                  'set_active', 'set_sys', 'setsys', 'skew', 'subtract_poly', 'subtract_rectangle',
+                                  'version', 'write_gcode'
                                   ]
 
-        self.ordinary_keywords = ['name', 'center_x', 'center_y', 'radius', 'x0', 'y0', 'x1', 'y1', 'box', 'axis',
-                                  'holes', 'grid', 'minoffset', 'gridoffset', 'axisoffset', 'dia', 'dist',
-                                  'gridoffsetx', 'gridoffsety', 'columns', 'rows', 'z_cut', 'z_move', 'feedrate',
-                                  'feedrate_rapid', 'tooldia', 'multidepth', 'extracut', 'depthperpass', 'ppname_g',
-                                  'outname', 'margin', 'gaps', 'gapsize', 'tools', 'drillz', 'travelz', 'spindlespeed',
-                                  'toolchange', 'toolchangez', 'endz', 'ppname_e', 'opt_type', 'preamble', 'postamble',
-                                  'filename', 'scale_factor', 'type', 'passes', 'overlap', 'combine', 'use_threads',
-                                  'x', 'y', 'follow', 'all', 'spacing_columns', 'spacing_rows', 'factor', 'value',
-                                  'angle_x', 'angle_y', 'gridx', 'gridy', 'True', 'False'
+
+        self.ordinary_keywords = ['all', 'angle_x', 'angle_y', 'axis', 'axisoffset', 'box', 'center_x', 'center_y',
+                                  'columns', 'combine', 'connect', 'contour', 'depthperpass', 'dia', 'dist', 'drillz',
+                                  'endz', 'extracut', 'factor', 'False', 'false', 'feedrate', 'feedrate_rapid',
+                                  'filename', 'follow', 'gaps', 'gapsize', 'grid', 'gridoffset', 'gridoffsetx',
+                                  'gridoffsety', 'gridx', 'gridy', 'has_offset', 'holes', 'margin', 'method',
+                                  'minoffset', 'multidepth', 'name', 'offset', 'opt_type', 'order', 'outname',
+                                  'overlap', 'passes', 'postamble', 'ppname_e', 'ppname_g', 'preamble', 'radius', 'ref',
+                                  'rest', 'rows', 'scale_factor', 'spacing_columns', 'spacing_rows', 'spindlespeed',
+                                  'toolchange', 'toolchangez', 'tooldia', 'tools', 'travelz', 'True', 'true', 'type',
+                                  'use_threads', 'value', 'x', 'x0', 'x1', 'y', 'y0', 'y1', 'z_cut', 'z_move'
                                   ]
 
         self.tcl_keywords = [
-            "after", "append", "apply", "array", "auto_execok", "auto_import", "auto_load", "auto_mkindex",
-            "auto_qualify", "auto_reset", "bgerror", "binary", "break", "case", "catch", "cd", "chan", "clock", "close",
-            "concat", "continue", "coroutine", "dict", "encoding", "eof", "error", "eval", "exec", "exit", "expr",
-            "fblocked", "fconfigure", "fcopy", "file", "fileevent", "flush", "for", "foreach", "format", "gets", "glob",
-            "global", "history", "if", "incr", "info", "interp", "join", "lappend", "lassign", "lindex", "linsert",
-            "list", "llength", "load", "lrange", "lrepeat", "lreplace", "lreverse", "lsearch", "lset", "lsort",
-            "mathfunc", "mathop", "memory", "my", "namespace", "next", "nextto", "open", "package", "parray", "pid",
-            "pkg_mkIndex", "platform", "proc", "puts", "pwd", "read", "refchan", "regexp", "regsub", "rename", "return",
-            "scan", "seek", "self", "set", "socket", "source", "split", "string", "subst", "switch", "tailcall",
-            "tcl_endOfWord", "tcl_findLibrary", "tcl_startOfNextWord", "tcl_startOfPreviousWord", "tcl_wordBreakAfter",
-            "tcl_wordBreakBefore", "tell", "throw", "time", "tm", "trace", "transchan", "try", "unknown", "unload",
-            "unset", "update", "uplevel", "upvar", "variable", "vwait", "while", "yield", "yieldto", "zlib",
-            "attemptckalloc", "attemptckrealloc", "ckalloc", "ckfree", "ckrealloc", "Tcl_Access", "Tcl_AddErrorInfo",
-            "Tcl_AddObjErrorInfo", "Tcl_AlertNotifier", "Tcl_Alloc", "Tcl_AllocStatBuf", "Tcl_AllowExceptions",
-            "Tcl_AppendAllObjTypes", "Tcl_AppendElement", "Tcl_AppendExportList", "Tcl_AppendFormatToObj",
-            "Tcl_AppendLimitedToObj", "Tcl_AppendObjToErrorInfo", "Tcl_AppendObjToObj", "Tcl_AppendPrintfToObj",
-            "Tcl_AppendResult", "Tcl_AppendResultVA", "Tcl_AppendStringsToObj", "Tcl_AppendStringsToObjVA",
-            "Tcl_AppendToObj", "Tcl_AppendUnicodeToObj", "Tcl_AppInit", "Tcl_AsyncCreate", "Tcl_AsyncDelete",
-            "Tcl_AsyncInvoke", "Tcl_AsyncMark", "Tcl_AsyncReady", "Tcl_AttemptAlloc", "Tcl_AttemptRealloc",
-            "Tcl_AttemptSetObjLength", "Tcl_BackgroundError", "Tcl_BackgroundException", "Tcl_Backslash",
-            "Tcl_BadChannelOption", "Tcl_CallWhenDeleted", "Tcl_Canceled", "Tcl_CancelEval", "Tcl_CancelIdleCall",
-            "Tcl_ChannelBlockModeProc", "Tcl_ChannelBuffered", "Tcl_ChannelClose2Proc", "Tcl_ChannelCloseProc",
-            "Tcl_ChannelFlushProc", "Tcl_ChannelGetHandleProc", "Tcl_ChannelGetOptionProc", "Tcl_ChannelHandlerProc",
-            "Tcl_ChannelInputProc", "Tcl_ChannelName", "Tcl_ChannelOutputProc", "Tcl_ChannelSeekProc",
-            "Tcl_ChannelSetOptionProc", "Tcl_ChannelThreadActionProc", "Tcl_ChannelTruncateProc", "Tcl_ChannelVersion",
-            "Tcl_ChannelWatchProc", "Tcl_ChannelWideSeekProc", "Tcl_Chdir", "Tcl_ClassGetMetadata",
-            "Tcl_ClassSetConstructor", "Tcl_ClassSetDestructor", "Tcl_ClassSetMetadata", "Tcl_ClearChannelHandlers",
-            "Tcl_Close", "Tcl_CommandComplete", "Tcl_CommandTraceInfo", "Tcl_Concat", "Tcl_ConcatObj",
-            "Tcl_ConditionFinalize", "Tcl_ConditionNotify", "Tcl_ConditionWait", "Tcl_ConvertCountedElement",
-            "Tcl_ConvertElement", "Tcl_ConvertToType", "Tcl_CopyObjectInstance", "Tcl_CreateAlias",
-            "Tcl_CreateAliasObj", "Tcl_CreateChannel", "Tcl_CreateChannelHandler", "Tcl_CreateCloseHandler",
-            "Tcl_CreateCommand", "Tcl_CreateEncoding", "Tcl_CreateEnsemble", "Tcl_CreateEventSource",
-            "Tcl_CreateExitHandler", "Tcl_CreateFileHandler", "Tcl_CreateHashEntry", "Tcl_CreateInterp",
-            "Tcl_CreateMathFunc", "Tcl_CreateNamespace", "Tcl_CreateObjCommand", "Tcl_CreateObjTrace",
-            "Tcl_CreateSlave", "Tcl_CreateThread", "Tcl_CreateThreadExitHandler", "Tcl_CreateTimerHandler",
-            "Tcl_CreateTrace", "Tcl_CutChannel", "Tcl_DecrRefCount", "Tcl_DeleteAssocData", "Tcl_DeleteChannelHandler",
-            "Tcl_DeleteCloseHandler", "Tcl_DeleteCommand", "Tcl_DeleteCommandFromToken", "Tcl_DeleteEvents",
-            "Tcl_DeleteEventSource", "Tcl_DeleteExitHandler", "Tcl_DeleteFileHandler", "Tcl_DeleteHashEntry",
-            "Tcl_DeleteHashTable", "Tcl_DeleteInterp", "Tcl_DeleteNamespace", "Tcl_DeleteThreadExitHandler",
-            "Tcl_DeleteTimerHandler", "Tcl_DeleteTrace", "Tcl_DetachChannel", "Tcl_DetachPids", "Tcl_DictObjDone",
-            "Tcl_DictObjFirst", "Tcl_DictObjGet", "Tcl_DictObjNext", "Tcl_DictObjPut", "Tcl_DictObjPutKeyList",
-            "Tcl_DictObjRemove", "Tcl_DictObjRemoveKeyList", "Tcl_DictObjSize", "Tcl_DiscardInterpState",
-            "Tcl_DiscardResult", "Tcl_DontCallWhenDeleted", "Tcl_DoOneEvent", "Tcl_DoWhenIdle", "Tcl_DStringAppend",
-            "Tcl_DStringAppendElement", "Tcl_DStringEndSublist", "Tcl_DStringFree", "Tcl_DStringGetResult",
-            "Tcl_DStringInit", "Tcl_DStringLength", "Tcl_DStringResult", "Tcl_DStringSetLength",
-            "Tcl_DStringStartSublist", "Tcl_DStringTrunc", "Tcl_DStringValue", "Tcl_DumpActiveMemory",
-            "Tcl_DuplicateObj", "Tcl_Eof", "Tcl_ErrnoId", "Tcl_ErrnoMsg", "Tcl_Eval", "Tcl_EvalEx", "Tcl_EvalFile",
-            "Tcl_EvalObjEx", "Tcl_EvalObjv", "Tcl_EvalTokens", "Tcl_EvalTokensStandard", "Tcl_EventuallyFree",
-            "Tcl_Exit", "Tcl_ExitThread", "Tcl_Export", "Tcl_ExposeCommand", "Tcl_ExprBoolean", "Tcl_ExprBooleanObj",
-            "Tcl_ExprDouble", "Tcl_ExprDoubleObj", "Tcl_ExprLong", "Tcl_ExprLongObj", "Tcl_ExprObj", "Tcl_ExprString",
-            "Tcl_ExternalToUtf", "Tcl_ExternalToUtfDString", "Tcl_Finalize", "Tcl_FinalizeNotifier",
-            "Tcl_FinalizeThread", "Tcl_FindCommand", "Tcl_FindEnsemble", "Tcl_FindExecutable", "Tcl_FindHashEntry",
-            "Tcl_FindNamespace", "Tcl_FirstHashEntry", "Tcl_Flush", "Tcl_ForgetImport", "Tcl_Format",
-            "Tcl_Free· Tcl_FreeEncoding", "Tcl_FreeParse", "Tcl_FreeResult", "Tcl_FSAccess", "Tcl_FSChdir",
-            "Tcl_FSConvertToPathType", "Tcl_FSCopyDirectory", "Tcl_FSCopyFile", "Tcl_FSCreateDirectory", "Tcl_FSData",
-            "Tcl_FSDeleteFile", "Tcl_FSEqualPaths", "Tcl_FSEvalFile", "Tcl_FSEvalFileEx", "Tcl_FSFileAttrsGet",
-            "Tcl_FSFileAttrsSet", "Tcl_FSFileAttrStrings", "Tcl_FSFileSystemInfo", "Tcl_FSGetCwd",
-            "Tcl_FSGetFileSystemForPath", "Tcl_FSGetInternalRep", "Tcl_FSGetNativePath", "Tcl_FSGetNormalizedPath",
-            "Tcl_FSGetPathType", "Tcl_FSGetTranslatedPath", "Tcl_FSGetTranslatedStringPath", "Tcl_FSJoinPath",
-            "Tcl_FSJoinToPath", "Tcl_FSLink· Tcl_FSListVolumes", "Tcl_FSLoadFile", "Tcl_FSLstat",
-            "Tcl_FSMatchInDirectory", "Tcl_FSMountsChanged", "Tcl_FSNewNativePath", "Tcl_FSOpenFileChannel",
-            "Tcl_FSPathSeparator", "Tcl_FSRegister", "Tcl_FSRemoveDirectory", "Tcl_FSRenameFile", "Tcl_FSSplitPath",
-            "Tcl_FSStat", "Tcl_FSUnloadFile", "Tcl_FSUnregister", "Tcl_FSUtime", "Tcl_GetAccessTimeFromStat",
-            "Tcl_GetAlias", "Tcl_GetAliasObj", "Tcl_GetAssocData", "Tcl_GetBignumFromObj", "Tcl_GetBlocksFromStat",
-            "Tcl_GetBlockSizeFromStat", "Tcl_GetBoolean", "Tcl_GetBooleanFromObj", "Tcl_GetByteArrayFromObj",
-            "Tcl_GetChangeTimeFromStat", "Tcl_GetChannel", "Tcl_GetChannelBufferSize", "Tcl_GetChannelError",
-            "Tcl_GetChannelErrorInterp", "Tcl_GetChannelHandle", "Tcl_GetChannelInstanceData", "Tcl_GetChannelMode",
-            "Tcl_GetChannelName", "Tcl_GetChannelNames", "Tcl_GetChannelNamesEx", "Tcl_GetChannelOption",
-            "Tcl_GetChannelThread", "Tcl_GetChannelType", "Tcl_GetCharLength", "Tcl_GetClassAsObject",
-            "Tcl_GetCommandFromObj", "Tcl_GetCommandFullName", "Tcl_GetCommandInfo", "Tcl_GetCommandInfoFromToken",
-            "Tcl_GetCommandName", "Tcl_GetCurrentNamespace", "Tcl_GetCurrentThread", "Tcl_GetCwd",
-            "Tcl_GetDefaultEncodingDir", "Tcl_GetDeviceTypeFromStat", "Tcl_GetDouble", "Tcl_GetDoubleFromObj",
-            "Tcl_GetEncoding", "Tcl_GetEncodingFromObj", "Tcl_GetEncodingName", "Tcl_GetEncodingNameFromEnvironment",
-            "Tcl_GetEncodingNames", "Tcl_GetEncodingSearchPath", "Tcl_GetEnsembleFlags", "Tcl_GetEnsembleMappingDict",
-            "Tcl_GetEnsembleNamespace", "Tcl_GetEnsembleParameterList", "Tcl_GetEnsembleSubcommandList",
-            "Tcl_GetEnsembleUnknownHandler", "Tcl_GetErrno", "Tcl_GetErrorLine", "Tcl_GetFSDeviceFromStat",
-            "Tcl_GetFSInodeFromStat", "Tcl_GetGlobalNamespace", "Tcl_GetGroupIdFromStat", "Tcl_GetHashKey",
-            "Tcl_GetHashValue", "Tcl_GetHostName", "Tcl_GetIndexFromObj", "Tcl_GetIndexFromObjStruct", "Tcl_GetInt",
-            "Tcl_GetInterpPath", "Tcl_GetIntFromObj", "Tcl_GetLinkCountFromStat", "Tcl_GetLongFromObj", "Tcl_GetMaster",
-            "Tcl_GetMathFuncInfo", "Tcl_GetModeFromStat", "Tcl_GetModificationTimeFromStat", "Tcl_GetNameOfExecutable",
-            "Tcl_GetNamespaceUnknownHandler", "Tcl_GetObjectAsClass", "Tcl_GetObjectCommand", "Tcl_GetObjectFromObj",
-            "Tcl_GetObjectName", "Tcl_GetObjectNamespace", "Tcl_GetObjResult", "Tcl_GetObjType", "Tcl_GetOpenFile",
-            "Tcl_GetPathType", "Tcl_GetRange", "Tcl_GetRegExpFromObj", "Tcl_GetReturnOptions", "Tcl_Gets",
-            "Tcl_GetServiceMode", "Tcl_GetSizeFromStat", "Tcl_GetSlave", "Tcl_GetsObj", "Tcl_GetStackedChannel",
-            "Tcl_GetStartupScript", "Tcl_GetStdChannel", "Tcl_GetString", "Tcl_GetStringFromObj", "Tcl_GetStringResult",
-            "Tcl_GetThreadData", "Tcl_GetTime", "Tcl_GetTopChannel", "Tcl_GetUniChar", "Tcl_GetUnicode",
-            "Tcl_GetUnicodeFromObj", "Tcl_GetUserIdFromStat", "Tcl_GetVar", "Tcl_GetVar2", "Tcl_GetVar2Ex",
-            "Tcl_GetVersion", "Tcl_GetWideIntFromObj", "Tcl_GlobalEval", "Tcl_GlobalEvalObj", "Tcl_HashStats",
-            "Tcl_HideCommand", "Tcl_Import", "Tcl_IncrRefCount", "Tcl_Init", "Tcl_InitCustomHashTable",
-            "Tcl_InitHashTable", "Tcl_InitMemory", "Tcl_InitNotifier", "Tcl_InitObjHashTable", "Tcl_InitStubs",
-            "Tcl_InputBlocked", "Tcl_InputBuffered", "Tcl_InterpActive", "Tcl_InterpDeleted", "Tcl_InvalidateStringRep",
-            "Tcl_IsChannelExisting", "Tcl_IsChannelRegistered", "Tcl_IsChannelShared", "Tcl_IsEnsemble", "Tcl_IsSafe",
-            "Tcl_IsShared", "Tcl_IsStandardChannel", "Tcl_JoinPath", "Tcl_JoinThread", "Tcl_LimitAddHandler",
-            "Tcl_LimitCheck", "Tcl_LimitExceeded", "Tcl_LimitGetCommands", "Tcl_LimitGetGranularity",
-            "Tcl_LimitGetTime", "Tcl_LimitReady", "Tcl_LimitRemoveHandler", "Tcl_LimitSetCommands",
-            "Tcl_LimitSetGranularity", "Tcl_LimitSetTime", "Tcl_LimitTypeEnabled", "Tcl_LimitTypeExceeded",
-            "Tcl_LimitTypeReset", "Tcl_LimitTypeSet", "Tcl_LinkVar", "Tcl_ListMathFuncs", "Tcl_ListObjAppendElement",
-            "Tcl_ListObjAppendList", "Tcl_ListObjGetElements", "Tcl_ListObjIndex", "Tcl_ListObjLength",
-            "Tcl_ListObjReplace", "Tcl_LogCommandInfo", "Tcl_Main", "Tcl_MakeFileChannel", "Tcl_MakeSafe",
-            "Tcl_MakeTcpClientChannel", "Tcl_Merge", "Tcl_MethodDeclarerClass", "Tcl_MethodDeclarerObject",
-            "Tcl_MethodIsPublic", "Tcl_MethodIsType", "Tcl_MethodName", "Tcl_MutexFinalize", "Tcl_MutexLock",
-            "Tcl_MutexUnlock", "Tcl_NewBignumObj", "Tcl_NewBooleanObj", "Tcl_NewByteArrayObj", "Tcl_NewDictObj",
-            "Tcl_NewDoubleObj", "Tcl_NewInstanceMethod", "Tcl_NewIntObj", "Tcl_NewListObj", "Tcl_NewLongObj",
-            "Tcl_NewMethod", "Tcl_NewObj", "Tcl_NewObjectInstance", "Tcl_NewStringObj", "Tcl_NewUnicodeObj",
-            "Tcl_NewWideIntObj", "Tcl_NextHashEntry", "Tcl_NotifyChannel", "Tcl_NRAddCallback", "Tcl_NRCallObjProc",
-            "Tcl_NRCmdSwap", "Tcl_NRCreateCommand", "Tcl_NREvalObj", "Tcl_NREvalObjv", "Tcl_NumUtfChars",
-            "Tcl_ObjectContextInvokeNext", "Tcl_ObjectContextIsFiltering", "Tcl_ObjectContextMethod",
-            "Tcl_ObjectContextObject", "Tcl_ObjectContextSkippedArgs", "Tcl_ObjectDeleted", "Tcl_ObjectGetMetadata",
-            "Tcl_ObjectGetMethodNameMapper", "Tcl_ObjectSetMetadata", "Tcl_ObjectSetMethodNameMapper", "Tcl_ObjGetVar2",
-            "Tcl_ObjPrintf", "Tcl_ObjSetVar2", "Tcl_OpenCommandChannel", "Tcl_OpenFileChannel", "Tcl_OpenTcpClient",
-            "Tcl_OpenTcpServer", "Tcl_OutputBuffered", "Tcl_Panic", "Tcl_PanicVA", "Tcl_ParseArgsObjv",
-            "Tcl_ParseBraces", "Tcl_ParseCommand", "Tcl_ParseExpr", "Tcl_ParseQuotedString", "Tcl_ParseVar",
-            "Tcl_ParseVarName", "Tcl_PkgPresent", "Tcl_PkgPresentEx", "Tcl_PkgProvide", "Tcl_PkgProvideEx",
-            "Tcl_PkgRequire", "Tcl_PkgRequireEx", "Tcl_PkgRequireProc", "Tcl_PosixError", "Tcl_Preserve",
-            "Tcl_PrintDouble", "Tcl_PutEnv", "Tcl_QueryTimeProc", "Tcl_QueueEvent", "Tcl_Read", "Tcl_ReadChars",
-            "Tcl_ReadRaw", "Tcl_Realloc", "Tcl_ReapDetachedProcs", "Tcl_RecordAndEval", "Tcl_RecordAndEvalObj",
-            "Tcl_RegExpCompile", "Tcl_RegExpExec", "Tcl_RegExpExecObj", "Tcl_RegExpGetInfo", "Tcl_RegExpMatch",
-            "Tcl_RegExpMatchObj", "Tcl_RegExpRange", "Tcl_RegisterChannel", "Tcl_RegisterConfig", "Tcl_RegisterObjType",
-            "Tcl_Release", "Tcl_ResetResult", "Tcl_RestoreInterpState", "Tcl_RestoreResult", "Tcl_SaveInterpState",
-            "Tcl_SaveResult", "Tcl_ScanCountedElement", "Tcl_ScanElement", "Tcl_Seek", "Tcl_ServiceAll",
-            "Tcl_ServiceEvent", "Tcl_ServiceModeHook", "Tcl_SetAssocData", "Tcl_SetBignumObj", "Tcl_SetBooleanObj",
-            "Tcl_SetByteArrayLength", "Tcl_SetByteArrayObj", "Tcl_SetChannelBufferSize", "Tcl_SetChannelError",
-            "Tcl_SetChannelErrorInterp", "Tcl_SetChannelOption", "Tcl_SetCommandInfo", "Tcl_SetCommandInfoFromToken",
-            "Tcl_SetDefaultEncodingDir", "Tcl_SetDoubleObj", "Tcl_SetEncodingSearchPath", "Tcl_SetEnsembleFlags",
-            "Tcl_SetEnsembleMappingDict", "Tcl_SetEnsembleParameterList", "Tcl_SetEnsembleSubcommandList",
-            "Tcl_SetEnsembleUnknownHandler", "Tcl_SetErrno", "Tcl_SetErrorCode", "Tcl_SetErrorCodeVA",
-            "Tcl_SetErrorLine", "Tcl_SetExitProc", "Tcl_SetHashValue", "Tcl_SetIntObj", "Tcl_SetListObj",
-            "Tcl_SetLongObj", "Tcl_SetMainLoop", "Tcl_SetMaxBlockTime", "Tcl_SetNamespaceUnknownHandler",
-            "Tcl_SetNotifier", "Tcl_SetObjErrorCode", "Tcl_SetObjLength", "Tcl_SetObjResult", "Tcl_SetPanicProc",
-            "Tcl_SetRecursionLimit", "Tcl_SetResult", "Tcl_SetReturnOptions", "Tcl_SetServiceMode",
-            "Tcl_SetStartupScript", "Tcl_SetStdChannel", "Tcl_SetStringObj", "Tcl_SetSystemEncoding", "Tcl_SetTimeProc",
-            "Tcl_SetTimer", "Tcl_SetUnicodeObj", "Tcl_SetVar", "Tcl_SetVar2", "Tcl_SetVar2Ex", "Tcl_SetWideIntObj",
-            "Tcl_SignalId", "Tcl_SignalMsg", "Tcl_Sleep", "Tcl_SourceRCFile", "Tcl_SpliceChannel", "Tcl_SplitList",
-            "Tcl_SplitPath", "Tcl_StackChannel", "Tcl_StandardChannels", "Tcl_Stat", "Tcl_StaticPackage",
-            "Tcl_StringCaseMatch", "Tcl_StringMatch", "Tcl_SubstObj", "Tcl_TakeBignumFromObj", "Tcl_Tell",
-            "Tcl_ThreadAlert", "Tcl_ThreadQueueEvent", "Tcl_TraceCommand", "Tcl_TraceVar", "Tcl_TraceVar2",
-            "Tcl_TransferResult", "Tcl_TranslateFileName", "Tcl_TruncateChannel", "Tcl_Ungets", "Tcl_UniChar",
-            "Tcl_UniCharAtIndex", "Tcl_UniCharCaseMatch", "Tcl_UniCharIsAlnum", "Tcl_UniCharIsAlpha",
-            "Tcl_UniCharIsControl", "Tcl_UniCharIsDigit", "Tcl_UniCharIsGraph", "Tcl_UniCharIsLower",
-            "Tcl_UniCharIsPrint", "Tcl_UniCharIsPunct", "Tcl_UniCharIsSpace", "Tcl_UniCharIsUpper",
-            "Tcl_UniCharIsWordChar", "Tcl_UniCharLen", "Tcl_UniCharNcasecmp", "Tcl_UniCharNcmp", "Tcl_UniCharToLower",
-            "Tcl_UniCharToTitle", "Tcl_UniCharToUpper", "Tcl_UniCharToUtf", "Tcl_UniCharToUtfDString", "Tcl_UnlinkVar",
-            "Tcl_UnregisterChannel", "Tcl_UnsetVar", "Tcl_UnsetVar2", "Tcl_UnstackChannel", "Tcl_UntraceCommand",
-            "Tcl_UntraceVar", "Tcl_UntraceVar2", "Tcl_UpdateLinkedVar", "Tcl_UpVar", "Tcl_UpVar2", "Tcl_UtfAtIndex",
-            "Tcl_UtfBackslash", "Tcl_UtfCharComplete", "Tcl_UtfFindFirst", "Tcl_UtfFindLast", "Tcl_UtfNext",
-            "Tcl_UtfPrev", "Tcl_UtfToExternal", "Tcl_UtfToExternalDString", "Tcl_UtfToLower", "Tcl_UtfToTitle",
-            "Tcl_UtfToUniChar", "Tcl_UtfToUniCharDString", "Tcl_UtfToUpper", "Tcl_ValidateAllMemory", "Tcl_VarEval",
-            "Tcl_VarEvalVA", "Tcl_VarTraceInfo", "Tcl_VarTraceInfo2", "Tcl_WaitForEvent", "Tcl_WaitPid",
-            "Tcl_WinTCharToUtf", "Tcl_WinUtfToTChar", "Tcl_Write", "Tcl_WriteChars", "Tcl_WriteObj", "Tcl_WriteRaw",
-            "Tcl_WrongNumArgs", "Tcl_ZlibAdler32", "Tcl_ZlibCRC32", "Tcl_ZlibDeflate", "Tcl_ZlibInflate",
-            "Tcl_ZlibStreamChecksum", "Tcl_ZlibStreamClose", "Tcl_ZlibStreamEof", "Tcl_ZlibStreamGet",
-            "Tcl_ZlibStreamGetCommandName", "Tcl_ZlibStreamInit", "Tcl_ZlibStreamPut", "dde", "http", "msgcat",
-            "registry", "tcltest", "Tcl_AllocHashEntryProc", "Tcl_AppInitProc", "Tcl_ArgvInfo", "Tcl_AsyncProc",
-            "Tcl_ChannelProc", "Tcl_ChannelType", "Tcl_CloneProc", "Tcl_CloseProc", "Tcl_CmdDeleteProc", "Tcl_CmdInfo",
-            "Tcl_CmdObjTraceDeleteProc", "Tcl_CmdObjTraceProc", "Tcl_CmdProc", "Tcl_CmdTraceProc",
-            "Tcl_CommandTraceProc", "Tcl_CompareHashKeysProc", "Tcl_Config", "Tcl_DriverBlockModeProc",
-            "Tcl_DriverClose2Proc", "Tcl_DriverCloseProc", "Tcl_DriverFlushProc", "Tcl_DriverGetHandleProc",
-            "Tcl_DriverGetOptionProc", "Tcl_DriverHandlerProc", "Tcl_DriverInputProc", "Tcl_DriverOutputProc",
-            "Tcl_DriverSeekProc", "Tcl_DriverSetOptionProc", "Tcl_DriverThreadActionProc", "Tcl_DriverTruncateProc",
-            "Tcl_DriverWatchProc", "Tcl_DriverWideSeekProc", "Tcl_DupInternalRepProc", "Tcl_EncodingConvertProc",
-            "Tcl_EncodingFreeProc", "Tcl_EncodingType", "Tcl_Event", "Tcl_EventCheckProc", "Tcl_EventDeleteProc",
-            "Tcl_EventProc", "Tcl_EventSetupProc", "Tcl_ExitProc", "Tcl_FileProc", "Tcl_Filesystem",
-            "Tcl_FreeHashEntryProc", "Tcl_FreeInternalRepProc", "Tcl_FreeProc", "Tcl_FSAccessProc", "Tcl_FSChdirProc",
-            "Tcl_FSCopyDirectoryProc", "Tcl_FSCopyFileProc", "Tcl_FSCreateDirectoryProc", "Tcl_FSCreateInternalRepProc",
-            "Tcl_FSDeleteFileProc", "Tcl_FSDupInternalRepProc", "Tcl_FSFileAttrsGetProc", "Tcl_FSFileAttrsSetProc",
-            "Tcl_FSFilesystemPathTypeProc", "Tcl_FSFilesystemSeparatorProc", "Tcl_FSFreeInternalRepProc",
-            "Tcl_FSGetCwdProc", "Tcl_FSInternalToNormalizedProc", "Tcl_FSLinkProc", "Tcl_FSListVolumesProc",
-            "Tcl_FSLoadFileProc", "Tcl_FSLstatProc", "Tcl_FSMatchInDirectoryProc", "Tcl_FSNormalizePathProc",
-            "Tcl_FSOpenFileChannelProc", "Tcl_FSPathInFilesystemProc", "Tcl_FSRemoveDirectoryProc",
-            "Tcl_FSRenameFileProc", "Tcl_FSStatProc", "Tcl_FSUnloadFileProc", "Tcl_FSUtimeProc", "Tcl_GlobTypeData",
-            "Tcl_HashKeyType", "Tcl_IdleProc", "Tcl_Interp", "Tcl_InterpDeleteProc", "Tcl_LimitHandlerDeleteProc",
-            "Tcl_LimitHandlerProc", "Tcl_MainLoopProc", "Tcl_MathProc", "Tcl_MethodCallProc", "Tcl_MethodDeleteProc",
-            "Tcl_MethodType", "Tcl_NamespaceDeleteProc", "Tcl_NotifierProcs", "Tcl_Obj", "Tcl_ObjCmdProc",
-            "Tcl_ObjectMapMethodNameProc", "Tcl_ObjectMetadataDeleteProc", "Tcl_ObjType", "Tcl_PackageInitProc",
-            "Tcl_PackageUnloadProc", "Tcl_PanicProc", "Tcl_RegExpIndices", "Tcl_RegExpInfo", "Tcl_ScaleTimeProc",
-            "Tcl_SetFromAnyProc", "Tcl_TcpAcceptProc", "Tcl_Time", "Tcl_TimerProc", "Tcl_Token", "Tcl_UpdateStringProc",
-            "Tcl_Value", "Tcl_VarTraceProc", "argc", "argv", "argv0", "auto_path", "env", "errorCode", "errorInfo",
-            "filename", "re_syntax", "safe", "Tcl", "tcl_interactive", "tcl_library", "TCL_MEM_DEBUG",
-            "tcl_nonwordchars", "tcl_patchLevel", "tcl_pkgPath", "tcl_platform", "tcl_precision", "tcl_rcFileName",
-            "tcl_traceCompile", "tcl_traceEval", "tcl_version", "tcl_wordchars"
+            'after', 'append', 'apply', 'argc', 'argv', 'argv0', 'array', 'attemptckalloc', 'attemptckrealloc',
+            'auto_execok', 'auto_import', 'auto_load', 'auto_mkindex', 'auto_path', 'auto_qualify', 'auto_reset',
+            'bgerror', 'binary', 'break', 'case', 'catch', 'cd', 'chan', 'ckalloc', 'ckfree', 'ckrealloc', 'clock',
+            'close', 'concat', 'continue', 'coroutine', 'dde', 'dict', 'encoding', 'env', 'eof', 'error', 'errorCode',
+            'errorInfo', 'eval', 'exec', 'exit', 'expr', 'fblocked', 'fconfigure', 'fcopy', 'file', 'fileevent',
+            'filename', 'flush', 'for', 'foreach', 'format', 'gets', 'glob', 'global', 'history', 'http', 'if', 'incr',
+            'info', 'interp', 'join', 'lappend', 'lassign', 'lindex', 'linsert', 'list', 'llength', 'load', 'lrange',
+            'lrepeat', 'lreplace', 'lreverse', 'lsearch', 'lset', 'lsort', 'mathfunc', 'mathop', 'memory', 'msgcat',
+            'my', 'namespace', 'next', 'nextto', 'open', 'package', 'parray', 'pid', 'pkg_mkIndex', 'platform',
+            'proc', 'puts', 'pwd', 're_syntax', 'read', 'refchan', 'regexp', 'registry', 'regsub', 'rename', 'return',
+            'safe', 'scan', 'seek', 'self', 'set', 'socket', 'source', 'split', 'string', 'subst', 'switch',
+            'tailcall', 'Tcl', 'Tcl_Access', 'Tcl_AddErrorInfo', 'Tcl_AddObjErrorInfo', 'Tcl_AlertNotifier',
+            'Tcl_Alloc', 'Tcl_AllocHashEntryProc', 'Tcl_AllocStatBuf', 'Tcl_AllowExceptions', 'Tcl_AppendAllObjTypes',
+            'Tcl_AppendElement', 'Tcl_AppendExportList', 'Tcl_AppendFormatToObj', 'Tcl_AppendLimitedToObj',
+            'Tcl_AppendObjToErrorInfo', 'Tcl_AppendObjToObj', 'Tcl_AppendPrintfToObj', 'Tcl_AppendResult',
+            'Tcl_AppendResultVA', 'Tcl_AppendStringsToObj', 'Tcl_AppendStringsToObjVA', 'Tcl_AppendToObj',
+            'Tcl_AppendUnicodeToObj', 'Tcl_AppInit', 'Tcl_AppInitProc', 'Tcl_ArgvInfo', 'Tcl_AsyncCreate',
+            'Tcl_AsyncDelete', 'Tcl_AsyncInvoke', 'Tcl_AsyncMark', 'Tcl_AsyncProc', 'Tcl_AsyncReady',
+            'Tcl_AttemptAlloc', 'Tcl_AttemptRealloc', 'Tcl_AttemptSetObjLength', 'Tcl_BackgroundError',
+            'Tcl_BackgroundException', 'Tcl_Backslash', 'Tcl_BadChannelOption', 'Tcl_CallWhenDeleted', 'Tcl_Canceled',
+            'Tcl_CancelEval', 'Tcl_CancelIdleCall', 'Tcl_ChannelBlockModeProc', 'Tcl_ChannelBuffered',
+            'Tcl_ChannelClose2Proc', 'Tcl_ChannelCloseProc', 'Tcl_ChannelFlushProc', 'Tcl_ChannelGetHandleProc',
+            'Tcl_ChannelGetOptionProc', 'Tcl_ChannelHandlerProc', 'Tcl_ChannelInputProc', 'Tcl_ChannelName',
+            'Tcl_ChannelOutputProc', 'Tcl_ChannelProc', 'Tcl_ChannelSeekProc', 'Tcl_ChannelSetOptionProc',
+            'Tcl_ChannelThreadActionProc', 'Tcl_ChannelTruncateProc', 'Tcl_ChannelType', 'Tcl_ChannelVersion',
+            'Tcl_ChannelWatchProc', 'Tcl_ChannelWideSeekProc', 'Tcl_Chdir', 'Tcl_ClassGetMetadata',
+            'Tcl_ClassSetConstructor', 'Tcl_ClassSetDestructor', 'Tcl_ClassSetMetadata', 'Tcl_ClearChannelHandlers',
+            'Tcl_CloneProc', 'Tcl_Close', 'Tcl_CloseProc', 'Tcl_CmdDeleteProc', 'Tcl_CmdInfo',
+            'Tcl_CmdObjTraceDeleteProc', 'Tcl_CmdObjTraceProc', 'Tcl_CmdProc', 'Tcl_CmdTraceProc',
+            'Tcl_CommandComplete', 'Tcl_CommandTraceInfo', 'Tcl_CommandTraceProc', 'Tcl_CompareHashKeysProc',
+            'Tcl_Concat', 'Tcl_ConcatObj', 'Tcl_ConditionFinalize', 'Tcl_ConditionNotify', 'Tcl_ConditionWait',
+            'Tcl_Config', 'Tcl_ConvertCountedElement', 'Tcl_ConvertElement', 'Tcl_ConvertToType',
+            'Tcl_CopyObjectInstance', 'Tcl_CreateAlias', 'Tcl_CreateAliasObj', 'Tcl_CreateChannel',
+            'Tcl_CreateChannelHandler', 'Tcl_CreateCloseHandler', 'Tcl_CreateCommand', 'Tcl_CreateEncoding',
+            'Tcl_CreateEnsemble', 'Tcl_CreateEventSource', 'Tcl_CreateExitHandler', 'Tcl_CreateFileHandler',
+            'Tcl_CreateHashEntry', 'Tcl_CreateInterp', 'Tcl_CreateMathFunc', 'Tcl_CreateNamespace',
+            'Tcl_CreateObjCommand', 'Tcl_CreateObjTrace', 'Tcl_CreateSlave', 'Tcl_CreateThread',
+            'Tcl_CreateThreadExitHandler', 'Tcl_CreateTimerHandler', 'Tcl_CreateTrace',
+            'Tcl_CutChannel', 'Tcl_DecrRefCount', 'Tcl_DeleteAssocData', 'Tcl_DeleteChannelHandler',
+            'Tcl_DeleteCloseHandler', 'Tcl_DeleteCommand', 'Tcl_DeleteCommandFromToken', 'Tcl_DeleteEvents',
+            'Tcl_DeleteEventSource', 'Tcl_DeleteExitHandler', 'Tcl_DeleteFileHandler', 'Tcl_DeleteHashEntry',
+            'Tcl_DeleteHashTable', 'Tcl_DeleteInterp', 'Tcl_DeleteNamespace', 'Tcl_DeleteThreadExitHandler',
+            'Tcl_DeleteTimerHandler', 'Tcl_DeleteTrace', 'Tcl_DetachChannel', 'Tcl_DetachPids', 'Tcl_DictObjDone',
+            'Tcl_DictObjFirst', 'Tcl_DictObjGet', 'Tcl_DictObjNext', 'Tcl_DictObjPut', 'Tcl_DictObjPutKeyList',
+            'Tcl_DictObjRemove', 'Tcl_DictObjRemoveKeyList', 'Tcl_DictObjSize', 'Tcl_DiscardInterpState',
+            'Tcl_DiscardResult', 'Tcl_DontCallWhenDeleted', 'Tcl_DoOneEvent', 'Tcl_DoWhenIdle',
+            'Tcl_DriverBlockModeProc', 'Tcl_DriverClose2Proc', 'Tcl_DriverCloseProc', 'Tcl_DriverFlushProc',
+            'Tcl_DriverGetHandleProc', 'Tcl_DriverGetOptionProc', 'Tcl_DriverHandlerProc', 'Tcl_DriverInputProc',
+            'Tcl_DriverOutputProc', 'Tcl_DriverSeekProc', 'Tcl_DriverSetOptionProc', 'Tcl_DriverThreadActionProc',
+            'Tcl_DriverTruncateProc', 'Tcl_DriverWatchProc', 'Tcl_DriverWideSeekProc', 'Tcl_DStringAppend',
+            'Tcl_DStringAppendElement', 'Tcl_DStringEndSublist', 'Tcl_DStringFree', 'Tcl_DStringGetResult',
+            'Tcl_DStringInit', 'Tcl_DStringLength', 'Tcl_DStringResult', 'Tcl_DStringSetLength',
+            'Tcl_DStringStartSublist', 'Tcl_DStringTrunc', 'Tcl_DStringValue', 'Tcl_DumpActiveMemory',
+            'Tcl_DupInternalRepProc', 'Tcl_DuplicateObj', 'Tcl_EncodingConvertProc', 'Tcl_EncodingFreeProc',
+            'Tcl_EncodingType', 'tcl_endOfWord', 'Tcl_Eof', 'Tcl_ErrnoId', 'Tcl_ErrnoMsg', 'Tcl_Eval', 'Tcl_EvalEx',
+            'Tcl_EvalFile', 'Tcl_EvalObjEx', 'Tcl_EvalObjv', 'Tcl_EvalTokens', 'Tcl_EvalTokensStandard', 'Tcl_Event',
+            'Tcl_EventCheckProc', 'Tcl_EventDeleteProc', 'Tcl_EventProc', 'Tcl_EventSetupProc', 'Tcl_EventuallyFree',
+            'Tcl_Exit', 'Tcl_ExitProc', 'Tcl_ExitThread', 'Tcl_Export', 'Tcl_ExposeCommand', 'Tcl_ExprBoolean',
+            'Tcl_ExprBooleanObj', 'Tcl_ExprDouble', 'Tcl_ExprDoubleObj', 'Tcl_ExprLong', 'Tcl_ExprLongObj',
+            'Tcl_ExprObj', 'Tcl_ExprString', 'Tcl_ExternalToUtf', 'Tcl_ExternalToUtfDString', 'Tcl_FileProc',
+            'Tcl_Filesystem', 'Tcl_Finalize', 'Tcl_FinalizeNotifier', 'Tcl_FinalizeThread', 'Tcl_FindCommand',
+            'Tcl_FindEnsemble', 'Tcl_FindExecutable', 'Tcl_FindHashEntry', 'tcl_findLibrary', 'Tcl_FindNamespace',
+            'Tcl_FirstHashEntry', 'Tcl_Flush', 'Tcl_ForgetImport', 'Tcl_Format', 'Tcl_FreeHashEntryProc',
+            'Tcl_FreeInternalRepProc', 'Tcl_FreeParse', 'Tcl_FreeProc', 'Tcl_FreeResult',
+            'Tcl_Free·\xa0Tcl_FreeEncoding', 'Tcl_FSAccess', 'Tcl_FSAccessProc', 'Tcl_FSChdir',
+            'Tcl_FSChdirProc', 'Tcl_FSConvertToPathType', 'Tcl_FSCopyDirectory', 'Tcl_FSCopyDirectoryProc',
+            'Tcl_FSCopyFile', 'Tcl_FSCopyFileProc', 'Tcl_FSCreateDirectory', 'Tcl_FSCreateDirectoryProc',
+            'Tcl_FSCreateInternalRepProc', 'Tcl_FSData', 'Tcl_FSDeleteFile', 'Tcl_FSDeleteFileProc',
+            'Tcl_FSDupInternalRepProc', 'Tcl_FSEqualPaths', 'Tcl_FSEvalFile', 'Tcl_FSEvalFileEx',
+            'Tcl_FSFileAttrsGet', 'Tcl_FSFileAttrsGetProc', 'Tcl_FSFileAttrsSet', 'Tcl_FSFileAttrsSetProc',
+            'Tcl_FSFileAttrStrings', 'Tcl_FSFileSystemInfo', 'Tcl_FSFilesystemPathTypeProc',
+            'Tcl_FSFilesystemSeparatorProc', 'Tcl_FSFreeInternalRepProc', 'Tcl_FSGetCwd', 'Tcl_FSGetCwdProc',
+            'Tcl_FSGetFileSystemForPath', 'Tcl_FSGetInternalRep', 'Tcl_FSGetNativePath', 'Tcl_FSGetNormalizedPath',
+            'Tcl_FSGetPathType', 'Tcl_FSGetTranslatedPath', 'Tcl_FSGetTranslatedStringPath',
+            'Tcl_FSInternalToNormalizedProc', 'Tcl_FSJoinPath', 'Tcl_FSJoinToPath', 'Tcl_FSLinkProc',
+            'Tcl_FSLink·\xa0Tcl_FSListVolumes', 'Tcl_FSListVolumesProc', 'Tcl_FSLoadFile', 'Tcl_FSLoadFileProc',
+            'Tcl_FSLstat', 'Tcl_FSLstatProc', 'Tcl_FSMatchInDirectory', 'Tcl_FSMatchInDirectoryProc',
+            'Tcl_FSMountsChanged', 'Tcl_FSNewNativePath', 'Tcl_FSNormalizePathProc', 'Tcl_FSOpenFileChannel',
+            'Tcl_FSOpenFileChannelProc', 'Tcl_FSPathInFilesystemProc', 'Tcl_FSPathSeparator', 'Tcl_FSRegister',
+            'Tcl_FSRemoveDirectory', 'Tcl_FSRemoveDirectoryProc', 'Tcl_FSRenameFile', 'Tcl_FSRenameFileProc',
+            'Tcl_FSSplitPath', 'Tcl_FSStat', 'Tcl_FSStatProc', 'Tcl_FSUnloadFile', 'Tcl_FSUnloadFileProc',
+            'Tcl_FSUnregister', 'Tcl_FSUtime', 'Tcl_FSUtimeProc', 'Tcl_GetAccessTimeFromStat', 'Tcl_GetAlias',
+            'Tcl_GetAliasObj', 'Tcl_GetAssocData', 'Tcl_GetBignumFromObj', 'Tcl_GetBlocksFromStat',
+            'Tcl_GetBlockSizeFromStat', 'Tcl_GetBoolean', 'Tcl_GetBooleanFromObj', 'Tcl_GetByteArrayFromObj',
+            'Tcl_GetChangeTimeFromStat', 'Tcl_GetChannel', 'Tcl_GetChannelBufferSize', 'Tcl_GetChannelError',
+            'Tcl_GetChannelErrorInterp', 'Tcl_GetChannelHandle', 'Tcl_GetChannelInstanceData', 'Tcl_GetChannelMode',
+            'Tcl_GetChannelName', 'Tcl_GetChannelNames', 'Tcl_GetChannelNamesEx', 'Tcl_GetChannelOption',
+            'Tcl_GetChannelThread', 'Tcl_GetChannelType', 'Tcl_GetCharLength', 'Tcl_GetClassAsObject',
+            'Tcl_GetCommandFromObj', 'Tcl_GetCommandFullName', 'Tcl_GetCommandInfo', 'Tcl_GetCommandInfoFromToken',
+            'Tcl_GetCommandName', 'Tcl_GetCurrentNamespace', 'Tcl_GetCurrentThread', 'Tcl_GetCwd',
+            'Tcl_GetDefaultEncodingDir', 'Tcl_GetDeviceTypeFromStat', 'Tcl_GetDouble', 'Tcl_GetDoubleFromObj',
+            'Tcl_GetEncoding', 'Tcl_GetEncodingFromObj', 'Tcl_GetEncodingName', 'Tcl_GetEncodingNameFromEnvironment',
+            'Tcl_GetEncodingNames', 'Tcl_GetEncodingSearchPath', 'Tcl_GetEnsembleFlags', 'Tcl_GetEnsembleMappingDict',
+            'Tcl_GetEnsembleNamespace', 'Tcl_GetEnsembleParameterList', 'Tcl_GetEnsembleSubcommandList',
+            'Tcl_GetEnsembleUnknownHandler', 'Tcl_GetErrno', 'Tcl_GetErrorLine', 'Tcl_GetFSDeviceFromStat',
+            'Tcl_GetFSInodeFromStat', 'Tcl_GetGlobalNamespace', 'Tcl_GetGroupIdFromStat', 'Tcl_GetHashKey',
+            'Tcl_GetHashValue', 'Tcl_GetHostName', 'Tcl_GetIndexFromObj', 'Tcl_GetIndexFromObjStruct', 'Tcl_GetInt',
+            'Tcl_GetInterpPath', 'Tcl_GetIntFromObj', 'Tcl_GetLinkCountFromStat', 'Tcl_GetLongFromObj',
+            'Tcl_GetMaster', 'Tcl_GetMathFuncInfo', 'Tcl_GetModeFromStat', 'Tcl_GetModificationTimeFromStat',
+            'Tcl_GetNameOfExecutable', 'Tcl_GetNamespaceUnknownHandler', 'Tcl_GetObjectAsClass', 'Tcl_GetObjectCommand',
+            'Tcl_GetObjectFromObj', 'Tcl_GetObjectName', 'Tcl_GetObjectNamespace', 'Tcl_GetObjResult', 'Tcl_GetObjType',
+            'Tcl_GetOpenFile', 'Tcl_GetPathType', 'Tcl_GetRange', 'Tcl_GetRegExpFromObj', 'Tcl_GetReturnOptions',
+            'Tcl_Gets', 'Tcl_GetServiceMode', 'Tcl_GetSizeFromStat', 'Tcl_GetSlave', 'Tcl_GetsObj',
+            'Tcl_GetStackedChannel', 'Tcl_GetStartupScript', 'Tcl_GetStdChannel', 'Tcl_GetString',
+            'Tcl_GetStringFromObj', 'Tcl_GetStringResult', 'Tcl_GetThreadData', 'Tcl_GetTime', 'Tcl_GetTopChannel',
+            'Tcl_GetUniChar', 'Tcl_GetUnicode', 'Tcl_GetUnicodeFromObj', 'Tcl_GetUserIdFromStat', 'Tcl_GetVar',
+            'Tcl_GetVar2', 'Tcl_GetVar2Ex', 'Tcl_GetVersion', 'Tcl_GetWideIntFromObj', 'Tcl_GlobalEval',
+            'Tcl_GlobalEvalObj', 'Tcl_GlobTypeData', 'Tcl_HashKeyType', 'Tcl_HashStats', 'Tcl_HideCommand',
+            'Tcl_IdleProc', 'Tcl_Import', 'Tcl_IncrRefCount', 'Tcl_Init', 'Tcl_InitCustomHashTable',
+            'Tcl_InitHashTable', 'Tcl_InitMemory', 'Tcl_InitNotifier', 'Tcl_InitObjHashTable', 'Tcl_InitStubs',
+            'Tcl_InputBlocked', 'Tcl_InputBuffered', 'tcl_interactive', 'Tcl_Interp', 'Tcl_InterpActive',
+            'Tcl_InterpDeleted', 'Tcl_InterpDeleteProc', 'Tcl_InvalidateStringRep', 'Tcl_IsChannelExisting',
+            'Tcl_IsChannelRegistered', 'Tcl_IsChannelShared', 'Tcl_IsEnsemble', 'Tcl_IsSafe', 'Tcl_IsShared',
+            'Tcl_IsStandardChannel', 'Tcl_JoinPath', 'Tcl_JoinThread', 'tcl_library', 'Tcl_LimitAddHandler',
+            'Tcl_LimitCheck', 'Tcl_LimitExceeded', 'Tcl_LimitGetCommands', 'Tcl_LimitGetGranularity',
+            'Tcl_LimitGetTime', 'Tcl_LimitHandlerDeleteProc', 'Tcl_LimitHandlerProc', 'Tcl_LimitReady',
+            'Tcl_LimitRemoveHandler', 'Tcl_LimitSetCommands', 'Tcl_LimitSetGranularity', 'Tcl_LimitSetTime',
+            'Tcl_LimitTypeEnabled', 'Tcl_LimitTypeExceeded', 'Tcl_LimitTypeReset', 'Tcl_LimitTypeSet',
+            'Tcl_LinkVar', 'Tcl_ListMathFuncs', 'Tcl_ListObjAppendElement', 'Tcl_ListObjAppendList',
+            'Tcl_ListObjGetElements', 'Tcl_ListObjIndex', 'Tcl_ListObjLength', 'Tcl_ListObjReplace',
+            'Tcl_LogCommandInfo', 'Tcl_Main', 'Tcl_MainLoopProc', 'Tcl_MakeFileChannel', 'Tcl_MakeSafe',
+            'Tcl_MakeTcpClientChannel', 'Tcl_MathProc', 'TCL_MEM_DEBUG', 'Tcl_Merge', 'Tcl_MethodCallProc',
+            'Tcl_MethodDeclarerClass', 'Tcl_MethodDeclarerObject', 'Tcl_MethodDeleteProc', 'Tcl_MethodIsPublic',
+            'Tcl_MethodIsType', 'Tcl_MethodName', 'Tcl_MethodType', 'Tcl_MutexFinalize', 'Tcl_MutexLock',
+            'Tcl_MutexUnlock', 'Tcl_NamespaceDeleteProc', 'Tcl_NewBignumObj', 'Tcl_NewBooleanObj',
+            'Tcl_NewByteArrayObj', 'Tcl_NewDictObj', 'Tcl_NewDoubleObj', 'Tcl_NewInstanceMethod', 'Tcl_NewIntObj',
+            'Tcl_NewListObj', 'Tcl_NewLongObj', 'Tcl_NewMethod', 'Tcl_NewObj', 'Tcl_NewObjectInstance',
+            'Tcl_NewStringObj', 'Tcl_NewUnicodeObj', 'Tcl_NewWideIntObj', 'Tcl_NextHashEntry', 'tcl_nonwordchars',
+            'Tcl_NotifierProcs', 'Tcl_NotifyChannel', 'Tcl_NRAddCallback', 'Tcl_NRCallObjProc', 'Tcl_NRCmdSwap',
+            'Tcl_NRCreateCommand', 'Tcl_NREvalObj', 'Tcl_NREvalObjv', 'Tcl_NumUtfChars', 'Tcl_Obj', 'Tcl_ObjCmdProc',
+            'Tcl_ObjectContextInvokeNext', 'Tcl_ObjectContextIsFiltering', 'Tcl_ObjectContextMethod',
+            'Tcl_ObjectContextObject', 'Tcl_ObjectContextSkippedArgs', 'Tcl_ObjectDeleted', 'Tcl_ObjectGetMetadata',
+            'Tcl_ObjectGetMethodNameMapper', 'Tcl_ObjectMapMethodNameProc', 'Tcl_ObjectMetadataDeleteProc',
+            'Tcl_ObjectSetMetadata', 'Tcl_ObjectSetMethodNameMapper', 'Tcl_ObjGetVar2', 'Tcl_ObjPrintf',
+            'Tcl_ObjSetVar2', 'Tcl_ObjType', 'Tcl_OpenCommandChannel', 'Tcl_OpenFileChannel', 'Tcl_OpenTcpClient',
+            'Tcl_OpenTcpServer', 'Tcl_OutputBuffered', 'Tcl_PackageInitProc', 'Tcl_PackageUnloadProc', 'Tcl_Panic',
+            'Tcl_PanicProc', 'Tcl_PanicVA', 'Tcl_ParseArgsObjv', 'Tcl_ParseBraces', 'Tcl_ParseCommand', 'Tcl_ParseExpr',
+            'Tcl_ParseQuotedString', 'Tcl_ParseVar', 'Tcl_ParseVarName', 'tcl_patchLevel', 'tcl_pkgPath',
+            'Tcl_PkgPresent', 'Tcl_PkgPresentEx', 'Tcl_PkgProvide', 'Tcl_PkgProvideEx', 'Tcl_PkgRequire',
+            'Tcl_PkgRequireEx', 'Tcl_PkgRequireProc', 'tcl_platform', 'Tcl_PosixError', 'tcl_precision',
+            'Tcl_Preserve', 'Tcl_PrintDouble', 'Tcl_PutEnv', 'Tcl_QueryTimeProc', 'Tcl_QueueEvent', 'tcl_rcFileName',
+            'Tcl_Read', 'Tcl_ReadChars', 'Tcl_ReadRaw', 'Tcl_Realloc', 'Tcl_ReapDetachedProcs', 'Tcl_RecordAndEval',
+            'Tcl_RecordAndEvalObj', 'Tcl_RegExpCompile', 'Tcl_RegExpExec', 'Tcl_RegExpExecObj', 'Tcl_RegExpGetInfo',
+            'Tcl_RegExpIndices', 'Tcl_RegExpInfo', 'Tcl_RegExpMatch', 'Tcl_RegExpMatchObj', 'Tcl_RegExpRange',
+            'Tcl_RegisterChannel', 'Tcl_RegisterConfig', 'Tcl_RegisterObjType', 'Tcl_Release', 'Tcl_ResetResult',
+            'Tcl_RestoreInterpState', 'Tcl_RestoreResult', 'Tcl_SaveInterpState', 'Tcl_SaveResult', 'Tcl_ScaleTimeProc',
+            'Tcl_ScanCountedElement', 'Tcl_ScanElement', 'Tcl_Seek', 'Tcl_ServiceAll', 'Tcl_ServiceEvent',
+            'Tcl_ServiceModeHook', 'Tcl_SetAssocData', 'Tcl_SetBignumObj', 'Tcl_SetBooleanObj',
+            'Tcl_SetByteArrayLength', 'Tcl_SetByteArrayObj', 'Tcl_SetChannelBufferSize', 'Tcl_SetChannelError',
+            'Tcl_SetChannelErrorInterp', 'Tcl_SetChannelOption', 'Tcl_SetCommandInfo', 'Tcl_SetCommandInfoFromToken',
+            'Tcl_SetDefaultEncodingDir', 'Tcl_SetDoubleObj', 'Tcl_SetEncodingSearchPath', 'Tcl_SetEnsembleFlags',
+            'Tcl_SetEnsembleMappingDict', 'Tcl_SetEnsembleParameterList', 'Tcl_SetEnsembleSubcommandList',
+            'Tcl_SetEnsembleUnknownHandler', 'Tcl_SetErrno', 'Tcl_SetErrorCode', 'Tcl_SetErrorCodeVA',
+            'Tcl_SetErrorLine', 'Tcl_SetExitProc', 'Tcl_SetFromAnyProc', 'Tcl_SetHashValue', 'Tcl_SetIntObj',
+            'Tcl_SetListObj', 'Tcl_SetLongObj', 'Tcl_SetMainLoop', 'Tcl_SetMaxBlockTime',
+            'Tcl_SetNamespaceUnknownHandler', 'Tcl_SetNotifier', 'Tcl_SetObjErrorCode', 'Tcl_SetObjLength',
+            'Tcl_SetObjResult', 'Tcl_SetPanicProc', 'Tcl_SetRecursionLimit', 'Tcl_SetResult', 'Tcl_SetReturnOptions',
+            'Tcl_SetServiceMode', 'Tcl_SetStartupScript', 'Tcl_SetStdChannel', 'Tcl_SetStringObj',
+            'Tcl_SetSystemEncoding', 'Tcl_SetTimeProc', 'Tcl_SetTimer', 'Tcl_SetUnicodeObj', 'Tcl_SetVar',
+            'Tcl_SetVar2', 'Tcl_SetVar2Ex', 'Tcl_SetWideIntObj', 'Tcl_SignalId', 'Tcl_SignalMsg', 'Tcl_Sleep',
+            'Tcl_SourceRCFile', 'Tcl_SpliceChannel', 'Tcl_SplitList', 'Tcl_SplitPath', 'Tcl_StackChannel',
+            'Tcl_StandardChannels', 'tcl_startOfNextWord', 'tcl_startOfPreviousWord', 'Tcl_Stat', 'Tcl_StaticPackage',
+            'Tcl_StringCaseMatch', 'Tcl_StringMatch', 'Tcl_SubstObj', 'Tcl_TakeBignumFromObj', 'Tcl_TcpAcceptProc',
+            'Tcl_Tell', 'Tcl_ThreadAlert', 'Tcl_ThreadQueueEvent', 'Tcl_Time', 'Tcl_TimerProc', 'Tcl_Token',
+            'Tcl_TraceCommand', 'tcl_traceCompile', 'tcl_traceEval', 'Tcl_TraceVar', 'Tcl_TraceVar2',
+            'Tcl_TransferResult', 'Tcl_TranslateFileName', 'Tcl_TruncateChannel', 'Tcl_Ungets', 'Tcl_UniChar',
+            'Tcl_UniCharAtIndex', 'Tcl_UniCharCaseMatch', 'Tcl_UniCharIsAlnum', 'Tcl_UniCharIsAlpha',
+            'Tcl_UniCharIsControl', 'Tcl_UniCharIsDigit', 'Tcl_UniCharIsGraph', 'Tcl_UniCharIsLower',
+            'Tcl_UniCharIsPrint', 'Tcl_UniCharIsPunct', 'Tcl_UniCharIsSpace', 'Tcl_UniCharIsUpper',
+            'Tcl_UniCharIsWordChar', 'Tcl_UniCharLen', 'Tcl_UniCharNcasecmp', 'Tcl_UniCharNcmp', 'Tcl_UniCharToLower',
+            'Tcl_UniCharToTitle', 'Tcl_UniCharToUpper', 'Tcl_UniCharToUtf', 'Tcl_UniCharToUtfDString', 'Tcl_UnlinkVar',
+            'Tcl_UnregisterChannel', 'Tcl_UnsetVar', 'Tcl_UnsetVar2', 'Tcl_UnstackChannel', 'Tcl_UntraceCommand',
+            'Tcl_UntraceVar', 'Tcl_UntraceVar2', 'Tcl_UpdateLinkedVar', 'Tcl_UpdateStringProc', 'Tcl_UpVar',
+            'Tcl_UpVar2', 'Tcl_UtfAtIndex', 'Tcl_UtfBackslash', 'Tcl_UtfCharComplete', 'Tcl_UtfFindFirst',
+            'Tcl_UtfFindLast', 'Tcl_UtfNext', 'Tcl_UtfPrev', 'Tcl_UtfToExternal', 'Tcl_UtfToExternalDString',
+            'Tcl_UtfToLower', 'Tcl_UtfToTitle', 'Tcl_UtfToUniChar', 'Tcl_UtfToUniCharDString', 'Tcl_UtfToUpper',
+            'Tcl_ValidateAllMemory', 'Tcl_Value', 'Tcl_VarEval', 'Tcl_VarEvalVA', 'Tcl_VarTraceInfo',
+            'Tcl_VarTraceInfo2', 'Tcl_VarTraceProc', 'tcl_version', 'Tcl_WaitForEvent', 'Tcl_WaitPid',
+            'Tcl_WinTCharToUtf', 'Tcl_WinUtfToTChar', 'tcl_wordBreakAfter', 'tcl_wordBreakBefore', 'tcl_wordchars',
+            'Tcl_Write', 'Tcl_WriteChars', 'Tcl_WriteObj', 'Tcl_WriteRaw', 'Tcl_WrongNumArgs', 'Tcl_ZlibAdler32',
+            'Tcl_ZlibCRC32', 'Tcl_ZlibDeflate', 'Tcl_ZlibInflate', 'Tcl_ZlibStreamChecksum', 'Tcl_ZlibStreamClose',
+            'Tcl_ZlibStreamEof', 'Tcl_ZlibStreamGet', 'Tcl_ZlibStreamGetCommandName', 'Tcl_ZlibStreamInit',
+            'Tcl_ZlibStreamPut', 'tcltest', 'tell', 'throw', 'time', 'tm', 'trace', 'transchan', 'try', 'unknown',
+            'unload', 'unset', 'update', 'uplevel', 'upvar', 'variable', 'vwait', 'while', 'yield', 'yieldto', 'zlib'
         ]
 
+
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
 
         # ###########################################
@@ -5256,11 +5263,12 @@ class App(QtCore.QObject):
 
         cursor = QtGui.QCursor()
 
-        canvas_origin = self.plotcanvas.vispy_canvas.native.mapToGlobal(QtCore.QPoint(0, 0))
-        jump_loc = self.plotcanvas.vispy_canvas.translate_coords_2((location[0], location[1]))
+        canvas_origin = self.plotcanvas.native.mapToGlobal(QtCore.QPoint(0, 0))
+        jump_loc = self.plotcanvas.translate_coords_2((location[0], location[1]))
 
         cursor.setPos(canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
         self.inform.emit(_("[success] Done."))
+        return location
 
     def on_copy_object(self):
         self.report_usage("on_copy_object()")
@@ -5480,7 +5488,7 @@ class App(QtCore.QObject):
     def on_set_zero_click(self, event):
         # this function will be available only for mouse left click
         pos = []
-        pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.plotcanvas.translate_coords(event.pos)
         if event.button == 1:
             if self.grid_status() == True:
                 pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -5835,7 +5843,7 @@ class App(QtCore.QObject):
 
         :return: None
         """
-        self.plotcanvas.vispy_canvas.update()           # TODO: Need update canvas?
+        self.plotcanvas.update()           # TODO: Need update canvas?
         self.on_zoom_fit(None)
         self.collection.update_view()
         # self.inform.emit(_("Plots updated ..."))
@@ -6008,11 +6016,11 @@ class App(QtCore.QObject):
         self.pos = []
 
         # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
+        self.plotcanvas.native.setFocus()
         # Set the mouse button for panning
-        self.plotcanvas.vispy_canvas.view.camera.pan_button_setting = self.defaults['global_pan_button']
+        self.plotcanvas.view.camera.pan_button_setting = self.defaults['global_pan_button']
 
-        self.pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        self.pos_canvas = self.plotcanvas.translate_coords(event.pos)
 
         if self.grid_status() == True:
             self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
@@ -6058,7 +6066,7 @@ class App(QtCore.QObject):
         """
 
         # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
+        self.plotcanvas.native.setFocus()
         self.pos_jump = event.pos
 
         self.ui.popMenu.mouse_is_panning = False
@@ -6071,7 +6079,7 @@ class App(QtCore.QObject):
 
         if self.rel_point1 is not None:
             try:  # May fail in case mouse not within axes
-                pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                pos_canvas = self.plotcanvas.translate_coords(event.pos)
                 if self.grid_status() == True:
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     self.app_cursor.enabled = True
@@ -6145,7 +6153,7 @@ class App(QtCore.QObject):
         :return:
         """
         pos = 0, 0
-        pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.plotcanvas.translate_coords(event.pos)
         if self.grid_status() == True:
             pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
@@ -6377,7 +6385,7 @@ class App(QtCore.QObject):
                     # curr_sel_obj.plot(color=self.FC_dark_blue, face_color=self.FC_light_blue)
 
                     # TODO: on selected objects change the object colors and do not draw the selection box
-                    # self.plotcanvas.vispy_canvas.update() # this updates the canvas
+                    # self.plotcanvas.update() # this updates the canvas
         except Exception as e:
             log.error("[ERROR] Something went bad. %s" % str(e))
             return
@@ -9064,7 +9072,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
         self.plotcanvas = PlotCanvas(plot_container, self)
 
         # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
+        self.plotcanvas.native.setFocus()
 
         self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_over_plot)
         self.plotcanvas.vis_connect('mouse_press', self.on_mouse_click_over_plot)
@@ -9076,7 +9084,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
 
         self.app_cursor = self.plotcanvas.new_cursor()
         self.app_cursor.enabled = False
-        self.hover_shapes = ShapeCollection(parent=self.plotcanvas.vispy_canvas.view.scene, layers=1)
+        self.hover_shapes = ShapeCollection(parent=self.plotcanvas.view.scene, layers=1)
 
     def on_zoom_fit(self, event):
         """

+ 1 - 1
FlatCAMObj.py

@@ -73,7 +73,7 @@ class FlatCAMObj(QtCore.QObject):
 
         self.kind = None  # Override with proper name
 
-        # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene)
+        # self.shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene)
         self.shapes = self.app.plotcanvas.new_shape_group()
 
         # self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)

+ 19 - 0
README.md

@@ -9,6 +9,25 @@ CAD program, and create G-Code for Isolation routing.
 
 =================================================
 
+25.08.2019
+
+- initial add of a new Tcl Command named CopperClear
+- remade the NCC Tool in preparation for the newly added TclCommand CopperClear
+- finished adding the TclCommandCopperClear that can be called with alias: 'ncc'
+- added new capability in NCC Tool when the reference object is of Gerber type and fixed some newly introduced errors
+
+24.08.2019
+
+- modified CutOut Tool so now the manual gaps adding will continue until the user is clicking the RMB
+- added ability to turn on/off the grid snapping and to jump to a location while in CutOut Tool manual gap adding action
+- made PlotCanvas class inherit from VisPy Canvas instead of creating an instance of it (work of JP)
+- fixed selection by dragging a selection shape in Geometry Editor
+- modified the Paint Tool. Now the Single Polygon and Area/Reference Object painting works with multiple tools too. The tools have to be selected in the Tool Table.
+- remade the TclCommand Paint to work in the new configuration of the the app (the painting functions are now in their own tool, Paint Tool)
+- fixed a bug in the Properties Tool
+- added a new TcL Command named Nregions who generate non-copper regions
+- added a new TclCommand named Bbox who generate a bounding box.
+
 23.08.2019
 
 - in Tool Cutout for the manual gaps, right mouse button click will exit from the action of adding gaps

+ 3 - 3
flatcamEditors/FlatCAMExcEditor.py

@@ -3262,7 +3262,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         :return: None
         """
 
-        self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event.pos)
 
         if self.app.grid_status() == True:
             self.pos  = self.app.geo_editor.snap(self.pos[0], self.pos[1])
@@ -3401,7 +3401,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event.pos)
 
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -3557,7 +3557,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         :return: None
         """
 
-        pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos = self.canvas.translate_coords(event.pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata

+ 27 - 12
flatcamEditors/FlatCAMGeoEditor.py

@@ -3576,7 +3576,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :return: None
         """
 
-        self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event.pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
@@ -3628,7 +3628,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         """
-        pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos = self.canvas.translate_coords(event.pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
         self.x = event.xdata
@@ -3704,7 +3704,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.app.selection_type = None
 
     def on_geo_click_release(self, event):
-        pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event.pos)
 
         if self.app.grid_status() == True:
             pos = self.snap(pos_canvas[0], pos_canvas[1])
@@ -3768,19 +3768,34 @@ class FlatCAMGeoEditor(QtCore.QObject):
         """
         poly_selection = Polygon([start_pos, (end_pos[0], start_pos[1]), end_pos, (start_pos[0], end_pos[1])])
 
+        key_modifier = QtWidgets.QApplication.keyboardModifiers()
+
+        if key_modifier == QtCore.Qt.ShiftModifier:
+            mod_key = 'Shift'
+        elif key_modifier == QtCore.Qt.ControlModifier:
+            mod_key = 'Control'
+        else:
+            mod_key = None
+
         self.app.delete_selection_shape()
+
+        sel_objects_list = []
         for obj in self.storage.get_objects():
             if (sel_type is True and poly_selection.contains(obj.geo)) or (sel_type is False and
                                                                            poly_selection.intersects(obj.geo)):
-                    if self.key == self.app.defaults["global_mselect_key"]:
-                        if obj in self.selected:
-                            self.selected.remove(obj)
-                        else:
-                            # add the object to the selected shapes
-                            self.selected.append(obj)
-                    else:
-                        if obj not in self.selected:
-                            self.selected.append(obj)
+                sel_objects_list.append(obj)
+
+        if mod_key == self.app.defaults["global_mselect_key"]:
+            for obj in sel_objects_list:
+                if obj in self.selected:
+                    self.selected.remove(obj)
+                else:
+                    # add the object to the selected shapes
+                    self.selected.append(obj)
+        else:
+            self.selected = []
+            self.selected = sel_objects_list
+
         self.replot()
 
     def draw_utility_geometry(self, geo):

+ 3 - 3
flatcamEditors/FlatCAMGrbEditor.py

@@ -4069,7 +4069,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :return: None
         """
 
-        self.pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        self.pos = self.canvas.translate_coords(event.pos)
 
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
@@ -4132,7 +4132,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
     def on_grb_click_release(self, event):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
 
-        pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event.pos)
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
@@ -4264,7 +4264,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :return: None
         """
 
-        pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.canvas.translate_coords(event.pos)
         event.xdata, event.ydata = pos_canvas[0], pos_canvas[1]
 
         self.x = event.xdata

+ 46 - 39
flatcamGUI/PlotCanvas.py

@@ -18,12 +18,12 @@ from vispy.geometry import Rect
 log = logging.getLogger('base')
 
 
-class PlotCanvas(QtCore.QObject):
+class PlotCanvas(QtCore.QObject, VisPyCanvas):
     """
     Class handling the plotting area in the application.
     """
 
-    def __init__(self, container, app):
+    def __init__(self, container, fcapp):
         """
         The constructor configures the VisPy figure that
         will contain all plots, creates the base axes and connects
@@ -34,8 +34,12 @@ class PlotCanvas(QtCore.QObject):
         """
 
         super(PlotCanvas, self).__init__()
+        VisPyCanvas.__init__(self)
 
-        self.app = app
+        # VisPyCanvas does not allow new attributes. Override.
+        self.unfreeze()
+
+        self.fcapp = fcapp
 
         # Parent container
         self.container = container
@@ -44,19 +48,19 @@ class PlotCanvas(QtCore.QObject):
         # which might decrease performance
         self.b_line, self.r_line, self.t_line, self.l_line = None, None, None, None
 
-        # Attach to parent
-        self.vispy_canvas = VisPyCanvas()
+        # <VisPyCanvas>
+        self.create_native()
+        self.native.setParent(self.fcapp.ui)
 
-        self.vispy_canvas.create_native()
-        self.vispy_canvas.native.setParent(self.app.ui)
-        self.container.addWidget(self.vispy_canvas.native)
+        # <QtCore.QObject>
+        self.container.addWidget(self.native)
 
         # ## AXIS # ##
         self.v_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=True,
-                                   parent=self.vispy_canvas.view.scene)
+                                   parent=self.view.scene)
 
         self.h_line = InfiniteLine(pos=0, color=(0.70, 0.3, 0.3, 1.0), vertical=False,
-                                   parent=self.vispy_canvas.view.scene)
+                                   parent=self.view.scene)
 
         # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
         # all CNC have a limited workspace
@@ -70,12 +74,15 @@ class PlotCanvas(QtCore.QObject):
         self.shape_collections = []
 
         self.shape_collection = self.new_shape_collection()
-        self.app.pool_recreated.connect(self.on_pool_recreated)
+        self.fcapp.pool_recreated.connect(self.on_pool_recreated)
         self.text_collection = self.new_text_collection()
 
         # TODO: Should be setting to show/hide CNC job annotations (global or per object)
         self.text_collection.enabled = True
 
+        # Keep VisPy canvas happy by letting it be "frozen" again.
+        self.freeze()
+
     # draw a rectangle made out of 4 lines on the canvas to serve as a hint for the work area
     # all CNC have a limited workspace
     def draw_workspace(self):
@@ -91,38 +98,38 @@ class PlotCanvas(QtCore.QObject):
         a3p_mm = np.array([(0, 0), (297, 0), (297, 420), (0, 420)])
         a3l_mm = np.array([(0, 0), (420, 0), (420, 297), (0, 297)])
 
-        if self.app.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
-            if self.app.defaults['global_workspaceT'] == 'A4P':
+        if self.fcapp.ui.general_defaults_form.general_app_group.units_radio.get_value().upper() == 'MM':
+            if self.fcapp.defaults['global_workspaceT'] == 'A4P':
                 a = a4p_mm
-            elif self.app.defaults['global_workspaceT'] == 'A4L':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A4L':
                 a = a4l_mm
-            elif self.app.defaults['global_workspaceT'] == 'A3P':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A3P':
                 a = a3p_mm
-            elif self.app.defaults['global_workspaceT'] == 'A3L':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A3L':
                 a = a3l_mm
         else:
-            if self.app.defaults['global_workspaceT'] == 'A4P':
+            if self.fcapp.defaults['global_workspaceT'] == 'A4P':
                 a = a4p_in
-            elif self.app.defaults['global_workspaceT'] == 'A4L':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A4L':
                 a = a4l_in
-            elif self.app.defaults['global_workspaceT'] == 'A3P':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A3P':
                 a = a3p_in
-            elif self.app.defaults['global_workspaceT'] == 'A3L':
+            elif self.fcapp.defaults['global_workspaceT'] == 'A3L':
                 a = a3l_in
 
         self.delete_workspace()
 
         self.b_line = Line(pos=a[0:2], color=(0.70, 0.3, 0.3, 1.0),
-                           antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
+                           antialias= True, method='agg', parent=self.view.scene)
         self.r_line = Line(pos=a[1:3], color=(0.70, 0.3, 0.3, 1.0),
-                           antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
+                           antialias= True, method='agg', parent=self.view.scene)
 
         self.t_line = Line(pos=a[2:4], color=(0.70, 0.3, 0.3, 1.0),
-                           antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
+                           antialias= True, method='agg', parent=self.view.scene)
         self.l_line = Line(pos=np.array((a[0], a[3])), color=(0.70, 0.3, 0.3, 1.0),
-                           antialias= True, method='agg', parent=self.vispy_canvas.view.scene)
+                           antialias= True, method='agg', parent=self.view.scene)
 
-        if self.app.defaults['global_workspace'] is False:
+        if self.fcapp.defaults['global_workspace'] is False:
             self.delete_workspace()
 
     # delete the workspace lines from the plot by removing the parent
@@ -138,21 +145,21 @@ class PlotCanvas(QtCore.QObject):
     # redraw the workspace lines on the plot by readding them to the parent view.scene
     def restore_workspace(self):
         try:
-            self.b_line.parent = self.vispy_canvas.view.scene
-            self.r_line.parent = self.vispy_canvas.view.scene
-            self.t_line.parent = self.vispy_canvas.view.scene
-            self.l_line.parent = self.vispy_canvas.view.scene
+            self.b_line.parent = self.view.scene
+            self.r_line.parent = self.view.scene
+            self.t_line.parent = self.view.scene
+            self.l_line.parent = self.view.scene
         except Exception as e:
             pass
 
     def vis_connect(self, event_name, callback):
-        return getattr(self.vispy_canvas.events, event_name).connect(callback)
+        return getattr(self.events, event_name).connect(callback)
 
     def vis_disconnect(self, event_name, callback=None):
         if callback is None:
-            getattr(self.vispy_canvas.events, event_name).disconnect()
+            getattr(self.events, event_name).disconnect()
         else:
-            getattr(self.vispy_canvas.events, event_name).disconnect(callback)
+            getattr(self.events, event_name).disconnect(callback)
 
     def zoom(self, factor, center=None):
         """
@@ -165,7 +172,7 @@ class PlotCanvas(QtCore.QObject):
         :type center: list
         :return: None
         """
-        self.vispy_canvas.view.camera.zoom(factor, center)
+        self.view.camera.zoom(factor, center)
 
     def new_shape_group(self, shape_collection=None):
         if shape_collection:
@@ -173,13 +180,13 @@ class PlotCanvas(QtCore.QObject):
         return ShapeGroup(self.shape_collection)
 
     def new_shape_collection(self, **kwargs):
-        # sc = ShapeCollection(parent=self.vispy_canvas.view.scene, pool=self.app.pool, **kwargs)
+        # sc = ShapeCollection(parent=self.view.scene, pool=self.app.pool, **kwargs)
         # self.shape_collections.append(sc)
         # return sc
-        return ShapeCollection(parent=self.vispy_canvas.view.scene, pool=self.app.pool, **kwargs)
+        return ShapeCollection(parent=self.view.scene, pool=self.fcapp.pool, **kwargs)
 
     def new_cursor(self):
-        c = Cursor(pos=np.empty((0, 2)), parent=self.vispy_canvas.view.scene)
+        c = Cursor(pos=np.empty((0, 2)), parent=self.view.scene)
         c.antialias = 0
         return c
 
@@ -190,7 +197,7 @@ class PlotCanvas(QtCore.QObject):
             return TextGroup(self.text_collection)
 
     def new_text_collection(self, **kwargs):
-        return TextCollection(parent=self.vispy_canvas.view.scene, **kwargs)
+        return TextCollection(parent=self.view.scene, **kwargs)
 
     def fit_view(self, rect=None):
 
@@ -212,7 +219,7 @@ class PlotCanvas(QtCore.QObject):
         rect.right *= 1.01
         rect.top *= 1.01
 
-        self.vispy_canvas.view.camera.rect = rect
+        self.view.camera.rect = rect
 
         self.shape_collection.unlock_updates()
 
@@ -227,7 +234,7 @@ class PlotCanvas(QtCore.QObject):
             except TypeError:
                 pass
 
-        self.vispy_canvas.view.camera.rect = rect
+        self.view.camera.rect = rect
 
         self.shape_collection.unlock_updates()
 

+ 67 - 46
flatcamTools/ToolCutOut.py

@@ -16,7 +16,6 @@ if '_' not in builtins.__dict__:
 class CutOut(FlatCAMTool):
 
     toolName = _("Cutout PCB")
-    gapFinished = pyqtSignal()
 
     def __init__(self, app):
         FlatCAMTool.__init__(self, app)
@@ -296,6 +295,13 @@ class CutOut(FlatCAMTool):
         # this is the Geometry object generated in this class to be used for adding manual gaps
         self.man_cutout_obj = None
 
+        # if mouse is dragging set the object True
+        self.mouse_is_dragging = False
+
+        # hold the mouse position here
+        self.x_pos = None
+        self.y_pos = None
+
         # Signals
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_cutout)
@@ -349,8 +355,6 @@ class CutOut(FlatCAMTool):
         self.gaps.set_value(self.app.defaults["tools_gaps_ff"])
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
 
-        self.gapFinished.connect(self.on_gap_finished)
-
     def on_freeform_cutout(self):
 
         # def subtract_rectangle(obj_, x0, y0, x1, y1):
@@ -774,40 +778,7 @@ class CutOut(FlatCAMTool):
         self.app.plotcanvas.vis_disconnect('mouse_move', self.app.on_mouse_move_over_plot)
         self.app.plotcanvas.vis_connect('key_press', self.on_key_press)
         self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
-        self.app.plotcanvas.vis_connect('mouse_release', self.doit)
-
-    # To be called after clicking on the plot.
-    def doit(self, event):
-        # do paint single only for left mouse clicks
-        if event.button == 1:
-            self.app.inform.emit(_("Making manual bridge gap..."))
-            pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
-            self.on_manual_cutout(click_pos=pos)
-
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-
-            self.app.geo_editor.tool_shape.clear(update=True)
-            self.app.geo_editor.tool_shape.enabled = False
-            self.gapFinished.emit()
-        # if RMB then we exit
-        elif event.button == 2:
-            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
-            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
-            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
-            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
-
-            # Remove any previous utility shape
-            self.app.geo_editor.tool_shape.clear(update=True)
-            self.app.geo_editor.tool_shape.enabled = False
+        self.app.plotcanvas.vis_connect('mouse_release', self.on_mouse_click_release)
 
     def on_manual_cutout(self, click_pos):
         name = self.man_object_combo.currentText()
@@ -836,12 +807,6 @@ class CutOut(FlatCAMTool):
 
         self.app.should_we_save = True
 
-    def on_gap_finished(self):
-        # if CTRL key modifier is pressed then repeat the bridge gap cut
-        key_modifier = QtWidgets.QApplication.keyboardModifiers()
-        if key_modifier == Qt.ControlModifier:
-            self.on_manual_gap_click()
-
     def on_manual_geo(self):
         name = self.obj_combo.currentText()
 
@@ -943,20 +908,65 @@ class CutOut(FlatCAMTool):
         cut_poly = box(xmin, ymin, xmax, ymax)
         return cut_poly
 
+    # To be called after clicking on the plot.
+    def on_mouse_click_release(self, event):
+
+        # do paint single only for left mouse clicks
+        if event.button == 1:
+            self.app.inform.emit(_("Making manual bridge gap..."))
+            pos = self.app.plotcanvas.translate_coords(event.pos)
+            self.on_manual_cutout(click_pos=pos)
+
+            # self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
+            # self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
+            # self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
+            # self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            # self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            # self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            # self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+
+            # self.app.geo_editor.tool_shape.clear(update=True)
+            # self.app.geo_editor.tool_shape.enabled = False
+            # self.gapFinished.emit()
+
+        # if RMB then we exit
+        elif event.button == 2 and self.mouse_is_dragging is False:
+            self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
+            self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
+            self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
+            self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
+            self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
+
+            # Remove any previous utility shape
+            self.app.geo_editor.tool_shape.clear(update=True)
+            self.app.geo_editor.tool_shape.enabled = False
+
     def on_mouse_move(self, event):
 
         self.app.on_mouse_move_over_plot(event=event)
 
-        pos = self.canvas.vispy_canvas.translate_coords(event.pos)
+        pos = self.canvas.translate_coords(event.pos)
         event.xdata, event.ydata = pos[0], pos[1]
 
+        if event.is_dragging is True:
+            self.mouse_is_dragging = True
+        else:
+            self.mouse_is_dragging = False
+
         try:
             x = float(event.xdata)
             y = float(event.ydata)
         except TypeError:
             return
 
-        snap_x, snap_y = self.app.geo_editor.snap(x, y)
+        if self.app.grid_status() == True:
+            snap_x, snap_y = self.app.geo_editor.snap(x, y)
+        else:
+            snap_x, snap_y = x, y
+
+        self.x_pos, self.y_pos = snap_x, snap_y
 
         # #################################################
         # ### This section makes the cutting geo to #######
@@ -1044,7 +1054,7 @@ class CutOut(FlatCAMTool):
         if key == QtCore.Qt.Key_Escape or key == 'Escape':
             self.app.plotcanvas.vis_disconnect('key_press', self.on_key_press)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
-            self.app.plotcanvas.vis_disconnect('mouse_release', self.doit)
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.on_mouse_click_release)
             self.app.plotcanvas.vis_connect('key_press', self.app.ui.keyPressEvent)
             self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
             self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
@@ -1054,6 +1064,17 @@ class CutOut(FlatCAMTool):
             self.app.geo_editor.tool_shape.clear(update=True)
             self.app.geo_editor.tool_shape.enabled = False
 
+        # Grid toggle
+        if key == QtCore.Qt.Key_G or key == 'G':
+            self.app.ui.grid_snap_btn.trigger()
+
+        # Jump to coords
+        if key == QtCore.Qt.Key_J or key == 'J':
+            l_x, l_y = self.app.on_jump_to()
+            self.app.geo_editor.tool_shape.clear(update=True)
+            geo = self.cutting_geo(pos=(l_x, l_y))
+            self.draw_utility_geometry(geo=geo)
+
     def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
         """
         Subtract polygon made from points from the given object.

+ 3 - 3
flatcamTools/ToolMeasurement.py

@@ -113,7 +113,7 @@ class Measurement(FlatCAMTool):
         self.original_call_source = 'app'
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
+        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
 
         self.measure_btn.clicked.connect(self.activate_measure_tool)
 
@@ -247,7 +247,7 @@ class Measurement(FlatCAMTool):
         log.debug("Measuring Tool --> mouse click release")
 
         if event.button == 1:
-            pos_canvas = self.canvas.vispy_canvas.translate_coords(event.pos)
+            pos_canvas = self.canvas.translate_coords(event.pos)
             # if GRID is active we need to get the snapped positions
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -286,7 +286,7 @@ class Measurement(FlatCAMTool):
 
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
-            pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+            pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 self.app.app_cursor.enabled = True

+ 4 - 4
flatcamTools/ToolMove.py

@@ -43,7 +43,7 @@ class ToolMove(FlatCAMTool):
         self.old_coords = []
 
         # VisPy visuals
-        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene, layers=1)
+        self.sel_shapes = ShapeCollection(parent=self.app.plotcanvas.view.scene, layers=1)
 
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs)
@@ -94,7 +94,7 @@ class ToolMove(FlatCAMTool):
 
         if event.button == 1:
             if self.clicked_move == 0:
-                pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
 
                 # if GRID is active we need to get the snapped positions
                 if self.app.grid_status() == True:
@@ -111,7 +111,7 @@ class ToolMove(FlatCAMTool):
 
             if self.clicked_move == 1:
                 try:
-                    pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                    pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
 
                     # delete the selection bounding box
                     self.delete_shape()
@@ -178,7 +178,7 @@ class ToolMove(FlatCAMTool):
             self.clicked_move = 1
 
     def on_move(self, event):
-        pos_canvas = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.app.plotcanvas.translate_coords(event.pos)
 
         # if GRID is active we need to get the snapped positions
         if self.app.grid_status() == True:

+ 717 - 215
flatcamTools/ToolNonCopperClear.py

@@ -79,15 +79,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # ################################################
         # ##### The object to be copper cleaned ##########
         # ################################################
-        self.obj_combo = QtWidgets.QComboBox()
-        self.obj_combo.setModel(self.app.collection)
-        self.obj_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
-        self.obj_combo.setCurrentIndex(1)
+        self.object_combo = QtWidgets.QComboBox()
+        self.object_combo.setModel(self.app.collection)
+        self.object_combo.setRootModelIndex(self.app.collection.index(0, 0, QtCore.QModelIndex()))
+        self.object_combo.setCurrentIndex(1)
 
         self.object_label = QtWidgets.QLabel('%s:' % _("Object"))
         self.object_label.setToolTip(_("Object to be cleared of excess copper."))
 
-        form_layout.addRow(self.object_label, self.obj_combo)
+        form_layout.addRow(self.object_label, self.object_combo)
 
         e_lab_0 = QtWidgets.QLabel('')
         form_layout.addRow(e_lab_0)
@@ -396,8 +396,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
     def on_type_obj_index_changed(self, index):
         obj_type = self.type_obj_combo.currentIndex()
-        self.obj_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
-        self.obj_combo.setCurrentIndex(0)
+        self.object_combo.setRootModelIndex(self.app.collection.index(obj_type, 0, QtCore.QModelIndex()))
+        self.object_combo.setCurrentIndex(0)
 
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
@@ -822,12 +822,70 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.build_ui()
 
     def on_ncc_click(self):
-        self.bound_obj = None
-        self.ncc_obj = None
 
-        ref_choice = self.reference_radio.get_value()
+        # init values for the next usage
+        self.reset_usage()
 
-        if ref_choice == 'itself':
+        self.app.report_usage(_("on_paint_button_click"))
+
+        try:
+            overlap = float(self.ncc_overlap_entry.get_value())
+        except ValueError:
+            # try to convert comma to decimal point. if it's still not working error message and return
+            try:
+                overlap = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
+            except ValueError:
+                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                       "use a number."))
+                return
+
+        if overlap >= 1 or overlap < 0:
+            self.app.inform.emit(_("[ERROR_NOTCL] Overlap value must be between "
+                                   "0 (inclusive) and 1 (exclusive), "))
+            return
+
+        connect = self.ncc_connect_cb.get_value()
+        contour = self.ncc_contour_cb.get_value()
+
+        has_offset = self.ncc_choice_offset_cb.isChecked()
+
+        rest = self.ncc_rest_cb.get_value()
+
+        self.obj_name = self.object_combo.currentText()
+        # Get source object.
+        try:
+            self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
+        except Exception as e:
+            self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
+            return "Could not retrieve object: %s" % self.obj_name
+
+        if self.ncc_obj is None:
+            self.app.inform.emit(_("[ERROR_NOTCL] Object not found: %s") % self.ncc_obj)
+            return
+
+        # use the selected tools in the tool table; get diameters
+        tooldia_list = list()
+        if self.tools_table.selectedItems():
+            for x in self.tools_table.selectedItems():
+                try:
+                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                except ValueError:
+                    # try to convert comma to decimal point. if it's still not working error message and return
+                    try:
+                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                    except ValueError:
+                        self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
+                                               "use a number."))
+                        continue
+                tooldia_list.append(tooldia)
+        else:
+            self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
+            return
+
+        o_name = '%s_ncc' % self.obj_name
+
+        select_method = self.reference_radio.get_value()
+        if select_method == 'itself':
             self.bound_obj_name = self.object_combo.currentText()
             # Get source object.
             try:
@@ -835,38 +893,37 @@ class NonCopperClear(FlatCAMTool, Gerber):
             except Exception as e:
                 self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
                 return "Could not retrieve object: %s" % self.obj_name
-            self.on_ncc()
-        elif ref_choice == 'box':
-            self.bound_obj_name = self.box_combo.currentText()
-            # Get source object.
-            try:
-                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
-            except Exception as e:
-                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.bound_obj_name)
-                return "Could not retrieve object: %s. Error: %s" % (self.bound_obj_name, str(e))
-            self.on_ncc()
-        else:
+
+            self.clear_copper(ncc_obj=self.ncc_obj,
+                              tooldia=tooldia_list,
+                              has_offset=has_offset,
+                              outname=o_name,
+                              overlap=overlap,
+                              connect=connect,
+                              contour=contour,
+                              rest=rest)
+        elif select_method == 'area':
             self.app.inform.emit(_("[WARNING_NOTCL] Click the start point of the area."))
 
             # use the first tool in the tool table; get the diameter
-            tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
+            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
 
             # To be called after clicking on the plot.
             def on_mouse_release(event):
-                # do paint single only for left mouse clicks
+                # do clear area only for left mouse clicks
                 if event.button == 1:
                     if self.first_click is False:
                         self.first_click = True
                         self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
 
-                        self.cursor_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
                         if self.app.grid_status() == True:
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                     else:
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.delete_selection_shape()
 
-                        curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
                         if self.app.grid_status() == True:
                             curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
 
@@ -891,14 +948,23 @@ class NonCopperClear(FlatCAMTool, Gerber):
                             self.first_click = False
                             return
 
+                        self.sel_rect = cascaded_union(self.sel_rect)
+                        self.clear_copper(ncc_obj=self.ncc_obj,
+                                          sel_obj=self.bound_obj,
+                                          tooldia=tooldia_list,
+                                          has_offset=has_offset,
+                                          outname=o_name,
+                                          overlap=overlap,
+                                          connect=connect,
+                                          contour=contour,
+                                          rest=rest)
+
                         self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
                         self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
 
                         self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
-
-                        self.on_ncc()
                 elif event.button == 2 and self.first_click is False and self.mouse_is_dragging is False:
                     self.first_click = False
                     self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
@@ -908,11 +974,20 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     self.app.plotcanvas.vis_connect('mouse_move', self.app.on_mouse_move_over_plot)
                     self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
 
-                    self.on_ncc()
+                    self.sel_rect = cascaded_union(self.sel_rect)
+                    self.clear_copper(ncc_obj=self.ncc_obj,
+                                      sel_obj=self.bound_obj,
+                                      tooldia=tooldia_list,
+                                      has_offset=has_offset,
+                                      outname=o_name,
+                                      overlap=overlap,
+                                      connect=connect,
+                                      contour=contour,
+                                      rest=rest)
 
             # called on mouse move
             def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
                 self.app.app_cursor.enabled = False
 
                 if event.button == 2:
@@ -940,107 +1015,207 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
             self.app.plotcanvas.vis_connect('mouse_release', on_mouse_release)
             self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
+        elif select_method == 'box':
+            self.bound_obj_name = self.box_combo.currentText()
+            # Get source object.
+            try:
+                self.bound_obj = self.app.collection.get_by_name(self.bound_obj_name)
+            except Exception as e:
+                self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.bound_obj_name)
+                return "Could not retrieve object: %s. Error: %s" % (self.bound_obj_name, str(e))
 
-    def on_ncc(self):
+            self.clear_copper(ncc_obj=self.ncc_obj,
+                              sel_obj=self.bound_obj,
+                              tooldia=tooldia_list,
+                              has_offset=has_offset,
+                              outname=o_name,
+                              overlap=overlap,
+                              connect=connect,
+                              contour=contour,
+                              rest=rest)
+
+    def clear_copper(self, ncc_obj,
+                     sel_obj=None,
+                     tooldia=None,
+                     margin=None,
+                     has_offset=None,
+                     offset=None,
+                     select_method=None,
+                     outname=None,
+                     overlap=None,
+                     connect=None,
+                     contour=None,
+                     order=None,
+                     method=None,
+                     rest=None,
+                     tools_storage=None):
+        """
+        Clear the excess copper from the entire object.
+
+        :param ncc_obj: ncc cleared object
+        :param tooldia: a tuple or single element made out of diameters of the tools to be used
+        :param overlap: value by which the paths will overlap
+        :param order: if the tools are ordered and how
+        :param select_method: if to do ncc on the whole object, on an defined area or on an area defined by
+        another object
+        :param has_offset: True if an offset is needed
+        :param offset: distance from the copper features where the copper clearing is stopping
+        :param margin: a border around cleared area
+        :param outname: name of the resulting object
+        :param connect: Connect lines to avoid tool lifts.
+        :param contour: Paint around the edges.
+        :param method: choice out of 'seed', 'normal', 'lines'
+        :param rest: True if to use rest-machining
+        :param tools_storage: whether to use the current tools_storage self.ncc_tools or a different one.
+        Usage of the different one is related to when this function is called from a TcL command.
+        :return:
+        """
 
-        try:
-            over = float(self.ncc_overlap_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
-            try:
-                over = float(self.ncc_overlap_entry.get_value().replace(',', '.'))
-            except ValueError:
-                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                       "use a number."))
-                return
-        over = over if over else self.app.defaults["tools_nccoverlap"]
+        proc = self.app.proc_container.new(_("Non-Copper clearing ..."))
 
-        if over >= 1 or over < 0:
-            self.app.inform.emit(_("[ERROR_NOTCL] Overlap value must be between "
-                                   "0 (inclusive) and 1 (exclusive), "))
-            return
+        # #####################################################################
+        # ####### Read the parameters #########################################
+        # #####################################################################
 
-        try:
-            margin = float(self.ncc_margin_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
+        ncc_method = method if method else self.ncc_method_radio.get_value()
+
+        if margin is not None:
+            ncc_margin = margin
+        else:
             try:
-                margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
+                ncc_margin = float(self.ncc_margin_entry.get_value())
             except ValueError:
-                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                       "use a number."))
-                return
-        margin = margin if margin is not None else float(self.app.defaults["tools_nccmargin"])
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    ncc_margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                           "use a number."))
+                    return
 
-        try:
-            ncc_offset_value = float(self.ncc_offset_spinner.get_value())
-        except ValueError:
-            self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                   "use a number."))
-            return
-        ncc_offset_value = ncc_offset_value if ncc_offset_value is not None \
-            else float(self.app.defaults["tools_ncc_offset_value"])
+        if select_method is not None:
+            ncc_select = select_method
+        else:
+            ncc_select = self.reference_radio.get_value()
 
-        connect = self.ncc_connect_cb.get_value()
+        overlap = overlap if overlap else self.app.defaults["tools_nccoverlap"]
         connect = connect if connect else self.app.defaults["tools_nccconnect"]
-
-        contour = self.ncc_contour_cb.get_value()
         contour = contour if contour else self.app.defaults["tools_ncccontour"]
+        order = order if order else self.ncc_order_radio.get_value()
 
-        clearing_method = self.ncc_rest_cb.get_value()
-        clearing_method = clearing_method if clearing_method else self.app.defaults["tools_nccrest"]
+        if tools_storage is not None:
+            tools_storage = tools_storage
+        else:
+            tools_storage = self.ncc_tools
 
-        pol_method = self.ncc_method_radio.get_value()
-        pol_method = pol_method if pol_method else self.app.defaults["tools_nccmethod"]
+        ncc_offset = 0.0
+        if has_offset is True:
+            if offset is not None:
+                ncc_offset = offset
+            else:
+                try:
+                    ncc_offset = float(self.ncc_offset_spinner.get_value())
+                except ValueError:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                           "use a number."))
+                    return
 
-        self.obj_name = self.obj_combo.currentText()
-        # Get source object.
+        # ######################################################################################################
+        # # Read the tooldia parameter and create a sorted list out them - they may be more than one diameter ##
+        # ######################################################################################################
+        sorted_tools = []
+        if tooldia is not None:
+            try:
+                sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+            except AttributeError:
+                if not isinstance(tooldia, list):
+                    sorted_tools = [float(tooldia)]
+                else:
+                    sorted_tools = tooldia
+        else:
+            for row in range(self.tools_table.rowCount()):
+                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
+
+        # ##############################################################################################################
+        # Prepare non-copper polygons. Create the bounding box area from which the copper features will be subtracted ##
+        # ##############################################################################################################
         try:
-            self.ncc_obj = self.app.collection.get_by_name(self.obj_name)
-        except Exception as e:
-            self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
-            return "Could not retrieve object: %s" % self.obj_name
+            if sel_obj is None or sel_obj == 'itself':
+                ncc_sel_obj = ncc_obj
+            else:
+                ncc_sel_obj = sel_obj
 
-        # Prepare non-copper polygons
-        if self.reference_radio.get_value() == 'area':
-            geo_n = self.sel_rect
+            bounding_box = None
+            if ncc_select == 'itself':
+                geo_n = ncc_sel_obj.solid_geometry
 
-            geo_buff_list = []
-            for poly in geo_n:
-                geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
-            bounding_box = cascaded_union(geo_buff_list)
-        else:
-            geo_n = self.bound_obj.solid_geometry
+                try:
+                    if isinstance(geo_n, MultiPolygon):
+                        env_obj = geo_n.convex_hull
+                    elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
+                            (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
+                        env_obj = cascaded_union(geo_n)
+                    else:
+                        env_obj = cascaded_union(geo_n)
+                        env_obj = env_obj.convex_hull
 
-            try:
-                if isinstance(geo_n, MultiPolygon):
-                    env_obj = geo_n.convex_hull
-                elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
-                        (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
-                    env_obj = cascaded_union(geo_n)
+                    bounding_box = env_obj.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
+                except Exception as e:
+                    log.debug("NonCopperClear.on_ncc() --> %s" % str(e))
+                    self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
+                    return
+            elif ncc_select == 'area':
+                geo_n = MultiPolygon(self.sel_rect)
+                try:
+                    __ = iter(geo_n)
+                except TypeError:
+                    geo_n = [geo_n]
+
+                geo_buff_list = []
+                for poly in geo_n:
+                    geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
+
+                bounding_box = cascaded_union(geo_buff_list)
+            elif ncc_select == 'box':
+                geo_n = ncc_sel_obj.solid_geometry
+                if isinstance(ncc_sel_obj, FlatCAMGeometry):
+                    try:
+                        __ = iter(geo_n)
+                    except TypeError:
+                        geo_n = [geo_n]
+
+                    geo_buff_list = []
+                    for poly in geo_n:
+                        geo_buff_list.append(poly.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre))
+
+                    bounding_box = cascaded_union(geo_buff_list)
+                elif isinstance(ncc_sel_obj, FlatCAMGerber):
+                    geo_n = cascaded_union(geo_n).convex_hull
+                    bounding_box = cascaded_union(self.ncc_obj.solid_geometry).convex_hull.intersection(geo_n)
+                    bounding_box = bounding_box.buffer(distance=ncc_margin, join_style=base.JOIN_STYLE.mitre)
                 else:
-                    env_obj = cascaded_union(geo_n)
-                    env_obj = env_obj.convex_hull
-                bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
-            except Exception as e:
-                log.debug("NonCopperClear.on_ncc() --> %s" % str(e))
-                self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
-                return
-
-        # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
-        if isinstance(self.ncc_obj, FlatCAMGerber):
-            if self.ncc_choice_offset_cb.isChecked():
+                    self.app.inform.emit(_("[ERROR_NOTCL] The reference object type is not supported."))
+                    return 'fail'
+        except Exception as e:
+            log.debug("NonCopperClear.clear_copper() --> %s" % str(e))
+            return 'fail'
+
+        # ###################################################################################################
+        # Calculate the empty area by subtracting the solid_geometry from the object bounding box geometry ##
+        # ###################################################################################################
+        if isinstance(ncc_obj, FlatCAMGerber):
+            if has_offset is True:
                 self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
-                offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value)
+                offseted_geo = ncc_obj.solid_geometry.buffer(distance=ncc_offset)
                 self.app.inform.emit(_("[success] Buffering finished ..."))
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
             else:
-                empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box)
-        elif isinstance(self.ncc_obj, FlatCAMGeometry):
-            sol_geo = cascaded_union(self.ncc_obj.solid_geometry)
-            if self.ncc_choice_offset_cb.isChecked():
+                empty = self.get_ncc_empty_area(target=ncc_obj.solid_geometry, boundary=bounding_box)
+        elif isinstance(ncc_obj, FlatCAMGeometry):
+            sol_geo = cascaded_union(ncc_obj.solid_geometry)
+            if has_offset is True:
                 self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
-                offseted_geo = sol_geo.buffer(distance=ncc_offset_value)
+                offseted_geo = sol_geo.buffer(distance=ncc_offset)
                 self.app.inform.emit(_("[success] Buffering finished ..."))
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
             else:
@@ -1049,56 +1224,37 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
             return
 
-        if type(empty) is Polygon:
-            empty = MultiPolygon([empty])
-
         if empty.is_empty:
             self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
             return
 
-        # clear non copper using standard algorithm
-        if clearing_method is False:
-            self.clear_non_copper(
-                empty=empty,
-                over=over,
-                pol_method=pol_method,
-                connect=connect,
-                contour=contour
-            )
-        # clear non copper using rest machining algorithm
-        else:
-            self.clear_non_copper_rest(
-                empty=empty,
-                over=over,
-                pol_method=pol_method,
-                connect=connect,
-                contour=contour
-            )
-
-    def clear_non_copper(self, empty, over, pol_method, outname=None, connect=True, contour=True):
-
-        name = outname if outname else self.obj_name + "_ncc"
-
-        # Sort tools in descending order
-        sorted_tools = []
-        for k, v in self.ncc_tools.items():
-            sorted_tools.append(float('%.4f' % float(v['tooldia'])))
+        if type(empty) is Polygon:
+            empty = MultiPolygon([empty])
 
-        order = self.ncc_order_radio.get_value()
-        if order == 'fwd':
-            sorted_tools.sort(reverse=False)
-        elif order == 'rev':
-            sorted_tools.sort(reverse=True)
+        # ########################################################################################################
+        # set the name for the future Geometry object
+        # I do it here because it is also stored inside the gen_clear_area() and gen_clear_area_rest() methods
+        # ########################################################################################################
+        rest_machining_choice = rest if rest is not None else self.app.defaults["tools_nccrest"]
+        if rest_machining_choice is True:
+            name = outname if outname is not None else self.obj_name + "_ncc_rm"
         else:
-            pass
-
-        # Do job in background
-        proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
+            name = outname if outname is not None else self.obj_name + "_ncc"
 
-        def initialize(geo_obj, app_obj):
+        # ##########################################################################################
+        # Initializes the new geometry object ######################################################
+        # ##########################################################################################
+        def gen_clear_area(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry), \
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
+            if order == 'fwd':
+                sorted_tools.sort(reverse=False)
+            elif order == 'rev':
+                sorted_tools.sort(reverse=True)
+            else:
+                pass
+
             cleared_geo = []
             # Already cleared area
             cleared = MultiPolygon()
@@ -1133,15 +1289,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                         for p in area.geoms:
                             try:
-                                if pol_method == 'standard':
+                                if ncc_method == 'standard':
                                     cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"],
-                                                            overlap=over, contour=contour, connect=connect)
-                                elif pol_method == 'seed':
+                                                            overlap=overlap, contour=contour, connect=connect)
+                                elif ncc_method == 'seed':
                                     cp = self.clear_polygon2(p, tool, self.app.defaults["gerber_circle_steps"],
-                                                             overlap=over, contour=contour, connect=connect)
+                                                             overlap=overlap, contour=contour, connect=connect)
                                 else:
                                     cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"],
-                                                             overlap=over, contour=contour, connect=connect)
+                                                             overlap=overlap, contour=contour, connect=connect)
                                 if cp:
                                     cleared_geo += list(cp.get_objects())
                             except Exception as e:
@@ -1152,7 +1308,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
                             # Overall cleared area
-                            cleared = empty.buffer(-offset * (1 + over)).buffer(-tool / 1.999999).buffer(
+                            cleared = empty.buffer(-offset * (1 + overlap)).buffer(-tool / 1.999999).buffer(
                                 tool / 1.999999)
 
                             # clean-up cleared geo
@@ -1160,7 +1316,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             # find the tooluid associated with the current tool_dia so we know where to add the tool
                             # solid_geometry
-                            for k, v in self.ncc_tools.items():
+                            for k, v in tools_storage.items():
                                 if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
                                     current_uid = int(k)
 
@@ -1169,57 +1325,53 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     v['solid_geometry'] = deepcopy(cleared_geo)
                                     v['data']['name'] = name
                                     break
-                            geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+                            geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
                         else:
                             log.debug("There are no geometries in the cleared polygon.")
 
+            # delete tools with empty geometry
+            keys_to_delete = []
+            # look for keys in the tools_storage dict that have 'solid_geometry' values empty
+            for uid in tools_storage:
+                # if the solid_geometry (type=list) is empty
+                if not tools_storage[uid]['solid_geometry']:
+                    keys_to_delete.append(uid)
+
+            # actual delete of keys from the tools_storage dict
+            for k in keys_to_delete:
+                tools_storage.pop(k, None)
+
             geo_obj.options["cnctooldia"] = str(tool)
             geo_obj.multigeo = True
-
-        def job_thread(app_obj):
-            try:
-                app_obj.new_object("geometry", name, initialize)
-            except Exception as e:
-                proc.done()
-                self.app.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s') % str(e))
+            geo_obj.tools.clear()
+            geo_obj.tools = dict(tools_storage)
+
+            # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
+            has_solid_geo = 0
+            for tooluid in geo_obj.tools:
+                if geo_obj.tools[tooluid]['solid_geometry']:
+                    has_solid_geo += 1
+            if has_solid_geo == 0:
+                self.app.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
+                                       "Usually it means that the tool diameter is too big for the painted geometry.\n"
+                                       "Change the painting parameters and try again."))
                 return
-            proc.done()
-
-            if app_obj.poly_not_cleared is False:
-                self.app.inform.emit(_('[success] NCC Tool finished.'))
-            else:
-                self.app.inform.emit(_('[WARNING_NOTCL] NCC Tool finished but some PCB features could not be cleared. '
-                                     'Check the result.'))
-            # reset the variable for next use
-            app_obj.poly_not_cleared = False
-
-            # focus on Selected Tab
-            self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
-
-        # Promise object with the new name
-        self.app.collection.promise(name)
 
-        # Background
-        self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
-
-    # clear copper with 'rest-machining' algorithm
-    def clear_non_copper_rest(self, empty, over, pol_method, outname=None, connect=True, contour=True):
-
-        name = outname if outname is not None else self.obj_name + "_ncc_rm"
-
-        # Sort tools in descending order
-        sorted_tools = []
-        for k, v in self.ncc_tools.items():
-            sorted_tools.append(float('%.4f' % float(v['tooldia'])))
-        sorted_tools.sort(reverse=True)
+            # Experimental...
+            # print("Indexing...", end=' ')
+            # geo_obj.make_index()
 
-        # Do job in background
-        proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
+            self.app.inform.emit(_("[success] Non-Copper clear all done."))
 
-        def initialize_rm(geo_obj, app_obj):
+        # ###########################################################################################
+        # Initializes the new geometry object for the case of the rest-machining ####################
+        # ###########################################################################################
+        def gen_clear_area_rest(geo_obj, app_obj):
             assert isinstance(geo_obj, FlatCAMGeometry), \
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
+            sorted_tools.sort(reverse=True)
+
             cleared_geo = []
             cleared_by_last_tool = []
             rest_geo = []
@@ -1262,17 +1414,17 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                         for p in area.geoms:
                             try:
-                                if pol_method == 'standard':
+                                if ncc_method == 'standard':
                                     cp = self.clear_polygon(p, tool_used, self.app.defaults["gerber_circle_steps"],
-                                                            overlap=over, contour=contour, connect=connect)
-                                elif pol_method == 'seed':
+                                                            overlap=overlap, contour=contour, connect=connect)
+                                elif ncc_method == 'seed':
                                     cp = self.clear_polygon2(p, tool_used,
                                                              self.app.defaults["gerber_circle_steps"],
-                                                             overlap=over, contour=contour, connect=connect)
+                                                             overlap=overlap, contour=contour, connect=connect)
                                 else:
                                     cp = self.clear_polygon3(p, tool_used,
                                                              self.app.defaults["gerber_circle_steps"],
-                                                             overlap=over, contour=contour, connect=connect)
+                                                             overlap=overlap, contour=contour, connect=connect)
                                 cleared_geo.append(list(cp.get_objects()))
                             except:
                                 log.warning("Polygon can't be cleared.")
@@ -1298,7 +1450,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
                             # find the tooluid associated with the current tool_dia so we know
                             # where to add the tool solid_geometry
-                            for k, v in self.ncc_tools.items():
+                            for k, v in tools_storage.items():
                                 if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
                                     current_uid = int(k)
 
@@ -1309,7 +1461,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     cleared_area[:] = []
                                     break
 
-                            geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+                            geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
                         else:
                             log.debug("There are no geometries in the cleared polygon.")
 
@@ -1326,26 +1478,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 app_obj.poly_not_cleared = False
                 return "fail"
 
+        # ###########################################################################################
+        # Create the Job function and send it to the worker to be processed in another thread #######
+        # ###########################################################################################
         def job_thread(app_obj):
             try:
-                app_obj.new_object("geometry", name, initialize_rm)
+                if rest_machining_choice is True:
+                    app_obj.new_object("geometry", name, gen_clear_area_rest)
+                else:
+                    app_obj.new_object("geometry", name, gen_clear_area)
             except Exception as e:
                 proc.done()
-                app_obj.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s') % str(e))
+                traceback.print_stack()
                 return
-
-            if app_obj.poly_not_cleared is True:
-                app_obj.inform.emit('[success] NCC Tool finished.')
-                # focus on Selected Tab
-                app_obj.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
-            else:
-                app_obj.inform.emit(_('[ERROR_NOTCL] NCC Tool finished but could not clear the object '
-                                     'with current settings.'))
-                # focus on Project Tab
-                app_obj.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
             proc.done()
-            # reset the variable for next use
-            app_obj.poly_not_cleared = False
+            # focus on Selected Tab
+            self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
 
         # Promise object with the new name
         self.app.collection.promise(name)
@@ -1353,6 +1501,360 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
+    # def on_ncc(self):
+    #
+    #     # Prepare non-copper polygons
+    #     if self.reference_radio.get_value() == 'area':
+    #         geo_n = self.sel_rect
+    #
+    #         geo_buff_list = []
+    #         for poly in geo_n:
+    #             geo_buff_list.append(poly.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre))
+    #         bounding_box = cascaded_union(geo_buff_list)
+    #     else:
+    #         geo_n = self.bound_obj.solid_geometry
+    #
+    #         try:
+    #             if isinstance(geo_n, MultiPolygon):
+    #                 env_obj = geo_n.convex_hull
+    #             elif (isinstance(geo_n, MultiPolygon) and len(geo_n) == 1) or \
+    #                     (isinstance(geo_n, list) and len(geo_n) == 1) and isinstance(geo_n[0], Polygon):
+    #                 env_obj = cascaded_union(geo_n)
+    #             else:
+    #                 env_obj = cascaded_union(geo_n)
+    #                 env_obj = env_obj.convex_hull
+    #             bounding_box = env_obj.buffer(distance=margin, join_style=base.JOIN_STYLE.mitre)
+    #         except Exception as e:
+    #             log.debug("NonCopperClear.on_ncc() --> %s" % str(e))
+    #             self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
+    #             return
+    #
+    #     # calculate the empty area by subtracting the solid_geometry from the object bounding box geometry
+    #     if isinstance(self.ncc_obj, FlatCAMGerber):
+    #         if self.ncc_choice_offset_cb.isChecked():
+    #             self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+    #             offseted_geo = self.ncc_obj.solid_geometry.buffer(distance=ncc_offset_value)
+    #             self.app.inform.emit(_("[success] Buffering finished ..."))
+    #             empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
+    #         else:
+    #             empty = self.get_ncc_empty_area(target=self.ncc_obj.solid_geometry, boundary=bounding_box)
+    #     elif isinstance(self.ncc_obj, FlatCAMGeometry):
+    #         sol_geo = cascaded_union(self.ncc_obj.solid_geometry)
+    #         if self.ncc_choice_offset_cb.isChecked():
+    #             self.app.inform.emit(_("[WARNING_NOTCL] Buffering ..."))
+    #             offseted_geo = sol_geo.buffer(distance=ncc_offset_value)
+    #             self.app.inform.emit(_("[success] Buffering finished ..."))
+    #             empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
+    #         else:
+    #             empty = self.get_ncc_empty_area(target=sol_geo, boundary=bounding_box)
+    #     else:
+    #         self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
+    #         return
+    #
+    #     if type(empty) is Polygon:
+    #         empty = MultiPolygon([empty])
+    #
+    #     if empty.is_empty:
+    #         self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
+    #         return
+    #
+    #     # clear non copper using standard algorithm
+    #     if clearing_method is False:
+    #         self.clear_non_copper(
+    #             empty=empty,
+    #             over=over,
+    #             pol_method=pol_method,
+    #             connect=connect,
+    #             contour=contour
+    #         )
+    #     # clear non copper using rest machining algorithm
+    #     else:
+    #         self.clear_non_copper_rest(
+    #             empty=empty,
+    #             over=over,
+    #             pol_method=pol_method,
+    #             connect=connect,
+    #             contour=contour
+    #         )
+    #
+    # def clear_non_copper(self, empty, over, pol_method, outname=None, connect=True, contour=True):
+    #
+    #     name = outname if outname else self.obj_name + "_ncc"
+    #
+    #     # Sort tools in descending order
+    #     sorted_tools = []
+    #     for k, v in self.ncc_tools.items():
+    #         sorted_tools.append(float('%.4f' % float(v['tooldia'])))
+    #
+    #     order = self.ncc_order_radio.get_value()
+    #     if order == 'fwd':
+    #         sorted_tools.sort(reverse=False)
+    #     elif order == 'rev':
+    #         sorted_tools.sort(reverse=True)
+    #     else:
+    #         pass
+    #
+    #     # Do job in background
+    #     proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
+    #
+    #     def initialize(geo_obj, app_obj):
+    #         assert isinstance(geo_obj, FlatCAMGeometry), \
+    #             "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+    #
+    #         cleared_geo = []
+    #         # Already cleared area
+    #         cleared = MultiPolygon()
+    #
+    #         # flag for polygons not cleared
+    #         app_obj.poly_not_cleared = False
+    #
+    #         # Generate area for each tool
+    #         offset = sum(sorted_tools)
+    #         current_uid = int(1)
+    #         tool = eval(self.app.defaults["tools_ncctools"])[0]
+    #
+    #         for tool in sorted_tools:
+    #             self.app.inform.emit(_('[success] Non-Copper Clearing with ToolDia = %s started.') % str(tool))
+    #             cleared_geo[:] = []
+    #
+    #             # Get remaining tools offset
+    #             offset -= (tool - 1e-12)
+    #
+    #             # Area to clear
+    #             area = empty.buffer(-offset)
+    #             try:
+    #                 area = area.difference(cleared)
+    #             except Exception as e:
+    #                 continue
+    #
+    #             # Transform area to MultiPolygon
+    #             if type(area) is Polygon:
+    #                 area = MultiPolygon([area])
+    #
+    #             if area.geoms:
+    #                 if len(area.geoms) > 0:
+    #                     for p in area.geoms:
+    #                         try:
+    #                             if pol_method == 'standard':
+    #                                 cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"],
+    #                                                         overlap=over, contour=contour, connect=connect)
+    #                             elif pol_method == 'seed':
+    #                                 cp = self.clear_polygon2(p, tool, self.app.defaults["gerber_circle_steps"],
+    #                                                          overlap=over, contour=contour, connect=connect)
+    #                             else:
+    #                                 cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"],
+    #                                                          overlap=over, contour=contour, connect=connect)
+    #                             if cp:
+    #                                 cleared_geo += list(cp.get_objects())
+    #                         except Exception as e:
+    #                             log.warning("Polygon can not be cleared. %s" % str(e))
+    #                             app_obj.poly_not_cleared = True
+    #                             continue
+    #
+    #                     # check if there is a geometry at all in the cleared geometry
+    #                     if cleared_geo:
+    #                         # Overall cleared area
+    #                         cleared = empty.buffer(-offset * (1 + over)).buffer(-tool / 1.999999).buffer(
+    #                             tool / 1.999999)
+    #
+    #                         # clean-up cleared geo
+    #                         cleared = cleared.buffer(0)
+    #
+    #                         # find the tooluid associated with the current tool_dia so we know where to add the tool
+    #                         # solid_geometry
+    #                         for k, v in self.ncc_tools.items():
+    #                             if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
+    #                                 current_uid = int(k)
+    #
+    #                                 # add the solid_geometry to the current too in self.paint_tools dictionary
+    #                                 # and then reset the temporary list that stored that solid_geometry
+    #                                 v['solid_geometry'] = deepcopy(cleared_geo)
+    #                                 v['data']['name'] = name
+    #                                 break
+    #                         geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+    #                     else:
+    #                         log.debug("There are no geometries in the cleared polygon.")
+    #
+    #         geo_obj.options["cnctooldia"] = str(tool)
+    #         geo_obj.multigeo = True
+    #
+    #     def job_thread(app_obj):
+    #         try:
+    #             app_obj.new_object("geometry", name, initialize)
+    #         except Exception as e:
+    #             proc.done()
+    #             self.app.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper() --> %s') % str(e))
+    #             return
+    #         proc.done()
+    #
+    #         if app_obj.poly_not_cleared is False:
+    #             self.app.inform.emit(_('[success] NCC Tool finished.'))
+    #         else:
+    #             self.app.inform.emit(_('[WARNING_NOTCL] NCC Tool finished but some PCB features could not be cleared. '
+    #                                  'Check the result.'))
+    #         # reset the variable for next use
+    #         app_obj.poly_not_cleared = False
+    #
+    #         # focus on Selected Tab
+    #         self.app.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+    #
+    #     # Promise object with the new name
+    #     self.app.collection.promise(name)
+    #
+    #     # Background
+    #     self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+    #
+    # # clear copper with 'rest-machining' algorithm
+    # def clear_non_copper_rest(self, empty, over, pol_method, outname=None, connect=True, contour=True):
+    #
+    #     name = outname if outname is not None else self.obj_name + "_ncc_rm"
+    #
+    #     # Sort tools in descending order
+    #     sorted_tools = []
+    #     for k, v in self.ncc_tools.items():
+    #         sorted_tools.append(float('%.4f' % float(v['tooldia'])))
+    #     sorted_tools.sort(reverse=True)
+    #
+    #     # Do job in background
+    #     proc = self.app.proc_container.new(_("Clearing Non-Copper areas."))
+    #
+    #     def initialize_rm(geo_obj, app_obj):
+    #         assert isinstance(geo_obj, FlatCAMGeometry), \
+    #             "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+    #
+    #         cleared_geo = []
+    #         cleared_by_last_tool = []
+    #         rest_geo = []
+    #         current_uid = 1
+    #         tool = eval(self.app.defaults["tools_ncctools"])[0]
+    #
+    #         # repurposed flag for final object, geo_obj. True if it has any solid_geometry, False if not.
+    #         app_obj.poly_not_cleared = True
+    #
+    #         area = empty.buffer(0)
+    #         # Generate area for each tool
+    #         while sorted_tools:
+    #             tool = sorted_tools.pop(0)
+    #             self.app.inform.emit(_('[success] Non-Copper Rest Clearing with ToolDia = %s started.') % str(tool))
+    #
+    #             tool_used = tool - 1e-12
+    #             cleared_geo[:] = []
+    #
+    #             # Area to clear
+    #             for poly in cleared_by_last_tool:
+    #                 try:
+    #                     area = area.difference(poly)
+    #                 except Exception as e:
+    #                     pass
+    #             cleared_by_last_tool[:] = []
+    #
+    #             # Transform area to MultiPolygon
+    #             if type(area) is Polygon:
+    #                 area = MultiPolygon([area])
+    #
+    #             # add the rest that was not able to be cleared previously; area is a MultyPolygon
+    #             # and rest_geo it's a list
+    #             allparts = [p.buffer(0) for p in area.geoms]
+    #             allparts += deepcopy(rest_geo)
+    #             rest_geo[:] = []
+    #             area = MultiPolygon(deepcopy(allparts))
+    #             allparts[:] = []
+    #
+    #             if area.geoms:
+    #                 if len(area.geoms) > 0:
+    #                     for p in area.geoms:
+    #                         try:
+    #                             if pol_method == 'standard':
+    #                                 cp = self.clear_polygon(p, tool_used, self.app.defaults["gerber_circle_steps"],
+    #                                                         overlap=over, contour=contour, connect=connect)
+    #                             elif pol_method == 'seed':
+    #                                 cp = self.clear_polygon2(p, tool_used,
+    #                                                          self.app.defaults["gerber_circle_steps"],
+    #                                                          overlap=over, contour=contour, connect=connect)
+    #                             else:
+    #                                 cp = self.clear_polygon3(p, tool_used,
+    #                                                          self.app.defaults["gerber_circle_steps"],
+    #                                                          overlap=over, contour=contour, connect=connect)
+    #                             cleared_geo.append(list(cp.get_objects()))
+    #                         except:
+    #                             log.warning("Polygon can't be cleared.")
+    #                             # this polygon should be added to a list and then try clear it with a smaller tool
+    #                             rest_geo.append(p)
+    #
+    #                     # check if there is a geometry at all in the cleared geometry
+    #                     if cleared_geo:
+    #                         # Overall cleared area
+    #                         cleared_area = list(self.flatten_list(cleared_geo))
+    #
+    #                         # cleared = MultiPolygon([p.buffer(tool_used / 2).buffer(-tool_used / 2)
+    #                         #                         for p in cleared_area])
+    #
+    #                         # here we store the poly's already processed in the original geometry by the current tool
+    #                         # into cleared_by_last_tool list
+    #                         # this will be sustracted from the original geometry_to_be_cleared and make data for
+    #                         # the next tool
+    #                         buffer_value = tool_used / 2
+    #                         for p in cleared_area:
+    #                             poly = p.buffer(buffer_value)
+    #                             cleared_by_last_tool.append(poly)
+    #
+    #                         # find the tooluid associated with the current tool_dia so we know
+    #                         # where to add the tool solid_geometry
+    #                         for k, v in self.ncc_tools.items():
+    #                             if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
+    #                                 current_uid = int(k)
+    #
+    #                                 # add the solid_geometry to the current too in self.paint_tools dictionary
+    #                                 # and then reset the temporary list that stored that solid_geometry
+    #                                 v['solid_geometry'] = deepcopy(cleared_area)
+    #                                 v['data']['name'] = name
+    #                                 cleared_area[:] = []
+    #                                 break
+    #
+    #                         geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+    #                     else:
+    #                         log.debug("There are no geometries in the cleared polygon.")
+    #
+    #         geo_obj.multigeo = True
+    #         geo_obj.options["cnctooldia"] = str(tool)
+    #
+    #         # check to see if geo_obj.tools is empty
+    #         # it will be updated only if there is a solid_geometry for tools
+    #         if geo_obj.tools:
+    #             return
+    #         else:
+    #             # I will use this variable for this purpose although it was meant for something else
+    #             # signal that we have no geo in the object therefore don't create it
+    #             app_obj.poly_not_cleared = False
+    #             return "fail"
+    #
+    #     def job_thread(app_obj):
+    #         try:
+    #             app_obj.new_object("geometry", name, initialize_rm)
+    #         except Exception as e:
+    #             proc.done()
+    #             app_obj.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s') % str(e))
+    #             return
+    #
+    #         if app_obj.poly_not_cleared is True:
+    #             app_obj.inform.emit('[success] NCC Tool finished.')
+    #             # focus on Selected Tab
+    #             app_obj.ui.notebook.setCurrentWidget(self.app.ui.selected_tab)
+    #         else:
+    #             app_obj.inform.emit(_('[ERROR_NOTCL] NCC Tool finished but could not clear the object '
+    #                                  'with current settings.'))
+    #             # focus on Project Tab
+    #             app_obj.ui.notebook.setCurrentWidget(self.app.ui.project_tab)
+    #         proc.done()
+    #         # reset the variable for next use
+    #         app_obj.poly_not_cleared = False
+    #
+    #     # Promise object with the new name
+    #     self.app.collection.promise(name)
+    #
+    #     # Background
+    #     self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
+
     @staticmethod
     def get_ncc_empty_area(target, boundary=None):
         """

+ 416 - 179
flatcamTools/ToolPaint.py

@@ -232,10 +232,10 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Method
         methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
         methodlabel.setToolTip(
-            _("Algorithm for non-copper clearing:<BR>"
-              "<B>Standard</B>: Fixed step inwards.<BR>"
-              "<B>Seed-based</B>: Outwards from seed.<BR>"
-              "<B>Line-based</B>: Parallel lines.")
+            _("Algorithm for painting:\n"
+              "- Standard: Fixed step inwards.\n"
+              "- Seed-based: Outwards from seed.\n"
+              "- Line-based: Parallel lines.")
         )
         grid3.addWidget(methodlabel, 3, 0)
         self.paintmethod_combo = RadioSet([
@@ -281,12 +281,12 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Polygon selection
         selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
         selectlabel.setToolTip(
-            _("How to select the polygons to paint.<BR>"
-              "Options:<BR>"
-              "- <B>Single Polygons</B>: left mouse click on the polygon to be painted.<BR>"
-              "- <B>Area Selection</B>: left mouse click to start selection of the area to be painted.<BR>"
-              "- <B>All Polygons</B>: paint all polygons.<BR>"
-              "- <B>Reference Object</B>: paint an area described by an external reference object.")
+            _("How to select Polygons to be painted.\n\n"
+              "- 'Area Selection' - left mouse click to start selection of the area to be painted.\n"
+              "Keeping a modifier key pressed (CTRL or SHIFT) will allow to add multiple areas.\n"
+              "- 'All Polygons' - the Paint will start after click.\n"
+              "- 'Reference Object' -  will do non copper clearing within the area\n"
+              "specified by another object.")
         )
         grid3.addWidget(selectlabel, 7, 0)
         # grid3 = QtWidgets.QGridLayout()
@@ -473,14 +473,14 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.rest_cb.set_value(False)
             self.rest_cb.setDisabled(True)
             # delete all tools except first row / tool for single polygon painting
-            list_to_del = list(range(1, self.tools_table.rowCount()))
-            if list_to_del:
-                self.on_tool_delete(rows_to_delete=list_to_del)
-            # disable addTool and delTool
-            self.addtool_entry.setDisabled(True)
-            self.addtool_btn.setDisabled(True)
-            self.deltool_btn.setDisabled(True)
-            self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
+            # list_to_del = list(range(1, self.tools_table.rowCount()))
+            # if list_to_del:
+            #     self.on_tool_delete(rows_to_delete=list_to_del)
+            # # disable addTool and delTool
+            # self.addtool_entry.setDisabled(True)
+            # self.addtool_btn.setDisabled(True)
+            # self.deltool_btn.setDisabled(True)
+            # self.tools_table.setContextMenuPolicy(Qt.NoContextMenu)
         if self.selectmethod_combo.get_value() == 'area':
             # disable rest-machining for single polygon painting
             self.rest_cb.set_value(False)
@@ -894,7 +894,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         # init values for the next usage
         self.reset_usage()
 
-        self.app.report_usage(_("geometry_on_paint_button"))
+        self.app.report_usage(_("on_paint_button_click"))
         # self.app.call_source = 'paint'
 
         try:
@@ -941,8 +941,28 @@ class ToolPaint(FlatCAMTool, Gerber):
 
         o_name = '%s_multitool_paint' % self.obj_name
 
+        # use the selected tools in the tool table; get diameters
+        tooldia_list = list()
+        if self.tools_table.selectedItems():
+            for x in self.tools_table.selectedItems():
+                try:
+                    tooldia = float(self.tools_table.item(x.row(), 1).text())
+                except ValueError:
+                    # try to convert comma to decimal point. if it's still not working error message and return
+                    try:
+                        tooldia = float(self.tools_table.item(x.row(), 1).text().replace(',', '.'))
+                    except ValueError:
+                        self.app.inform.emit(_("[ERROR_NOTCL] Wrong Tool Dia value format entered, "
+                                               "use a number."))
+                        continue
+                tooldia_list.append(tooldia)
+        else:
+            self.app.inform.emit(_("[ERROR_NOTCL] No selected tools in Tool Table."))
+            return
+
         if select_method == "all":
             self.paint_poly_all(self.paint_obj,
+                                tooldia=tooldia_list,
                                 outname=o_name,
                                 overlap=overlap,
                                 connect=connect,
@@ -952,7 +972,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
 
             # use the first tool in the tool table; get the diameter
-            tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
+            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
 
             # To be called after clicking on the plot.
             def doit(event):
@@ -961,18 +981,20 @@ class ToolPaint(FlatCAMTool, Gerber):
                     self.app.inform.emit(_("Painting polygon..."))
                     self.app.plotcanvas.vis_disconnect('mouse_press', doit)
 
-                    pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                    pos = self.app.plotcanvas.translate_coords(event.pos)
                     if self.app.grid_status() == True:
                         pos = self.app.geo_editor.snap(pos[0], pos[1])
 
                     self.paint_poly(self.paint_obj,
                                     inside_pt=[pos[0], pos[1]],
-                                    tooldia=tooldia,
+                                    tooldia=tooldia_list,
                                     overlap=overlap,
                                     connect=connect,
                                     contour=contour)
                     self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
+                    self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_over_plot)
 
+            self.app.plotcanvas.vis_disconnect('mouse_release', self.app.on_mouse_click_release_over_plot)
             self.app.plotcanvas.vis_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
             self.app.plotcanvas.vis_connect('mouse_press', doit)
 
@@ -980,7 +1002,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.app.inform.emit(_("[WARNING_NOTCL] Click the start point of the paint area."))
 
             # use the first tool in the tool table; get the diameter
-            tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
+            # tooldia = float('%.4f' % float(self.tools_table.item(0, 1).text()))
 
             # To be called after clicking on the plot.
             def on_mouse_release(event):
@@ -990,14 +1012,14 @@ class ToolPaint(FlatCAMTool, Gerber):
                         self.first_click = True
                         self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
 
-                        self.cursor_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        self.cursor_pos = self.app.plotcanvas.translate_coords(event.pos)
                         if self.app.grid_status() == True:
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                     else:
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.delete_selection_shape()
 
-                        curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                        curr_pos = self.app.plotcanvas.translate_coords(event.pos)
                         if self.app.grid_status() == True:
                             curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
 
@@ -1024,6 +1046,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                         self.sel_rect = cascaded_union(self.sel_rect)
                         self.paint_poly_area(obj=self.paint_obj,
+                                             tooldia=tooldia_list,
                                              sel_obj= self.sel_rect,
                                              outname=o_name,
                                              overlap=overlap,
@@ -1047,6 +1070,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
                     self.sel_rect = cascaded_union(self.sel_rect)
                     self.paint_poly_area(obj=self.paint_obj,
+                                         tooldia=tooldia_list,
                                          sel_obj=self.sel_rect,
                                          outname=o_name,
                                          overlap=overlap,
@@ -1055,7 +1079,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             # called on mouse move
             def on_mouse_move(event):
-                curr_pos = self.app.plotcanvas.vispy_canvas.translate_coords(event.pos)
+                curr_pos = self.app.plotcanvas.translate_coords(event.pos)
                 self.app.app_cursor.enabled = False
 
                 if event.button == 2:
@@ -1093,32 +1117,27 @@ class ToolPaint(FlatCAMTool, Gerber):
                 self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
                 return "Could not retrieve object: %s" % self.obj_name
 
-            geo = self.bound_obj.solid_geometry
-            try:
-                if isinstance(geo, MultiPolygon):
-                    env_obj = geo.convex_hull
-                elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
-                    (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
-                    env_obj = cascaded_union(self.bound_obj.solid_geometry)
-                else:
-                    env_obj = cascaded_union(self.bound_obj.solid_geometry)
-                    env_obj = env_obj.convex_hull
-                sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
-            except Exception as e:
-                log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
-                self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
-                return
-
-            self.paint_poly_area(obj=self.paint_obj,
-                                 sel_obj=sel_rect,
-                                 outname=o_name,
-                                 overlap=overlap,
-                                 connect=connect,
-                                 contour=contour)
+            self.paint_poly_ref(obj=self.paint_obj,
+                                sel_obj=self.bound_obj,
+                                tooldia=tooldia_list,
+                                overlap=overlap,
+                                outname=o_name,
+                                connect=connect,
+                                contour=contour)
 
-    def paint_poly(self, obj, inside_pt, tooldia, overlap, outname=None, connect=True, contour=True):
+    def paint_poly(self, obj,
+                   inside_pt=None,
+                   tooldia=None,
+                   overlap=None,
+                   order=None,
+                   margin=None,
+                   method=None,
+                   outname=None,
+                   connect=None,
+                   contour=None,
+                   tools_storage=None):
         """
-        Paints a polygon selected by clicking on its interior.
+        Paints a polygon selected by clicking on its interior or by having a point coordinates given
 
         Note:
             * The margin is taken directly from the form.
@@ -1126,27 +1145,35 @@ class ToolPaint(FlatCAMTool, Gerber):
         :param inside_pt: [x, y]
         :param tooldia: Diameter of the painting tool
         :param overlap: Overlap of the tool between passes.
+        :param order: if the tools are ordered and how
+        :param margin: a border around painting area
         :param outname: Name of the resulting Geometry Object.
         :param connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
+        :param method: choice out of 'seed', 'normal', 'lines'
+        :param tools_storage: whether to use the current tools_storage self.paints_tools or a different one.
+        Usage of the different one is related to when this function is called from a TcL command.
         :return: None
         """
 
         # Which polygon.
         # poly = find_polygon(self.solid_geometry, inside_pt)
         poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
-        paint_method = self.paintmethod_combo.get_value()
+        paint_method = method if method is None else self.paintmethod_combo.get_value()
 
-        try:
-            paint_margin = float(self.paintmargin_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
+        if margin is not None:
+            paint_margin = margin
+        else:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
-                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                     "use a number."))
-                return
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                         "use a number."))
+                    return
 
         # No polygon?
         if poly is None:
@@ -1156,41 +1183,72 @@ class ToolPaint(FlatCAMTool, Gerber):
 
         proc = self.app.proc_container.new(_("Painting polygon."))
 
-        name = outname if outname else self.obj_name + "_paint"
+        name = outname if outname is not None else self.obj_name + "_paint"
+
+        over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
+        conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
+        cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
+        order = order if order is not None else self.order_radio.get_value()
+
+        sorted_tools = []
+        if tooldia is not None:
+            try:
+                sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+            except AttributeError:
+                if not isinstance(tooldia, list):
+                    sorted_tools = [float(tooldia)]
+                else:
+                    sorted_tools = tooldia
+        else:
+            for row in range(self.tools_table.rowCount()):
+                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
+
+        if tools_storage is not None:
+            tools_storage = tools_storage
+        else:
+            tools_storage = self.paint_tools
 
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry), \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+            # assert isinstance(geo_obj, FlatCAMGeometry), \
+            #     "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
             # assert isinstance(app_obj, App)
 
-            def paint_p(polyg):
+            tool_dia = None
+            if order == 'fwd':
+                sorted_tools.sort(reverse=False)
+            elif order == 'rev':
+                sorted_tools.sort(reverse=True)
+            else:
+                pass
+
+            def paint_p(polyg, tooldia):
                 if paint_method == "seed":
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon2(polyg,
                                                 tooldia=tooldia,
                                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                overlap=overlap,
-                                                contour=contour,
-                                                connect=connect)
+                                                overlap=over,
+                                                contour=cont,
+                                                connect=conn)
 
                 elif paint_method == "lines":
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon3(polyg,
                                                 tooldia=tooldia,
                                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                overlap=overlap,
-                                                contour=contour,
-                                                connect=connect)
+                                                overlap=over,
+                                                contour=cont,
+                                                connect=conn)
 
                 else:
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon(polyg,
                                                tooldia=tooldia,
                                                steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                               overlap=overlap,
-                                               contour=contour,
-                                               connect=connect)
+                                               overlap=over,
+                                               contour=cont,
+                                               connect=conn)
 
                 if cpoly is not None:
                     geo_obj.solid_geometry += list(cpoly.get_objects())
@@ -1199,8 +1257,6 @@ class ToolPaint(FlatCAMTool, Gerber):
                     self.app.inform.emit(_('[ERROR_NOTCL] Geometry could not be painted completely'))
                     return None
 
-            geo_obj.solid_geometry = []
-
             try:
                 a, b, c, d = poly.bounds
                 geo_obj.options['xmin'] = a
@@ -1211,39 +1267,78 @@ class ToolPaint(FlatCAMTool, Gerber):
                 log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
                 return
 
-            try:
-                poly_buf = poly.buffer(-paint_margin)
-                if isinstance(poly_buf, MultiPolygon):
-                    cp = []
-                    for pp in poly_buf:
-                        cp.append(paint_p(pp))
-                else:
-                    cp = paint_p(poly_buf)
-            except Exception as e:
-                log.debug("Could not Paint the polygons. %s" % str(e))
-                self.app.inform.emit(
-                    _("[ERROR] Could not do Paint. Try a different combination of parameters. "
-                      "Or a different strategy of paint\n%s") % str(e))
-                return
+            total_geometry = []
+            current_uid = int(1)
 
-            if cp is not None:
-                if isinstance(cp, list):
-                    for x in cp:
-                        geo_obj.solid_geometry += list(x.get_objects())
-                else:
-                    geo_obj.solid_geometry = list(cp.get_objects())
+            geo_obj.solid_geometry = []
+
+            for tool_dia in sorted_tools:
+                # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
+                for k, v in tools_storage.items():
+                    if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
+                        current_uid = int(k)
+                        break
+
+                try:
+                    poly_buf = poly.buffer(-paint_margin)
+                    if isinstance(poly_buf, MultiPolygon):
+                        cp = []
+                        for pp in poly_buf:
+                            cp.append(paint_p(pp, tooldia=tool_dia))
+                    else:
+                        cp = paint_p(poly_buf, tooldia=tool_dia)
+
+                    if cp is not None:
+                        if isinstance(cp, list):
+                            for x in cp:
+                                total_geometry += list(x.get_objects())
+                        else:
+                            total_geometry = list(cp.get_objects())
+                except Exception as e:
+                    log.debug("Could not Paint the polygons. %s" % str(e))
+                    self.app.inform.emit(
+                        _("[ERROR] Could not do Paint. Try a different combination of parameters. "
+                          "Or a different strategy of paint\n%s") % str(e))
+                    return
+
+                # add the solid_geometry to the current too in self.paint_tools (tools_storage)
+                # dictionary and then reset the temporary list that stored that solid_geometry
+                tools_storage[current_uid]['solid_geometry'] = deepcopy(total_geometry)
+
+                tools_storage[current_uid]['data']['name'] = name
+                total_geometry[:] = []
+
+            # delete tools with empty geometry
+            keys_to_delete = []
+            # look for keys in the tools_storage dict that have 'solid_geometry' values empty
+            for uid in tools_storage:
+                # if the solid_geometry (type=list) is empty
+                if not tools_storage[uid]['solid_geometry']:
+                    keys_to_delete.append(uid)
 
-            geo_obj.options["cnctooldia"] = str(tooldia)
+            # actual delete of keys from the tools_storage dict
+            for k in keys_to_delete:
+                tools_storage.pop(k, None)
+
+            geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
-            geo_obj.multigeo = False
+            geo_obj.multigeo = True
             geo_obj.multitool = True
+            geo_obj.tools.clear()
+            geo_obj.tools = dict(tools_storage)
 
-            current_uid = int(self.tools_table.item(0, 3).text())
-            for k, v in self.paint_tools.items():
-                if k == current_uid:
-                    v['data']['name'] = name
+            # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
+            has_solid_geo = 0
+            for tooluid in geo_obj.tools:
+                if geo_obj.tools[tooluid]['solid_geometry']:
+                    has_solid_geo += 1
+            if has_solid_geo == 0:
+                self.app.inform.emit(_("[ERROR] There is no Painting Geometry in the file.\n"
+                                       "Usually it means that the tool diameter is too big for the painted geometry.\n"
+                                       "Change the painting parameters and try again."))
+                return
 
-            geo_obj.tools = dict(self.paint_tools)
+            self.app.inform.emit(_("[success] Paint Single Done."))
 
             # Experimental...
             # print("Indexing...", end=' ')
@@ -1278,36 +1373,73 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
-    def paint_poly_all(self, obj, overlap, outname=None, connect=True, contour=True):
+    def paint_poly_all(self, obj,
+                       tooldia=None,
+                       overlap=None,
+                       order=None,
+                       margin=None,
+                       method=None,
+                       outname=None,
+                       connect=None,
+                       contour=None,
+                       tools_storage=None):
         """
         Paints all polygons in this object.
 
         :param obj: painted object
-        :param overlap:
-        :param outname:
+        :param tooldia: a tuple or single element made out of diameters of the tools to be used
+        :param overlap: value by which the paths will overlap
+        :param order: if the tools are ordered and how
+        :param margin: a border around painting area
+        :param outname: name of the resulting object
         :param connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
+        :param method: choice out of 'seed', 'normal', 'lines'
+        :param tools_storage: whether to use the current tools_storage self.paints_tools or a different one.
+        Usage of the different one is related to when this function is called from a TcL command.
         :return:
         """
-        paint_method = self.paintmethod_combo.get_value()
+        paint_method = method if method is None else self.paintmethod_combo.get_value()
 
-        try:
-            paint_margin = float(self.paintmargin_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
+        if margin is not None:
+            paint_margin = margin
+        else:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
-                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                     "use a number."))
-                return
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                         "use a number."))
+                    return
 
         proc = self.app.proc_container.new(_("Painting polygon..."))
-        name = outname if outname else self.obj_name + "_paint"
-        over = overlap
-        conn = connect
-        cont = contour
+        name = outname if outname is not None else self.obj_name + "_paint"
+
+        over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
+        conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
+        cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
+        order = order if order is not None else self.order_radio.get_value()
+
+        sorted_tools = []
+        if tooldia is not None:
+            try:
+                sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+            except AttributeError:
+                if not isinstance(tooldia, list):
+                    sorted_tools = [float(tooldia)]
+                else:
+                    sorted_tools = tooldia
+        else:
+            for row in range(self.tools_table.rowCount()):
+                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
 
+        if tools_storage is not None:
+            tools_storage = tools_storage
+        else:
+            tools_storage = self.paint_tools
         # This is a recursive generator of individual Polygons.
         # Note: Double check correct implementation. Might exit
         #       early if it finds something that is not a Polygon?
@@ -1355,15 +1487,10 @@ class ToolPaint(FlatCAMTool, Gerber):
 
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry), \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
-            tool_dia = None
-
-            sorted_tools = []
-            for row in range(self.tools_table.rowCount()):
-                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
+            # assert isinstance(geo_obj, FlatCAMGeometry), \
+            #     "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
-            order = self.order_radio.get_value()
+            tool_dia = None
             if order == 'fwd':
                 sorted_tools.sort(reverse=False)
             elif order == 'rev':
@@ -1383,10 +1510,12 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             total_geometry = []
             current_uid = int(1)
+
             geo_obj.solid_geometry = []
             for tool_dia in sorted_tools:
+
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
-                for k, v in self.paint_tools.items():
+                for k, v in tools_storage.items():
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         break
@@ -1434,19 +1563,31 @@ class ToolPaint(FlatCAMTool, Gerber):
                               "Or a different Method of paint\n%s") % str(e))
                         return
 
-                # add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
-                # temporary list that stored that solid_geometry
-                self.paint_tools[current_uid]['solid_geometry'] = deepcopy(total_geometry)
+                # add the solid_geometry to the current too in self.paint_tools (tools_storage)
+                # dictionary and then reset the temporary list that stored that solid_geometry
+                tools_storage[current_uid]['solid_geometry'] = deepcopy(total_geometry)
 
-                self.paint_tools[current_uid]['data']['name'] = name
+                tools_storage[current_uid]['data']['name'] = name
                 total_geometry[:] = []
 
+            # delete tools with empty geometry
+            keys_to_delete = []
+            # look for keys in the tools_storage dict that have 'solid_geometry' values empty
+            for uid in tools_storage:
+                # if the solid_geometry (type=list) is empty
+                if not tools_storage[uid]['solid_geometry']:
+                    keys_to_delete.append(uid)
+
+            # actual delete of keys from the tools_storage dict
+            for k in keys_to_delete:
+                tools_storage.pop(k, None)
+
             geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
-            geo_obj.tools = dict(self.paint_tools)
+            geo_obj.tools = dict(tools_storage)
 
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
@@ -1471,9 +1612,6 @@ class ToolPaint(FlatCAMTool, Gerber):
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
             tool_dia = None
-            sorted_tools = []
-            for row in range(self.tools_table.rowCount()):
-                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
             sorted_tools.sort(reverse=True)
 
             cleared_geo = []
@@ -1526,16 +1664,16 @@ class ToolPaint(FlatCAMTool, Gerber):
                         return
 
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
-                for k, v in self.paint_tools.items():
+                for k, v in tools_storage.items():
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         break
 
-                # add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
-                # temporary list that stored that solid_geometry
-                self.paint_tools[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
+                # add the solid_geometry to the current too in self.paint_tools (or tools_storage) dictionary and
+                # then reset the temporary list that stored that solid_geometry
+                tools_storage[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
 
-                self.paint_tools[current_uid]['data']['name'] = name
+                tools_storage[current_uid]['data']['name'] = name
                 cleared_geo[:] = []
 
             geo_obj.options["cnctooldia"] = str(tool_dia)
@@ -1543,7 +1681,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
-            geo_obj.tools = dict(self.paint_tools)
+            geo_obj.tools = dict(tools_storage)
 
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
@@ -1584,36 +1722,74 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
-    def paint_poly_area(self, obj, sel_obj, overlap, outname=None, connect=True, contour=True):
+    def paint_poly_area(self, obj, sel_obj,
+                        tooldia=None,
+                        overlap=None,
+                        order=None,
+                        margin=None,
+                        method=None,
+                        outname=None,
+                        connect=None,
+                        contour=None,
+                        tools_storage=None):
         """
         Paints all polygons in this object that are within the sel_obj object
 
         :param obj: painted object
         :param sel_obj: paint only what is inside this object bounds
-        :param overlap:
-        :param outname:
+        :param tooldia: a tuple or single element made out of diameters of the tools to be used
+        :param overlap: value by which the paths will overlap
+        :param order: if the tools are ordered and how
+        :param margin: a border around painting area
+        :param outname: name of the resulting object
         :param connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
+        :param method: choice out of 'seed', 'normal', 'lines'
+        :param tools_storage: whether to use the current tools_storage self.paints_tools or a different one.
+        Usage of the different one is related to when this function is called from a TcL command.
         :return:
         """
-        paint_method = self.paintmethod_combo.get_value()
+        paint_method = method if method is None else self.paintmethod_combo.get_value()
 
-        try:
-            paint_margin = float(self.paintmargin_entry.get_value())
-        except ValueError:
-            # try to convert comma to decimal point. if it's still not working error message and return
+        if margin is not None:
+            paint_margin = margin
+        else:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
-                self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
-                                     "use a number."))
-                return
+                # try to convert comma to decimal point. if it's still not working error message and return
+                try:
+                    paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                except ValueError:
+                    self.app.inform.emit(_("[ERROR_NOTCL] Wrong value format entered, "
+                                         "use a number."))
+                    return
 
         proc = self.app.proc_container.new(_("Painting polygon..."))
-        name = outname if outname else self.obj_name + "_paint"
-        over = overlap
-        conn = connect
-        cont = contour
+        name = outname if outname is not None else self.obj_name + "_paint"
+
+        over = overlap if overlap is not None else float(self.app.defaults["tools_paintoverlap"])
+        conn = connect if connect is not None else self.app.defaults["tools_pathconnect"]
+        cont = contour if contour is not None else self.app.defaults["tools_paintcontour"]
+        order = order if order is not None else self.order_radio.get_value()
+
+        sorted_tools = []
+        if tooldia is not None:
+            try:
+                sorted_tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+            except AttributeError:
+                if not isinstance(tooldia, list):
+                    sorted_tools = [float(tooldia)]
+                else:
+                    sorted_tools = tooldia
+        else:
+            for row in range(self.tools_table.rowCount()):
+                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
+
+        if tools_storage is not None:
+            tools_storage = tools_storage
+        else:
+            tools_storage = self.paint_tools
 
         def recurse(geometry, reset=True):
             """
@@ -1648,15 +1824,9 @@ class ToolPaint(FlatCAMTool, Gerber):
 
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
-            assert isinstance(geo_obj, FlatCAMGeometry), \
-                "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
+            # assert isinstance(geo_obj, FlatCAMGeometry), \
+            #     "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
             tool_dia = None
-
-            sorted_tools = []
-            for row in range(self.tools_table.rowCount()):
-                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
-
-            order = self.order_radio.get_value()
             if order == 'fwd':
                 sorted_tools.sort(reverse=False)
             elif order == 'rev':
@@ -1664,6 +1834,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             else:
                 pass
 
+            # this is were heavy lifting is done and creating the geometry to be painted
             geo_to_paint = []
             if not isinstance(obj.solid_geometry, list):
                 target_geo = [obj.solid_geometry]
@@ -1686,10 +1857,12 @@ class ToolPaint(FlatCAMTool, Gerber):
 
             total_geometry = []
             current_uid = int(1)
+
             geo_obj.solid_geometry = []
             for tool_dia in sorted_tools:
+
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
-                for k, v in self.paint_tools.items():
+                for k, v in tools_storage.items():
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         break
@@ -1737,19 +1910,31 @@ class ToolPaint(FlatCAMTool, Gerber):
                               "Or a different Method of paint\n%s") % str(e))
                         return
 
-                # add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
-                # temporary list that stored that solid_geometry
-                self.paint_tools[current_uid]['solid_geometry'] = deepcopy(total_geometry)
+                # add the solid_geometry to the current too in self.paint_tools (tools_storage)
+                # dictionary and then reset the temporary list that stored that solid_geometry
+                tools_storage[current_uid]['solid_geometry'] = deepcopy(total_geometry)
 
-                self.paint_tools[current_uid]['data']['name'] = name
+                tools_storage[current_uid]['data']['name'] = name
                 total_geometry[:] = []
 
+            # delete tools with empty geometry
+            keys_to_delete = []
+            # look for keys in the tools_storage dict that have 'solid_geometry' values empty
+            for uid in tools_storage:
+                # if the solid_geometry (type=list) is empty
+                if not tools_storage[uid]['solid_geometry']:
+                    keys_to_delete.append(uid)
+
+            # actual delete of keys from the tools_storage dict
+            for k in keys_to_delete:
+                tools_storage.pop(k, None)
+
             geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
-            geo_obj.tools = dict(self.paint_tools)
+            geo_obj.tools = dict(tools_storage)
 
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
@@ -1766,7 +1951,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             # print("Indexing...", end=' ')
             # geo_obj.make_index()
 
-            self.app.inform.emit(_("[success] Paint All Done."))
+            self.app.inform.emit(_("[success] Paint Area Done."))
 
         # Initializes the new geometry object
         def gen_paintarea_rest_machining(geo_obj, app_obj):
@@ -1774,9 +1959,6 @@ class ToolPaint(FlatCAMTool, Gerber):
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
             tool_dia = None
-            sorted_tools = []
-            for row in range(self.tools_table.rowCount()):
-                sorted_tools.append(float(self.tools_table.item(row, 1).text()))
             sorted_tools.sort(reverse=True)
 
             cleared_geo = []
@@ -1829,16 +2011,16 @@ class ToolPaint(FlatCAMTool, Gerber):
                         return
 
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
-                for k, v in self.paint_tools.items():
+                for k, v in tools_storage.items():
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         break
 
-                # add the solid_geometry to the current too in self.paint_tools dictionary and then reset the
-                # temporary list that stored that solid_geometry
-                self.paint_tools[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
+                # add the solid_geometry to the current too in self.paint_tools (or tools_storage) dictionary and
+                # then reset the temporary list that stored that solid_geometry
+                tools_storage[current_uid]['solid_geometry'] = deepcopy(cleared_geo)
 
-                self.paint_tools[current_uid]['data']['name'] = name
+                tools_storage[current_uid]['data']['name'] = name
                 cleared_geo[:] = []
 
             geo_obj.options["cnctooldia"] = str(tool_dia)
@@ -1887,6 +2069,61 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
 
+    def paint_poly_ref(self, obj, sel_obj,
+                       tooldia=None,
+                       overlap=None,
+                       order=None,
+                       margin=None,
+                       method=None,
+                       outname=None,
+                       connect=None,
+                       contour=None,
+                       tools_storage=None):
+        """
+        Paints all polygons in this object that are within the sel_obj object
+
+        :param obj: painted object
+        :param sel_obj: paint only what is inside this object bounds
+        :param tooldia: a tuple or single element made out of diameters of the tools to be used
+        :param overlap: value by which the paths will overlap
+        :param order: if the tools are ordered and how
+        :param margin: a border around painting area
+        :param outname: name of the resulting object
+        :param connect: Connect lines to avoid tool lifts.
+        :param contour: Paint around the edges.
+        :param method: choice out of 'seed', 'normal', 'lines'
+        :param tools_storage: whether to use the current tools_storage self.paints_tools or a different one.
+        Usage of the different one is related to when this function is called from a TcL command.
+        :return:
+        """
+        geo = sel_obj.solid_geometry
+        try:
+            if isinstance(geo, MultiPolygon):
+                env_obj = geo.convex_hull
+            elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
+                    (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
+                env_obj = cascaded_union(self.bound_obj.solid_geometry)
+            else:
+                env_obj = cascaded_union(self.bound_obj.solid_geometry)
+                env_obj = env_obj.convex_hull
+            sel_rect = env_obj.buffer(distance=0.0000001, join_style=base.JOIN_STYLE.mitre)
+        except Exception as e:
+            log.debug("ToolPaint.on_paint_button_click() --> %s" % str(e))
+            self.app.inform.emit(_("[ERROR_NOTCL] No object available."))
+            return
+
+        self.paint_poly_area(obj=obj,
+                             sel_obj=sel_rect,
+                             tooldia=tooldia,
+                             overlap=overlap,
+                             order=order,
+                             margin=margin,
+                             method=method,
+                             outname=outname,
+                             connect=connect,
+                             contour=contour,
+                             tools_storage=tools_storage)
+
     @staticmethod
     def paint_bounds(geometry):
         def bounds_rec(o):

+ 2 - 2
flatcamTools/ToolProperties.py

@@ -175,10 +175,10 @@ class Properties(FlatCAMTool):
             env_obj = geo.convex_hull
         elif (isinstance(geo, MultiPolygon) and len(geo) == 1) or \
                 (isinstance(geo, list) and len(geo) == 1) and isinstance(geo[0], Polygon):
-            env_obj = cascaded_union(self.bound_obj.solid_geometry)
+            env_obj = cascaded_union(obj.solid_geometry)
             env_obj = env_obj.convex_hull
         else:
-            env_obj = cascaded_union(self.bound_obj.solid_geometry)
+            env_obj = cascaded_union(obj.solid_geometry)
             env_obj = env_obj.convex_hull
 
         area_chull = env_obj.area

+ 2 - 3
tclCommands/TclCommand.py

@@ -202,7 +202,6 @@ class TclCommand(object):
         """
 
         arguments, options = self.parse_arguments(args)
-
         named_args = {}
         unnamed_args = []
 
@@ -274,7 +273,7 @@ class TclCommand(object):
         :return: None, output text or exception
         """
 
-        #self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
+        # self.worker_task.emit({'fcn': self.exec_command_test, 'params': [text, False]})
 
         try:
             self.log.debug("TCL command '%s' executed." % str(self.__class__))
@@ -283,7 +282,7 @@ class TclCommand(object):
             return self.execute(args, unnamed_args)
         except Exception as unknown:
             error_info = sys.exc_info()
-            self.log.error("TCL command '%s' failed." % str(self))
+            self.log.error("TCL command '%s' failed. Error text: %s" % (str(self), str(unknown)))
             self.app.display_tcl_error(unknown, error_info)
             self.raise_tcl_unknown_error(unknown)
 

+ 95 - 0
tclCommands/TclCommandBbox.py

@@ -0,0 +1,95 @@
+from ObjectCollection import *
+from tclCommands.TclCommand import TclCommand
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class TclCommandBbox(TclCommand):
+    """
+    Tcl shell command to follow a Gerber file
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['bounding_box', 'bbox']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str)
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+        ('outname', str),
+        ('margin', float),
+        ('rounded', bool)
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Creates a Geometry object that surrounds the object.",
+        'args': collections.OrderedDict([
+            ('name', 'Object name for which to create bounding box. String'),
+            ('outname', 'Name of the resulting Geometry object. String.'),
+            ('margin', "Distance of the edges of the box to the nearest polygon."
+                       "Float number."),
+            ('rounded', "If the bounding box is to have rounded corners their radius is equal to the margin. "
+                        "True or False.")
+        ]),
+        'examples': ['bbox name -outname name_bbox']
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+
+        if 'outname' not in args:
+            args['outname'] = name + "_bbox"
+
+        obj = self.app.collection.get_by_name(name)
+        if obj is None:
+            self.raise_tcl_error("%s: %s" % (_("Object not found"), name))
+
+        if not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMGeometry):
+            self.raise_tcl_error('%s %s: %s.' % (
+                _("Expected FlatCAMGerber or FlatCAMGeometry, got"), name, type(obj)))
+
+        if 'margin' not in args:
+            args['margin'] = float(self.app.defaults["gerber_bboxmargin"])
+        margin = args['margin']
+
+        if 'rounded' not in args:
+            args['rounded'] = self.app.defaults["gerber_bboxrounded"]
+        rounded = args['rounded']
+
+        del args['name']
+
+        try:
+            def geo_init(geo_obj, app_obj):
+                assert isinstance(geo_obj, FlatCAMGeometry)
+
+                # Bounding box with rounded corners
+                geo = cascaded_union(obj.solid_geometry)
+                bounding_box = geo.envelope.buffer(float(margin))
+                if not rounded:  # Remove rounded corners
+                    bounding_box = bounding_box.envelope
+                geo_obj.solid_geometry = bounding_box
+
+            self.app.new_object("geometry", args['outname'], geo_init)
+        except Exception as e:
+            return "Operation failed: %s" % str(e)

+ 266 - 0
tclCommands/TclCommandCopperClear.py

@@ -0,0 +1,266 @@
+from ObjectCollection import *
+from tclCommands.TclCommand import TclCommand
+
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class TclCommandCopperClear(TclCommand):
+    """
+    Clear the non-copper areas.
+    """
+
+    # Array of all command aliases, to be able use old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['ncc_clear', 'ncc']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str),
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+        ('tooldia', str),
+        ('overlap', float),
+        ('order', str),
+        ('margin', float),
+        ('method', str),
+        ('connect', bool),
+        ('contour', bool),
+        ('has_offset', bool),
+        ('offset', float),
+        ('rest', bool),
+        ('all', int),
+        ('ref', int),
+        ('box', str),
+        ('outname', str),
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Clear excess copper in polygons. Basically it's a negative Paint.",
+        'args': collections.OrderedDict([
+            ('name', 'Name of the source Geometry object. String.'),
+            ('tooldia', 'Diameter of the tool to be used. Can be a comma separated list of diameters. No space is '
+                        'allowed between tool diameters. E.g: correct: 0.5,1 / incorrect: 0.5, 1'),
+            ('overlap', 'Fraction of the tool diameter to overlap cuts. Float number.'),
+            ('margin', 'Bounding box margin. Float number.'),
+            ('order', 'Can have the values: "no", "fwd" and "rev". String.'
+                      'It is useful when there are multiple tools in tooldia parameter.'
+                      '"no" -> the order used is the one provided.'
+                      '"fwd" -> tools are ordered from smallest to biggest.'
+                      '"rev" -> tools are ordered from biggest to smallest.'),
+            ('method', 'Algorithm for copper clearing. Can be: "standard", "seed" or "lines".'),
+            ('connect', 'Draw lines to minimize tool lifts. True or False'),
+            ('contour', 'Cut around the perimeter of the painting. True or False'),
+            ('rest', 'Use rest-machining. True or False'),
+            ('has_offset', 'The offset will used only if this is set True or present in args. True or False.'),
+            ('offset', 'The copper clearing will finish to a distance from copper features. Float number.'),
+            ('all', 'Will copper clear the whole object. 1 = enabled, anything else = disabled'),
+            ('ref', 'Will clear of extra copper all polygons within a specified object with the name in "box" '
+                    'parameter. 1 = enabled, anything else = disabled'),
+            ('box', 'Name of the object to be used as reference. Required when selecting "ref" = 1. String.'),
+            ('outname', 'Name of the resulting Geometry object. String.'),
+        ]),
+        'examples': []
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+
+        if 'tooldia' in args:
+            tooldia = str(args['tooldia'])
+        else:
+            tooldia = self.app.defaults["tools_ncctools"]
+
+        if 'overlap' in args:
+            overlap = float(args['overlap'])
+        else:
+            overlap = float(self.app.defaults["tools_nccoverlap"])
+
+        if 'order' in args:
+            order = args['order']
+        else:
+            order = str(self.app.defaults["tools_nccorder"])
+
+        if 'margin' in args:
+            margin = float(args['margin'])
+        else:
+            margin = float(self.app.defaults["tools_nccmargin"])
+
+        if 'method' in args:
+            method = args['method']
+        else:
+            method = str(self.app.defaults["tools_nccmethod"])
+
+        if 'connect' in args:
+            connect = eval(str(args['connect']).capitalize())
+        else:
+            connect = eval(str(self.app.defaults["tools_nccconnect"]))
+
+        if 'contour' in args:
+            contour = eval(str(args['contour']).capitalize())
+        else:
+            contour = eval(str(self.app.defaults["tools_ncccontour"]))
+
+        offset = 0.0
+        if 'has_offset' in args:
+            has_offset = args['has_offset']
+            if args['has_offset'] is True:
+                if 'offset' in args:
+                    offset = float(args['margin'])
+                else:
+                    # 'offset' has to be in args if 'has_offset' is and it is set True
+                    self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name))
+        else:
+            has_offset = False
+
+        try:
+            tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+        except AttributeError:
+            tools = [float(tooldia)]
+        # store here the default data for Geometry Data
+        default_data = {}
+        default_data.update({
+            "name": '_paint',
+            "plot": self.app.defaults["geometry_plot"],
+            "cutz": self.app.defaults["geometry_cutz"],
+            "vtipdia": 0.1,
+            "vtipangle": 30,
+            "travelz": self.app.defaults["geometry_travelz"],
+            "feedrate": self.app.defaults["geometry_feedrate"],
+            "feedrate_z": self.app.defaults["geometry_feedrate_z"],
+            "feedrate_rapid": self.app.defaults["geometry_feedrate_rapid"],
+            "dwell": self.app.defaults["geometry_dwell"],
+            "dwelltime": self.app.defaults["geometry_dwelltime"],
+            "multidepth": self.app.defaults["geometry_multidepth"],
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "depthperpass": self.app.defaults["geometry_depthperpass"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "toolchange": self.app.defaults["geometry_toolchange"],
+            "toolchangez": self.app.defaults["geometry_toolchangez"],
+            "endz": self.app.defaults["geometry_endz"],
+            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
+            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
+            "startz": self.app.defaults["geometry_startz"],
+
+            "tooldia": self.app.defaults["tools_painttooldia"],
+            "paintmargin": self.app.defaults["tools_paintmargin"],
+            "paintmethod": self.app.defaults["tools_paintmethod"],
+            "selectmethod": self.app.defaults["tools_selectmethod"],
+            "pathconnect": self.app.defaults["tools_pathconnect"],
+            "paintcontour": self.app.defaults["tools_paintcontour"],
+            "paintoverlap": self.app.defaults["tools_paintoverlap"]
+        })
+        ncc_tools = dict()
+
+        tooluid = 0
+        for tool in tools:
+            tooluid += 1
+            ncc_tools.update({
+                int(tooluid): {
+                    'tooldia': float('%.4f' % tool),
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': 'Iso',
+                    'tool_type': 'C1',
+                    'data': dict(default_data),
+                    'solid_geometry': []
+                }
+            })
+
+        if 'rest' in args:
+            rest = eval(str(args['rest']).capitalize())
+        else:
+            rest = eval(str(self.app.defaults["tools_nccrest"]))
+
+        if 'outname' in args:
+            outname = args['outname']
+        else:
+            if rest is True:
+                outname = name + "_ncc"
+            else:
+                outname = name + "_ncc_rm"
+
+        # Get source object.
+        try:
+            obj = self.app.collection.get_by_name(str(name))
+        except Exception as e:
+            log.debug("TclCommandCopperClear.execute() --> %s" % str(e))
+            self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name))
+            return "Could not retrieve object: %s" % name
+
+        if obj is None:
+            return "Object not found: %s" % name
+
+        # Non-Copper clear all polygons in the non-copper clear object
+        if 'all' in args and args['all'] == 1:
+            self.app.ncclear_tool.clear_copper(ncc_obj=obj,
+                                               select_method='itself',
+                                               tooldia=tooldia,
+                                               overlap=overlap,
+                                               order=order,
+                                               margin=margin,
+                                               has_offset=has_offset,
+                                               offset=offset,
+                                               method=method,
+                                               outname=outname,
+                                               connect=connect,
+                                               contour=contour,
+                                               rest=rest,
+                                               tools_storage=ncc_tools)
+            return
+
+        # Non-Copper clear all polygons found within the box object from the the non_copper cleared object
+        elif 'ref' in args and args['ref'] == 1:
+            if 'box' not in args:
+                self.raise_tcl_error('%s' % _("Expected -box <value>."))
+            else:
+                box_name = args['box']
+
+                # Get box source object.
+                try:
+                    box_obj = self.app.collection.get_by_name(str(box_name))
+                except Exception as e:
+                    log.debug("TclCommandCopperClear.execute() --> %s" % str(e))
+                    self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name))
+                    return "Could not retrieve object: %s" % name
+
+                self.app.ncclear_tool.clear_copper(ncc_obj=obj,
+                                                   sel_obj=box_obj,
+                                                   select_method='box',
+                                                   tooldia=tooldia,
+                                                   overlap=overlap,
+                                                   order=order,
+                                                   margin=margin,
+                                                   has_offset=has_offset,
+                                                   offset=offset,
+                                                   method=method,
+                                                   outname=outname,
+                                                   connect=connect,
+                                                   contour=contour,
+                                                   rest=rest,
+                                                   tools_storage=ncc_tools)
+            return
+        else:
+            self.raise_tcl_error("%s:" % _("None of the following args: 'ref', 'all' were found or none was set to 1.\n"
+                                           "Copper clearing failed."))
+            return "None of the following args: 'ref', 'all' were found or none was set to 1.\n" \
+                   "Copper clearing failed."

+ 97 - 0
tclCommands/TclCommandNregions.py

@@ -0,0 +1,97 @@
+from ObjectCollection import *
+from tclCommands.TclCommand import TclCommand
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
+
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class TclCommandNregions(TclCommand):
+    """
+    Tcl shell command to follow a Gerber file
+    """
+
+    # array of all command aliases, to be able use  old names for backward compatibility (add_poly, add_polygon)
+    aliases = ['non_copper_regions', 'ncr']
+
+    # dictionary of types from Tcl command, needs to be ordered
+    arg_names = collections.OrderedDict([
+        ('name', str)
+    ])
+
+    # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
+    option_types = collections.OrderedDict([
+        ('outname', str),
+        ('margin', float),
+        ('rounded', bool)
+    ])
+
+    # array of mandatory options for current Tcl command: required = {'name','outname'}
+    required = ['name']
+
+    # structured help for current command, args needs to be ordered
+    help = {
+        'main': "Creates a geometry object with the non-copper regions.",
+        'args': collections.OrderedDict([
+            ('name', 'Object name for which to create non-copper regions. String'),
+            ('outname', 'Name of the resulting Geometry object. String.'),
+            ('margin', "Specify the edge of the PCB by drawing a box around all objects with this minimum distance. "
+                       "Float number."),
+            ('rounded', "Resulting geometry will have rounded corners. True or False.")
+        ]),
+        'examples': ['ncr name -outname name_ncr']
+    }
+
+    def execute(self, args, unnamed_args):
+        """
+        execute current TCL shell command
+
+        :param args: array of known named arguments and options
+        :param unnamed_args: array of other values which were passed into command
+            without -somename and  we do not have them in known arg_names
+        :return: None or exception
+        """
+
+        name = args['name']
+
+        if 'outname' not in args:
+            args['outname'] = name + "_noncopper"
+
+        obj = self.app.collection.get_by_name(name)
+        if obj is None:
+            self.raise_tcl_error("%s: %s" % (_("Object not found"), name))
+
+        if not isinstance(obj, FlatCAMGerber) and not isinstance(obj, FlatCAMGeometry):
+            self.raise_tcl_error('%s %s: %s.' % (_("Expected FlatCAMGerber or FlatCAMGeometry, got"), name, type(obj)))
+
+        if 'margin' not in args:
+            args['margin'] = float(self.app.defaults["gerber_noncoppermargin"])
+        margin = args['margin']
+
+        if 'rounded' not in args:
+            args['rounded'] = self.app.defaults["gerber_noncopperrounded"]
+        rounded = args['rounded']
+
+        del args['name']
+
+        try:
+            def geo_init(geo_obj, app_obj):
+                assert isinstance(geo_obj, FlatCAMGeometry)
+
+                geo = cascaded_union(obj.solid_geometry)
+                bounding_box = geo.envelope.buffer(float(margin))
+                if not rounded:
+                    bounding_box = bounding_box.envelope
+
+                non_copper = bounding_box.difference(geo)
+                geo_obj.solid_geometry = non_copper
+
+            self.app.new_object("geometry", args['outname'], geo_init)
+        except Exception as e:
+            return "Operation failed: %s" % str(e)
+
+        # in the end toggle the visibility of the origin object so we can see the generated Geometry
+        self.app.collection.get_by_name(name).ui.plot_cb.toggle()

+ 204 - 28
tclCommands/TclCommandPaint.py

@@ -1,8 +1,16 @@
 from ObjectCollection import *
-from tclCommands.TclCommand import TclCommandSignaled
+from tclCommands.TclCommand import TclCommand
 
+import gettext
+import FlatCAMTranslation as fcTranslate
+import builtins
 
-class TclCommandPaint(TclCommandSignaled):
+fcTranslate.apply_language('strings')
+if '_' not in builtins.__dict__:
+    _ = gettext.gettext
+
+
+class TclCommandPaint(TclCommand):
     """
     Paint the interior of polygons
     """
@@ -13,32 +21,54 @@ class TclCommandPaint(TclCommandSignaled):
     # dictionary of types from Tcl command, needs to be ordered
     arg_names = collections.OrderedDict([
         ('name', str),
-        ('tooldia', float),
-        ('overlap', float)
     ])
 
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
     option_types = collections.OrderedDict([
-        ('outname', str),
+        ('tooldia', str),
+        ('overlap', float),
+        ('order', str),
+        ('margin', float),
+        ('method', str),
+        ('connect', bool),
+        ('contour', bool),
+
         ('all', bool),
+        ('single', bool),
+        ('ref', bool),
+        ('box', str),
         ('x', float),
-        ('y', float)
+        ('y', float),
+        ('outname', str),
     ])
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
-    required = ['name', 'tooldia', 'overlap']
+    required = ['name']
 
     # structured help for current command, args needs to be ordered
     help = {
         'main': "Paint polygons",
         'args': collections.OrderedDict([
-            ('name', 'Name of the source Geometry object.'),
-            ('tooldia', 'Diameter of the tool to be used.'),
-            ('overlap', 'Fraction of the tool diameter to overlap cuts.'),
-            ('outname', 'Name of the resulting Geometry object.'),
-            ('all', 'Paint all polygons in the object.'),
-            ('x', 'X value of coordinate for the selection of a single polygon.'),
-            ('y', 'Y value of coordinate for the selection of a single polygon.')
+            ('name', 'Name of the source Geometry object. String.'),
+            ('tooldia', 'Diameter of the tool to be used. Can be a comma separated list of diameters. No space is '
+                        'allowed between tool diameters. E.g: correct: 0.5,1 / incorrect: 0.5, 1'),
+            ('overlap', 'Fraction of the tool diameter to overlap cuts. Float number.'),
+            ('margin', 'Bounding box margin. Float number.'),
+            ('order', 'Can have the values: "no", "fwd" and "rev". String.'
+                      'It is useful when there are multiple tools in tooldia parameter.'
+                      '"no" -> the order used is the one provided.'
+                      '"fwd" -> tools are ordered from smallest to biggest.'
+                      '"rev" -> tools are ordered from biggest to smallest.'),
+            ('method', 'Algorithm for painting. Can be: "standard", "seed" or "lines".'),
+            ('connect', 'Draw lines to minimize tool lifts. True or False'),
+            ('contour', 'Cut around the perimeter of the painting. True or False'),
+            ('all', 'Paint all polygons in the object. True or False'),
+            ('single', 'Paint a single polygon specified by "x" and "y" parameters. True or False'),
+            ('ref', 'Paint all polygons within a specified object with the name in "box" parameter. True or False'),
+            ('box', 'name of the object to be used as paint reference when selecting "ref"" True. String.'),
+            ('x', 'X value of coordinate for the selection of a single polygon. Float number.'),
+            ('y', 'Y value of coordinate for the selection of a single polygon. Float number.'),
+            ('outname', 'Name of the resulting Geometry object. String.'),
         ]),
         'examples': []
     }
@@ -54,31 +84,177 @@ class TclCommandPaint(TclCommandSignaled):
         """
 
         name = args['name']
-        tooldia = args['tooldia']
-        overlap = args['overlap']
+
+        if 'tooldia' in args:
+            tooldia = str(args['tooldia'])
+        else:
+            tooldia = float(self.app.defaults["tools_paintoverlap"])
+
+        if 'overlap' in args:
+            overlap = float(args['overlap'])
+        else:
+            overlap = float(self.app.defaults["tools_paintoverlap"])
+
+        if 'order' in args:
+            order = args['order']
+        else:
+            order = str(self.app.defaults["tools_paintorder"])
+
+        if 'margin' in args:
+            margin = float(args['margin'])
+        else:
+            margin = float(self.app.defaults["tools_paintmargin"])
+
+        if 'method' in args:
+            method = args['method']
+        else:
+            method = str(self.app.defaults["tools_paintmethod"])
+
+        if 'connect' in args:
+            connect = eval(str(args['connect']).capitalize())
+        else:
+            connect = eval(str(self.app.defaults["tools_pathconnect"]))
+
+        if 'contour' in args:
+            contour = eval(str(args['contour']).capitalize())
+        else:
+            contour = eval(str(self.app.defaults["tools_paintcontour"]))
 
         if 'outname' in args:
             outname = args['outname']
         else:
             outname = name + "_paint"
 
-        obj = self.app.collection.get_by_name(name)
-        if obj is None:
-            self.raise_tcl_error("Object not found: %s" % name)
+        # Get source object.
+        try:
+            obj = self.app.collection.get_by_name(str(name))
+        except Exception as e:
+            log.debug("TclCommandPaint.execute() --> %s" % str(e))
+            self.raise_tcl_error("%s: %s" % (_("Could not retrieve object"), name))
+            return "Could not retrieve object: %s" % name
+
+        try:
+            tools = [float(eval(dia)) for dia in tooldia.split(",") if dia != '']
+        except AttributeError:
+            tools = [float(tooldia)]
+        # store here the default data for Geometry Data
+        default_data = {}
+        default_data.update({
+            "name": '_paint',
+            "plot": self.app.defaults["geometry_plot"],
+            "cutz": self.app.defaults["geometry_cutz"],
+            "vtipdia": 0.1,
+            "vtipangle": 30,
+            "travelz": self.app.defaults["geometry_travelz"],
+            "feedrate": self.app.defaults["geometry_feedrate"],
+            "feedrate_z": self.app.defaults["geometry_feedrate_z"],
+            "feedrate_rapid": self.app.defaults["geometry_feedrate_rapid"],
+            "dwell": self.app.defaults["geometry_dwell"],
+            "dwelltime": self.app.defaults["geometry_dwelltime"],
+            "multidepth": self.app.defaults["geometry_multidepth"],
+            "ppname_g": self.app.defaults["geometry_ppname_g"],
+            "depthperpass": self.app.defaults["geometry_depthperpass"],
+            "extracut": self.app.defaults["geometry_extracut"],
+            "toolchange": self.app.defaults["geometry_toolchange"],
+            "toolchangez": self.app.defaults["geometry_toolchangez"],
+            "endz": self.app.defaults["geometry_endz"],
+            "spindlespeed": self.app.defaults["geometry_spindlespeed"],
+            "toolchangexy": self.app.defaults["geometry_toolchangexy"],
+            "startz": self.app.defaults["geometry_startz"],
 
-        if not isinstance(obj, Geometry):
-            self.raise_tcl_error('Expected Geometry, got %s %s.' % (name, type(obj)))
+            "tooldia": self.app.defaults["tools_painttooldia"],
+            "paintmargin": self.app.defaults["tools_paintmargin"],
+            "paintmethod": self.app.defaults["tools_paintmethod"],
+            "selectmethod": self.app.defaults["tools_selectmethod"],
+            "pathconnect": self.app.defaults["tools_pathconnect"],
+            "paintcontour": self.app.defaults["tools_paintcontour"],
+            "paintoverlap": self.app.defaults["tools_paintoverlap"]
+        })
+        paint_tools = dict()
 
-        if 'all' in args and args['all']:
-            obj.paint_poly_all(tooldia, overlap, outname)
+        tooluid = 0
+        for tool in tools:
+            tooluid += 1
+            paint_tools.update({
+                int(tooluid): {
+                    'tooldia': float('%.4f' % tool),
+                    'offset': 'Path',
+                    'offset_value': 0.0,
+                    'type': 'Iso',
+                    'tool_type': 'C1',
+                    'data': dict(default_data),
+                    'solid_geometry': []
+                }
+            })
+
+        if obj is None:
+            return "Object not found: %s" % name
+
+        # Paint all polygons in the painted object
+        if 'all' in args and args['all'] is True:
+            self.app.paint_tool.paint_poly_all(obj=obj,
+                                               tooldia=tooldia,
+                                               overlap=overlap,
+                                               order=order,
+                                               margin=margin,
+                                               method=method,
+                                               outname=outname,
+                                               connect=connect,
+                                               contour=contour,
+                                               tools_storage=paint_tools)
             return
 
-        if 'x' not in args or 'y' not in args:
-            self.raise_tcl_error('Expected -all 1 or -x <value> and -y <value>.')
+        # Paint single polygon in the painted object
+        elif 'single' in args and args['single'] is True:
+            if 'x' not in args or 'y' not in args:
+                self.raise_tcl_error('%s' % _("Expected -x <value> and -y <value>."))
+            else:
+                x = args['x']
+                y = args['y']
 
-        x = args['x']
-        y = args['y']
+                self.app.paint_tool.paint_poly(obj=obj,
+                                               inside_pt=[x, y],
+                                               tooldia=tooldia,
+                                               overlap=overlap,
+                                               order=order,
+                                               margin=margin,
+                                               method=method,
+                                               outname=outname,
+                                               connect=connect,
+                                               contour=contour,
+                                               tools_storage=paint_tools)
+            return
 
-        obj.paint_poly_single_click([x, y], tooldia, overlap, outname)
+        # Paint all polygons found within the box object from the the painted object
+        elif 'ref' in args and args['ref'] is True:
+            if 'box' not in args:
+                self.raise_tcl_error('%s' % _("Expected -box <value>."))
+            else:
+                box_name = args['box']
 
+                # Get box source object.
+                try:
+                    box_obj = self.app.collection.get_by_name(str(box_name))
+                except Exception as e:
+                    log.debug("TclCommandPaint.execute() --> %s" % str(e))
+                    self.raise_tcl_error("%s: %s" % (_("Could not retrieve box object"), name))
+                    return "Could not retrieve object: %s" % name
 
+                self.app.paint_tool.paint_poly_ref(obj=obj,
+                                                   sel_obj=box_obj,
+                                                   tooldia=tooldia,
+                                                   overlap=overlap,
+                                                   order=order,
+                                                   margin=margin,
+                                                   method=method,
+                                                   outname=outname,
+                                                   connect=connect,
+                                                   contour=contour,
+                                                   tools_storage=paint_tools)
+            return
+
+        else:
+            self.raise_tcl_error("%s:" % _("There was none of the following args: 'ref', 'single', 'all'.\n"
+                                           "Paint failed."))
+            return "There was none of the following args: 'ref', 'single', 'all'.\n" \
+                   "Paint failed."

+ 3 - 0
tclCommands/__init__.py

@@ -9,8 +9,10 @@ import tclCommands.TclCommandAddPolyline
 import tclCommands.TclCommandAddRectangle
 import tclCommands.TclCommandAlignDrill
 import tclCommands.TclCommandAlignDrillGrid
+import tclCommands.TclCommandBbox
 import tclCommands.TclCommandClearShell
 import tclCommands.TclCommandCncjob
+import tclCommands.TclCommandCopperClear
 import tclCommands.TclCommandCutout
 import tclCommands.TclCommandDelete
 import tclCommands.TclCommandDrillcncjob
@@ -31,6 +33,7 @@ import tclCommands.TclCommandListSys
 import tclCommands.TclCommandMillHoles
 import tclCommands.TclCommandMirror
 import tclCommands.TclCommandNew
+import tclCommands.TclCommandNregions
 import tclCommands.TclCommandNewGeometry
 import tclCommands.TclCommandOffset
 import tclCommands.TclCommandOpenExcellon