Ver código fonte

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

Beta
Marius Stanciu 6 anos atrás
pai
commit
5b493c2450

+ 222 - 214
FlatCAMApp.py

@@ -98,8 +98,8 @@ class App(QtCore.QObject):
     # ####################################
     # ####################################
     # Version and VERSION DATE ###########
     # Version and VERSION DATE ###########
     # ####################################
     # ####################################
-    version = 8.96
-    version_date = "2019/08/23"
+    version = 8.97
+    version_date = "2019/08/31"
     beta = True
     beta = True
 
 
     # current date now
     # current date now
@@ -1755,212 +1755,219 @@ class App(QtCore.QObject):
         # #######Auto-complete KEYWORDS #############
         # #######Auto-complete KEYWORDS #############
         # ###########################################
         # ###########################################
         self.tcl_commands_list = ['add_circle', 'add_poly', 'add_polygon', 'add_polyline', 'add_rectangle',
         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 = [
         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
         self.myKeywords = self.tcl_commands_list + self.ordinary_keywords + self.tcl_keywords
 
 
         # ###########################################
         # ###########################################
@@ -5256,11 +5263,12 @@ class App(QtCore.QObject):
 
 
         cursor = QtGui.QCursor()
         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]))
         cursor.setPos(canvas_origin.x() + jump_loc[0], (canvas_origin.y() + jump_loc[1]))
         self.inform.emit(_("[success] Done."))
         self.inform.emit(_("[success] Done."))
+        return location
 
 
     def on_copy_object(self):
     def on_copy_object(self):
         self.report_usage("on_copy_object()")
         self.report_usage("on_copy_object()")
@@ -5480,7 +5488,7 @@ class App(QtCore.QObject):
     def on_set_zero_click(self, event):
     def on_set_zero_click(self, event):
         # this function will be available only for mouse left click
         # this function will be available only for mouse left click
         pos = []
         pos = []
-        pos_canvas = self.plotcanvas.vispy_canvas.translate_coords(event.pos)
+        pos_canvas = self.plotcanvas.translate_coords(event.pos)
         if event.button == 1:
         if event.button == 1:
             if self.grid_status() == True:
             if self.grid_status() == True:
                 pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -5835,7 +5843,7 @@ class App(QtCore.QObject):
 
 
         :return: None
         :return: None
         """
         """
-        self.plotcanvas.vispy_canvas.update()           # TODO: Need update canvas?
+        self.plotcanvas.update()           # TODO: Need update canvas?
         self.on_zoom_fit(None)
         self.on_zoom_fit(None)
         self.collection.update_view()
         self.collection.update_view()
         # self.inform.emit(_("Plots updated ..."))
         # self.inform.emit(_("Plots updated ..."))
@@ -6008,11 +6016,11 @@ class App(QtCore.QObject):
         self.pos = []
         self.pos = []
 
 
         # So it can receive key presses
         # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
+        self.plotcanvas.native.setFocus()
         # Set the mouse button for panning
         # 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:
         if self.grid_status() == True:
             self.pos = self.geo_editor.snap(self.pos_canvas[0], self.pos_canvas[1])
             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
         # So it can receive key presses
-        self.plotcanvas.vispy_canvas.native.setFocus()
+        self.plotcanvas.native.setFocus()
         self.pos_jump = event.pos
         self.pos_jump = event.pos
 
 
         self.ui.popMenu.mouse_is_panning = False
         self.ui.popMenu.mouse_is_panning = False
@@ -6071,7 +6079,7 @@ class App(QtCore.QObject):
 
 
         if self.rel_point1 is not None:
         if self.rel_point1 is not None:
             try:  # May fail in case mouse not within axes
             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:
                 if self.grid_status() == True:
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                     self.app_cursor.enabled = True
                     self.app_cursor.enabled = True
@@ -6145,7 +6153,7 @@ class App(QtCore.QObject):
         :return:
         :return:
         """
         """
         pos = 0, 0
         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:
         if self.grid_status() == True:
             pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
             pos = self.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
         else:
@@ -6377,7 +6385,7 @@ class App(QtCore.QObject):
                     # curr_sel_obj.plot(color=self.FC_dark_blue, face_color=self.FC_light_blue)
                     # 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
                     # 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:
         except Exception as e:
             log.error("[ERROR] Something went bad. %s" % str(e))
             log.error("[ERROR] Something went bad. %s" % str(e))
             return
             return
@@ -9064,7 +9072,7 @@ The normal flow when working in FlatCAM is the following:</span></p>
         self.plotcanvas = PlotCanvas(plot_container, self)
         self.plotcanvas = PlotCanvas(plot_container, self)
 
 
         # So it can receive key presses
         # 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_move', self.on_mouse_move_over_plot)
         self.plotcanvas.vis_connect('mouse_press', self.on_mouse_click_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 = self.plotcanvas.new_cursor()
         self.app_cursor.enabled = False
         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):
     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.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.shapes = self.app.plotcanvas.new_shape_group()
 
 
         # self.mark_shapes = self.app.plotcanvas.new_shape_collection(layers=2)
         # 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
 23.08.2019
 
 
 - in Tool Cutout for the manual gaps, right mouse button click will exit from the action of adding gaps
 - 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
         :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:
         if self.app.grid_status() == True:
             self.pos  = self.app.geo_editor.snap(self.pos[0], self.pos[1])
             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
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         :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:
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
@@ -3557,7 +3557,7 @@ class FlatCAMExcEditor(QtCore.QObject):
         :return: None
         :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]
         event.xdata, event.ydata = pos[0], pos[1]
 
 
         self.x = event.xdata
         self.x = event.xdata

