309 lines
11 KiB
Python
309 lines
11 KiB
Python
# SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
# ***************************************************************************
|
|
# * Copyright (c) 2016 sliptonic <shopinthewoods@gmail.com> *
|
|
# * *
|
|
# * This program is free software; you can redistribute it and/or modify *
|
|
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
|
# * as published by the Free Software Foundation; either version 2 of *
|
|
# * the License, or (at your option) any later version. *
|
|
# * for detail see the LICENCE text file. *
|
|
# * *
|
|
# * This program is distributed in the hope that it will be useful, *
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
# * GNU Library General Public License for more details. *
|
|
# * *
|
|
# * You should have received a copy of the GNU Library General Public *
|
|
# * License along with this program; if not, write to the Free Software *
|
|
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
|
# * USA *
|
|
# * *
|
|
# ***************************************************************************
|
|
|
|
import FreeCAD
|
|
import Part
|
|
import Path
|
|
import traceback
|
|
|
|
import Path.Dressup.Utils as PathDressup
|
|
|
|
from PathScripts.PathUtils import loopdetect
|
|
from PathScripts.PathUtils import wiredetect
|
|
from PathScripts.PathUtils import horizontalEdgeLoop
|
|
from PathScripts.PathUtils import tangentEdgeLoop
|
|
from PathScripts.PathUtils import horizontalFaceLoop
|
|
from PathScripts.PathUtils import addToJob
|
|
from PathScripts.PathUtils import findParentJob
|
|
|
|
from PySide.QtCore import QT_TRANSLATE_NOOP
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
|
|
translate = FreeCAD.Qt.translate
|
|
|
|
__title__ = "FreeCAD Path Commands"
|
|
__author__ = "sliptonic"
|
|
__url__ = "https://www.freecad.org"
|
|
|
|
|
|
class _CommandSelectLoop:
|
|
"the Path command to complete loop selection definition"
|
|
|
|
def __init__(self):
|
|
self.obj = None
|
|
self.sub = []
|
|
self.active = False
|
|
|
|
def GetResources(self):
|
|
return {
|
|
"Pixmap": "CAM_SelectLoop",
|
|
"MenuText": QT_TRANSLATE_NOOP("CAM_SelectLoop", "Finish Selecting Loop"),
|
|
"Accel": "P, L",
|
|
"ToolTip": QT_TRANSLATE_NOOP(
|
|
"CAM_SelectLoop",
|
|
"Completes the selection of edges or faces that form a loop"
|
|
"\n\nSelect faces: searching loop faces which form the walls."
|
|
"\n\nSelect one edge: searching loop edges in horizontal plane"
|
|
"\nor wire which contain selected edge."
|
|
"\n\nSelect two edges: searching loop edges in wires of the shape"
|
|
"\nor tangent edges.",
|
|
),
|
|
"CmdType": "ForEdit",
|
|
}
|
|
|
|
def IsActive(self):
|
|
if bool(FreeCADGui.Selection.getSelection()) is False:
|
|
return False
|
|
try:
|
|
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
|
if sel.Object == self.obj and sel.SubElementNames == self.sub:
|
|
return self.active
|
|
self.obj = sel.Object
|
|
self.sub = sel.SubElementNames
|
|
if sel.SubObjects:
|
|
# self.active = self.formsPartOfALoop(sel.Object, sel.SubObjects[0], sel.SubElementNames)
|
|
self.active = True
|
|
else:
|
|
self.active = False
|
|
return self.active
|
|
except Exception as exc:
|
|
Path.Log.error(exc)
|
|
traceback.print_exc(exc)
|
|
return False
|
|
|
|
def Activated(self):
|
|
from PathScripts.PathUtils import horizontalEdgeLoop, horizontalFaceLoop
|
|
|
|
if not FreeCADGui.Selection.getSelectionEx():
|
|
return
|
|
|
|
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
|
if not sel.SubObjects:
|
|
return
|
|
|
|
obj = sel.Object
|
|
sub = sel.SubObjects
|
|
names = sel.SubElementNames
|
|
loop = None
|
|
|
|
# Face selection
|
|
if "Face" in names[0]:
|
|
loop = horizontalFaceLoop(obj, sub[0], names)
|
|
if loop:
|
|
FreeCADGui.Selection.clearSelection()
|
|
FreeCADGui.Selection.addSelection(obj, loop)
|
|
return
|
|
|
|
elif "Edge" in names[0]:
|
|
if len(sub) == 1:
|
|
# One edge selected: searching horizontal edge loop
|
|
loop = horizontalEdgeLoop(obj, sub[0], verbose=True)
|
|
|
|
elif len(sub) >= 2:
|
|
# Two edges selected: searching wire in shape which contain both edges
|
|
loop = loopdetect(obj, sub[0], sub[1])
|
|
|
|
if not loop:
|
|
# Two edges selected: searching edges in tangency
|
|
loop = tangentEdgeLoop(obj, sub[0])
|
|
|
|
if not loop:
|
|
# Searching any wire with first selected edge
|
|
loop = wiredetect(obj, names[0])
|
|
|
|
if isinstance(loop, list) and len(loop) > 0 and isinstance(loop[0], Part.Edge):
|
|
# Select edges from list
|
|
objEdges = obj.Shape.Edges
|
|
FreeCADGui.Selection.clearSelection()
|
|
for el in loop:
|
|
for eo in objEdges:
|
|
if eo.hashCode() == el.hashCode():
|
|
FreeCADGui.Selection.addSelection(obj, f"Edge{objEdges.index(eo) + 1}")
|
|
return
|
|
|
|
Path.Log.warning(translate("CAM_SelectLoop", "Closed loop detection failed."))
|
|
|
|
def formsPartOfALoop(self, obj, sub, names):
|
|
try:
|
|
if names[0][0:4] != "Edge":
|
|
if names[0][0:4] == "Face" and horizontalFaceLoop(obj, sub, names):
|
|
return True
|
|
return False
|
|
if len(names) == 1 and horizontalEdgeLoop(obj, sub):
|
|
return True
|
|
if len(names) == 1 or names[1][0:4] != "Edge":
|
|
return False
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand("CAM_SelectLoop", _CommandSelectLoop())
|
|
|
|
|
|
class _ToggleOperation:
|
|
"command definition to toggle Operation Active state"
|
|
|
|
def GetResources(self):
|
|
return {
|
|
"Pixmap": "CAM_OpActive",
|
|
"MenuText": QT_TRANSLATE_NOOP("CAM_OpActiveToggle", "Toggle Operation"),
|
|
"Accel": "P, X",
|
|
"ToolTip": QT_TRANSLATE_NOOP(
|
|
"CAM_OpActiveToggle", "Toggles the active state of the operation"
|
|
),
|
|
"CmdType": "ForEdit",
|
|
}
|
|
|
|
def IsActive(self):
|
|
selection = FreeCADGui.Selection.getSelection()
|
|
if not selection:
|
|
return False
|
|
|
|
if len(selection) == 1:
|
|
# allows to toggle all operations in Job
|
|
sel = selection[0]
|
|
if hasattr(sel, "Group") and sel.Name.startswith("Job"):
|
|
return True
|
|
if hasattr(sel, "Group") and sel.Name.startswith("Operations"):
|
|
return True
|
|
|
|
for sel in selection:
|
|
baseOp = Path.Dressup.Utils.baseOp(sel)
|
|
if not hasattr(baseOp, "Active"):
|
|
return False
|
|
|
|
return True
|
|
|
|
def Activated(self):
|
|
selection = FreeCADGui.Selection.getSelection()
|
|
if (len(selection) == 1 and hasattr(selection[0], "Group")) and (
|
|
selection[0].Name.startswith("Job") or selection[0].Name.startswith("Operations")
|
|
):
|
|
sel = selection[0]
|
|
# process all Operations in Job
|
|
if sel.Name.startswith("Job"):
|
|
selection = sel.Operations.Group
|
|
elif sel.Name.startswith("Operations"):
|
|
selection = sel.Group
|
|
|
|
states = [Path.Dressup.Utils.baseOp(sel).Active for sel in selection]
|
|
if all(states) or not any(states):
|
|
# all operations in one state (active or inactive) - toggle state
|
|
for sel in selection:
|
|
baseOp = Path.Dressup.Utils.baseOp(sel)
|
|
baseOp.Active = not baseOp.Active
|
|
else:
|
|
# operations in different states - set Active state
|
|
for sel in selection:
|
|
baseOp = Path.Dressup.Utils.baseOp(sel)
|
|
baseOp.Active = True
|
|
|
|
else:
|
|
for sel in selection:
|
|
baseOp = Path.Dressup.Utils.baseOp(sel)
|
|
baseOp.Active = not baseOp.Active
|
|
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand("CAM_OpActiveToggle", _ToggleOperation())
|
|
|
|
|
|
class _CopyOperation:
|
|
"the Path Copy Operation command definition"
|
|
|
|
def GetResources(self):
|
|
return {
|
|
"Pixmap": "CAM_OpCopy",
|
|
"MenuText": QT_TRANSLATE_NOOP("CAM_OperationCopy", "Copy Operation"),
|
|
"ToolTip": QT_TRANSLATE_NOOP("CAM_OperationCopy", "Copies the operation in the job"),
|
|
"CmdType": "ForEdit",
|
|
}
|
|
|
|
def IsActive(self):
|
|
selection = FreeCADGui.Selection.getSelection()
|
|
if not selection:
|
|
return False
|
|
if any([not hasattr(sel, "Path") for sel in selection]):
|
|
return False
|
|
if any([sel.Name.startswith("Job") for sel in selection]):
|
|
return False
|
|
|
|
return True
|
|
|
|
def Activated(self):
|
|
selection = FreeCADGui.Selection.getSelection()
|
|
for sel in selection:
|
|
job = findParentJob(sel)
|
|
prevOp = PathDressup.baseOp(sel)
|
|
prevOpCopy = FreeCAD.ActiveDocument.copyObject(prevOp, False)
|
|
while prevOp != sel:
|
|
# recursive processing Dressup
|
|
op = sel
|
|
while op.Base != prevOp:
|
|
# get higher level operation
|
|
op = op.Base
|
|
opCopy = FreeCAD.ActiveDocument.copyObject(op, False)
|
|
opCopy.Base = prevOpCopy
|
|
prevOpCopy = opCopy
|
|
prevOp = op
|
|
|
|
# add to Job top object
|
|
addToJob(prevOpCopy, job.Name)
|
|
|
|
|
|
if FreeCAD.GuiUp:
|
|
FreeCADGui.addCommand("CAM_OperationCopy", _CopyOperation())
|
|
|
|
|
|
# \c findShape() is referenced from Gui/Command.cpp and used by Path.Area commands.
|
|
# Do not remove!
|
|
def findShape(shape, subname=None, subtype=None):
|
|
"""To find a higher order shape containing the subshape with subname.
|
|
E.g. to find the wire containing 'Edge1' in shape,
|
|
findShape(shape,'Edge1','Wires')
|
|
"""
|
|
if not subname:
|
|
return shape
|
|
ret = shape.getElement(subname)
|
|
if not subtype or not ret or ret.isNull():
|
|
return ret
|
|
if subname.startswith("Face"):
|
|
tp = "Faces"
|
|
elif subname.startswith("Edge"):
|
|
tp = "Edges"
|
|
elif subname.startswith("Vertex"):
|
|
tp = "Vertex"
|
|
else:
|
|
return ret
|
|
for obj in getattr(shape, subtype):
|
|
for sobj in getattr(obj, tp):
|
|
if sobj.isEqual(ret):
|
|
return obj
|
|
return ret
|