+ 27 - 12
flatcamEditors/FlatCAMGeoEditor.py

@@ -3576,7 +3576,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
         :return: None
         :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:
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
             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
         :param event: Event object dispatched by VisPy SceneCavas
         :return: None
         :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]
         event.xdata, event.ydata = pos[0], pos[1]
 
 
         self.x = event.xdata
         self.x = event.xdata
@@ -3704,7 +3704,7 @@ class FlatCAMGeoEditor(QtCore.QObject):
             self.app.selection_type = None
             self.app.selection_type = None
 
 
     def on_geo_click_release(self, event):
     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:
         if self.app.grid_status() == True:
             pos = self.snap(pos_canvas[0], pos_canvas[1])
             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])])
         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()
         self.app.delete_selection_shape()
+
+        sel_objects_list = []
         for obj in self.storage.get_objects():
         for obj in self.storage.get_objects():
             if (sel_type is True and poly_selection.contains(obj.geo)) or (sel_type is False and
             if (sel_type is True and poly_selection.contains(obj.geo)) or (sel_type is False and
                                                                            poly_selection.intersects(obj.geo)):
                                                                            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()
         self.replot()
 
 
     def draw_utility_geometry(self, geo):
     def draw_utility_geometry(self, geo):

+ 3 - 3
flatcamEditors/FlatCAMGrbEditor.py

@@ -4069,7 +4069,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :return: None
         :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:
         if self.app.grid_status() == True:
             self.pos = self.app.geo_editor.snap(self.pos[0], self.pos[1])
             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):
     def on_grb_click_release(self, event):
         self.modifiers = QtWidgets.QApplication.keyboardModifiers()
         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:
         if self.app.grid_status() == True:
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
             pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
         else:
         else:
@@ -4264,7 +4264,7 @@ class FlatCAMGrbEditor(QtCore.QObject):
         :return: None
         :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]
         event.xdata, event.ydata = pos_canvas[0], pos_canvas[1]
 
 
         self.x = event.xdata
         self.x = event.xdata

+ 46 - 39
flatcamGUI/PlotCanvas.py

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

+ 67 - 46
flatcamTools/ToolCutOut.py

@@ -16,7 +16,6 @@ if '_' not in builtins.__dict__:
 class CutOut(FlatCAMTool):
 class CutOut(FlatCAMTool):
 
 
     toolName = _("Cutout PCB")
     toolName = _("Cutout PCB")
-    gapFinished = pyqtSignal()
 
 
     def __init__(self, app):
     def __init__(self, app):
         FlatCAMTool.__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
         # this is the Geometry object generated in this class to be used for adding manual gaps
         self.man_cutout_obj = None
         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
         # Signals
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.ff_cutout_object_btn.clicked.connect(self.on_freeform_cutout)
         self.rect_cutout_object_btn.clicked.connect(self.on_rectangular_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.gaps.set_value(self.app.defaults["tools_gaps_ff"])
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
         self.convex_box.set_value(self.app.defaults['tools_cutout_convexshape'])
 
 
-        self.gapFinished.connect(self.on_gap_finished)
-
     def on_freeform_cutout(self):
     def on_freeform_cutout(self):
 
 
         # def subtract_rectangle(obj_, x0, y0, x1, y1):
         # 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_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('key_press', self.on_key_press)
         self.app.plotcanvas.vis_connect('mouse_move', self.on_mouse_move)
         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):
     def on_manual_cutout(self, click_pos):
         name = self.man_object_combo.currentText()
         name = self.man_object_combo.currentText()
@@ -836,12 +807,6 @@ class CutOut(FlatCAMTool):
 
 
         self.app.should_we_save = True
         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):
     def on_manual_geo(self):
         name = self.obj_combo.currentText()
         name = self.obj_combo.currentText()
 
 
@@ -943,20 +908,65 @@ class CutOut(FlatCAMTool):
         cut_poly = box(xmin, ymin, xmax, ymax)
         cut_poly = box(xmin, ymin, xmax, ymax)
         return cut_poly
         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):
     def on_mouse_move(self, event):
 
 
         self.app.on_mouse_move_over_plot(event=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]
         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:
         try:
             x = float(event.xdata)
             x = float(event.xdata)
             y = float(event.ydata)
             y = float(event.ydata)
         except TypeError:
         except TypeError:
             return
             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 #######
         # ### This section makes the cutting geo to #######
@@ -1044,7 +1054,7 @@ class CutOut(FlatCAMTool):
         if key == QtCore.Qt.Key_Escape or key == 'Escape':
         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('key_press', self.on_key_press)
             self.app.plotcanvas.vis_disconnect('mouse_move', self.on_mouse_move)
             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('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_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_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.clear(update=True)
             self.app.geo_editor.tool_shape.enabled = False
             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):
     def subtract_poly_from_geo(self, solid_geo, x0, y0, x1, y1):
         """
         """
         Subtract polygon made from points from the given object.
         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'
         self.original_call_source = 'app'
 
 
         # VisPy visuals
         # 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)
         self.measure_btn.clicked.connect(self.activate_measure_tool)
 
 
@@ -247,7 +247,7 @@ class Measurement(FlatCAMTool):
         log.debug("Measuring Tool --> mouse click release")
         log.debug("Measuring Tool --> mouse click release")
 
 
         if event.button == 1:
         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 GRID is active we need to get the snapped positions
             if self.app.grid_status() == True:
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 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):
     def on_mouse_move_meas(self, event):
         try:  # May fail in case mouse not within axes
         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:
             if self.app.grid_status() == True:
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 pos = self.app.geo_editor.snap(pos_canvas[0], pos_canvas[1])
                 self.app.app_cursor.enabled = True
                 self.app.app_cursor.enabled = True

+ 4 - 4
flatcamTools/ToolMove.py

@@ -43,7 +43,7 @@ class ToolMove(FlatCAMTool):
         self.old_coords = []
         self.old_coords = []
 
 
         # VisPy visuals
         # 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):
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='M', **kwargs)
@@ -94,7 +94,7 @@ class ToolMove(FlatCAMTool):
 
 
         if event.button == 1:
         if event.button == 1:
             if self.clicked_move == 0:
             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 GRID is active we need to get the snapped positions
                 if self.app.grid_status() == True:
                 if self.app.grid_status() == True:
@@ -111,7 +111,7 @@ class ToolMove(FlatCAMTool):
 
 
             if self.clicked_move == 1:
             if self.clicked_move == 1:
                 try:
                 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
                     # delete the selection bounding box
                     self.delete_shape()
                     self.delete_shape()
@@ -178,7 +178,7 @@ class ToolMove(FlatCAMTool):
             self.clicked_move = 1
             self.clicked_move = 1
 
 
     def on_move(self, event):
     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 GRID is active we need to get the snapped positions
         if self.app.grid_status() == True:
         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 ##########
         # ##### 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 = QtWidgets.QLabel('%s:' % _("Object"))
         self.object_label.setToolTip(_("Object to be cleared of excess copper."))
         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('')
         e_lab_0 = QtWidgets.QLabel('')
         form_layout.addRow(e_lab_0)
         form_layout.addRow(e_lab_0)
@@ -396,8 +396,8 @@ class NonCopperClear(FlatCAMTool, Gerber):
 
 
     def on_type_obj_index_changed(self, index):
     def on_type_obj_index_changed(self, index):
         obj_type = self.type_obj_combo.currentIndex()
         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):
     def install(self, icon=None, separator=None, **kwargs):
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
         FlatCAMTool.install(self, icon, separator, shortcut='ALT+N', **kwargs)
@@ -822,12 +822,70 @@ class NonCopperClear(FlatCAMTool, Gerber):
         self.build_ui()
         self.build_ui()
 
 
     def on_ncc_click(self):
     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()
             self.bound_obj_name = self.object_combo.currentText()
             # Get source object.
             # Get source object.
             try:
             try:
@@ -835,38 +893,37 @@ class NonCopperClear(FlatCAMTool, Gerber):
             except Exception as e:
             except Exception as e:
                 self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
                 self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
                 return "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."))
             self.app.inform.emit(_("[WARNING_NOTCL] Click the start point of the area."))
 
 
             # use the first tool in the tool table; get the diameter
             # 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.
             # To be called after clicking on the plot.
             def on_mouse_release(event):
             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 event.button == 1:
                     if self.first_click is False:
                     if self.first_click is False:
                         self.first_click = True
                         self.first_click = True
                         self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
                         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:
                         if self.app.grid_status() == True:
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                     else:
                     else:
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.delete_selection_shape()
                         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:
                         if self.app.grid_status() == True:
                             curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
                             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
                             self.first_click = False
                             return
                             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_release', on_mouse_release)
                         self.app.plotcanvas.vis_disconnect('mouse_move', on_mouse_move)
                         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_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_move', self.app.on_mouse_move_over_plot)
                         self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_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:
                 elif event.button == 2 and self.first_click is False and self.mouse_is_dragging is False:
                     self.first_click = False
                     self.first_click = False
                     self.app.plotcanvas.vis_disconnect('mouse_release', on_mouse_release)
                     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_move', self.app.on_mouse_move_over_plot)
                     self.app.plotcanvas.vis_connect('mouse_release', self.app.on_mouse_click_release_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
             # called on mouse move
             def on_mouse_move(event):
             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
                 self.app.app_cursor.enabled = False
 
 
                 if event.button == 2:
                 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_release', on_mouse_release)
             self.app.plotcanvas.vis_connect('mouse_move', on_mouse_move)
             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:
             try:
-                margin = float(self.ncc_margin_entry.get_value().replace(',', '.'))
+                ncc_margin = float(self.ncc_margin_entry.get_value())
             except ValueError:
             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"]
         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"]
         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:
         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:
                 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 ..."))
                 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 ..."))
                 self.app.inform.emit(_("[success] Buffering finished ..."))
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
             else:
             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 ..."))
                 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 ..."))
                 self.app.inform.emit(_("[success] Buffering finished ..."))
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
                 empty = self.get_ncc_empty_area(target=offseted_geo, boundary=bounding_box)
             else:
             else:
@@ -1049,56 +1224,37 @@ class NonCopperClear(FlatCAMTool, Gerber):
             self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
             self.inform.emit(_('[ERROR_NOTCL] The selected object is not suitable for copper clearing.'))
             return
             return
 
 
-        if type(empty) is Polygon:
-            empty = MultiPolygon([empty])
-
         if empty.is_empty:
         if empty.is_empty:
             self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
             self.app.inform.emit(_("[ERROR_NOTCL] Could not get the extent of the area to be non copper cleared."))
             return
             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:
         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), \
             assert isinstance(geo_obj, FlatCAMGeometry), \
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
                 "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 = []
             cleared_geo = []
             # Already cleared area
             # Already cleared area
             cleared = MultiPolygon()
             cleared = MultiPolygon()
@@ -1133,15 +1289,15 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                     if len(area.geoms) > 0:
                         for p in area.geoms:
                         for p in area.geoms:
                             try:
                             try:
-                                if pol_method == 'standard':
+                                if ncc_method == 'standard':
                                     cp = self.clear_polygon(p, tool, self.app.defaults["gerber_circle_steps"],
                                     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"],
                                     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:
                                 else:
                                     cp = self.clear_polygon3(p, tool, self.app.defaults["gerber_circle_steps"],
                                     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:
                                 if cp:
                                     cleared_geo += list(cp.get_objects())
                                     cleared_geo += list(cp.get_objects())
                             except Exception as e:
                             except Exception as e:
@@ -1152,7 +1308,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                         # check if there is a geometry at all in the cleared geometry
                         # check if there is a geometry at all in the cleared geometry
                         if cleared_geo:
                         if cleared_geo:
                             # Overall cleared area
                             # 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)
                                 tool / 1.999999)
 
 
                             # clean-up cleared geo
                             # 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
                             # find the tooluid associated with the current tool_dia so we know where to add the tool
                             # solid_geometry
                             # 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):
                                 if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
                                     current_uid = int(k)
                                     current_uid = int(k)
 
 
@@ -1169,57 +1325,53 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     v['solid_geometry'] = deepcopy(cleared_geo)
                                     v['solid_geometry'] = deepcopy(cleared_geo)
                                     v['data']['name'] = name
                                     v['data']['name'] = name
                                     break
                                     break
-                            geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+                            geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
                         else:
                         else:
                             log.debug("There are no geometries in the cleared polygon.")
                             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.options["cnctooldia"] = str(tool)
             geo_obj.multigeo = True
             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
                 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), \
             assert isinstance(geo_obj, FlatCAMGeometry), \
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
 
+            sorted_tools.sort(reverse=True)
+
             cleared_geo = []
             cleared_geo = []
             cleared_by_last_tool = []
             cleared_by_last_tool = []
             rest_geo = []
             rest_geo = []
@@ -1262,17 +1414,17 @@ class NonCopperClear(FlatCAMTool, Gerber):
                     if len(area.geoms) > 0:
                     if len(area.geoms) > 0:
                         for p in area.geoms:
                         for p in area.geoms:
                             try:
                             try:
-                                if pol_method == 'standard':
+                                if ncc_method == 'standard':
                                     cp = self.clear_polygon(p, tool_used, self.app.defaults["gerber_circle_steps"],
                                     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,
                                     cp = self.clear_polygon2(p, tool_used,
                                                              self.app.defaults["gerber_circle_steps"],
                                                              self.app.defaults["gerber_circle_steps"],
-                                                             overlap=over, contour=contour, connect=connect)
+                                                             overlap=overlap, contour=contour, connect=connect)
                                 else:
                                 else:
                                     cp = self.clear_polygon3(p, tool_used,
                                     cp = self.clear_polygon3(p, tool_used,
                                                              self.app.defaults["gerber_circle_steps"],
                                                              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()))
                                 cleared_geo.append(list(cp.get_objects()))
                             except:
                             except:
                                 log.warning("Polygon can't be cleared.")
                                 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
                             # find the tooluid associated with the current tool_dia so we know
                             # where to add the tool solid_geometry
                             # 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):
                                 if float('%.4f' % v['tooldia']) == float('%.4f' % tool):
                                     current_uid = int(k)
                                     current_uid = int(k)
 
 
@@ -1309,7 +1461,7 @@ class NonCopperClear(FlatCAMTool, Gerber):
                                     cleared_area[:] = []
                                     cleared_area[:] = []
                                     break
                                     break
 
 
-                            geo_obj.tools[current_uid] = dict(self.ncc_tools[current_uid])
+                            geo_obj.tools[current_uid] = dict(tools_storage[current_uid])
                         else:
                         else:
                             log.debug("There are no geometries in the cleared polygon.")
                             log.debug("There are no geometries in the cleared polygon.")
 
 
@@ -1326,26 +1478,22 @@ class NonCopperClear(FlatCAMTool, Gerber):
                 app_obj.poly_not_cleared = False
                 app_obj.poly_not_cleared = False
                 return "fail"
                 return "fail"
 
 
+        # ###########################################################################################
+        # Create the Job function and send it to the worker to be processed in another thread #######
+        # ###########################################################################################
         def job_thread(app_obj):
         def job_thread(app_obj):
             try:
             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:
             except Exception as e:
                 proc.done()
                 proc.done()
-                app_obj.inform.emit(_('[ERROR_NOTCL] NCCTool.clear_non_copper_rest() --> %s') % str(e))
+                traceback.print_stack()
                 return
                 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()
             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
         # Promise object with the new name
         self.app.collection.promise(name)
         self.app.collection.promise(name)
@@ -1353,6 +1501,360 @@ class NonCopperClear(FlatCAMTool, Gerber):
         # Background
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         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
     @staticmethod
     def get_ncc_empty_area(target, boundary=None):
     def get_ncc_empty_area(target, boundary=None):
         """
         """

+ 416 - 179
flatcamTools/ToolPaint.py

@@ -232,10 +232,10 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Method
         # Method
         methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
         methodlabel = QtWidgets.QLabel('%s:' % _('Method'))
         methodlabel.setToolTip(
         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)
         grid3.addWidget(methodlabel, 3, 0)
         self.paintmethod_combo = RadioSet([
         self.paintmethod_combo = RadioSet([
@@ -281,12 +281,12 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Polygon selection
         # Polygon selection
         selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
         selectlabel = QtWidgets.QLabel('%s:' % _('Selection'))
         selectlabel.setToolTip(
         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.addWidget(selectlabel, 7, 0)
         # grid3 = QtWidgets.QGridLayout()
         # grid3 = QtWidgets.QGridLayout()
@@ -473,14 +473,14 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.rest_cb.set_value(False)
             self.rest_cb.set_value(False)
             self.rest_cb.setDisabled(True)
             self.rest_cb.setDisabled(True)
             # delete all tools except first row / tool for single polygon painting
             # 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':
         if self.selectmethod_combo.get_value() == 'area':
             # disable rest-machining for single polygon painting
             # disable rest-machining for single polygon painting
             self.rest_cb.set_value(False)
             self.rest_cb.set_value(False)
@@ -894,7 +894,7 @@ class ToolPaint(FlatCAMTool, Gerber):
         # init values for the next usage
         # init values for the next usage
         self.reset_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'
         # self.app.call_source = 'paint'
 
 
         try:
         try:
@@ -941,8 +941,28 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         o_name = '%s_multitool_paint' % self.obj_name
         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":
         if select_method == "all":
             self.paint_poly_all(self.paint_obj,
             self.paint_poly_all(self.paint_obj,
+                                tooldia=tooldia_list,
                                 outname=o_name,
                                 outname=o_name,
                                 overlap=overlap,
                                 overlap=overlap,
                                 connect=connect,
                                 connect=connect,
@@ -952,7 +972,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
             self.app.inform.emit(_("[WARNING_NOTCL] Click inside the desired polygon."))
 
 
             # use the first tool in the tool table; get the diameter
             # 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.
             # To be called after clicking on the plot.
             def doit(event):
             def doit(event):
@@ -961,18 +981,20 @@ class ToolPaint(FlatCAMTool, Gerber):
                     self.app.inform.emit(_("Painting polygon..."))
                     self.app.inform.emit(_("Painting polygon..."))
                     self.app.plotcanvas.vis_disconnect('mouse_press', doit)
                     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:
                     if self.app.grid_status() == True:
                         pos = self.app.geo_editor.snap(pos[0], pos[1])
                         pos = self.app.geo_editor.snap(pos[0], pos[1])
 
 
                     self.paint_poly(self.paint_obj,
                     self.paint_poly(self.paint_obj,
                                     inside_pt=[pos[0], pos[1]],
                                     inside_pt=[pos[0], pos[1]],
-                                    tooldia=tooldia,
+                                    tooldia=tooldia_list,
                                     overlap=overlap,
                                     overlap=overlap,
                                     connect=connect,
                                     connect=connect,
                                     contour=contour)
                                     contour=contour)
                     self.app.plotcanvas.vis_connect('mouse_press', self.app.on_mouse_click_over_plot)
                     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_disconnect('mouse_press', self.app.on_mouse_click_over_plot)
             self.app.plotcanvas.vis_connect('mouse_press', doit)
             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."))
             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
             # 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.
             # To be called after clicking on the plot.
             def on_mouse_release(event):
             def on_mouse_release(event):
@@ -990,14 +1012,14 @@ class ToolPaint(FlatCAMTool, Gerber):
                         self.first_click = True
                         self.first_click = True
                         self.app.inform.emit(_("[WARNING_NOTCL] Click the end point of the paint area."))
                         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:
                         if self.app.grid_status() == True:
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                             self.cursor_pos = self.app.geo_editor.snap(self.cursor_pos[0], self.cursor_pos[1])
                     else:
                     else:
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.inform.emit(_("Zone added. Right click to finish."))
                         self.app.delete_selection_shape()
                         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:
                         if self.app.grid_status() == True:
                             curr_pos = self.app.geo_editor.snap(curr_pos[0], curr_pos[1])
                             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.sel_rect = cascaded_union(self.sel_rect)
                         self.paint_poly_area(obj=self.paint_obj,
                         self.paint_poly_area(obj=self.paint_obj,
+                                             tooldia=tooldia_list,
                                              sel_obj= self.sel_rect,
                                              sel_obj= self.sel_rect,
                                              outname=o_name,
                                              outname=o_name,
                                              overlap=overlap,
                                              overlap=overlap,
@@ -1047,6 +1070,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
                     self.sel_rect = cascaded_union(self.sel_rect)
                     self.sel_rect = cascaded_union(self.sel_rect)
                     self.paint_poly_area(obj=self.paint_obj,
                     self.paint_poly_area(obj=self.paint_obj,
+                                         tooldia=tooldia_list,
                                          sel_obj=self.sel_rect,
                                          sel_obj=self.sel_rect,
                                          outname=o_name,
                                          outname=o_name,
                                          overlap=overlap,
                                          overlap=overlap,
@@ -1055,7 +1079,7 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
             # called on mouse move
             # called on mouse move
             def on_mouse_move(event):
             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
                 self.app.app_cursor.enabled = False
 
 
                 if event.button == 2:
                 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)
                 self.app.inform.emit(_("[ERROR_NOTCL] Could not retrieve object: %s") % self.obj_name)
                 return "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:
         Note:
             * The margin is taken directly from the form.
             * The margin is taken directly from the form.
@@ -1126,27 +1145,35 @@ class ToolPaint(FlatCAMTool, Gerber):
         :param inside_pt: [x, y]
         :param inside_pt: [x, y]
         :param tooldia: Diameter of the painting tool
         :param tooldia: Diameter of the painting tool
         :param overlap: Overlap of the tool between passes.
         :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 outname: Name of the resulting Geometry Object.
         :param connect: Connect lines to avoid tool lifts.
         :param connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
         :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
         :return: None
         """
         """
 
 
         # Which polygon.
         # Which polygon.
         # poly = find_polygon(self.solid_geometry, inside_pt)
         # poly = find_polygon(self.solid_geometry, inside_pt)
         poly = self.find_polygon(point=inside_pt, geoset=obj.solid_geometry)
         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:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
             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?
         # No polygon?
         if poly is None:
         if poly is None:
@@ -1156,41 +1183,72 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         proc = self.app.proc_container.new(_("Painting polygon."))
         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
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
         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)
             # 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":
                 if paint_method == "seed":
                     # Type(cp) == FlatCAMRTreeStorage | None
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon2(polyg,
                     cpoly = self.clear_polygon2(polyg,
                                                 tooldia=tooldia,
                                                 tooldia=tooldia,
                                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
                                                 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":
                 elif paint_method == "lines":
                     # Type(cp) == FlatCAMRTreeStorage | None
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon3(polyg,
                     cpoly = self.clear_polygon3(polyg,
                                                 tooldia=tooldia,
                                                 tooldia=tooldia,
                                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
                                                 steps_per_circle=self.app.defaults["geometry_circle_steps"],
-                                                overlap=overlap,
-                                                contour=contour,
-                                                connect=connect)
+                                                overlap=over,
+                                                contour=cont,
+                                                connect=conn)
 
 
                 else:
                 else:
                     # Type(cp) == FlatCAMRTreeStorage | None
                     # Type(cp) == FlatCAMRTreeStorage | None
                     cpoly = self.clear_polygon(polyg,
                     cpoly = self.clear_polygon(polyg,
                                                tooldia=tooldia,
                                                tooldia=tooldia,
                                                steps_per_circle=self.app.defaults["geometry_circle_steps"],
                                                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:
                 if cpoly is not None:
                     geo_obj.solid_geometry += list(cpoly.get_objects())
                     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'))
                     self.app.inform.emit(_('[ERROR_NOTCL] Geometry could not be painted completely'))
                     return None
                     return None
 
 
-            geo_obj.solid_geometry = []
-
             try:
             try:
                 a, b, c, d = poly.bounds
                 a, b, c, d = poly.bounds
                 geo_obj.options['xmin'] = a
                 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))
                 log.debug("ToolPaint.paint_poly.gen_paintarea() bounds error --> %s" % str(e))
                 return
                 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
             # this turn on the FlatCAMCNCJob plot for multiple tools
-            geo_obj.multigeo = False
+            geo_obj.multigeo = True
             geo_obj.multitool = 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...
             # Experimental...
             # print("Indexing...", end=' ')
             # print("Indexing...", end=' ')
@@ -1278,36 +1373,73 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         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.
         Paints all polygons in this object.
 
 
         :param obj: painted 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 connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
         :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:
         :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:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
             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..."))
         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.
         # This is a recursive generator of individual Polygons.
         # Note: Double check correct implementation. Might exit
         # Note: Double check correct implementation. Might exit
         #       early if it finds something that is not a Polygon?
         #       early if it finds something that is not a Polygon?
@@ -1355,15 +1487,10 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         # Initializes the new geometry object
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
         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':
             if order == 'fwd':
                 sorted_tools.sort(reverse=False)
                 sorted_tools.sort(reverse=False)
             elif order == 'rev':
             elif order == 'rev':
@@ -1383,10 +1510,12 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
             total_geometry = []
             total_geometry = []
             current_uid = int(1)
             current_uid = int(1)
+
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
             for tool_dia in sorted_tools:
             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
                 # 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):
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         current_uid = int(k)
                         break
                         break
@@ -1434,19 +1563,31 @@ class ToolPaint(FlatCAMTool, Gerber):
                               "Or a different Method of paint\n%s") % str(e))
                               "Or a different Method of paint\n%s") % str(e))
                         return
                         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[:] = []
                 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)
             geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
             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
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
             has_solid_geo = 0
@@ -1471,9 +1612,6 @@ class ToolPaint(FlatCAMTool, Gerber):
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
 
             tool_dia = None
             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)
             sorted_tools.sort(reverse=True)
 
 
             cleared_geo = []
             cleared_geo = []
@@ -1526,16 +1664,16 @@ class ToolPaint(FlatCAMTool, Gerber):
                         return
                         return
 
 
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
                 # 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):
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         current_uid = int(k)
                         break
                         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[:] = []
                 cleared_geo[:] = []
 
 
             geo_obj.options["cnctooldia"] = str(tool_dia)
             geo_obj.options["cnctooldia"] = str(tool_dia)
@@ -1543,7 +1681,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             geo_obj.multigeo = True
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
             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
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
             has_solid_geo = 0
@@ -1584,36 +1722,74 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         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
         Paints all polygons in this object that are within the sel_obj object
 
 
         :param obj: painted object
         :param obj: painted object
         :param sel_obj: paint only what is inside this object bounds
         :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 connect: Connect lines to avoid tool lifts.
         :param contour: Paint around the edges.
         :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:
         :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:
             try:
-                paint_margin = float(self.paintmargin_entry.get_value().replace(',', '.'))
+                paint_margin = float(self.paintmargin_entry.get_value())
             except ValueError:
             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..."))
         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):
         def recurse(geometry, reset=True):
             """
             """
@@ -1648,15 +1824,9 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
         # Initializes the new geometry object
         # Initializes the new geometry object
         def gen_paintarea(geo_obj, app_obj):
         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
             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':
             if order == 'fwd':
                 sorted_tools.sort(reverse=False)
                 sorted_tools.sort(reverse=False)
             elif order == 'rev':
             elif order == 'rev':
@@ -1664,6 +1834,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             else:
             else:
                 pass
                 pass
 
 
+            # this is were heavy lifting is done and creating the geometry to be painted
             geo_to_paint = []
             geo_to_paint = []
             if not isinstance(obj.solid_geometry, list):
             if not isinstance(obj.solid_geometry, list):
                 target_geo = [obj.solid_geometry]
                 target_geo = [obj.solid_geometry]
@@ -1686,10 +1857,12 @@ class ToolPaint(FlatCAMTool, Gerber):
 
 
             total_geometry = []
             total_geometry = []
             current_uid = int(1)
             current_uid = int(1)
+
             geo_obj.solid_geometry = []
             geo_obj.solid_geometry = []
             for tool_dia in sorted_tools:
             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
                 # 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):
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         current_uid = int(k)
                         break
                         break
@@ -1737,19 +1910,31 @@ class ToolPaint(FlatCAMTool, Gerber):
                               "Or a different Method of paint\n%s") % str(e))
                               "Or a different Method of paint\n%s") % str(e))
                         return
                         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[:] = []
                 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)
             geo_obj.options["cnctooldia"] = str(tool_dia)
             # this turn on the FlatCAMCNCJob plot for multiple tools
             # this turn on the FlatCAMCNCJob plot for multiple tools
             geo_obj.multigeo = True
             geo_obj.multigeo = True
             geo_obj.multitool = True
             geo_obj.multitool = True
             geo_obj.tools.clear()
             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
             # test if at least one tool has solid_geometry. If no tool has solid_geometry we raise an Exception
             has_solid_geo = 0
             has_solid_geo = 0
@@ -1766,7 +1951,7 @@ class ToolPaint(FlatCAMTool, Gerber):
             # print("Indexing...", end=' ')
             # print("Indexing...", end=' ')
             # geo_obj.make_index()
             # 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
         # Initializes the new geometry object
         def gen_paintarea_rest_machining(geo_obj, app_obj):
         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)
                 "Initializer expected a FlatCAMGeometry, got %s" % type(geo_obj)
 
 
             tool_dia = None
             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)
             sorted_tools.sort(reverse=True)
 
 
             cleared_geo = []
             cleared_geo = []
@@ -1829,16 +2011,16 @@ class ToolPaint(FlatCAMTool, Gerber):
                         return
                         return
 
 
                 # find the tooluid associated with the current tool_dia so we know where to add the tool solid_geometry
                 # 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):
                     if float('%.4f' % v['tooldia']) == float('%.4f' % tool_dia):
                         current_uid = int(k)
                         current_uid = int(k)
                         break
                         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[:] = []
                 cleared_geo[:] = []
 
 
             geo_obj.options["cnctooldia"] = str(tool_dia)
             geo_obj.options["cnctooldia"] = str(tool_dia)
@@ -1887,6 +2069,61 @@ class ToolPaint(FlatCAMTool, Gerber):
         # Background
         # Background
         self.app.worker_task.emit({'fcn': job_thread, 'params': [self.app]})
         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
     @staticmethod
     def paint_bounds(geometry):
     def paint_bounds(geometry):
         def bounds_rec(o):
         def bounds_rec(o):

+ 2 - 2
flatcamTools/ToolProperties.py

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

+ 2 - 3
tclCommands/TclCommand.py

@@ -202,7 +202,6 @@ class TclCommand(object):
         """
         """
 
 
         arguments, options = self.parse_arguments(args)
         arguments, options = self.parse_arguments(args)
-
         named_args = {}
         named_args = {}
         unnamed_args = []
         unnamed_args = []
 
 
@@ -274,7 +273,7 @@ class TclCommand(object):
         :return: None, output text or exception
         :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:
         try:
             self.log.debug("TCL command '%s' executed." % str(self.__class__))
             self.log.debug("TCL command '%s' executed." % str(self.__class__))
@@ -283,7 +282,7 @@ class TclCommand(object):
             return self.execute(args, unnamed_args)
             return self.execute(args, unnamed_args)
         except Exception as unknown:
         except Exception as unknown:
             error_info = sys.exc_info()
             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.app.display_tcl_error(unknown, error_info)
             self.raise_tcl_unknown_error(unknown)
             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 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
     Paint the interior of polygons
     """
     """
@@ -13,32 +21,54 @@ class TclCommandPaint(TclCommandSignaled):
     # dictionary of types from Tcl command, needs to be ordered
     # dictionary of types from Tcl command, needs to be ordered
     arg_names = collections.OrderedDict([
     arg_names = collections.OrderedDict([
         ('name', str),
         ('name', str),
-        ('tooldia', float),
-        ('overlap', float)
     ])
     ])
 
 
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
     # dictionary of types from Tcl command, needs to be ordered , this  is  for options  like -optionname value
     option_types = collections.OrderedDict([
     option_types = collections.OrderedDict([
-        ('outname', str),
+        ('tooldia', str),
+        ('overlap', float),
+        ('order', str),
+        ('margin', float),
+        ('method', str),
+        ('connect', bool),
+        ('contour', bool),
+
         ('all', bool),
         ('all', bool),
+        ('single', bool),
+        ('ref', bool),
+        ('box', str),
         ('x', float),
         ('x', float),
-        ('y', float)
+        ('y', float),
+        ('outname', str),
     ])
     ])
 
 
     # array of mandatory options for current Tcl command: required = {'name','outname'}
     # 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
     # structured help for current command, args needs to be ordered
     help = {
     help = {
         'main': "Paint polygons",
         'main': "Paint polygons",
         'args': collections.OrderedDict([
         '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': []
         'examples': []
     }
     }
@@ -54,31 +84,177 @@ class TclCommandPaint(TclCommandSignaled):
         """
         """
 
 
         name = args['name']
         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:
         if 'outname' in args:
             outname = args['outname']
             outname = args['outname']
         else:
         else:
             outname = name + "_paint"
             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
             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.TclCommandAddRectangle
 import tclCommands.TclCommandAlignDrill
 import tclCommands.TclCommandAlignDrill
 import tclCommands.TclCommandAlignDrillGrid
 import tclCommands.TclCommandAlignDrillGrid
+import tclCommands.TclCommandBbox
 import tclCommands.TclCommandClearShell
 import tclCommands.TclCommandClearShell
 import tclCommands.TclCommandCncjob
 import tclCommands.TclCommandCncjob
+import tclCommands.TclCommandCopperClear
 import tclCommands.TclCommandCutout
 import tclCommands.TclCommandCutout
 import tclCommands.TclCommandDelete
 import tclCommands.TclCommandDelete
 import tclCommands.TclCommandDrillcncjob
 import tclCommands.TclCommandDrillcncjob
@@ -31,6 +33,7 @@ import tclCommands.TclCommandListSys
 import tclCommands.TclCommandMillHoles
 import tclCommands.TclCommandMillHoles
 import tclCommands.TclCommandMirror
 import tclCommands.TclCommandMirror
 import tclCommands.TclCommandNew
 import tclCommands.TclCommandNew
+import tclCommands.TclCommandNregions
 import tclCommands.TclCommandNewGeometry
 import tclCommands.TclCommandNewGeometry
 import tclCommands.TclCommandOffset
 import tclCommands.TclCommandOffset
 import tclCommands.TclCommandOpenExcellon
 import tclCommands.TclCommandOpenExcellon