Merge branch 'master' into PD-Hole-DrillPoint
@@ -71,8 +71,8 @@ Gui::GuiNativeEvent* Gui::GuiNativeEvent::gMouseInput = 0;
|
||||
#define _TRACE_RI_TYPE 0
|
||||
#define _TRACE_RIDI_DEVICENAME 0
|
||||
#define _TRACE_RIDI_DEVICEINFO 0
|
||||
#define _TRACE_RI_RAWDATA 1
|
||||
#define _TRACE_3DINPUT_PERIOD 1
|
||||
#define _TRACE_RI_RAWDATA 0
|
||||
#define _TRACE_3DINPUT_PERIOD 0
|
||||
|
||||
#ifdef _WIN64
|
||||
typedef unsigned __int64 QWORD;
|
||||
|
||||
@@ -345,12 +345,12 @@ const char circleLine11_marker[CIRCLE_LINE11_WIDTH * CIRCLE_LINE11_HEIGHT + 1] =
|
||||
" "
|
||||
" xxxxxx "
|
||||
" xxxxxxxx "
|
||||
" xx xx "
|
||||
" xxx xxx"
|
||||
" xx xx"
|
||||
" xx xx"
|
||||
" xx xx"
|
||||
" xx xx"
|
||||
" xx xx "
|
||||
" xxx xxx"
|
||||
" xxxxxxxx "
|
||||
" xxxxxx "};
|
||||
|
||||
@@ -381,7 +381,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] =
|
||||
" "
|
||||
" xxxxxx "
|
||||
" xxxxxxxxxx "
|
||||
" xx xx "
|
||||
" xxx xxx "
|
||||
" xx xx "
|
||||
" xx xx"
|
||||
" xx xx"
|
||||
@@ -390,7 +390,7 @@ const char circleLine15_marker[CIRCLE_LINE15_WIDTH * CIRCLE_LINE15_HEIGHT + 1] =
|
||||
" xx xx"
|
||||
" xx xx"
|
||||
" xx xx "
|
||||
" xx xxxx"
|
||||
" xxx xxx "
|
||||
" xxxxxxxxxx "
|
||||
" xxxxxx "};
|
||||
|
||||
@@ -401,12 +401,12 @@ const char circleFilled11_marker[CIRCLE_FILLED11_WIDTH * CIRCLE_FILLED11_HEIGHT
|
||||
" "
|
||||
" xxxxxx "
|
||||
" xxxxxxxx "
|
||||
" xxxxxxxx "
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxx "
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxxxx"
|
||||
" xxxxxxxx "
|
||||
" xxxxxx "};
|
||||
|
||||
@@ -446,7 +446,7 @@ const char circleFilled15_marker[CIRCLE_FILLED15_WIDTH * CIRCLE_FILLED15_HEIGHT
|
||||
" xxxxxxxxxxxxxx"
|
||||
" xxxxxxxxxxxxxx"
|
||||
" xxxxxxxxxxxx "
|
||||
" xxxxxxxxxxxx"
|
||||
" xxxxxxxxxxxx "
|
||||
" xxxxxxxxxx "
|
||||
" xxxxxx "};
|
||||
|
||||
|
||||
@@ -774,47 +774,53 @@ bool View3DInventor::setCamera(const char* pCamera)
|
||||
SoNode * Cam;
|
||||
SoDB::read(&in,Cam);
|
||||
|
||||
if (!Cam){
|
||||
if (!Cam || !Cam->isOfType(SoCamera::getClassTypeId())) {
|
||||
throw Base::RuntimeError("Camera settings failed to read");
|
||||
}
|
||||
|
||||
// this is to make sure to reliably delete the node
|
||||
CoinPtr<SoNode> camPtr(Cam, true);
|
||||
|
||||
// toggle between perspective and orthographic camera
|
||||
if (Cam->getTypeId() != CamViewer->getTypeId())
|
||||
{
|
||||
if (Cam->getTypeId() != CamViewer->getTypeId()) {
|
||||
_viewer->setCameraType(Cam->getTypeId());
|
||||
CamViewer = _viewer->getSoRenderManager()->getCamera();
|
||||
}
|
||||
|
||||
SoPerspectiveCamera * CamViewerP = 0;
|
||||
SoOrthographicCamera * CamViewerO = 0;
|
||||
SoPerspectiveCamera * CamViewerP = nullptr;
|
||||
SoOrthographicCamera * CamViewerO = nullptr;
|
||||
|
||||
if (CamViewer->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
|
||||
CamViewerP = (SoPerspectiveCamera *)CamViewer; // safe downward cast, knows the type
|
||||
} else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
||||
CamViewerO = (SoOrthographicCamera *)CamViewer; // safe downward cast, knows the type
|
||||
CamViewerP = static_cast<SoPerspectiveCamera *>(CamViewer); // safe downward cast, knows the type
|
||||
}
|
||||
else if (CamViewer->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
||||
CamViewerO = static_cast<SoOrthographicCamera *>(CamViewer); // safe downward cast, knows the type
|
||||
}
|
||||
|
||||
if (Cam->getTypeId() == SoPerspectiveCamera::getClassTypeId()) {
|
||||
if (CamViewerP){
|
||||
CamViewerP->position = ((SoPerspectiveCamera *)Cam)->position;
|
||||
CamViewerP->orientation = ((SoPerspectiveCamera *)Cam)->orientation;
|
||||
CamViewerP->nearDistance = ((SoPerspectiveCamera *)Cam)->nearDistance;
|
||||
CamViewerP->farDistance = ((SoPerspectiveCamera *)Cam)->farDistance;
|
||||
CamViewerP->focalDistance = ((SoPerspectiveCamera *)Cam)->focalDistance;
|
||||
} else {
|
||||
CamViewerP->position = static_cast<SoPerspectiveCamera *>(Cam)->position;
|
||||
CamViewerP->orientation = static_cast<SoPerspectiveCamera *>(Cam)->orientation;
|
||||
CamViewerP->nearDistance = static_cast<SoPerspectiveCamera *>(Cam)->nearDistance;
|
||||
CamViewerP->farDistance = static_cast<SoPerspectiveCamera *>(Cam)->farDistance;
|
||||
CamViewerP->focalDistance = static_cast<SoPerspectiveCamera *>(Cam)->focalDistance;
|
||||
}
|
||||
else {
|
||||
throw Base::TypeError("Camera type mismatch");
|
||||
}
|
||||
} else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
||||
}
|
||||
else if (Cam->getTypeId() == SoOrthographicCamera::getClassTypeId()) {
|
||||
if (CamViewerO){
|
||||
CamViewerO->viewportMapping = ((SoOrthographicCamera *)Cam)->viewportMapping;
|
||||
CamViewerO->position = ((SoOrthographicCamera *)Cam)->position;
|
||||
CamViewerO->orientation = ((SoOrthographicCamera *)Cam)->orientation;
|
||||
CamViewerO->nearDistance = ((SoOrthographicCamera *)Cam)->nearDistance;
|
||||
CamViewerO->farDistance = ((SoOrthographicCamera *)Cam)->farDistance;
|
||||
CamViewerO->focalDistance = ((SoOrthographicCamera *)Cam)->focalDistance;
|
||||
CamViewerO->aspectRatio = ((SoOrthographicCamera *)Cam)->aspectRatio ;
|
||||
CamViewerO->height = ((SoOrthographicCamera *)Cam)->height;
|
||||
} else {
|
||||
CamViewerO->viewportMapping = static_cast<SoOrthographicCamera *>(Cam)->viewportMapping;
|
||||
CamViewerO->position = static_cast<SoOrthographicCamera *>(Cam)->position;
|
||||
CamViewerO->orientation = static_cast<SoOrthographicCamera *>(Cam)->orientation;
|
||||
CamViewerO->nearDistance = static_cast<SoOrthographicCamera *>(Cam)->nearDistance;
|
||||
CamViewerO->farDistance = static_cast<SoOrthographicCamera *>(Cam)->farDistance;
|
||||
CamViewerO->focalDistance = static_cast<SoOrthographicCamera *>(Cam)->focalDistance;
|
||||
CamViewerO->aspectRatio = static_cast<SoOrthographicCamera *>(Cam)->aspectRatio ;
|
||||
CamViewerO->height = static_cast<SoOrthographicCamera *>(Cam)->height;
|
||||
}
|
||||
else {
|
||||
throw Base::TypeError("Camera type mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,13 +68,11 @@ void ViewProviderLine::attach ( App::DocumentObject *obj ) {
|
||||
sep->addChild ( pCoords );
|
||||
|
||||
SoIndexedLineSet *pLines = new SoIndexedLineSet ();
|
||||
pLines->ref();
|
||||
pLines->coordIndex.setNum(3);
|
||||
pLines->coordIndex.setValues(0, 3, lines);
|
||||
sep->addChild ( pLines );
|
||||
|
||||
SoTranslation *textTranslation = new SoTranslation ();
|
||||
textTranslation->ref ();
|
||||
textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size / 30., 0 ) );
|
||||
sep->addChild ( textTranslation );
|
||||
|
||||
|
||||
@@ -70,13 +70,11 @@ void ViewProviderPlane::attach ( App::DocumentObject *obj ) {
|
||||
sep->addChild ( pCoords );
|
||||
|
||||
SoIndexedLineSet *pLines = new SoIndexedLineSet ();
|
||||
pLines->ref();
|
||||
pLines->coordIndex.setNum(6);
|
||||
pLines->coordIndex.setValues(0, 6, lines);
|
||||
sep->addChild ( pLines );
|
||||
|
||||
SoTranslation *textTranslation = new SoTranslation ();
|
||||
textTranslation->ref ();
|
||||
textTranslation->translation.setValue ( SbVec3f ( -size * 49. / 50., size * 9./10., 0 ) );
|
||||
sep->addChild ( textTranslation );
|
||||
|
||||
|
||||
@@ -220,7 +220,7 @@ class _ArchSchedule:
|
||||
prop = args[0].upper()
|
||||
fval = args[1].upper()
|
||||
if prop == "TYPE":
|
||||
prop == "IFCTYPE"
|
||||
prop = "IFCTYPE"
|
||||
if inv:
|
||||
if prop in props:
|
||||
csprop = o.PropertiesList[props.index(prop)]
|
||||
|
||||
@@ -410,14 +410,8 @@ class Snapper:
|
||||
if (not self.maxEdges) or (len(shape.Edges) <= self.maxEdges):
|
||||
if "Edge" in comp:
|
||||
# we are snapping to an edge
|
||||
edge = None
|
||||
if shape.ShapeType == "Edge":
|
||||
edge = shape
|
||||
else:
|
||||
en = int(comp[4:])-1
|
||||
if len(shape.Edges) > en:
|
||||
edge = shape.Edges[en]
|
||||
if edge:
|
||||
snaps.extend(self.snapToEndpoints(edge))
|
||||
snaps.extend(self.snapToMidpoint(edge))
|
||||
snaps.extend(self.snapToPerpendicular(edge, lastpoint))
|
||||
@@ -433,9 +427,9 @@ class Snapper:
|
||||
# extra ellipse options
|
||||
snaps.extend(self.snapToCenter(edge))
|
||||
elif "Face" in comp:
|
||||
en = int(comp[4:])-1
|
||||
if len(shape.Faces) > en:
|
||||
face = shape.Faces[en]
|
||||
# we are snapping to a face
|
||||
if shape.ShapeType == "Face":
|
||||
face = shape
|
||||
snaps.extend(self.snapToFace(face))
|
||||
elif "Vertex" in comp:
|
||||
# directly snapped to a vertex
|
||||
|
||||
@@ -226,7 +226,7 @@ void PropertyGeometryList::Restore(Base::XMLReader &reader)
|
||||
reader.readEndElement("GeometryList");
|
||||
|
||||
// assignment
|
||||
setValues(values);
|
||||
setValues(std::move(values));
|
||||
}
|
||||
|
||||
App::Property *PropertyGeometryList::Copy(void) const
|
||||
|
||||
@@ -1133,7 +1133,10 @@ void ViewProviderPartExt::updateVisual()
|
||||
const TopoDS_Face &actFace = TopoDS::Face(faceMap(i));
|
||||
// get the mesh of the shape
|
||||
Handle (Poly_Triangulation) mesh = BRep_Tool::Triangulation(actFace,aLoc);
|
||||
if (mesh.IsNull()) continue;
|
||||
if (mesh.IsNull()) {
|
||||
parts[ii] = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// getting the transformation of the shape/face
|
||||
gp_Trsf myTransf;
|
||||
|
||||
@@ -43,7 +43,7 @@ pairs, or a placement, or a vector</UserDocu>
|
||||
</Methode>
|
||||
<Methode Name="setFromGCode">
|
||||
<Documentation>
|
||||
<UserDocu>toGCode(): returns a GCode representation of the command</UserDocu>
|
||||
<UserDocu>setFromGCode(): sets the path from the contents of the given GCode string</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<Methode Name="transform">
|
||||
@@ -51,5 +51,8 @@ pairs, or a placement, or a vector</UserDocu>
|
||||
<UserDocu>transform(Placement): returns a copy of this command transformed by the given placement</UserDocu>
|
||||
</Documentation>
|
||||
</Methode>
|
||||
<ClassDeclarations>
|
||||
mutable Py::Dict parameters_copy_dict;
|
||||
</ClassDeclarations>
|
||||
</PythonExport>
|
||||
</GenerateModel>
|
||||
|
||||
@@ -56,6 +56,11 @@ std::string CommandPy::representation(void) const
|
||||
return str.str();
|
||||
}
|
||||
|
||||
//
|
||||
// Py::Dict parameters_copy_dict is now a class member to avoid delete/create/copy on every read access from python code
|
||||
// Now the pre-filled Py::Dict is returned which is more consistent with normal python behaviour.
|
||||
// It should be cleared whenever the c++ Parameters object is changed eg setParameters() or other objects invalidate its content, eg setPlacement()
|
||||
// https://forum.freecadweb.org/viewtopic.php?f=15&t=50583
|
||||
|
||||
PyObject *CommandPy::PyMake(struct _typeobject *, PyObject *, PyObject *) // Python wrapper
|
||||
{
|
||||
@@ -117,6 +122,7 @@ int CommandPy::PyInit(PyObject* args, PyObject* kwd)
|
||||
}
|
||||
getCommandPtr()->Parameters[ckey]=cvalue;
|
||||
}
|
||||
parameters_copy_dict.clear();
|
||||
return 0;
|
||||
}
|
||||
PyErr_Clear(); // set by PyArg_ParseTuple()
|
||||
@@ -157,11 +163,13 @@ void CommandPy::setName(Py::String arg)
|
||||
|
||||
Py::Dict CommandPy::getParameters(void) const
|
||||
{
|
||||
Py::Dict dict;
|
||||
for(std::map<std::string,double>::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) {
|
||||
dict.setItem(i->first, Py::Float(i->second));
|
||||
// dict now a class member , https://forum.freecadweb.org/viewtopic.php?f=15&t=50583
|
||||
if (parameters_copy_dict.length()==0) {
|
||||
for(std::map<std::string,double>::iterator i = getCommandPtr()->Parameters.begin(); i != getCommandPtr()->Parameters.end(); ++i) {
|
||||
parameters_copy_dict.setItem(i->first, Py::Float(i->second));
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
return parameters_copy_dict;
|
||||
}
|
||||
|
||||
void CommandPy::setParameters(Py::Dict arg)
|
||||
@@ -200,6 +208,7 @@ void CommandPy::setParameters(Py::Dict arg)
|
||||
throw Py::TypeError("The dictionary can only contain number values");
|
||||
}
|
||||
getCommandPtr()->Parameters[ckey]=cvalue;
|
||||
parameters_copy_dict.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,6 +233,7 @@ PyObject* CommandPy::setFromGCode(PyObject *args)
|
||||
std::string gcode(pstr);
|
||||
try {
|
||||
getCommandPtr()->setFromGCode(gcode);
|
||||
parameters_copy_dict.clear();
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
PyErr_SetString(PyExc_ValueError, e.what());
|
||||
@@ -249,6 +259,7 @@ void CommandPy::setPlacement(Py::Object arg)
|
||||
Py::Type PlacementType(pyType.o);
|
||||
if(arg.isType(PlacementType)) {
|
||||
getCommandPtr()->setFromPlacement( *static_cast<Base::PlacementPy*>((*arg))->getPlacementPtr() );
|
||||
parameters_copy_dict.clear();
|
||||
} else
|
||||
throw Py::TypeError("Argument must be a placement");
|
||||
}
|
||||
@@ -259,6 +270,7 @@ PyObject* CommandPy::transform(PyObject *args)
|
||||
if ( PyArg_ParseTuple(args, "O!", &(Base::PlacementPy::Type), &placement) ) {
|
||||
Base::PlacementPy *p = static_cast<Base::PlacementPy*>(placement);
|
||||
Path::Command trCmd = getCommandPtr()->transform( *p->getPlacementPtr() );
|
||||
parameters_copy_dict.clear();
|
||||
return new CommandPy(new Path::Command(trCmd));
|
||||
} else
|
||||
throw Py::TypeError("Argument must be a placement");
|
||||
@@ -302,6 +314,7 @@ int CommandPy::setCustomAttributes(const char* attr, PyObject* obj)
|
||||
return 0;
|
||||
}
|
||||
getCommandPtr()->Parameters[satt]=cvalue;
|
||||
parameters_copy_dict.clear();
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,10 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PathProfileFaces.py
|
||||
PathScripts/PathProfileFacesGui.py
|
||||
PathScripts/PathProfileGui.py
|
||||
PathScripts/PathProperty.py
|
||||
PathScripts/PathPropertyBag.py
|
||||
PathScripts/PathPropertyBagGui.py
|
||||
PathScripts/PathPropertyEditor.py
|
||||
PathScripts/PathSanity.py
|
||||
PathScripts/PathSelection.py
|
||||
PathScripts/PathSetupSheet.py
|
||||
@@ -198,6 +202,7 @@ SET(PathTests_SRCS
|
||||
PathTests/TestPathOpTools.py
|
||||
PathTests/TestPathPost.py
|
||||
PathTests/TestPathPreferences.py
|
||||
PathTests/TestPathPropertyBag.py
|
||||
PathTests/TestPathSetupSheet.py
|
||||
PathTests/TestPathStock.py
|
||||
PathTests/TestPathThreadMilling.py
|
||||
@@ -208,6 +213,9 @@ SET(PathTests_SRCS
|
||||
PathTests/TestPathUtil.py
|
||||
PathTests/TestPathVcarve.py
|
||||
PathTests/TestPathVoronoi.py
|
||||
PathTests/Tools/Bit/test-path-tool-bit-bit-00.fctb
|
||||
PathTests/Tools/Library/test-path-tool-bit-library-00.fctl
|
||||
PathTests/Tools/Shape/test-path-tool-bit-shape-00.fcstd
|
||||
PathTests/boxtest.fcstd
|
||||
PathTests/test_centroid_00.ngc
|
||||
PathTests/test_geomop.fcstd
|
||||
@@ -279,6 +287,14 @@ INSTALL(
|
||||
Mod/Path/PathTests
|
||||
)
|
||||
|
||||
INSTALL(
|
||||
DIRECTORY
|
||||
PathTests/Tools
|
||||
DESTINATION
|
||||
Mod/Path/PathTests
|
||||
)
|
||||
|
||||
|
||||
INSTALL(
|
||||
FILES
|
||||
${PathScripts_post_SRCS}
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
<file>panels/PageOpVcarveEdit.ui</file>
|
||||
<file>panels/PathEdit.ui</file>
|
||||
<file>panels/PointEdit.ui</file>
|
||||
<file>panels/PropertyBag.ui</file>
|
||||
<file>panels/PropertyCreate.ui</file>
|
||||
<file>panels/SetupGlobal.ui</file>
|
||||
<file>panels/SetupOp.ui</file>
|
||||
<file>panels/ToolBitEditor.ui</file>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>250</height>
|
||||
<width>476</width>
|
||||
<height>342</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -23,94 +23,28 @@
|
||||
<iconset resource="../Path.qrc">
|
||||
<normaloff>:/icons/Path_BaseGeometry.svg</normaloff>:/icons/Path_BaseGeometry.svg</iconset>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="geometryImportButton">
|
||||
<property name="text">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="deleteBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QComboBox" name="geometryImportList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="QListWidget" name="baseList">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html></string>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="2">
|
||||
<widget class="QPushButton" name="clearBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Clears list of base geometries.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QPushButton" name="addBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>All objects will be processed using the same operation properties</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="addBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Add selected features to the list of base geometries for this operation.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="deleteBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Remove the selected list items from the list of base geometries. The operation will not be applied to them.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
@@ -123,14 +57,70 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QPushButton" name="clearBase">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Clears list of base geometries.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>All objects will be processed using the same operation properties</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QListWidget" name="baseList">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Select one or more features in the 3d view and press 'Add' to add them as the base items for this operation.</p><p><br/></p><p>Selected features can be deleted entirely.</p></body></html></string>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="geometryImportButton">
|
||||
<property name="text">
|
||||
<string>Import</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QComboBox" name="geometryImportList">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>List of operations with Base Geometry in current Job.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>baseList</tabstop>
|
||||
<tabstop>addBase</tabstop>
|
||||
<tabstop>deleteBase</tabstop>
|
||||
<tabstop>clearBase</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../Path.qrc"/>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>552</width>
|
||||
<height>651</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Property Bag</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTableView" name="table">
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AllEditTriggers</set>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="modify">
|
||||
<property name="text">
|
||||
<string>Modify...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="text">
|
||||
<string>Add...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>table</tabstop>
|
||||
<tabstop>add</tabstop>
|
||||
<tabstop>remove</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>480</width>
|
||||
<height>452</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create Property</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelName">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="propertyName">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Name of property.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="propertyGroup">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The category group the property belongs to.</p></body></html></string>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelGroup">
|
||||
<property name="text">
|
||||
<string>Group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="propertyType">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The type of the property value.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="labelType">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="propertyEnum">
|
||||
<property name="placeholderText">
|
||||
<string>val1,val2,val3,...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QTextEdit" name="propertyInfo">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>ToolTip to be displayed when user hovers mouse over property.</p></body></html></string>
|
||||
</property>
|
||||
<property name="tabChangesFocus">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="labelEnum">
|
||||
<property name="text">
|
||||
<string>Enums</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="labelInfo">
|
||||
<property name="text">
|
||||
<string>ToolTip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="createAnother">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Check if you want to create several properties in a batch.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Create another</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>propertyName</tabstop>
|
||||
<tabstop>propertyGroup</tabstop>
|
||||
<tabstop>propertyType</tabstop>
|
||||
<tabstop>propertyEnum</tabstop>
|
||||
<tabstop>propertyInfo</tabstop>
|
||||
<tabstop>createAnother</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>254</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>Dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>260</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>274</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
@@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>401</width>
|
||||
<width>489</width>
|
||||
<height>715</height>
|
||||
</rect>
|
||||
</property>
|
||||
@@ -17,7 +17,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
@@ -32,6 +32,12 @@
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Tool Bit</string>
|
||||
</property>
|
||||
@@ -65,6 +71,12 @@
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
@@ -83,6 +95,9 @@
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The file which defines the type and shape of the Tool Bit.</p></body></html></string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>path</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -104,7 +119,7 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="bitParams">
|
||||
<property name="title">
|
||||
<string>Bit Parameter</string>
|
||||
<string>Parameter</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="fieldGrowthPolicy">
|
||||
@@ -184,7 +199,7 @@
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTableView" name="attrTable">
|
||||
<widget class="QTreeView" name="attrTree">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -200,9 +215,6 @@
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::AllEditTriggers</set>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -173,6 +173,12 @@
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Box</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -154,6 +154,10 @@ class PathWorkbench (Workbench):
|
||||
if extracmdlist:
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist)
|
||||
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"])
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Utils")],
|
||||
["Path_PropertyBag"])
|
||||
|
||||
self.dressupcmds = dressupcmdlist
|
||||
|
||||
curveAccuracy = PathPreferences.defaultLibAreaCurveAccuracy()
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathCustom as PathCustom
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathDeburr as PathDeburr
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathGetPoint as PathGetPoint
|
||||
import PathScripts.PathDressupHoldingTags as PathDressupTag
|
||||
@@ -418,7 +419,7 @@ class PathDressupTagViewProvider:
|
||||
'''this makes sure that the base operation is added back to the job and visible'''
|
||||
# pylint: disable=unused-argument
|
||||
PathLog.track()
|
||||
if self.obj.Base.ViewObject:
|
||||
if self.obj.Base and self.obj.Base.ViewObject:
|
||||
self.obj.Base.ViewObject.Visibility = True
|
||||
job = PathUtils.findParentJob(self.obj)
|
||||
if arg1.Object and arg1.Object.Base and job:
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathDrilling as PathDrilling
|
||||
import PathScripts.PathGui as PathGui
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathEngrave as PathEngrave
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
@@ -48,6 +48,7 @@ def updateInputField(obj, prop, widget, onBeforeChange=None):
|
||||
If onBeforeChange is specified it is called before a new value is assigned to the property.
|
||||
Returns True if a new value was assigned, False otherwise (new value is the same as the current).
|
||||
'''
|
||||
PathLog.track()
|
||||
value = widget.property('rawValue')
|
||||
attr = PathUtil.getProperty(obj, prop)
|
||||
attrValue = attr.Value if hasattr(attr, 'Value') else attr
|
||||
@@ -98,10 +99,12 @@ class QuantitySpinBox:
|
||||
PathLog.track(widget)
|
||||
self.widget = widget
|
||||
self.onBeforeChange = onBeforeChange
|
||||
self.prop = None
|
||||
self.attachTo(obj, prop)
|
||||
|
||||
def attachTo(self, obj, prop = None):
|
||||
'''attachTo(obj, prop=None) ... use an existing editor for the given object and property'''
|
||||
PathLog.track(self.prop, prop)
|
||||
self.obj = obj
|
||||
self.prop = prop
|
||||
if obj and prop:
|
||||
@@ -119,12 +122,14 @@ class QuantitySpinBox:
|
||||
|
||||
def expression(self):
|
||||
'''expression() ... returns the expression if one is bound to the property'''
|
||||
PathLog.track(self.prop, self.valid)
|
||||
if self.valid:
|
||||
return self.widget.property('expression')
|
||||
return ''
|
||||
|
||||
def setMinimum(self, quantity):
|
||||
'''setMinimum(quantity) ... set the minimum'''
|
||||
PathLog.track(self.prop, self.valid)
|
||||
if self.valid:
|
||||
value = quantity.Value if hasattr(quantity, 'Value') else quantity
|
||||
self.widget.setProperty('setMinimum', value)
|
||||
@@ -133,6 +138,7 @@ class QuantitySpinBox:
|
||||
'''updateSpinBox(quantity=None) ... update the display value of the spin box.
|
||||
If no value is provided the value of the bound property is used.
|
||||
quantity can be of type Quantity or Float.'''
|
||||
PathLog.track(self.prop, self.valid)
|
||||
if self.valid:
|
||||
if quantity is None:
|
||||
quantity = PathUtil.getProperty(self.obj, self.prop)
|
||||
@@ -141,6 +147,7 @@ class QuantitySpinBox:
|
||||
|
||||
def updateProperty(self):
|
||||
'''updateProperty() ... update the bound property with the value from the spin box'''
|
||||
PathLog.track(self.prop, self.valid)
|
||||
if self.valid:
|
||||
return updateInputField(self.obj, self.prop, self.widget, self.onBeforeChange)
|
||||
return None
|
||||
|
||||
@@ -66,6 +66,7 @@ def Startup():
|
||||
# from PathScripts import PathProfileEdgesGui
|
||||
# from PathScripts import PathProfileFacesGui
|
||||
from PathScripts import PathProfileGui
|
||||
from PathScripts import PathPropertyBagGui
|
||||
from PathScripts import PathSanity
|
||||
from PathScripts import PathSetupSheetGui
|
||||
from PathScripts import PathSimpleCopy
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathHelix as PathHelix
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -31,6 +31,7 @@ from PySide import QtCore, QtGui
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathJob as PathJob
|
||||
import PathScripts.PathJobCmd as PathJobCmd
|
||||
import PathScripts.PathJobDlg as PathJobDlg
|
||||
@@ -521,6 +522,7 @@ class StockCreateCylinderEdit(StockEdit):
|
||||
class StockFromExistingEdit(StockEdit):
|
||||
Index = 3
|
||||
StockType = PathStock.StockType.Unknown
|
||||
StockLabelPrefix = 'Stock'
|
||||
|
||||
def editorFrame(self):
|
||||
return self.form.stockFromExisting
|
||||
@@ -529,7 +531,7 @@ class StockFromExistingEdit(StockEdit):
|
||||
stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex())
|
||||
if not (hasattr(obj.Stock, 'Objects') and len(obj.Stock.Objects) == 1 and obj.Stock.Objects[0] == stock):
|
||||
if stock:
|
||||
stock = PathJob.createResourceClone(obj, stock, 'Stock', 'Stock')
|
||||
stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix , 'Stock')
|
||||
stock.ViewObject.Visibility = True
|
||||
PathStock.SetupStockObject(stock, PathStock.StockType.Unknown)
|
||||
stock.Proxy.execute(stock)
|
||||
@@ -555,7 +557,9 @@ class StockFromExistingEdit(StockEdit):
|
||||
index = -1
|
||||
for i, solid in enumerate(self.candidates(obj)):
|
||||
self.form.stockExisting.addItem(solid.Label, solid)
|
||||
if solid.Label == stockName:
|
||||
label="{}-{}".format(self.StockLabelPrefix, solid.Label)
|
||||
|
||||
if label == stockName:
|
||||
index = i
|
||||
self.form.stockExisting.setCurrentIndex(index if index != -1 else 0)
|
||||
|
||||
|
||||
@@ -422,7 +422,10 @@ class ObjectOp(object):
|
||||
zmax = max(zmax, bb.ZMax)
|
||||
for sub in sublist:
|
||||
try:
|
||||
fbb = base.Shape.getElement(sub).BoundBox
|
||||
if sub:
|
||||
fbb = base.Shape.getElement(sub).BoundBox
|
||||
else:
|
||||
fbb = base.Shape.BoundBox
|
||||
zmin = max(zmin, faceZmin(bb, fbb))
|
||||
zmax = max(zmax, fbb.ZMax)
|
||||
except Part.OCCError as e:
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathGetPoint as PathGetPoint
|
||||
import PathScripts.PathGui as PathGui
|
||||
@@ -42,13 +43,8 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Base classes and framework for Path operation's UI"
|
||||
|
||||
LOGLEVEL = False
|
||||
|
||||
if LOGLEVEL:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
@@ -566,6 +562,7 @@ class TaskPanelBaseGeometryPage(TaskPanelPage):
|
||||
return False
|
||||
|
||||
def addBase(self):
|
||||
PathLog.track()
|
||||
if self.addBaseGeometry(FreeCADGui.Selection.getSelectionEx()):
|
||||
# self.obj.Proxy.execute(self.obj)
|
||||
self.setFields(self.obj)
|
||||
@@ -636,11 +633,18 @@ class TaskPanelBaseGeometryPage(TaskPanelPage):
|
||||
# Set base geometry list window to resize based on contents
|
||||
# Code reference:
|
||||
# https://stackoverflow.com/questions/6337589/qlistwidget-adjust-size-to-content
|
||||
# ml: disabling this logic because I can't get it to work on HPD monitor.
|
||||
# On my systems the values returned by the list object are also incorrect on
|
||||
# creation, leading to a list object of size 15. count() always returns 0 until
|
||||
# the list is actually displayed. The same is true for sizeHintForRow(0), which
|
||||
# returns -1 until the widget is rendered. The widget claims to have a size of
|
||||
# (100, 30), once it becomes visible the size is (535, 192).
|
||||
# Leaving the framework here in case somebody figures out how to set this up
|
||||
# properly.
|
||||
qList = self.form.baseList
|
||||
# qList.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
col = qList.width() # 300
|
||||
row = (qList.count() + qList.frameWidth()) * 15
|
||||
qList.setFixedSize(col, row)
|
||||
#qList.setMinimumHeight(row)
|
||||
PathLog.debug("baseList({}, {}) {} * {}".format(qList.size(), row, qList.count(), qList.sizeHintForRow(0)))
|
||||
|
||||
|
||||
class TaskPanelBaseLocationPage(TaskPanelPage):
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -151,26 +151,9 @@ def searchPathsPost():
|
||||
return paths
|
||||
|
||||
|
||||
def searchPathsTool(sub='Bit'):
|
||||
def searchPathsTool(sub):
|
||||
paths = []
|
||||
|
||||
if 'Bit' == sub:
|
||||
paths.append("{}/Bit".format(os.path.dirname(lastPathToolLibrary())))
|
||||
paths.append(lastPathToolBit())
|
||||
|
||||
if 'Library' == sub:
|
||||
paths.append(lastPathToolLibrary())
|
||||
if 'Shape' == sub:
|
||||
paths.append(lastPathToolShape())
|
||||
|
||||
def appendPath(p, sub):
|
||||
if p:
|
||||
paths.append(os.path.join(p, 'Tools', sub))
|
||||
paths.append(os.path.join(p, sub))
|
||||
paths.append(p)
|
||||
appendPath(defaultFilePath(), sub)
|
||||
appendPath(macroFilePath(), sub)
|
||||
appendPath(os.path.join(FreeCAD.getHomePath(), "Mod/Path/"), sub)
|
||||
paths.append(os.path.join(FreeCAD.getHomePath(), 'Mod', 'Path', 'Tools', sub))
|
||||
return paths
|
||||
|
||||
|
||||
@@ -263,15 +246,19 @@ def setDefaultTaskPanelLayout(style):
|
||||
def experimentalFeaturesEnabled():
|
||||
return preferences().GetBool(EnableExperimentalFeatures, False)
|
||||
|
||||
|
||||
def suppressAllSpeedsWarning():
|
||||
return preferences().GetBool(WarningSuppressAllSpeeds, True)
|
||||
|
||||
|
||||
def suppressRapidSpeedsWarning():
|
||||
return suppressAllSpeedsWarning() or preferences().GetBool(WarningSuppressRapidSpeeds, True)
|
||||
|
||||
|
||||
def suppressSelectionModeWarning():
|
||||
return preferences().GetBool(WarningSuppressSelectionMode, True)
|
||||
|
||||
|
||||
def suppressOpenCamLibWarning():
|
||||
return preferences().GetBool(WarningSuppressOpenCamLib, True)
|
||||
|
||||
@@ -316,7 +303,8 @@ def lastPathToolLibrary():
|
||||
def setLastPathToolLibrary(path):
|
||||
PathLog.track(path)
|
||||
curLib = lastFileToolLibrary()
|
||||
if os.path.split(curLib)[0] != path:
|
||||
PathLog.debug('curLib: {}'.format(curLib))
|
||||
if curLib and os.path.split(curLib)[0] != path:
|
||||
setLastFileToolLibrary('') # a path is known but not specific file
|
||||
return preferences().SetString(LastPathToolLibrary, path)
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathProbe as PathProbe
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathGui as PathGui
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathProfile as PathProfile
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 PathScripts.PathLog as PathLog
|
||||
|
||||
__title__ = "Property type abstraction for editing purposes"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Prototype objects to allow extraction of setup sheet values and editing."
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
class Property(object):
|
||||
'''Base class for all prototype properties'''
|
||||
def __init__(self, name, propType, category, info):
|
||||
self.name = name
|
||||
self.propType = propType
|
||||
self.category = category
|
||||
self.info = info
|
||||
self.editorMode = 0
|
||||
self.value = None
|
||||
|
||||
def setValue(self, value):
|
||||
self.value = value
|
||||
def getValue(self):
|
||||
return self.value
|
||||
|
||||
def setEditorMode(self, mode):
|
||||
self.editorMode = mode
|
||||
|
||||
def displayString(self):
|
||||
if self.value is None:
|
||||
t = self.typeString()
|
||||
p = 'an' if t[0] in ['A', 'E', 'I', 'O', 'U'] else 'a'
|
||||
return "%s %s" % (p, t)
|
||||
return self.value
|
||||
|
||||
def typeString(self):
|
||||
return "Property"
|
||||
|
||||
def setupProperty(self, obj, name, category, value):
|
||||
created = False
|
||||
if not hasattr(obj, name):
|
||||
obj.addProperty(self.propType, name, category, self.info)
|
||||
self.initProperty(obj, name)
|
||||
created = True
|
||||
setattr(obj, name, value)
|
||||
return created
|
||||
|
||||
def initProperty(self, obj, name):
|
||||
pass
|
||||
|
||||
def setValueFromString(self, string):
|
||||
self.setValue(self.valueFromString(string))
|
||||
|
||||
def valueFromString(self, string):
|
||||
return string
|
||||
|
||||
class PropertyEnumeration(Property):
|
||||
def typeString(self):
|
||||
return "Enumeration"
|
||||
|
||||
def setValue(self, value):
|
||||
if list == type(value):
|
||||
self.enums = value # pylint: disable=attribute-defined-outside-init
|
||||
else:
|
||||
super(PropertyEnumeration, self).setValue(value)
|
||||
|
||||
def getEnumValues(self):
|
||||
return self.enums
|
||||
|
||||
def initProperty(self, obj, name):
|
||||
setattr(obj, name, self.enums)
|
||||
|
||||
class PropertyQuantity(Property):
|
||||
def displayString(self):
|
||||
if self.value is None:
|
||||
return Property.displayString(self)
|
||||
return self.value.getUserPreferred()[0]
|
||||
|
||||
class PropertyAngle(PropertyQuantity):
|
||||
def typeString(self):
|
||||
return "Angle"
|
||||
|
||||
class PropertyDistance(PropertyQuantity):
|
||||
def typeString(self):
|
||||
return "Distance"
|
||||
|
||||
class PropertyLength(PropertyQuantity):
|
||||
def typeString(self):
|
||||
return "Length"
|
||||
|
||||
class PropertyPercent(Property):
|
||||
def typeString(self):
|
||||
return "Percent"
|
||||
|
||||
class PropertyFloat(Property):
|
||||
def typeString(self):
|
||||
return "Float"
|
||||
|
||||
def valueFromString(self, string):
|
||||
return float(string)
|
||||
|
||||
class PropertyInteger(Property):
|
||||
def typeString(self):
|
||||
return "Integer"
|
||||
|
||||
def valueFromString(self, string):
|
||||
return int(string)
|
||||
|
||||
class PropertyBool(Property):
|
||||
def typeString(self):
|
||||
return "Bool"
|
||||
|
||||
def valueFromString(self, string):
|
||||
return bool(string)
|
||||
|
||||
class PropertyString(Property):
|
||||
def typeString(self):
|
||||
return "String"
|
||||
|
||||
class PropertyMap(Property):
|
||||
def typeString(self):
|
||||
return "Map"
|
||||
|
||||
def displayString(self, value):
|
||||
return str(value)
|
||||
|
||||
class OpPrototype(object):
|
||||
|
||||
PropertyType = {
|
||||
'App::PropertyAngle': PropertyAngle,
|
||||
'App::PropertyBool': PropertyBool,
|
||||
'App::PropertyDistance': PropertyDistance,
|
||||
'App::PropertyEnumeration': PropertyEnumeration,
|
||||
'App::PropertyFile': PropertyString,
|
||||
'App::PropertyFloat': PropertyFloat,
|
||||
'App::PropertyFloatConstraint': Property,
|
||||
'App::PropertyFloatList': Property,
|
||||
'App::PropertyInteger': PropertyInteger,
|
||||
'App::PropertyIntegerList': PropertyInteger,
|
||||
'App::PropertyLength': PropertyLength,
|
||||
'App::PropertyLink': Property,
|
||||
'App::PropertyLinkList': Property,
|
||||
'App::PropertyLinkSubListGlobal': Property,
|
||||
'App::PropertyMap': PropertyMap,
|
||||
'App::PropertyPercent': PropertyPercent,
|
||||
'App::PropertyString': PropertyString,
|
||||
'App::PropertyStringList': Property,
|
||||
'App::PropertyVectorDistance': Property,
|
||||
'App::PropertyVectorList': Property,
|
||||
'Part::PropertyPartShape': Property,
|
||||
}
|
||||
|
||||
def __init__(self, name):
|
||||
self.Label = name
|
||||
self.properties = {}
|
||||
self.DoNotSetDefaultValues = True
|
||||
self.Proxy = None
|
||||
|
||||
def __setattr__(self, name, val):
|
||||
if name in ['Label', 'DoNotSetDefaultValues', 'properties', 'Proxy']:
|
||||
if name == 'Proxy':
|
||||
val = None # make sure the proxy is never set
|
||||
return super(OpPrototype, self).__setattr__(name, val)
|
||||
self.properties[name].setValue(val)
|
||||
|
||||
def addProperty(self, typeString, name, category, info = None):
|
||||
prop = self.PropertyType[typeString](name, typeString, category, info)
|
||||
self.properties[name] = prop
|
||||
return self
|
||||
|
||||
def setEditorMode(self, name, mode):
|
||||
self.properties[name].setEditorMode(mode)
|
||||
|
||||
def getProperty(self, name):
|
||||
return self.properties[name]
|
||||
|
||||
def setupProperties(self, setup):
|
||||
return [p for p in self.properties if p.name in setup]
|
||||
@@ -0,0 +1,113 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 PySide
|
||||
|
||||
__title__ = 'Generic property container to store some values.'
|
||||
__author__ = 'sliptonic (Brad Collette)'
|
||||
__url__ = 'https://www.freecadweb.org'
|
||||
__doc__ = 'A generic container for typed properties in arbitrary categories.'
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
SupportedPropertyType = {
|
||||
'Angle' : 'App::PropertyAngle',
|
||||
'Bool' : 'App::PropertyBool',
|
||||
'Distance' : 'App::PropertyDistance',
|
||||
'Enumeration' : 'App::PropertyEnumeration',
|
||||
'File' : 'App::PropertyFile',
|
||||
'Float' : 'App::PropertyFloat',
|
||||
'Integer' : 'App::PropertyInteger',
|
||||
'Length' : 'App::PropertyLength',
|
||||
'Percent' : 'App::PropertyPercent',
|
||||
'String' : 'App::PropertyString',
|
||||
}
|
||||
|
||||
def getPropertyTypeName(o):
|
||||
for typ in SupportedPropertyType:
|
||||
if SupportedPropertyType[typ] == o:
|
||||
return typ
|
||||
raise IndexError()
|
||||
|
||||
class PropertyBag(object):
|
||||
'''Property container object.'''
|
||||
|
||||
CustomPropertyGroups = 'CustomPropertyGroups'
|
||||
CustomPropertyGroupDefault = 'User'
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty('App::PropertyStringList', self.CustomPropertyGroups, 'Base', PySide.QtCore.QT_TRANSLATE_NOOP('PathPropertyBag', 'List of custom property groups'))
|
||||
self.onDocumentRestored(obj)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
self.obj = obj
|
||||
obj.setEditorMode(self.CustomPropertyGroups, 2) # hide
|
||||
|
||||
def getCustomProperties(self):
|
||||
'''getCustomProperties() ... Return a list of all custom properties created in this container.'''
|
||||
return [p for p in self.obj.PropertiesList if self.obj.getGroupOfProperty(p) in self.obj.CustomPropertyGroups]
|
||||
|
||||
def addCustomProperty(self, propertyType, name, group=None, desc=None):
|
||||
'''addCustomProperty(propertyType, name, group=None, desc=None) ... adds a custom property and tracks its group.'''
|
||||
if desc is None:
|
||||
desc = ''
|
||||
if group is None:
|
||||
group = self.CustomPropertyGroupDefault
|
||||
groups = self.obj.CustomPropertyGroups
|
||||
if not group in groups:
|
||||
groups.append(group)
|
||||
self.obj.CustomPropertyGroups = groups
|
||||
self.obj.addProperty(propertyType, name, group, desc)
|
||||
|
||||
def refreshCustomPropertyGroups(self):
|
||||
'''refreshCustomPropertyGroups() ... removes empty property groups, should be called after deleting properties.'''
|
||||
customGroups = []
|
||||
for p in self.obj.PropertiesList:
|
||||
group = self.obj.getGroupOfProperty(p)
|
||||
if group in self.obj.CustomPropertyGroups and not group in customGroups:
|
||||
customGroups.append(group)
|
||||
self.obj.CustomPropertyGroups = customGroups
|
||||
|
||||
|
||||
def Create(name = 'PropertyBag'):
|
||||
obj = FreeCAD.ActiveDocument.addObject('App::FeaturePython', name)
|
||||
obj.Proxy = PropertyBag(obj)
|
||||
return obj
|
||||
|
||||
def IsPropertyBag(obj):
|
||||
'''Returns True if the supplied object is a property container (or its Proxy).'''
|
||||
|
||||
if type(obj) == PropertyBag:
|
||||
return True
|
||||
if hasattr(obj, 'Proxy'):
|
||||
return IsPropertyBag(obj.Proxy)
|
||||
return False
|
||||
|
||||
@@ -0,0 +1,425 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathIconViewProvider as PathIconViewProvider
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPropertyBag as PathPropertyBag
|
||||
import PathScripts.PathPropertyEditor as PathPropertyEditor
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
__title__ = "Property Bag Editor"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Task panel editor for a PropertyBag"
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class ViewProvider(object):
|
||||
'''ViewProvider for a PropertyBag.
|
||||
It's sole job is to provide an icon and invoke the TaskPanel on edit.'''
|
||||
|
||||
def __init__(self, vobj, name):
|
||||
PathLog.track(name)
|
||||
vobj.Proxy = self
|
||||
self.icon = name
|
||||
# mode = 2
|
||||
self.obj = None
|
||||
self.vobj = None
|
||||
|
||||
def attach(self, vobj):
|
||||
PathLog.track()
|
||||
self.vobj = vobj
|
||||
self.obj = vobj.Object
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Path-SetupSheet.svg"
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
# pylint: disable=unused-argument
|
||||
return None
|
||||
|
||||
def getDisplayMode(self, mode):
|
||||
# pylint: disable=unused-argument
|
||||
return 'Default'
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
# pylint: disable=unused-argument
|
||||
PathLog.track()
|
||||
taskPanel = TaskPanel(vobj)
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCADGui.Control.showDialog(taskPanel)
|
||||
taskPanel.setupUi()
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode):
|
||||
# pylint: disable=unused-argument
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return
|
||||
|
||||
def claimChildren(self):
|
||||
return []
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
self.setEdit(vobj)
|
||||
|
||||
class Delegate(QtGui.QStyledItemDelegate):
|
||||
RoleObject = QtCore.Qt.UserRole + 1
|
||||
RoleProperty = QtCore.Qt.UserRole + 2
|
||||
RoleEditor = QtCore.Qt.UserRole + 3
|
||||
|
||||
|
||||
#def paint(self, painter, option, index):
|
||||
# #PathLog.track(index.column(), type(option))
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
# pylint: disable=unused-argument
|
||||
editor = PathPropertyEditor.Editor(index.data(self.RoleObject), index.data(self.RoleProperty))
|
||||
index.model().setData(index, editor, self.RoleEditor)
|
||||
return editor.widget(parent)
|
||||
|
||||
def setEditorData(self, widget, index):
|
||||
PathLog.track(index.row(), index.column())
|
||||
index.data(self.RoleEditor).setEditorData(widget)
|
||||
|
||||
def setModelData(self, widget, model, index):
|
||||
# pylint: disable=unused-argument
|
||||
PathLog.track(index.row(), index.column())
|
||||
editor = index.data(self.RoleEditor)
|
||||
editor.setModelData(widget)
|
||||
index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole)
|
||||
|
||||
def updateEditorGeometry(self, widget, option, index):
|
||||
# pylint: disable=unused-argument
|
||||
widget.setGeometry(option.rect)
|
||||
|
||||
class PropertyCreate(object):
|
||||
|
||||
def __init__(self, obj, grp, typ, another):
|
||||
self.obj = obj
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui")
|
||||
|
||||
obj.Proxy.refreshCustomPropertyGroups()
|
||||
for g in sorted(obj.CustomPropertyGroups):
|
||||
self.form.propertyGroup.addItem(g)
|
||||
if grp:
|
||||
self.form.propertyGroup.setCurrentText(grp)
|
||||
|
||||
for t in sorted(PathPropertyBag.SupportedPropertyType):
|
||||
self.form.propertyType.addItem(t)
|
||||
if PathPropertyBag.SupportedPropertyType[t] == typ:
|
||||
typ = t
|
||||
if typ:
|
||||
self.form.propertyType.setCurrentText(typ)
|
||||
else:
|
||||
self.form.propertyType.setCurrentText('String')
|
||||
self.form.createAnother.setChecked(another)
|
||||
|
||||
self.form.propertyGroup.currentTextChanged.connect(self.updateUI)
|
||||
self.form.propertyGroup.currentIndexChanged.connect(self.updateUI)
|
||||
self.form.propertyName.textChanged.connect(self.updateUI)
|
||||
self.form.propertyType.currentIndexChanged.connect(self.updateUI)
|
||||
self.form.propertyEnum.textChanged.connect(self.updateUI)
|
||||
|
||||
def updateUI(self):
|
||||
typeSet = True
|
||||
if self.propertyIsEnumeration():
|
||||
self.form.labelEnum.setEnabled(True)
|
||||
self.form.propertyEnum.setEnabled(True)
|
||||
typeSet = self.form.propertyEnum.text().strip() != ''
|
||||
else:
|
||||
self.form.labelEnum.setEnabled(False)
|
||||
self.form.propertyEnum.setEnabled(False)
|
||||
if self.form.propertyEnum.text().strip():
|
||||
self.form.propertyEnum.setText('')
|
||||
|
||||
ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok)
|
||||
|
||||
if typeSet and self.propertyName() and self.propertyGroup():
|
||||
ok.setEnabled(True)
|
||||
else:
|
||||
ok.setEnabled(False)
|
||||
|
||||
def propertyName(self):
|
||||
return self.form.propertyName.text().strip()
|
||||
def propertyGroup(self):
|
||||
return self.form.propertyGroup.currentText().strip()
|
||||
def propertyType(self):
|
||||
return PathPropertyBag.SupportedPropertyType[self.form.propertyType.currentText()].strip()
|
||||
def propertyInfo(self):
|
||||
return self.form.propertyInfo.toPlainText().strip()
|
||||
def createAnother(self):
|
||||
return self.form.createAnother.isChecked()
|
||||
def propertyEnumerations(self):
|
||||
return [s.strip() for s in self.form.propertyEnum.text().strip().split(',')]
|
||||
def propertyIsEnumeration(self):
|
||||
return self.propertyType() == 'App::PropertyEnumeration'
|
||||
|
||||
def exec_(self, name):
|
||||
if name:
|
||||
# property exists - this is an edit operation
|
||||
self.form.propertyName.setText(name)
|
||||
if self.propertyIsEnumeration():
|
||||
self.form.propertyEnum.setText(','.join(self.obj.getEnumerationsOfProperty(name)))
|
||||
self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name))
|
||||
|
||||
self.form.labelName.setEnabled(False)
|
||||
self.form.propertyName.setEnabled(False)
|
||||
self.form.labelType.setEnabled(False)
|
||||
self.form.propertyType.setEnabled(False)
|
||||
self.form.createAnother.setEnabled(False)
|
||||
|
||||
else:
|
||||
self.form.propertyName.setText('')
|
||||
self.form.propertyInfo.setText('')
|
||||
self.form.propertyEnum.setText('')
|
||||
#self.form.propertyName.setFocus()
|
||||
|
||||
self.updateUI()
|
||||
|
||||
return self.form.exec_()
|
||||
|
||||
Panel = []
|
||||
|
||||
class TaskPanel(object):
|
||||
ColumnName = 0
|
||||
#ColumnType = 1
|
||||
ColumnVal = 1
|
||||
#TableHeaders = ['Property', 'Type', 'Value']
|
||||
TableHeaders = ['Property', 'Value']
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.obj = vobj.Object
|
||||
self.props = sorted(self.obj.Proxy.getCustomProperties())
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui")
|
||||
|
||||
# initialized later
|
||||
self.model = None
|
||||
self.delegate = None
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Edit PropertyBag"))
|
||||
Panel.append(self)
|
||||
|
||||
def updateData(self, topLeft, bottomRight):
|
||||
pass
|
||||
|
||||
|
||||
def _setupProperty(self, i, name):
|
||||
typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name))
|
||||
val = PathUtil.getPropertyValueString(self.obj, name)
|
||||
info = self.obj.getDocumentationOfProperty(name)
|
||||
|
||||
self.model.setData(self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole)
|
||||
#self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole)
|
||||
self.model.setData(self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject)
|
||||
self.model.setData(self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty)
|
||||
self.model.setData(self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole)
|
||||
|
||||
self.model.setData(self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole)
|
||||
#self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole)
|
||||
self.model.setData(self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole)
|
||||
|
||||
self.model.item(i, self.ColumnName).setEditable(False)
|
||||
#self.model.item(i, self.ColumnType).setEditable(False)
|
||||
|
||||
def setupUi(self):
|
||||
PathLog.track()
|
||||
|
||||
self.delegate = Delegate(self.form)
|
||||
self.model = QtGui.QStandardItemModel(len(self.props), len(self.TableHeaders), self.form)
|
||||
self.model.setHorizontalHeaderLabels(self.TableHeaders)
|
||||
|
||||
for i,name in enumerate(self.props):
|
||||
self._setupProperty(i, name)
|
||||
|
||||
self.form.table.setModel(self.model)
|
||||
self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate)
|
||||
self.form.table.resizeColumnsToContents()
|
||||
|
||||
self.model.dataChanged.connect(self.updateData)
|
||||
self.form.table.selectionModel().selectionChanged.connect(self.propertySelected)
|
||||
self.form.add.clicked.connect(self.propertyAdd)
|
||||
self.form.remove.clicked.connect(self.propertyRemove)
|
||||
self.form.modify.clicked.connect(self.propertyModify)
|
||||
self.form.table.doubleClicked.connect(self.propertyModifyIndex)
|
||||
self.propertySelected([])
|
||||
|
||||
def accept(self):
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def reject(self):
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def propertySelected(self, selection):
|
||||
PathLog.track()
|
||||
if selection:
|
||||
self.form.modify.setEnabled(True)
|
||||
self.form.remove.setEnabled(True)
|
||||
else:
|
||||
self.form.modify.setEnabled(False)
|
||||
self.form.remove.setEnabled(False)
|
||||
|
||||
def addCustomProperty(self, obj, dialog):
|
||||
name = dialog.propertyName()
|
||||
typ = dialog.propertyType()
|
||||
grp = dialog.propertyGroup()
|
||||
info = dialog.propertyInfo()
|
||||
self.obj.Proxy.addCustomProperty(typ, name, grp, info)
|
||||
if dialog.propertyIsEnumeration():
|
||||
setattr(self.obj, name, dialog.propertyEnumerations())
|
||||
return (name, info)
|
||||
|
||||
def propertyAdd(self):
|
||||
PathLog.track()
|
||||
more = False
|
||||
grp = None
|
||||
typ = None
|
||||
while True:
|
||||
dialog = PropertyCreate(self.obj, grp, typ, more)
|
||||
if dialog.exec_(None):
|
||||
# if we block signals the view doesn't get updated, surprise, surprise
|
||||
#self.model.blockSignals(True)
|
||||
name, info = self.addCustomProperty(self.obj, dialog)
|
||||
index = 0
|
||||
for i in range(self.model.rowCount()):
|
||||
index = i
|
||||
if self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole) > dialog.propertyName():
|
||||
break
|
||||
self.model.insertRows(index, 1)
|
||||
self._setupProperty(index, name)
|
||||
self.form.table.selectionModel().setCurrentIndex(self.model.index(index, 0), QtCore.QItemSelectionModel.Rows)
|
||||
#self.model.blockSignals(False)
|
||||
more = dialog.createAnother()
|
||||
else:
|
||||
more = False
|
||||
if not more:
|
||||
break
|
||||
|
||||
def propertyModifyIndex(self, index):
|
||||
PathLog.track(index.row(), index.column())
|
||||
row = index.row()
|
||||
|
||||
obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject)
|
||||
nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty)
|
||||
grp = obj.getGroupOfProperty(nam)
|
||||
typ = obj.getTypeIdOfProperty(nam)
|
||||
|
||||
dialog = PropertyCreate(self.obj, grp, typ, False)
|
||||
if dialog.exec_(nam):
|
||||
val = getattr(obj, nam)
|
||||
obj.removeProperty(nam)
|
||||
name, info = self.addCustomProperty(self.obj, dialog)
|
||||
try:
|
||||
setattr(obj, nam, val)
|
||||
except:
|
||||
# this can happen if the old enumeration value doesn't exist anymore
|
||||
pass
|
||||
newVal = PathUtil.getPropertyValueString(obj, nam)
|
||||
self.model.setData(self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole)
|
||||
|
||||
#self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole)
|
||||
self.model.setData(self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole)
|
||||
|
||||
def propertyModify(self):
|
||||
PathLog.track()
|
||||
rows = []
|
||||
for index in self.form.table.selectionModel().selectedIndexes():
|
||||
row = index.row()
|
||||
if row in rows:
|
||||
continue
|
||||
rows.append(row)
|
||||
|
||||
self.propertyModifyIndex(index)
|
||||
|
||||
|
||||
def propertyRemove(self):
|
||||
PathLog.track()
|
||||
# first find all rows which need to be removed
|
||||
rows = []
|
||||
for index in self.form.table.selectionModel().selectedIndexes():
|
||||
if not index.row() in rows:
|
||||
rows.append(index.row())
|
||||
|
||||
# then remove them in reverse order so the indexes of the remaining rows
|
||||
# to delete are still valid
|
||||
for row in reversed(sorted(rows)):
|
||||
self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole))
|
||||
self.model.removeRow(row)
|
||||
|
||||
|
||||
def Create(name = 'PropertyBag'):
|
||||
'''Create(name = 'PropertyBag') ... creates a new setup sheet'''
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathPropertyBag", "Create PropertyBag"))
|
||||
pcont = PathPropertyBag.Create(name)
|
||||
PathIconViewProvider.Attach(pcont.ViewObject, name)
|
||||
return pcont
|
||||
|
||||
PathIconViewProvider.RegisterViewProvider('PropertyBag', ViewProvider)
|
||||
|
||||
class PropertyBagCreateCommand(object):
|
||||
'''Command to create a property container object'''
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def GetResources(self):
|
||||
return {'MenuText': translate('PathPropertyBag', 'Property Bag'),
|
||||
'ToolTip': translate('PathPropertyBag', 'Creates an object which can be used to store reference properties.')}
|
||||
|
||||
def IsActive(self):
|
||||
return not FreeCAD.ActiveDocument is None
|
||||
|
||||
def Activated(self):
|
||||
sel = FreeCADGui.Selection.getSelectionEx()
|
||||
obj = Create()
|
||||
body = None
|
||||
if sel:
|
||||
if 'PartDesign::Body' == sel[0].Object.TypeId:
|
||||
body = sel[0].Object
|
||||
elif hasattr(sel[0].Object, 'getParentGeoFeatureGroup'):
|
||||
body = sel[0].Object.getParentGeoFeatureGroup()
|
||||
if body:
|
||||
obj.Label = 'Attributes'
|
||||
group = body.Group
|
||||
group.append(obj)
|
||||
body.Group = group
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Path_PropertyBag', PropertyBagCreateCommand())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n")
|
||||
@@ -0,0 +1,228 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
__title__ = "Path Property Editor"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Task panel editor for Properties"
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
class _PropertyEditor(object):
|
||||
'''Base class of all property editors - just outlines the TableView delegate interface.'''
|
||||
def __init__(self, obj, prop):
|
||||
self.obj = obj
|
||||
self.prop = prop
|
||||
|
||||
def widget(self, parent):
|
||||
'''widget(parent) ... called by the delegate to get a new editor widget.
|
||||
Must be implemented by subclasses and return the widget.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setEditorData(self, widget):
|
||||
'''setEditorData(widget) ... called by the delegate to initialize the editor.
|
||||
The widget is the object returned by widget().
|
||||
Must be implemented by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setModelData(self, widget):
|
||||
'''setModelData(widget) ... called by the delegate to store new values.
|
||||
Must be implemented by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def propertyValue(self):
|
||||
return self.obj.getPropertyByName(self.prop)
|
||||
|
||||
def setProperty(self, value):
|
||||
setattr(self.obj, self.prop, value)
|
||||
|
||||
def displayString(self):
|
||||
return self.propertyValue()
|
||||
|
||||
class _PropertyEditorBool(_PropertyEditor):
|
||||
'''Editor for boolean values - uses a combo box.'''
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QComboBox(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
widget.clear()
|
||||
widget.addItems([str(False), str(True)])
|
||||
index = 1 if self.propertyValue() else 0
|
||||
widget.setCurrentIndex(index)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.currentText() == str(True))
|
||||
|
||||
class _PropertyEditorString(_PropertyEditor):
|
||||
'''Editor for string values - uses a line edit.'''
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QLineEdit(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
text = '' if self.propertyValue() is None else self.propertyValue()
|
||||
widget.setText(text)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.text())
|
||||
|
||||
class _PropertyEditorQuantity(_PropertyEditor):
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QLineEdit(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
quantity = self.propertyValue()
|
||||
if quantity is None:
|
||||
quantity = self.defaultQuantity()
|
||||
widget.setText(quantity.getUserPreferred()[0])
|
||||
|
||||
def defaultQuantity(self):
|
||||
pass
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(FreeCAD.Units.Quantity(widget.text()))
|
||||
|
||||
def displayString(self):
|
||||
if self.propertyValue() is None:
|
||||
return ''
|
||||
return self.propertyValue().getUserPreferred()[0]
|
||||
|
||||
class _PropertyEditorAngle(_PropertyEditorQuantity):
|
||||
'''Editor for angle values - uses a line edit'''
|
||||
|
||||
def defaultQuantity(self):
|
||||
return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle)
|
||||
|
||||
class _PropertyEditorLength(_PropertyEditorQuantity):
|
||||
'''Editor for length values - uses a line edit.'''
|
||||
|
||||
def defaultQuantity(self):
|
||||
return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length)
|
||||
|
||||
class _PropertyEditorPercent(_PropertyEditor):
|
||||
'''Editor for percent values - uses a spin box.'''
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QSpinBox(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
widget.setRange(0, 100)
|
||||
value = self.propertyValue()
|
||||
if value is None:
|
||||
value = 0
|
||||
widget.setValue(value)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
class _PropertyEditorInteger(_PropertyEditor):
|
||||
'''Editor for integer values - uses a spin box.'''
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QSpinBox(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
value = self.propertyValue()
|
||||
if value is None:
|
||||
value = 0
|
||||
widget.setValue(value)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
class _PropertyEditorFloat(_PropertyEditor):
|
||||
'''Editor for float values - uses a double spin box.'''
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QDoubleSpinBox(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
value = self.propertyValue()
|
||||
if value is None:
|
||||
value = 0.0
|
||||
widget.setValue(value)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
class _PropertyEditorFile(_PropertyEditor):
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QLineEdit(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
text = '' if self.propertyValue() is None else self.propertyValue()
|
||||
widget.setText(text)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.text())
|
||||
|
||||
class _PropertyEditorEnumeration(_PropertyEditor):
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QComboBox(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
widget.clear()
|
||||
widget.addItems(self.obj.getEnumerationsOfProperty(self.prop))
|
||||
widget.setCurrentText(self.propertyValue())
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.currentText())
|
||||
|
||||
_EditorFactory = {
|
||||
'App::PropertyAngle' : _PropertyEditorAngle,
|
||||
'App::PropertyBool' : _PropertyEditorBool,
|
||||
'App::PropertyDistance' : _PropertyEditorLength,
|
||||
'App::PropertyEnumeration' : _PropertyEditorEnumeration,
|
||||
#'App::PropertyFile' : _PropertyEditorFile,
|
||||
'App::PropertyFloat' : _PropertyEditorFloat,
|
||||
'App::PropertyInteger' : _PropertyEditorInteger,
|
||||
'App::PropertyLength' : _PropertyEditorLength,
|
||||
'App::PropertyPercent' : _PropertyEditorPercent,
|
||||
'App::PropertyString' : _PropertyEditorString,
|
||||
}
|
||||
|
||||
def Types():
|
||||
'''Return the types of properties supported.'''
|
||||
return [t for t in _EditorFactory]
|
||||
|
||||
def Editor(obj, prop):
|
||||
'''Returns an editor class to be used for the given property.'''
|
||||
factory = _EditorFactory[obj.getTypeIdOfProperty(prop)]
|
||||
if factory:
|
||||
return factory(obj, prop)
|
||||
return None
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathIconViewProvider as PathIconViewProvider
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -67,8 +67,7 @@ class CommandPathSimpleCopy:
|
||||
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.addModule("PathScripts.PathCustom")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","' + selection[0].Name + '_SimpleCopy")')
|
||||
FreeCADGui.doCommand('PathScripts.PathCustom.ObjectCustom(obj)')
|
||||
FreeCADGui.doCommand('obj = PathScripts.PathCustom.Create("' + selection[0].Name + '_SimpleCopy")')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy = 0')
|
||||
FreeCADGui.doCommand('obj.Gcode = [c.toGCode() for c in srcpath.Commands]')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathDressup as PathDressup
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathSlot as PathSlot
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathSurface as PathSurface
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathThreadMilling as PathThreadMilling
|
||||
import PathScripts.PathGui as PathGui
|
||||
|
||||
@@ -24,6 +24,7 @@ import FreeCAD
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathPropertyBag as PathPropertyBag
|
||||
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PySide
|
||||
@@ -42,74 +43,73 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Class to deal with and represent a tool bit."
|
||||
|
||||
# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
# PathLog.trackModule()
|
||||
PropertyGroupShape = 'Shape'
|
||||
|
||||
_DebugFindTool = False
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule()
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
def _findToolFile(name, containerFile, typ):
|
||||
PathLog.track(name)
|
||||
if os.path.exists(name): # absolute reference
|
||||
return name
|
||||
|
||||
ParameterTypeConstraint = {
|
||||
'Angle': 'App::PropertyAngle',
|
||||
'Distance': 'App::PropertyLength',
|
||||
'DistanceX': 'App::PropertyLength',
|
||||
'DistanceY': 'App::PropertyLength',
|
||||
'Radius': 'App::PropertyLength'}
|
||||
if containerFile:
|
||||
rootPath = os.path.dirname(os.path.dirname(containerFile))
|
||||
paths = [os.path.join(rootPath, typ)]
|
||||
else:
|
||||
paths = []
|
||||
paths.extend(PathPreferences.searchPathsTool(typ))
|
||||
|
||||
def _findFile(path, name):
|
||||
PathLog.track(path, name)
|
||||
fullPath = os.path.join(path, name)
|
||||
if os.path.exists(fullPath):
|
||||
return (True, fullPath)
|
||||
for root, ds, fs in os.walk(path):
|
||||
for d in ds:
|
||||
found, fullPath = _findFile(d, name)
|
||||
if found:
|
||||
return (True, fullPath)
|
||||
return (False, None)
|
||||
|
||||
|
||||
def _findTool(path, typ, dbg=False):
|
||||
if os.path.exists(path): # absolute reference
|
||||
if dbg:
|
||||
PathLog.debug("Found {} at {}".format(typ, path))
|
||||
return path
|
||||
|
||||
def searchFor(pname, fname):
|
||||
# PathLog.debug("pname: {} fname: {}".format(pname, fname))
|
||||
if dbg:
|
||||
PathLog.debug("Looking for {}".format(pname))
|
||||
if fname:
|
||||
for p in PathPreferences.searchPathsTool(typ):
|
||||
PathLog.track(p)
|
||||
f = os.path.join(p, fname)
|
||||
if dbg:
|
||||
PathLog.debug(" Checking {}".format(f))
|
||||
if os.path.exists(f):
|
||||
if dbg:
|
||||
PathLog.debug(" Found {} at {}".format(typ, f))
|
||||
return f
|
||||
if pname and os.path.sep != pname:
|
||||
PathLog.track(pname)
|
||||
ppname, pfname = os.path.split(pname)
|
||||
ffname = os.path.join(pfname, fname) if fname else pfname
|
||||
return searchFor(ppname, ffname)
|
||||
return None
|
||||
|
||||
return searchFor(path, '')
|
||||
for p in paths:
|
||||
found, path = _findFile(p, name)
|
||||
if found:
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def findShape(path):
|
||||
'''
|
||||
findShape(path) ... search for path, full and partially
|
||||
in all known shape directories.
|
||||
'''
|
||||
return _findTool(path, 'Shape')
|
||||
def findToolShape(name, path=None):
|
||||
'''findToolShape(name, path) ... search for name, if relative path look in path'''
|
||||
PathLog.track(name, path)
|
||||
return _findToolFile(name, path, 'Shape')
|
||||
|
||||
|
||||
def findBit(path):
|
||||
PathLog.track(path)
|
||||
if path.endswith('.fctb'):
|
||||
return _findTool(path, 'Bit')
|
||||
return _findTool("{}.fctb".format(path), 'Bit')
|
||||
def findToolBit(name, path=None):
|
||||
'''findToolBit(name, path) ... search for name, if relative path look in path'''
|
||||
PathLog.track(name, path)
|
||||
if name.endswith('.fctb'):
|
||||
return _findToolFile(name, path, 'Bit')
|
||||
return _findToolFile("{}.fctb".format(name), path, 'Bit')
|
||||
|
||||
|
||||
def findLibrary(path, dbg=False):
|
||||
if path.endswith('.fctl'):
|
||||
return _findTool(path, 'Library', dbg)
|
||||
return _findTool("{}.fctl".format(path), 'Library', dbg)
|
||||
def findToolLibrary(name, path=None):
|
||||
'''findToolLibrary(name, path) ... search for name, if relative path look in path'''
|
||||
PathLog.track(name, path)
|
||||
if name.endswith('.fctl'):
|
||||
return _findToolFile(name, path, 'Library')
|
||||
return _findToolFile("{}.fctl".format(name), path, 'Library')
|
||||
|
||||
|
||||
def _findRelativePath(path, typ):
|
||||
PathLog.track(path, typ)
|
||||
relative = path
|
||||
for p in PathPreferences.searchPathsTool(typ):
|
||||
if path.startswith(p):
|
||||
@@ -132,48 +132,19 @@ def findRelativePathTool(path):
|
||||
def findRelativePathLibrary(path):
|
||||
return _findRelativePath(path, 'Library')
|
||||
|
||||
|
||||
def updateConstraint(sketch, name, value):
|
||||
for i, constraint in enumerate(sketch.Constraints):
|
||||
if constraint.Name.split(';')[0] == name:
|
||||
constr = None
|
||||
if constraint.Type in ['DistanceX', 'DistanceY', 'Distance', 'Radius', 'Angle']:
|
||||
constr = Sketcher.Constraint(constraint.Type, constraint.First, constraint.FirstPos, constraint.Second, constraint.SecondPos, value)
|
||||
else:
|
||||
print(constraint.Name, constraint.Type)
|
||||
|
||||
if constr is not None:
|
||||
if not PathGeom.isRoughly(constraint.Value, value.Value):
|
||||
PathLog.track(name, constraint.Type,
|
||||
'update', i, "(%.2f -> %.2f)"
|
||||
% (constraint.Value, value.Value))
|
||||
sketch.delConstraint(i)
|
||||
sketch.recompute()
|
||||
n = sketch.addConstraint(constr)
|
||||
sketch.renameConstraint(n, constraint.Name)
|
||||
else:
|
||||
PathLog.track(name, constraint.Type, 'unchanged')
|
||||
break
|
||||
|
||||
|
||||
PropertyGroupBit = 'Bit'
|
||||
PropertyGroupAttribute = 'Attribute'
|
||||
|
||||
|
||||
class ToolBit(object):
|
||||
|
||||
def __init__(self, obj, shapeFile):
|
||||
PathLog.track(obj.Label, shapeFile)
|
||||
def __init__(self, obj, shapeFile, path=None):
|
||||
PathLog.track(obj.Label, shapeFile, path)
|
||||
self.obj = obj
|
||||
obj.addProperty('App::PropertyFile', 'BitShape', 'Base',
|
||||
translate('PathToolBit', 'Shape for bit shape'))
|
||||
obj.addProperty('App::PropertyLink', 'BitBody', 'Base',
|
||||
translate('PathToolBit',
|
||||
'The parametrized body representing the tool bit'))
|
||||
obj.addProperty('App::PropertyFile', 'File', 'Base',
|
||||
translate('PathToolBit', 'The file of the tool'))
|
||||
obj.addProperty('App::PropertyString', 'ShapeName', 'Base',
|
||||
translate('PathToolBit', 'The name of the shape file'))
|
||||
obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape'))
|
||||
obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit'))
|
||||
obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool'))
|
||||
obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file'))
|
||||
obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit'))
|
||||
|
||||
if path:
|
||||
obj.File = path
|
||||
if shapeFile is None:
|
||||
obj.BitShape = 'endmill.fcstd'
|
||||
self._setupBitShape(obj)
|
||||
@@ -193,12 +164,6 @@ class ToolBit(object):
|
||||
break
|
||||
return None
|
||||
|
||||
def propertyNamesBit(self, obj):
|
||||
return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupBit]
|
||||
|
||||
def propertyNamesAttribute(self, obj):
|
||||
return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupAttribute]
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
# when files are shared it is essential to be able to change/set the shape file,
|
||||
# otherwise the file is hard to use
|
||||
@@ -206,19 +171,39 @@ class ToolBit(object):
|
||||
obj.setEditorMode('BitBody', 2)
|
||||
obj.setEditorMode('File', 1)
|
||||
obj.setEditorMode('Shape', 2)
|
||||
if not hasattr(obj, 'BitPropertyNames'):
|
||||
obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit'))
|
||||
propNames = []
|
||||
for prop in obj.PropertiesList:
|
||||
if obj.getGroupOfProperty(prop) == 'Bit':
|
||||
val = obj.getPropertyByName(prop)
|
||||
typ = obj.getTypeIdOfProperty(prop)
|
||||
dsc = obj.getDocumentationOfProperty(prop)
|
||||
|
||||
for prop in self.propertyNamesBit(obj):
|
||||
obj.setEditorMode(prop, 1)
|
||||
# I currently don't see why these need to be read-only
|
||||
# for prop in self.propertyNamesAttribute(obj):
|
||||
# obj.setEditorMode(prop, 1)
|
||||
obj.removeProperty(prop)
|
||||
obj.addProperty(typ, prop, PropertyGroupShape, dsc)
|
||||
|
||||
PathUtil.setProperty(obj, prop, val)
|
||||
propNames.append(prop)
|
||||
elif obj.getGroupOfProperty(prop) == 'Attribute':
|
||||
propNames.append(prop)
|
||||
obj.BitPropertyNames = propNames
|
||||
obj.setEditorMode('BitPropertyNames', 2)
|
||||
|
||||
for prop in obj.BitPropertyNames:
|
||||
if obj.getGroupOfProperty(prop) == PropertyGroupShape:
|
||||
# properties in the Shape group can only be modified while the actual
|
||||
# shape is loaded, so we have to disable direct property editing
|
||||
obj.setEditorMode(prop, 1)
|
||||
else:
|
||||
# all other custom properties can and should be edited directly in the
|
||||
# property editor widget, not much value in re-implementing that
|
||||
obj.setEditorMode(prop, 0)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
PathLog.track(obj.Label, prop)
|
||||
if prop == 'BitShape' and 'Restore' not in obj.State:
|
||||
self._setupBitShape(obj)
|
||||
# elif obj.getGroupOfProperty(prop) == PropertyGroupBit:
|
||||
# self._updateBitShape(obj, [prop])
|
||||
|
||||
def onDelete(self, obj, arg2=None):
|
||||
PathLog.track(obj.Label)
|
||||
@@ -227,12 +212,19 @@ class ToolBit(object):
|
||||
|
||||
def _updateBitShape(self, obj, properties=None):
|
||||
if obj.BitBody is not None:
|
||||
if not properties:
|
||||
properties = self.propertyNamesBit(obj)
|
||||
for prop in properties:
|
||||
for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']:
|
||||
PathLog.track(obj.Label, sketch.Label, prop)
|
||||
updateConstraint(sketch, prop, obj.getPropertyByName(prop))
|
||||
for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]:
|
||||
for prop in attributes.Proxy.getCustomProperties():
|
||||
# the property might not exist in our local object (new attribute in shape)
|
||||
# for such attributes we just keep the default
|
||||
if hasattr(obj, prop):
|
||||
setattr(attributes, prop, obj.getPropertyByName(prop))
|
||||
else:
|
||||
# if the template shape has a new attribute defined we should add that
|
||||
# to the local object
|
||||
self._setupProperty(obj, prop, attributes)
|
||||
propNames = obj.BitPropertyNames
|
||||
propNames.append(prop)
|
||||
obj.BitPropertyNames = propNames
|
||||
self._copyBitShape(obj)
|
||||
|
||||
def _copyBitShape(self, obj):
|
||||
@@ -243,6 +235,7 @@ class ToolBit(object):
|
||||
obj.Shape = Part.Shape()
|
||||
|
||||
def _loadBitBody(self, obj, path=None):
|
||||
PathLog.track(obj.Label, path)
|
||||
p = path if path else obj.BitShape
|
||||
docOpened = False
|
||||
doc = None
|
||||
@@ -251,12 +244,15 @@ class ToolBit(object):
|
||||
doc = FreeCAD.getDocument(d)
|
||||
break
|
||||
if doc is None:
|
||||
p = findShape(p)
|
||||
p = findToolShape(p, path if path else obj.File)
|
||||
if not path and p != obj.BitShape:
|
||||
obj.BitShape = p
|
||||
PathLog.debug("ToolBit {} using shape file: {}".format(obj.Label, p))
|
||||
doc = FreeCAD.openDocument(p, True)
|
||||
obj.ShapeName = doc.Name
|
||||
docOpened = True
|
||||
else:
|
||||
PathLog.debug("ToolBit {} already open: {}".format(obj.Label, doc))
|
||||
return (doc, docOpened)
|
||||
|
||||
def _removeBitBody(self, obj):
|
||||
@@ -269,7 +265,7 @@ class ToolBit(object):
|
||||
PathLog.track(obj.Label)
|
||||
self._removeBitBody(obj)
|
||||
self._copyBitShape(obj)
|
||||
for prop in self.propertyNamesBit(obj):
|
||||
for prop in obj.BitPropertyNames:
|
||||
obj.removeProperty(prop)
|
||||
|
||||
def loadBitBody(self, obj, force=False):
|
||||
@@ -287,6 +283,20 @@ class ToolBit(object):
|
||||
def unloadBitBody(self, obj):
|
||||
self._removeBitBody(obj)
|
||||
|
||||
def _setupProperty(self, obj, prop, orig):
|
||||
# extract property parameters and values so it can be copied
|
||||
val = orig.getPropertyByName(prop)
|
||||
typ = orig.getTypeIdOfProperty(prop)
|
||||
grp = orig.getGroupOfProperty(prop)
|
||||
dsc = orig.getDocumentationOfProperty(prop)
|
||||
|
||||
obj.addProperty(typ, prop, grp, dsc)
|
||||
if 'App::PropertyEnumeration' == typ:
|
||||
setattr(obj, prop, orig.getEnumerationsOfProperty(prop))
|
||||
|
||||
obj.setEditorMode(prop, 1)
|
||||
PathUtil.setProperty(obj, prop, val)
|
||||
|
||||
def _setupBitShape(self, obj, path=None):
|
||||
PathLog.track(obj.Label)
|
||||
|
||||
@@ -296,6 +306,8 @@ class ToolBit(object):
|
||||
obj.Label = doc.RootObjects[0].Label
|
||||
self._deleteBitSetup(obj)
|
||||
bitBody = obj.Document.copyObject(doc.RootObjects[0], True)
|
||||
|
||||
docName = doc.Name
|
||||
if docOpened:
|
||||
FreeCAD.setActiveDocument(activeDoc.Name)
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
@@ -303,29 +315,44 @@ class ToolBit(object):
|
||||
if bitBody.ViewObject:
|
||||
bitBody.ViewObject.Visibility = False
|
||||
|
||||
for sketch in [o for o in bitBody.Group if o.TypeId == 'Sketcher::SketchObject']:
|
||||
for constraint in [c for c in sketch.Constraints if c.Name != '']:
|
||||
typ = ParameterTypeConstraint.get(constraint.Type)
|
||||
PathLog.track(constraint, typ)
|
||||
if typ is not None:
|
||||
parts = [p.strip() for p in constraint.Name.split(';')]
|
||||
prop = parts[0]
|
||||
desc = ''
|
||||
if len(parts) > 1:
|
||||
desc = parts[1]
|
||||
obj.addProperty(typ, prop, PropertyGroupBit, desc)
|
||||
obj.setEditorMode(prop, 1)
|
||||
value = constraint.Value
|
||||
if constraint.Type == 'Angle':
|
||||
value = value * 180 / math.pi
|
||||
PathUtil.setProperty(obj, prop, value)
|
||||
PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody)))
|
||||
|
||||
propNames = []
|
||||
for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]:
|
||||
PathLog.debug("Process properties from {}".format(attributes.Label))
|
||||
for prop in attributes.Proxy.getCustomProperties():
|
||||
self._setupProperty(obj, prop, attributes)
|
||||
propNames.append(prop)
|
||||
if not propNames:
|
||||
PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?').format(docName))
|
||||
|
||||
# has to happen last because it could trigger op.execute evaluations
|
||||
obj.BitPropertyNames = propNames
|
||||
obj.BitBody = bitBody
|
||||
self._copyBitShape(obj)
|
||||
|
||||
def toolShapeProperties(self, obj):
|
||||
'''toolShapeProperties(obj) ... return all properties defining it's shape'''
|
||||
return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape])
|
||||
|
||||
def toolAdditionalProperties(self, obj):
|
||||
'''toolShapeProperties(obj) ... return all properties unrelated to it's shape'''
|
||||
return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) != PropertyGroupShape])
|
||||
|
||||
def toolGroupsAndProperties(self, obj, includeShape=True):
|
||||
'''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.'''
|
||||
category = {}
|
||||
for prop in obj.BitPropertyNames:
|
||||
group = obj.getGroupOfProperty(prop)
|
||||
if includeShape or group != PropertyGroupShape:
|
||||
properties = category.get(group, [])
|
||||
properties.append(prop)
|
||||
category[group] = properties
|
||||
return category
|
||||
|
||||
def getBitThumbnail(self, obj):
|
||||
if obj.BitShape:
|
||||
path = findShape(obj.BitShape)
|
||||
path = findToolShape(obj.BitShape)
|
||||
if path:
|
||||
with open(path, 'rb') as fd:
|
||||
try:
|
||||
@@ -347,8 +374,7 @@ class ToolBit(object):
|
||||
obj.File = path
|
||||
return True
|
||||
except (OSError, IOError) as e:
|
||||
PathLog.error("Could not save tool {} to {} ({})".format(
|
||||
obj.Label, path, e))
|
||||
PathLog.error("Could not save tool {} to {} ({})".format(obj.Label, path, e))
|
||||
raise
|
||||
|
||||
def templateAttrs(self, obj):
|
||||
@@ -360,16 +386,10 @@ class ToolBit(object):
|
||||
else:
|
||||
attrs['shape'] = findRelativePathShape(obj.BitShape)
|
||||
params = {}
|
||||
for name in self.propertyNamesBit(obj):
|
||||
for name in obj.BitPropertyNames:
|
||||
params[name] = PathUtil.getPropertyValueString(obj, name)
|
||||
attrs['parameter'] = params
|
||||
params = {}
|
||||
for name in self.propertyNamesAttribute(obj):
|
||||
if name == "UserAttributes":
|
||||
for key, value in obj.UserAttributes.items():
|
||||
params[key] = value
|
||||
else:
|
||||
params[name] = PathUtil.getPropertyValueString(obj, name)
|
||||
attrs['attribute'] = params
|
||||
return attrs
|
||||
|
||||
@@ -380,73 +400,33 @@ def Declaration(path):
|
||||
return json.load(fp)
|
||||
|
||||
|
||||
class AttributePrototype(PathSetupSheetOpPrototype.OpPrototype):
|
||||
|
||||
def __init__(self):
|
||||
PathSetupSheetOpPrototype.OpPrototype.__init__(self, 'ToolBitAttribute')
|
||||
self.addProperty('App::PropertyEnumeration', 'Material',
|
||||
PropertyGroupAttribute,
|
||||
translate('PathToolBit', 'Tool bit material'))
|
||||
self.Material = ['Carbide', 'CastAlloy', 'Ceramics', 'Diamond',
|
||||
'HighCarbonToolSteel', 'HighSpeedSteel', 'Sialon']
|
||||
self.addProperty('App::PropertyDistance', 'LengthOffset',
|
||||
PropertyGroupAttribute, translate('PathToolBit',
|
||||
'Length offset in Z direction'))
|
||||
self.addProperty('App::PropertyInteger', 'Flutes',
|
||||
PropertyGroupAttribute, translate('PathToolBit',
|
||||
'The number of flutes'))
|
||||
self.addProperty('App::PropertyDistance', 'ChipLoad',
|
||||
PropertyGroupAttribute, translate('PathToolBit',
|
||||
'Chipload as per manufacturer'))
|
||||
self.addProperty('App::PropertyMap', 'UserAttributes',
|
||||
PropertyGroupAttribute, translate('PathToolBit',
|
||||
'User Defined Values'))
|
||||
self.addProperty('App::PropertyBool', 'SpindlePower',
|
||||
PropertyGroupAttribute, translate('PathToolBit',
|
||||
'Whether Spindle Power should be allowed'))
|
||||
|
||||
|
||||
class ToolBitFactory(object):
|
||||
|
||||
def CreateFromAttrs(self, attrs, name='ToolBit'):
|
||||
PathLog.debug(attrs)
|
||||
obj = Factory.Create(name, attrs['shape'])
|
||||
def CreateFromAttrs(self, attrs, name='ToolBit', path=None):
|
||||
PathLog.track(attrs, path)
|
||||
obj = Factory.Create(name, attrs['shape'], path)
|
||||
obj.Label = attrs['name']
|
||||
params = attrs['parameter']
|
||||
for prop in params:
|
||||
PathUtil.setProperty(obj, prop, params[prop])
|
||||
obj.Proxy._updateBitShape(obj)
|
||||
obj.Proxy.unloadBitBody(obj)
|
||||
params = attrs['attribute']
|
||||
proto = AttributePrototype()
|
||||
uservals = {}
|
||||
for pname in params:
|
||||
try:
|
||||
prop = proto.getProperty(pname)
|
||||
prop.setupProperty(obj, pname, PropertyGroupAttribute, prop.valueFromString(params[pname]))
|
||||
except Exception:
|
||||
prop = proto.getProperty("UserAttributes")
|
||||
uservals.update({pname: params[pname]})
|
||||
|
||||
if len(uservals.items()) > 0:
|
||||
prop.setupProperty(obj, "UserAttributes",
|
||||
PropertyGroupAttribute, uservals)
|
||||
|
||||
return obj
|
||||
|
||||
def CreateFrom(self, path, name='ToolBit'):
|
||||
PathLog.track(name, path)
|
||||
try:
|
||||
data = Declaration(path)
|
||||
bit = Factory.CreateFromAttrs(data, name)
|
||||
bit.File = path
|
||||
bit = Factory.CreateFromAttrs(data, name, path)
|
||||
return bit
|
||||
except (OSError, IOError) as e:
|
||||
PathLog.error("%s not a valid tool file (%s)" % (path, e))
|
||||
raise
|
||||
|
||||
def Create(self, name='ToolBit', shapeFile=None):
|
||||
def Create(self, name='ToolBit', shapeFile=None, path=None):
|
||||
PathLog.track(name, shapeFile, path)
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name)
|
||||
obj.Proxy = ToolBit(obj, shapeFile)
|
||||
obj.Proxy = ToolBit(obj, shapeFile, path)
|
||||
return obj
|
||||
|
||||
|
||||
|
||||
@@ -20,12 +20,14 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathSetupSheetGui as PathSetupSheetGui
|
||||
import PathScripts.PathPropertyEditor as PathPropertyEditor
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import os
|
||||
import re
|
||||
|
||||
@@ -39,6 +41,31 @@ PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class _Delegate(QtGui.QStyledItemDelegate):
|
||||
'''Handles the creation of an appropriate editing widget for a given property.'''
|
||||
ObjectRole = QtCore.Qt.UserRole + 1
|
||||
PropertyRole = QtCore.Qt.UserRole + 2
|
||||
EditorRole = QtCore.Qt.UserRole + 3
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = index.data(self.EditorRole)
|
||||
if editor is None:
|
||||
obj = index.data(self.ObjectRole)
|
||||
prp = index.data(self.PropertyRole)
|
||||
editor = PathPropertyEditor.Editor(obj, prp)
|
||||
index.model().setData(index, editor, self.EditorRole)
|
||||
return editor.widget(parent)
|
||||
|
||||
def setEditorData(self, widget, index):
|
||||
# called to update the widget with the current data
|
||||
index.data(self.EditorRole).setEditorData(widget)
|
||||
|
||||
def setModelData(self, widget, model, index):
|
||||
# called to update the model with the data from the widget
|
||||
editor = index.data(self.EditorRole)
|
||||
editor.setModelData(widget)
|
||||
index.model().setData(index, PathUtil.getPropertyValueString(editor.obj, editor.prop), QtCore.Qt.DisplayRole)
|
||||
|
||||
|
||||
class ToolBitEditor(object):
|
||||
'''UI and controller for editing a ToolBit.
|
||||
@@ -84,32 +111,35 @@ class ToolBitEditor(object):
|
||||
|
||||
layout = self.form.bitParams.layout()
|
||||
ui = FreeCADGui.UiLoader()
|
||||
nr = 0
|
||||
|
||||
# for all properties either assign them to existing labels and editors
|
||||
# or create additional ones for them if not enough have already been
|
||||
# created.
|
||||
for name in tool.PropertiesList:
|
||||
if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit:
|
||||
if nr < len(self.widgets):
|
||||
PathLog.debug("re-use row: {} [{}]".format(nr, name))
|
||||
label, qsb, editor = self.widgets[nr]
|
||||
label.setText(labelText(name))
|
||||
editor.attachTo(tool, name)
|
||||
label.show()
|
||||
qsb.show()
|
||||
else:
|
||||
qsb = ui.createWidget('Gui::QuantitySpinBox')
|
||||
editor = PathGui.QuantitySpinBox(qsb, tool, name)
|
||||
label = QtGui.QLabel(labelText(name))
|
||||
self.widgets.append((label, qsb, editor))
|
||||
PathLog.debug("create row: {} [{}]".format(nr, name))
|
||||
if nr >= layout.rowCount():
|
||||
layout.addRow(label, qsb)
|
||||
nr = nr + 1
|
||||
usedRows = 0
|
||||
for nr, name in enumerate(tool.Proxy.toolShapeProperties(tool)):
|
||||
if nr < len(self.widgets):
|
||||
PathLog.debug("re-use row: {} [{}]".format(nr, name))
|
||||
label, qsb, editor = self.widgets[nr]
|
||||
label.setText(labelText(name))
|
||||
editor.attachTo(tool, name)
|
||||
label.show()
|
||||
qsb.show()
|
||||
else:
|
||||
qsb = ui.createWidget('Gui::QuantitySpinBox')
|
||||
editor = PathGui.QuantitySpinBox(qsb, tool, name)
|
||||
label = QtGui.QLabel(labelText(name))
|
||||
self.widgets.append((label, qsb, editor))
|
||||
PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb)))
|
||||
if hasattr(qsb, 'editingFinished'):
|
||||
qsb.editingFinished.connect(self.updateTool)
|
||||
|
||||
if nr >= layout.rowCount():
|
||||
layout.addRow(label, qsb)
|
||||
usedRows = usedRows + 1
|
||||
|
||||
# hide all rows which aren't being used
|
||||
for i in range(nr, len(self.widgets)):
|
||||
PathLog.track(usedRows, len(self.widgets))
|
||||
for i in range(usedRows, len(self.widgets)):
|
||||
label, qsb, editor = self.widgets[i]
|
||||
label.hide()
|
||||
qsb.hide()
|
||||
@@ -124,98 +154,48 @@ class ToolBitEditor(object):
|
||||
|
||||
def setupAttributes(self, tool):
|
||||
PathLog.track()
|
||||
self.proto = PathToolBit.AttributePrototype()
|
||||
self.props = sorted(self.proto.properties)
|
||||
self.delegate = PathSetupSheetGui.Delegate(self.form)
|
||||
self.model = QtGui.QStandardItemModel(len(self.props)-1, 3, self.form)
|
||||
self.model.setHorizontalHeaderLabels(['Set', 'Property', 'Value'])
|
||||
|
||||
for i, name in enumerate(self.props):
|
||||
PathLog.debug("propname: %s " % name)
|
||||
setup = True
|
||||
if not hasattr(self, 'delegate'):
|
||||
self.delegate = _Delegate(self.form.attrTree)
|
||||
self.model = QtGui.QStandardItemModel(self.form.attrTree)
|
||||
self.model.setHorizontalHeaderLabels(['Property', 'Value'])
|
||||
else:
|
||||
self.model.removeRows(0, self.model.rowCount())
|
||||
setup = False
|
||||
|
||||
prop = self.proto.getProperty(name)
|
||||
isset = hasattr(tool, name)
|
||||
attributes = tool.Proxy.toolGroupsAndProperties(tool, False)
|
||||
for name in attributes:
|
||||
group = QtGui.QStandardItem()
|
||||
group.setData(name, QtCore.Qt.EditRole)
|
||||
group.setEditable(False)
|
||||
for prop in attributes[name]:
|
||||
label = QtGui.QStandardItem()
|
||||
label.setData(prop, QtCore.Qt.EditRole)
|
||||
label.setEditable(False)
|
||||
|
||||
if isset:
|
||||
prop.setValue(getattr(tool, name))
|
||||
value = QtGui.QStandardItem()
|
||||
value.setData(PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole)
|
||||
value.setData(tool, _Delegate.ObjectRole)
|
||||
value.setData(prop, _Delegate.PropertyRole)
|
||||
|
||||
if name == "UserAttributes":
|
||||
continue
|
||||
group.appendRow([label, value])
|
||||
self.model.appendRow(group)
|
||||
|
||||
else:
|
||||
|
||||
self.model.setData(self.model.index(i, 0), isset,
|
||||
QtCore.Qt.EditRole)
|
||||
self.model.setData(self.model.index(i, 1), name,
|
||||
QtCore.Qt.EditRole)
|
||||
self.model.setData(self.model.index(i, 2), prop,
|
||||
PathSetupSheetGui.Delegate.PropertyRole)
|
||||
self.model.setData(self.model.index(i, 2),
|
||||
prop.displayString(),
|
||||
QtCore.Qt.DisplayRole)
|
||||
|
||||
self.model.item(i, 0).setCheckable(True)
|
||||
self.model.item(i, 0).setText('')
|
||||
self.model.item(i, 1).setEditable(False)
|
||||
self.model.item(i, 1).setToolTip(prop.info)
|
||||
self.model.item(i, 2).setToolTip(prop.info)
|
||||
|
||||
if isset:
|
||||
self.model.item(i, 0).setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
self.model.item(i, 0).setCheckState(QtCore.Qt.Unchecked)
|
||||
self.model.item(i, 1).setEnabled(False)
|
||||
self.model.item(i, 2).setEnabled(False)
|
||||
|
||||
if hasattr(tool, "UserAttributes"):
|
||||
for key, value in tool.UserAttributes.items():
|
||||
PathLog.debug(key, value)
|
||||
c1 = QtGui.QStandardItem()
|
||||
c1.setCheckable(False)
|
||||
c1.setEditable(False)
|
||||
c1.setCheckState(QtCore.Qt.CheckState.Checked)
|
||||
|
||||
c1.setText('')
|
||||
c2 = QtGui.QStandardItem(key)
|
||||
c2.setEditable(False)
|
||||
c3 = QtGui.QStandardItem(value)
|
||||
c3.setEditable(False)
|
||||
|
||||
self.model.appendRow([c1, c2, c3])
|
||||
|
||||
self.form.attrTable.setModel(self.model)
|
||||
self.form.attrTable.setItemDelegateForColumn(2, self.delegate)
|
||||
self.form.attrTable.resizeColumnsToContents()
|
||||
self.form.attrTable.verticalHeader().hide()
|
||||
|
||||
self.model.dataChanged.connect(self.updateData)
|
||||
|
||||
def updateData(self, topLeft, bottomRight):
|
||||
PathLog.track()
|
||||
if 0 == topLeft.column():
|
||||
isset = self.model.item(topLeft.row(),
|
||||
0).checkState() == QtCore.Qt.Checked
|
||||
self.model.item(topLeft.row(), 1).setEnabled(isset)
|
||||
self.model.item(topLeft.row(), 2).setEnabled(isset)
|
||||
if setup:
|
||||
self.form.attrTree.setModel(self.model)
|
||||
self.form.attrTree.setItemDelegateForColumn(1, self.delegate)
|
||||
self.form.attrTree.expandAll()
|
||||
self.form.attrTree.resizeColumnToContents(0)
|
||||
self.form.attrTree.resizeColumnToContents(1)
|
||||
#self.form.attrTree.collapseAll()
|
||||
|
||||
def accept(self):
|
||||
PathLog.track()
|
||||
self.refresh()
|
||||
self.tool.Proxy.unloadBitBody(self.tool)
|
||||
|
||||
# get the attributes
|
||||
for i, name in enumerate(self.props):
|
||||
PathLog.debug('in accept: {}'.format(name))
|
||||
prop = self.proto.getProperty(name)
|
||||
if self.model.item(i, 0) is not None:
|
||||
enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked
|
||||
if enabled and not prop.getValue() is None:
|
||||
prop.setupProperty(self.tool, name,
|
||||
PathToolBit.PropertyGroupAttribute,
|
||||
prop.getValue())
|
||||
elif hasattr(self.tool, name):
|
||||
self.tool.removeProperty(name)
|
||||
|
||||
def reject(self):
|
||||
PathLog.track()
|
||||
self.tool.Proxy.unloadBitBody(self.tool)
|
||||
@@ -228,27 +208,45 @@ class ToolBitEditor(object):
|
||||
for lbl, qsb, editor in self.widgets:
|
||||
editor.updateSpinBox()
|
||||
|
||||
def _updateBitShape(self, shapePath):
|
||||
# Only need to go through this exercise if the shape actually changed.
|
||||
if self.tool.BitShape != shapePath:
|
||||
# Before setting a new bitshape we need to make sure that none of
|
||||
# editors fires an event and tries to access its old property, which
|
||||
# might not exist anymore.
|
||||
for lbl, qsb, editor in self.widgets:
|
||||
editor.attachTo(self.tool, 'File')
|
||||
self.tool.BitShape = shapePath
|
||||
self.setupTool(self.tool)
|
||||
self.form.toolName.setText(self.tool.Label)
|
||||
if self.tool.BitBody and self.tool.BitBody.ViewObject:
|
||||
if not self.tool.BitBody.ViewObject.Visibility:
|
||||
self.tool.BitBody.ViewObject.Visibility = True
|
||||
self.setupAttributes(self.tool)
|
||||
return True
|
||||
return False
|
||||
|
||||
def updateShape(self):
|
||||
PathLog.track()
|
||||
shapePath = str(self.form.shapePath.text())
|
||||
# Only need to go through this exercise if the shape actually changed.
|
||||
if self.tool.BitShape != shapePath:
|
||||
self.tool.BitShape = shapePath
|
||||
self.setupTool(self.tool)
|
||||
self.form.toolName.setText(self.tool.Label)
|
||||
|
||||
if self._updateBitShape(shapePath):
|
||||
for lbl, qsb, editor in self.widgets:
|
||||
editor.updateSpinBox()
|
||||
|
||||
def updateTool(self):
|
||||
PathLog.track()
|
||||
self.tool.Label = str(self.form.toolName.text())
|
||||
self.tool.BitShape = str(self.form.shapePath.text())
|
||||
|
||||
label = str(self.form.toolName.text())
|
||||
shape = str(self.form.shapePath.text())
|
||||
if self.tool.Label != label:
|
||||
self.tool.Label = label
|
||||
self._updateBitShape(shape)
|
||||
|
||||
for lbl, qsb, editor in self.widgets:
|
||||
editor.updateProperty()
|
||||
|
||||
# self.tool.Proxy._updateBitShape(self.tool)
|
||||
self.tool.Proxy._updateBitShape(self.tool)
|
||||
|
||||
def refresh(self):
|
||||
PathLog.track()
|
||||
@@ -262,10 +260,7 @@ class ToolBitEditor(object):
|
||||
path = self.tool.BitShape
|
||||
if not path:
|
||||
path = PathPreferences.lastPathToolShape()
|
||||
foo = QtGui.QFileDialog.getOpenFileName(self.form,
|
||||
"Path - Tool Shape",
|
||||
path,
|
||||
"*.fcstd")
|
||||
foo = QtGui.QFileDialog.getOpenFileName(self.form, "Path - Tool Shape", path, "*.fcstd")
|
||||
if foo and foo[0]:
|
||||
PathPreferences.setLastPathToolShape(os.path.dirname(foo[0]))
|
||||
self.form.shapePath.setText(foo[0])
|
||||
|
||||
@@ -170,13 +170,13 @@ class TaskPanel:
|
||||
|
||||
class ToolBitGuiFactory(PathToolBit.ToolBitFactory):
|
||||
|
||||
def Create(self, name='ToolBit', shapeFile=None):
|
||||
def Create(self, name='ToolBit', shapeFile=None, path=None):
|
||||
'''Create(name = 'ToolBit') ... creates a new tool bit.
|
||||
It is assumed the tool will be edited immediately so the internal bit body is still attached.'''
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit',
|
||||
'Create ToolBit'))
|
||||
tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile)
|
||||
PathLog.track(name, shapeFile, path)
|
||||
FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit'))
|
||||
tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path)
|
||||
PathIconViewProvider.Attach(tool.ViewObject, name)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
return tool
|
||||
@@ -247,7 +247,8 @@ def GetToolShapeFile(parent=None):
|
||||
location, '*.fcstd')
|
||||
if fname and fname[0]:
|
||||
if fname != location:
|
||||
PathPreferences.setLastPathToolShape(location)
|
||||
newloc = os.path.dirname(fname[0])
|
||||
PathPreferences.setLastPathToolShape(newloc)
|
||||
return fname[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
@@ -24,23 +24,25 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
import PathScripts.PathToolBitGui as PathToolBitGui
|
||||
import PathScripts.PathToolBitEdit as PathToolBitEdit
|
||||
import PathScripts.PathToolBitGui as PathToolBitGui
|
||||
import PathScripts.PathToolControllerGui as PathToolControllerGui
|
||||
import PathScripts.PathUtilsGui as PathUtilsGui
|
||||
from PySide import QtCore, QtGui
|
||||
import PySide
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import glob
|
||||
import uuid as UUID
|
||||
from functools import partial
|
||||
import shutil
|
||||
import uuid as UUID
|
||||
|
||||
# PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
from functools import partial
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
_UuidRole = PySide.QtCore.Qt.UserRole + 1
|
||||
@@ -50,6 +52,75 @@ _PathRole = PySide.QtCore.Qt.UserRole + 2
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
def checkWorkingDir():
|
||||
# users shouldn't use the example toolbits and libraries.
|
||||
# working directory should be writable
|
||||
PathLog.track()
|
||||
|
||||
workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())
|
||||
defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath())
|
||||
|
||||
PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir))
|
||||
|
||||
dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK))
|
||||
|
||||
if dirOK():
|
||||
return True
|
||||
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No)
|
||||
|
||||
if ret == qm.No:
|
||||
return False
|
||||
|
||||
msg = translate("Path", "Choose a writable location for your toolbits", None)
|
||||
while not dirOK():
|
||||
workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg,
|
||||
PathPreferences.filePath())
|
||||
|
||||
if workingdir[-8:] == os.path.sep + 'Library':
|
||||
workingdir = workingdir[:-8] # trim off trailing /Library if user chose it
|
||||
|
||||
PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep))
|
||||
PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep))
|
||||
PathLog.debug('setting workingdir to: {}'.format(workingdir))
|
||||
|
||||
subdirlist = ['Bit', 'Library', 'Shape']
|
||||
mode = 0o777
|
||||
for dir in subdirlist.copy():
|
||||
subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
|
||||
if os.path.exists(subdir):
|
||||
subdirlist.remove(dir)
|
||||
|
||||
if len(subdirlist) >= 1:
|
||||
needed = ', '.join([str(d) for d in subdirlist])
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No)
|
||||
|
||||
if ret == qm.No:
|
||||
return False
|
||||
else:
|
||||
for dir in subdirlist:
|
||||
subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
|
||||
os.mkdir(subdir, mode)
|
||||
if dir != 'Shape':
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No)
|
||||
if ret == qm.Yes:
|
||||
src="{}{}{}".format(defaultdir, os.path.sep, dir)
|
||||
src_files = os.listdir(src)
|
||||
for file_name in src_files:
|
||||
full_file_name = os.path.join(src, file_name)
|
||||
if os.path.isfile(full_file_name):
|
||||
shutil.copy(full_file_name, subdir)
|
||||
|
||||
|
||||
# if no library is set, choose the first one in the Library directory
|
||||
if PathPreferences.lastFileToolLibrary() is None:
|
||||
libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')]
|
||||
PathPreferences.setLastFileToolLibrary(libFiles[0])
|
||||
|
||||
return True
|
||||
|
||||
class _TableView(PySide.QtGui.QTableView):
|
||||
'''Subclass of QTableView to support rearrange and copying of ToolBits'''
|
||||
@@ -141,7 +212,7 @@ class ModelFactory(object):
|
||||
for toolBit in library['tools']:
|
||||
try:
|
||||
nr = toolBit['nr']
|
||||
bit = PathToolBit.findBit(toolBit['path'])
|
||||
bit = PathToolBit.findToolBit(toolBit['path'], path)
|
||||
if bit:
|
||||
PathLog.track(bit)
|
||||
tool = PathToolBit.Declaration(bit)
|
||||
@@ -203,15 +274,15 @@ class ModelFactory(object):
|
||||
path = PathPreferences.lastPathToolLibrary()
|
||||
|
||||
if os.path.isdir(path): # opening all tables in a directory
|
||||
libFiles = [f for f in glob.glob(path + '/*.fctl')]
|
||||
libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')]
|
||||
libFiles.sort()
|
||||
for libFile in libFiles:
|
||||
loc, fnlong = os.path.split(libFile)
|
||||
fn, ext = os.path.splitext(fnlong)
|
||||
libItem = QtGui.QStandardItem(fn)
|
||||
libItem = PySide.QtGui.QStandardItem(fn)
|
||||
libItem.setToolTip(loc)
|
||||
libItem.setData(libFile, _PathRole)
|
||||
libItem.setIcon(QtGui.QPixmap(':/icons/Path_ToolTable.svg'))
|
||||
libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg'))
|
||||
model.appendRow(libItem)
|
||||
|
||||
PathLog.debug('model rows: {}'.format(model.rowCount()))
|
||||
@@ -241,6 +312,7 @@ class ToolBitSelector(object):
|
||||
'''Controller for displaying a library and creating ToolControllers'''
|
||||
|
||||
def __init__(self):
|
||||
checkWorkingDir()
|
||||
self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui')
|
||||
self.factory = ModelFactory()
|
||||
self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
|
||||
@@ -250,20 +322,20 @@ class ToolBitSelector(object):
|
||||
def columnNames(self):
|
||||
return ['#', 'Tool']
|
||||
|
||||
def curLib(self):
|
||||
def currentLibrary(self, shortNameOnly):
|
||||
libfile = PathPreferences.lastFileToolLibrary()
|
||||
if libfile is None or libfile == "":
|
||||
return ""
|
||||
else:
|
||||
libfile = os.path.split(PathPreferences.lastFileToolLibrary())[1]
|
||||
libfile = os.path.splitext(libfile)[0]
|
||||
elif shortNameOnly:
|
||||
return os.path.splitext(os.path.basename(libfile))[0]
|
||||
return libfile
|
||||
|
||||
def loadData(self):
|
||||
PathLog.track()
|
||||
self.toolModel.clear()
|
||||
self.toolModel.setHorizontalHeaderLabels(self.columnNames())
|
||||
self.form.lblLibrary.setText(self.curLib())
|
||||
self.form.lblLibrary.setText(self.currentLibrary(True))
|
||||
self.form.lblLibrary.setToolTip(self.currentLibrary(False))
|
||||
self.factory.libraryOpen(self.toolModel)
|
||||
self.toolModel.takeColumn(3)
|
||||
self.toolModel.takeColumn(2)
|
||||
@@ -271,7 +343,6 @@ class ToolBitSelector(object):
|
||||
def setupUI(self):
|
||||
PathLog.track()
|
||||
self.loadData()
|
||||
|
||||
self.form.tools.setModel(self.toolModel)
|
||||
self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons)
|
||||
self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers))
|
||||
@@ -340,7 +411,7 @@ class ToolBitSelector(object):
|
||||
|
||||
def open(self, path=None):
|
||||
''' load library stored in path and bring up ui'''
|
||||
docs = FreeCADGui.getMainWindow().findChildren(QtGui.QDockWidget)
|
||||
docs = FreeCADGui.getMainWindow().findChildren(PySide.QtGui.QDockWidget)
|
||||
for doc in docs:
|
||||
if doc.objectName() == "ToolSelector":
|
||||
if doc.isVisible():
|
||||
@@ -351,7 +422,7 @@ class ToolBitSelector(object):
|
||||
return
|
||||
|
||||
mw = FreeCADGui.getMainWindow()
|
||||
mw.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.form,
|
||||
mw.addDockWidget(PySide.QtCore.Qt.RightDockWidgetArea, self.form,
|
||||
PySide.QtCore.Qt.Orientation.Vertical)
|
||||
|
||||
|
||||
@@ -361,9 +432,7 @@ class ToolBitLibrary(object):
|
||||
|
||||
def __init__(self):
|
||||
PathLog.track()
|
||||
if not self.checkWorkingDir():
|
||||
return
|
||||
|
||||
checkWorkingDir()
|
||||
self.factory = ModelFactory()
|
||||
self.temptool = None
|
||||
self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
|
||||
@@ -375,55 +444,6 @@ class ToolBitLibrary(object):
|
||||
self.setupUI()
|
||||
self.title = self.form.windowTitle()
|
||||
|
||||
def checkWorkingDir(self):
|
||||
# users shouldn't use the example toolbits and libraries.
|
||||
# working directory should be writable
|
||||
PathLog.track()
|
||||
|
||||
workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())
|
||||
defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath())
|
||||
|
||||
dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK))
|
||||
|
||||
if dirOK():
|
||||
return True
|
||||
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No)
|
||||
|
||||
if ret == qm.No:
|
||||
return False
|
||||
|
||||
msg = translate("Path", "Choose a writable location for your toolbits", None)
|
||||
while not dirOK():
|
||||
workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg,
|
||||
PathPreferences.filePath())
|
||||
|
||||
PathPreferences.setLastPathToolLibrary("{}/Library".format(workingdir))
|
||||
|
||||
subdirlist = ['Bit', 'Library', 'Shape']
|
||||
mode = 0o777
|
||||
for dir in subdirlist:
|
||||
subdir = "{}/{}".format(workingdir, dir)
|
||||
if not os.path.exists(subdir):
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Toolbit Working directory {} should contain a '{}' subdirectory. Create it?".format(workingdir, dir), qm.Yes | qm.No)
|
||||
|
||||
if ret == qm.Yes:
|
||||
os.mkdir(subdir, mode)
|
||||
qm = PySide.QtGui.QMessageBox
|
||||
ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No)
|
||||
if ret == qm.Yes:
|
||||
src="{}/{}".format(defaultdir, dir)
|
||||
src_files = os.listdir(src)
|
||||
for file_name in src_files:
|
||||
full_file_name = os.path.join(src, file_name)
|
||||
if os.path.isfile(full_file_name):
|
||||
shutil.copy(full_file_name, subdir)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def toolBitNew(self):
|
||||
PathLog.track()
|
||||
|
||||
@@ -439,7 +459,7 @@ class ToolBitLibrary(object):
|
||||
# Parse out the name of the file and write the structure
|
||||
loc, fil = os.path.split(filename)
|
||||
fname = os.path.splitext(fil)[0]
|
||||
fullpath = "{}/{}.fctb".format(loc, fname)
|
||||
fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname)
|
||||
PathLog.debug(fullpath)
|
||||
|
||||
self.temptool = PathToolBit.ToolBitFactory().Create(name=fname)
|
||||
@@ -464,7 +484,7 @@ class ToolBitLibrary(object):
|
||||
|
||||
loc, fil = os.path.split(f)
|
||||
fname = os.path.splitext(fil)[0]
|
||||
fullpath = "{}/{}.fctb".format(loc, fname)
|
||||
fullpath = "{}{}{}.fctb".format(loc, os.path.sep, fname)
|
||||
|
||||
self.factory.newTool(self.toolModel, fullpath)
|
||||
|
||||
@@ -557,8 +577,8 @@ class ToolBitLibrary(object):
|
||||
self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool')
|
||||
self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False)
|
||||
|
||||
QBtn = QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel
|
||||
buttonBox = QtGui.QDialogButtonBox(QBtn)
|
||||
QBtn = PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel
|
||||
buttonBox = PySide.QtGui.QDialogButtonBox(QBtn)
|
||||
buttonBox.accepted.connect(self.accept)
|
||||
buttonBox.rejected.connect(self.reject)
|
||||
|
||||
@@ -604,8 +624,9 @@ class ToolBitLibrary(object):
|
||||
else:
|
||||
tools.append({'nr': toolNr, 'path': PathToolBit.findRelativePathTool(toolPath)})
|
||||
|
||||
with open(self.path, 'w') as fp:
|
||||
json.dump(library, fp, sort_keys=True, indent=2)
|
||||
if self.path is not None:
|
||||
with open(self.path, 'w') as fp:
|
||||
json.dump(library, fp, sort_keys=True, indent=2)
|
||||
|
||||
def libraryOk(self):
|
||||
self.librarySave()
|
||||
@@ -652,7 +673,7 @@ class ToolBitLibrary(object):
|
||||
|
||||
if curIndex:
|
||||
sm = self.form.TableList.selectionModel()
|
||||
sm.select(curIndex, QtCore.QItemSelectionModel.Select)
|
||||
sm.select(curIndex, PySide.QtCore.QItemSelectionModel.Select)
|
||||
|
||||
self.toolTableView.setUpdatesEnabled(True)
|
||||
self.form.TableList.setUpdatesEnabled(True)
|
||||
|
||||
@@ -250,7 +250,8 @@ def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider=
|
||||
if tool.ViewObject:
|
||||
tool.ViewObject.Visibility = False
|
||||
|
||||
obj.Tool = tool
|
||||
if tool:
|
||||
obj.Tool = tool
|
||||
obj.ToolNumber = toolNumber
|
||||
return obj
|
||||
|
||||
@@ -260,7 +261,7 @@ def FromTemplate(template, assignViewProvider=True):
|
||||
PathLog.track()
|
||||
|
||||
name = template.get(ToolControllerTemplate.Name, ToolControllerTemplate.Label)
|
||||
obj = Create(name, assignViewProvider=True)
|
||||
obj = Create(name, tool=False, assignViewProvider=True)
|
||||
obj.Proxy.setFromTemplate(obj, template)
|
||||
|
||||
return obj
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
@@ -71,6 +71,11 @@ def getPropertyValueString(obj, prop):
|
||||
def setProperty(obj, prop, value):
|
||||
'''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.'''
|
||||
o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable
|
||||
if not attr is None and type(value) == str:
|
||||
if type(attr) == int:
|
||||
value = int(value, 0)
|
||||
elif type(attr) == bool:
|
||||
value = value.lower() in ['true', '1', 'yes', 'ok']
|
||||
if o and name:
|
||||
setattr(o, name, value)
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts
|
||||
import PathScripts.PathJobCmd as PathJobCmd
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
@@ -210,11 +210,6 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Additional base objects to be engraved"))
|
||||
obj.setEditorMode('BaseShapes', 2) # hide
|
||||
if not hasattr(obj, 'BaseObject'):
|
||||
obj.addProperty("App::PropertyLink", "BaseObject", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Additional base objects to be engraved"))
|
||||
obj.setEditorMode('BaseObject', 2) # hide
|
||||
|
||||
def initOperation(self, obj):
|
||||
'''initOperation(obj) ... create vcarve specific properties.'''
|
||||
@@ -242,7 +237,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
edges.append(_getPartEdge(e, geom))
|
||||
return edges
|
||||
|
||||
def buildPathMedial(self, obj, Faces):
|
||||
def buildPathMedial(self, obj, faces):
|
||||
'''constructs a medial axis path using openvoronoi'''
|
||||
|
||||
def insert_many_wires(vd, wires):
|
||||
@@ -271,7 +266,7 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
|
||||
VD.clear()
|
||||
voronoiWires = []
|
||||
for f in Faces:
|
||||
for f in faces:
|
||||
vd = Path.Voronoi()
|
||||
insert_many_wires(vd, f.Wires)
|
||||
|
||||
@@ -311,41 +306,39 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
PathLog.track()
|
||||
|
||||
if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Vcarve", "VCarve requires an engraving \
|
||||
cutter with CuttingEdgeAngle") + "\n")
|
||||
PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle"))
|
||||
|
||||
if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0:
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Vcarve",
|
||||
"Engraver Cutting Edge Angle must be < 180 degrees.") + "\n")
|
||||
PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees."))
|
||||
return
|
||||
|
||||
try:
|
||||
if obj.Base:
|
||||
PathLog.track()
|
||||
for base in obj.Base:
|
||||
faces = []
|
||||
for sub in base[1]:
|
||||
shape = getattr(base[0].Shape, sub)
|
||||
if isinstance(shape, Part.Face):
|
||||
faces.append(shape)
|
||||
faces = []
|
||||
|
||||
modelshape = Part.makeCompound(faces)
|
||||
for base in obj.BaseShapes:
|
||||
faces.extend(base.Shape.Faces)
|
||||
|
||||
elif len(self.model) == 1 and self.model[0].isDerivedFrom('Sketcher::SketchObject') or \
|
||||
self.model[0].isDerivedFrom('Part::Part2DObject'):
|
||||
PathLog.track()
|
||||
for base in obj.Base:
|
||||
for sub in base[1]:
|
||||
shape = getattr(base[0].Shape, sub)
|
||||
if isinstance(shape, Part.Face):
|
||||
faces.append(shape)
|
||||
|
||||
modelshape = self.model[0].Shape
|
||||
self.buildPathMedial(obj, modelshape.Faces)
|
||||
if not faces:
|
||||
for model in self.model:
|
||||
if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'):
|
||||
faces.extend(model.Shape.Faces)
|
||||
|
||||
if faces:
|
||||
self.buildPathMedial(obj, faces)
|
||||
else:
|
||||
PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.'))
|
||||
|
||||
except Exception as e:
|
||||
PathLog.error(e)
|
||||
traceback.print_exc()
|
||||
PathLog.error(translate('PathVcarve', 'The Job Base Object has \
|
||||
no engraveable element. Engraving \
|
||||
operation will produce no output.'))
|
||||
raise e
|
||||
#PathLog.error(e)
|
||||
#traceback.print_exc()
|
||||
PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.'))
|
||||
#raise e
|
||||
|
||||
def opUpdateDepths(self, obj, ignoreErrors=False):
|
||||
'''updateDepths(obj) ... engraving is always done at the top most z-value'''
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathVcarve as PathVcarve
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
@@ -53,6 +54,7 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
return super(TaskPanelBaseGeometryPage, self)
|
||||
|
||||
def addBaseGeometry(self, selection):
|
||||
PathLog.track(selection)
|
||||
added = False
|
||||
shapes = self.obj.BaseShapes
|
||||
for sel in selection:
|
||||
@@ -78,10 +80,12 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
shapes.append(base)
|
||||
self.obj.BaseShapes = shapes
|
||||
added = True
|
||||
else:
|
||||
# user wants us to engrave an edge of face of a base model
|
||||
base = self.super().addBaseGeometry(selection)
|
||||
added = added or base
|
||||
|
||||
if not added:
|
||||
# user wants us to engrave an edge of face of a base model
|
||||
PathLog.info(" call default")
|
||||
base = self.super().addBaseGeometry(selection)
|
||||
added = added or base
|
||||
|
||||
return added
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathWaterline as PathWaterline
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2014 sliptonic <[email protected]> *
|
||||
# * Copyright (c) 2021 shadowbane1000 <[email protected]> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * 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. *
|
||||
# * *
|
||||
# * FreeCAD 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 Lesser General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with FreeCAD; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
from __future__ import print_function
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import Path
|
||||
import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
import os.path
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
|
||||
TOOLTIP = '''
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
take a pseudo-gcode fragment outputted by a Path object, and output
|
||||
real GCode suitable should be suitable for most Fanuc controllers.
|
||||
It has only been tested on a 21i-MB controller on a 3 axis mill.
|
||||
This postprocessor, once placed in the appropriate PathScripts folder,
|
||||
can be used directly from inside FreeCAD, via the GUI importer or via
|
||||
python scripts with:
|
||||
|
||||
import fanuc_post
|
||||
fanuc_post.export(object,"/path/to/file.ncc","")
|
||||
'''
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
parser = argparse.ArgumentParser(prog='fanuc', add_help=False)
|
||||
parser.add_argument('--no-header', action='store_true', help='suppress header output')
|
||||
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
|
||||
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
|
||||
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
|
||||
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
|
||||
parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17\nG90"')
|
||||
parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M05\nG17 G90\nM2"')
|
||||
parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)')
|
||||
parser.add_argument('--no-modal', action='store_true', help='Don\'t output the Same G-command Name USE NonModal Mode')
|
||||
parser.add_argument('--no-axis-modal', action='store_true', help='Don\'t output the Same Axis Value Mode')
|
||||
parser.add_argument('--no-tlo', action='store_true', help='suppress tool length offset (G43) following tool changes')
|
||||
|
||||
TOOLTIP_ARGS = parser.format_help()
|
||||
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
SHOW_EDITOR = True
|
||||
MODAL = True # if true commands are suppressed if the same as previous line.
|
||||
USE_TLO = True # if true G43 will be output following tool changes
|
||||
OUTPUT_DOUBLES = False # if false duplicate axis values are suppressed if the same as previous line.
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G21" # G21 for metric, G20 for us standard
|
||||
UNIT_SPEED_FORMAT = 'mm/min'
|
||||
UNIT_FORMAT = 'mm'
|
||||
|
||||
MACHINE_NAME = "fanuc"
|
||||
CORNER_MIN = {'x': 0, 'y': 0, 'z': 0}
|
||||
CORNER_MAX = {'x': 500, 'y': 300, 'z': 300}
|
||||
PRECISION = 3
|
||||
|
||||
# this global is used to pass spindle speed from the tool command into the machining command for
|
||||
# rigid tapping.
|
||||
tapSpeed = 0
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = '''G17 G54 G40 G49 G80 G90
|
||||
'''
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = '''M05
|
||||
G17 G54 G90 G80 G40
|
||||
M6 T0
|
||||
M2
|
||||
'''
|
||||
|
||||
# Pre operation text will be inserted before every operation
|
||||
PRE_OPERATION = ''''''
|
||||
|
||||
# Post operation text will be inserted after every operation
|
||||
POST_OPERATION = ''''''
|
||||
|
||||
# Tool Change commands will be inserted before a tool change
|
||||
TOOL_CHANGE = ''''''
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ in ['__builtin__','io']:
|
||||
pythonopen = open
|
||||
|
||||
|
||||
def processArguments(argstring):
|
||||
# pylint: disable=global-statement
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global PRECISION
|
||||
global PREAMBLE
|
||||
global POSTAMBLE
|
||||
global UNITS
|
||||
global UNIT_SPEED_FORMAT
|
||||
global UNIT_FORMAT
|
||||
global MODAL
|
||||
global USE_TLO
|
||||
global OUTPUT_DOUBLES
|
||||
|
||||
try:
|
||||
args = parser.parse_args(shlex.split(argstring))
|
||||
if args.no_header:
|
||||
OUTPUT_HEADER = False
|
||||
if args.no_comments:
|
||||
OUTPUT_COMMENTS = False
|
||||
if args.line_numbers:
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
if args.no_show_editor:
|
||||
SHOW_EDITOR = False
|
||||
print("Show editor = %d" % SHOW_EDITOR)
|
||||
PRECISION = args.precision
|
||||
if args.preamble is not None:
|
||||
PREAMBLE = args.preamble
|
||||
if args.postamble is not None:
|
||||
POSTAMBLE = args.postamble
|
||||
if args.inches:
|
||||
UNITS = 'G20'
|
||||
UNIT_SPEED_FORMAT = 'in/min'
|
||||
UNIT_FORMAT = 'in'
|
||||
PRECISION = 4
|
||||
if args.no_modal:
|
||||
MODAL = False
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
if args.no_axis_modal:
|
||||
OUTPUT_DOUBLES = true
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
# pylint: disable=global-statement
|
||||
if not processArguments(argstring):
|
||||
return None
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
global HORIZRAPID
|
||||
global VERTRAPID
|
||||
|
||||
for obj in objectslist:
|
||||
if not hasattr(obj, "Path"):
|
||||
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
|
||||
return None
|
||||
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# write header
|
||||
if OUTPUT_HEADER:
|
||||
gcode += "%\n"
|
||||
gcode += ";\n"
|
||||
gcode += os.path.split(filename)[-1]+" ("+"FREECAD-FILENAME-GOES-HERE" + ", " + "JOB-NAME-GOES-HERE"+")\n"
|
||||
gcode += linenumber() + "("+filename.upper()+",EXPORTED BY FREECAD!)\n"
|
||||
gcode += linenumber() + "(POST PROCESSOR: " + __name__.upper() + ")\n"
|
||||
gcode += linenumber() + "(OUTPUT TIME:" + str(now).upper() + ")\n"
|
||||
|
||||
# Write the preamble
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(BEGIN PREAMBLE)\n"
|
||||
for line in PREAMBLE.splitlines(False):
|
||||
gcode += linenumber() + line + "\n"
|
||||
gcode += linenumber() + UNITS + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
|
||||
# Skip inactive operations
|
||||
if hasattr(obj, 'Active'):
|
||||
if not obj.Active:
|
||||
continue
|
||||
if hasattr(obj, 'Base') and hasattr(obj.Base, 'Active'):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# fetch machine details
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
myMachine = 'not set'
|
||||
|
||||
if hasattr(job, "MachineName"):
|
||||
myMachine = job.MachineName
|
||||
|
||||
if hasattr(job, "MachineUnits"):
|
||||
if job.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
UNIT_FORMAT = 'mm'
|
||||
UNIT_SPEED_FORMAT = 'mm/min'
|
||||
else:
|
||||
UNITS = "G20"
|
||||
UNIT_FORMAT = 'in'
|
||||
UNIT_SPEED_FORMAT = 'in/min'
|
||||
|
||||
if hasattr(job, "SetupSheet"):
|
||||
if hasattr(job.SetupSheet, "HorizRapid"):
|
||||
HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
if hasattr(job.SetupSheet, "VertRapid"):
|
||||
VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper()
|
||||
gcode += linenumber() + "(MACHINE: %s, %s)\n" % (myMachine.upper(), UNIT_SPEED_FORMAT.upper())
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
# get coolant mode
|
||||
coolantMode = 'None'
|
||||
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
|
||||
if hasattr(obj, "CoolantMode"):
|
||||
coolantMode = obj.CoolantMode
|
||||
else:
|
||||
coolantMode = obj.Base.CoolantMode
|
||||
|
||||
# turn coolant on if required
|
||||
if OUTPUT_COMMENTS:
|
||||
if not coolantMode == 'None':
|
||||
gcode += linenumber() + '(COOLANT ON:' + coolantMode.upper() + ')\n'
|
||||
if coolantMode == 'Flood':
|
||||
gcode += linenumber() + 'M8' + '\n'
|
||||
if coolantMode == 'Mist':
|
||||
gcode += linenumber() + 'M7' + '\n'
|
||||
|
||||
# process the operation gcode
|
||||
gcode += parse(obj)
|
||||
|
||||
# do the post_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(FINISH OPERATION: %s)\n" % obj.Label.upper()
|
||||
for line in POST_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
# turn coolant off if required
|
||||
if not coolantMode == 'None':
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + '(COOLANT OFF:' + coolantMode.upper() + ')\n'
|
||||
gcode += linenumber() +'M9' + '\n'
|
||||
|
||||
# do the post_amble
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += "(BEGIN POSTAMBLE)\n"
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
gcode += "%\n"
|
||||
|
||||
if FreeCAD.GuiUp and SHOW_EDITOR:
|
||||
dia = PostUtils.GCodeEditorDialog()
|
||||
dia.editor.setText(gcode)
|
||||
result = dia.exec_()
|
||||
if result:
|
||||
final = dia.editor.toPlainText()
|
||||
else:
|
||||
final = gcode
|
||||
else:
|
||||
final = gcode
|
||||
|
||||
print("done postprocessing.")
|
||||
|
||||
if not filename == '-':
|
||||
gfile = pythonopen(filename, "w")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
# pylint: disable=global-statement
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
|
||||
def parse(pathobj):
|
||||
# pylint: disable=global-statement
|
||||
global PRECISION
|
||||
global MODAL
|
||||
global OUTPUT_DOUBLES
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
global tapSpeed
|
||||
|
||||
out = ""
|
||||
lastcommand = None
|
||||
precision_string = '.' + str(PRECISION) + 'f'
|
||||
currLocation = {} # keep track for no doubles
|
||||
print("Startup!")
|
||||
|
||||
# the order of parameters
|
||||
# arcs need work. original code from mach3_4 doesn't want K properties on XY plane. Not sure
|
||||
# what fanuc does here.
|
||||
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H', 'D', 'P']
|
||||
firstmove = Path.Command("G0", {"X": -1, "Y": -1, "Z": -1, "F": 0.0})
|
||||
currLocation.update(firstmove.Parameters) # set First location Parameters
|
||||
|
||||
if hasattr(pathobj, "Group"): # We have a compound or project.
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(compound: " + pathobj.Label + ")\n"
|
||||
for p in pathobj.Group:
|
||||
out += parse(p)
|
||||
return out
|
||||
else: # parsing simple path
|
||||
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
adaptiveOp = False
|
||||
opHorizRapid = 0
|
||||
opVertRapid = 0
|
||||
|
||||
if 'Adaptive' in pathobj.Name:
|
||||
adaptiveOp = True
|
||||
if hasattr(pathobj, 'ToolController'):
|
||||
if hasattr(pathobj.ToolController, 'HorizRapid') and pathobj.ToolController.HorizRapid > 0:
|
||||
opHorizRapid = Units.Quantity(pathobj.ToolController.HorizRapid, FreeCAD.Units.Velocity)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning('Tool Controller Horizontal Rapid Values are unset'+ '\n')
|
||||
|
||||
if hasattr(pathobj.ToolController, 'VertRapid') and pathobj.ToolController.VertRapid > 0:
|
||||
opVertRapid = Units.Quantity(pathobj.ToolController.VertRapid, FreeCAD.Units.Velocity)
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning('Tool Controller Vertical Rapid Values are unset'+ '\n')
|
||||
|
||||
for index,c in enumerate(pathobj.Path.Commands):
|
||||
|
||||
outstring = []
|
||||
command = c.Name
|
||||
if index+1 == len(pathobj.Path.Commands):
|
||||
nextcommand = ""
|
||||
else:
|
||||
nextcommand = pathobj.Path.Commands[index+1].Name
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
command = 'G1'
|
||||
else:
|
||||
outstring.append('(TOOL CONTROLLER RAPID VALUES ARE UNSET)' + '\n')
|
||||
|
||||
# suppress moves in fixture selection
|
||||
if pathobj.Label == "Fixture":
|
||||
if command == "G0":
|
||||
continue
|
||||
|
||||
# if it's a tap, we rigid tap, so don't start the spindle yet...
|
||||
if command == "M03" or command == "M3":
|
||||
if pathobj.Tool.ToolType == "Tap":
|
||||
tapSpeed = int(pathobj.SpindleSpeed)
|
||||
continue
|
||||
|
||||
# convert drill cycles to tap cycles if tool is a tap
|
||||
if command == "G81" or command == "G83":
|
||||
if hasattr(pathobj, 'ToolController') and pathobj.ToolController.Tool.ToolType == "Tap":
|
||||
command = "G84"
|
||||
out += linenumber() + "G95\n"
|
||||
paramstring = ""
|
||||
for param in [ "X", "Y" ]:
|
||||
if param in c.Parameters:
|
||||
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)
|
||||
if paramstring != "":
|
||||
out += linenumber() + "G00"+paramstring+"\n"
|
||||
|
||||
if "S" in c.Parameters:
|
||||
tapSpeed = int(c.Parameters['S'])
|
||||
out += "M29 S"+str(tapSpeed)+"\n"
|
||||
|
||||
for param in [ "Z", "R" ]:
|
||||
if param in c.Parameters:
|
||||
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
paramstring += " " + param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string)
|
||||
# in this mode, F is the distance per revolution of the thread (pitch)
|
||||
# P is the dwell time in seconds at the bottom of the thread
|
||||
# Q is the peck depth of the threading operation
|
||||
for param in [ "F", "P", "Q" ]:
|
||||
if param in c.Parameters:
|
||||
value = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
paramstring += " " + param + format(float(value.getValueAs(UNIT_FORMAT)), precision_string)
|
||||
|
||||
out += linenumber() + "G84" + paramstring + "\n"
|
||||
out += linenumber() + "G80\n"
|
||||
out += linenumber() + "G94\n"
|
||||
continue
|
||||
|
||||
|
||||
outstring.append(command)
|
||||
|
||||
# if modal: suppress the command if it is the same as the last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
|
||||
# suppress a G80 between two identical command
|
||||
if command == "G80" and lastcommand == nextcommand:
|
||||
continue
|
||||
|
||||
if c.Name[0] == '(' and not OUTPUT_COMMENTS: # command is a comment
|
||||
continue
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == 'F' and (currLocation[param] != c.Parameters[param] or OUTPUT_DOUBLES):
|
||||
if c.Name not in ["G0", "G00"]: # fanuc doesn't use rapid speeds
|
||||
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
|
||||
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
|
||||
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
|
||||
else:
|
||||
continue
|
||||
elif param == 'T':
|
||||
outstring.append(param + str(int(c.Parameters['T'])))
|
||||
elif param == 'H':
|
||||
outstring.append(param + str(int(c.Parameters['H'])))
|
||||
elif param == 'D':
|
||||
outstring.append(param + str(int(c.Parameters['D'])))
|
||||
elif param == 'S':
|
||||
outstring.append(param + str(int(c.Parameters['S'])))
|
||||
currentSpeed = int(c.Parameters['S'])
|
||||
else:
|
||||
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
|
||||
continue
|
||||
else:
|
||||
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
|
||||
outstring.append(
|
||||
param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
|
||||
|
||||
if adaptiveOp and c.Name in ["G0", "G00"]:
|
||||
if opHorizRapid and opVertRapid:
|
||||
if 'Z' not in c.Parameters:
|
||||
outstring.append('F' + format(float(opHorizRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
|
||||
else:
|
||||
outstring.append('F' + format(float(opVertRapid.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
currLocation.update(c.Parameters)
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == 'M6':
|
||||
# stop the spindle
|
||||
currentSpeed = 0
|
||||
out += linenumber() + "M5\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# add height offset
|
||||
if USE_TLO:
|
||||
tool_height = '\nG43 H' + str(int(c.Parameters['T']))
|
||||
outstring.append(tool_height)
|
||||
|
||||
if command == "message":
|
||||
if OUTPUT_COMMENTS is False:
|
||||
out = []
|
||||
else:
|
||||
outstring.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(outstring) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
outstring.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in outstring:
|
||||
out += w.upper() + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
# print(__name__ + " gcode postprocessor loaded.")
|
||||
@@ -384,7 +384,7 @@ def export(objectslist, filename, argstring):
|
||||
# #\better: append iff MODAL == False
|
||||
# if command == lastcommand:
|
||||
# outstring.pop(0)
|
||||
if c.Parameters >= 1:
|
||||
if len(c.Parameters) >= 1:
|
||||
for param in params:
|
||||
# test print("param: " + param + ", command: " + command)
|
||||
if param in c.Parameters:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2021 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 PathScripts.PathPropertyBag as PathPropertyBag
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
|
||||
class TestPathPropertyBag(PathTestUtils.PathTestBase):
|
||||
|
||||
def setUp(self):
|
||||
self.doc = FreeCAD.newDocument('test-property-bag')
|
||||
|
||||
def tearDown(self):
|
||||
FreeCAD.closeDocument(self.doc.Name)
|
||||
|
||||
def test00(self):
|
||||
'''basic PropertyBag creation and access test'''
|
||||
bag = PathPropertyBag.Create()
|
||||
self.assertTrue(hasattr(bag, 'Proxy'))
|
||||
self.assertEqual(bag.Proxy.getCustomProperties(), [])
|
||||
self.assertEqual(bag.CustomPropertyGroups, [])
|
||||
|
||||
def test01(self):
|
||||
'''adding properties to a PropertyBag is tracked properly'''
|
||||
bag = PathPropertyBag.Create()
|
||||
proxy = bag.Proxy
|
||||
proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description')
|
||||
self.assertTrue(hasattr(bag, 'Title'))
|
||||
bag.Title = 'Madame'
|
||||
self.assertEqual(bag.Title, 'Madame')
|
||||
self.assertEqual(bag.Proxy.getCustomProperties(), ['Title'])
|
||||
self.assertEqual(bag.CustomPropertyGroups, ['Address'])
|
||||
|
||||
def test02(self):
|
||||
'''refreshCustomPropertyGroups deletes empty groups'''
|
||||
bag = PathPropertyBag.Create()
|
||||
proxy = bag.Proxy
|
||||
proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description')
|
||||
bag.Title = 'Madame'
|
||||
bag.removeProperty('Title')
|
||||
proxy.refreshCustomPropertyGroups()
|
||||
self.assertEqual(bag.Proxy.getCustomProperties(), [])
|
||||
self.assertEqual(bag.CustomPropertyGroups, [])
|
||||
|
||||
def test03(self):
|
||||
'''refreshCustomPropertyGroups does not delete non-empty groups'''
|
||||
bag = PathPropertyBag.Create()
|
||||
proxy = bag.Proxy
|
||||
proxy.addCustomProperty('App::PropertyString', 'Title', 'Address', 'Some description')
|
||||
proxy.addCustomProperty('App::PropertyString', 'Gender', 'Attributes')
|
||||
bag.Title = 'Madame'
|
||||
bag.Gender = 'Female'
|
||||
bag.removeProperty('Gender')
|
||||
proxy.refreshCustomPropertyGroups()
|
||||
self.assertEqual(bag.Proxy.getCustomProperties(), ['Title'])
|
||||
self.assertEqual(bag.CustomPropertyGroups, ['Address'])
|
||||
|
||||
@@ -22,37 +22,143 @@
|
||||
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
import PathTests.PathTestUtils as PathTestUtils
|
||||
import glob
|
||||
import os
|
||||
|
||||
TestToolDir = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'Tools')
|
||||
TestInvalidDir = os.path.join(TestToolDir, 'some', 'silly', 'path', 'that', 'should', 'not', 'exist')
|
||||
|
||||
TestToolBitName = 'test-path-tool-bit-bit-00.fctb'
|
||||
TestToolShapeName = 'test-path-tool-bit-shape-00.fcstd'
|
||||
TestToolLibraryName = 'test-path-tool-bit-library-00.fctl'
|
||||
|
||||
def testToolShape(path = TestToolDir, name = TestToolShapeName):
|
||||
return os.path.join(path, 'Shape', name)
|
||||
|
||||
def testToolBit(path = TestToolDir, name = TestToolBitName):
|
||||
return os.path.join(path, 'Bit', name)
|
||||
|
||||
def testToolLibrary(path = TestToolDir, name = TestToolLibraryName):
|
||||
return os.path.join(path, 'Library', name)
|
||||
|
||||
def printTree(path, indent):
|
||||
print("{} {}".format(indent, os.path.basename(path)))
|
||||
if os.path.isdir(path):
|
||||
if os.path.basename(path).startswith('__'):
|
||||
print("{} ...".format(indent))
|
||||
else:
|
||||
for foo in sorted(glob.glob(os.path.join(path, '*'))):
|
||||
printTree(foo, "{} ".format(indent))
|
||||
|
||||
class TestPathToolBit(PathTestUtils.PathTestBase):
|
||||
|
||||
def test00(self):
|
||||
'''Find a tool shapee from file name'''
|
||||
def test(self):
|
||||
'''Log test setup directory structure'''
|
||||
# Enable this test if there are errors showing up in the build system with the
|
||||
# paths that work OK locally. It'll print out the directory tree, and if it
|
||||
# doesn't look right you know where to look for it
|
||||
print()
|
||||
print("realpath : {}".format(os.path.realpath(__file__)))
|
||||
print(" Tools : {}".format(TestToolDir))
|
||||
print(" dir : {}".format(os.path.dirname(os.path.realpath(__file__))))
|
||||
printTree(os.path.dirname(os.path.realpath(__file__)), " :")
|
||||
|
||||
path = PathToolBit.findShape('endmill.fcstd')
|
||||
def test00(self):
|
||||
'''Find a tool shape from file name'''
|
||||
path = PathToolBit.findToolShape('endmill.fcstd')
|
||||
self.assertIsNot(path, None)
|
||||
self.assertNotEqual(path, 'endmill.fcstd')
|
||||
|
||||
def test01(self):
|
||||
'''Find a tool shapee from an invalid absolute path.'''
|
||||
|
||||
path = PathToolBit.findShape('/this/is/unlikely/a/valid/path/v-bit.fcstd')
|
||||
def test01(self):
|
||||
'''Not find a relative path shape if not stored in default location'''
|
||||
path = PathToolBit.findToolShape(TestToolShapeName)
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test02(self):
|
||||
'''Find a relative path shape if it's local to a bit path'''
|
||||
path = PathToolBit.findToolShape(TestToolShapeName, testToolBit())
|
||||
self.assertIsNot(path, None)
|
||||
self.assertNotEqual(path, '/this/is/unlikely/a/valid/path/v-bit.fcstd')
|
||||
self.assertEqual(path, testToolShape())
|
||||
|
||||
|
||||
def test03(self):
|
||||
'''Not find a tool shape from an invalid absolute path.'''
|
||||
path = PathToolBit.findToolShape(testToolShape(TestInvalidDir))
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test04(self):
|
||||
'''Find a tool shape from a valid absolute path.'''
|
||||
path = PathToolBit.findToolShape(testToolShape())
|
||||
self.assertIsNot(path, None)
|
||||
self.assertEqual(path, testToolShape())
|
||||
|
||||
|
||||
def test10(self):
|
||||
'''find the relative path of a tool bit'''
|
||||
shape = 'endmill.fcstd'
|
||||
path = PathToolBit.findShape(shape)
|
||||
'''Find a tool bit from file name'''
|
||||
path = PathToolBit.findToolBit('5mm_Endmill.fctb')
|
||||
self.assertIsNot(path, None)
|
||||
self.assertGreater(len(path), len(shape))
|
||||
rel = PathToolBit.findRelativePathShape(path)
|
||||
self.assertEqual(rel, shape)
|
||||
self.assertNotEqual(path, '5mm_Endmill.fctb')
|
||||
|
||||
|
||||
def test11(self):
|
||||
'''store full path if relative path isn't found'''
|
||||
path = '/this/is/unlikely/a/valid/path/v-bit.fcstd'
|
||||
rel = PathToolBit.findRelativePathShape(path)
|
||||
self.assertEqual(rel, path)
|
||||
'''Not find a relative path bit if not stored in default location'''
|
||||
path = PathToolBit.findToolBit(TestToolBitName)
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test12(self):
|
||||
'''Find a relative path bit if it's local to a library path'''
|
||||
path = PathToolBit.findToolBit(TestToolBitName, testToolLibrary())
|
||||
self.assertIsNot(path, None)
|
||||
self.assertEqual(path, testToolBit())
|
||||
|
||||
|
||||
def test13(self):
|
||||
'''Not find a tool bit from an invalid absolute path.'''
|
||||
path = PathToolBit.findToolBit(testToolBit(TestInvalidDir))
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test14(self):
|
||||
'''Find a tool bit from a valid absolute path.'''
|
||||
path = PathToolBit.findToolBit(testToolBit())
|
||||
self.assertIsNot(path, None)
|
||||
self.assertEqual(path, testToolBit())
|
||||
|
||||
|
||||
|
||||
def test20(self):
|
||||
'''Find a tool library from file name'''
|
||||
path = PathToolBit.findToolLibrary('Default.fctl')
|
||||
self.assertIsNot(path, None)
|
||||
self.assertNotEqual(path, 'Default.fctl')
|
||||
|
||||
|
||||
def test21(self):
|
||||
'''Not find a relative path library if not stored in default location'''
|
||||
path = PathToolBit.findToolLibrary(TestToolLibraryName)
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test22(self):
|
||||
'''[skipped] Find a relative path library if it's local to <what?>'''
|
||||
# this is not a valid test for libraries because t
|
||||
self.assertTrue(True)
|
||||
|
||||
|
||||
def test23(self):
|
||||
'''Not find a tool library from an invalid absolute path.'''
|
||||
path = PathToolBit.findToolLibrary(testToolLibrary(TestInvalidDir))
|
||||
self.assertIsNone(path)
|
||||
|
||||
|
||||
def test24(self):
|
||||
'''Find a tool library from a valid absolute path.'''
|
||||
path = PathToolBit.findToolBit(testToolBit())
|
||||
self.assertIsNot(path, None)
|
||||
self.assertEqual(path, testToolBit())
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "5mm Endmill",
|
||||
"shape": "endmill.fcstd",
|
||||
"parameter": {
|
||||
"CuttingEdgeHeight": "30.0000 mm",
|
||||
"Diameter": "5.0000 mm",
|
||||
"Length": "50.0000 mm",
|
||||
"ShankDiameter": "3.0000 mm"
|
||||
},
|
||||
"attribute": {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"tools": [
|
||||
{
|
||||
"nr": 1,
|
||||
"path": "5mm_Endmill.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 2,
|
||||
"path": "5mm_Drill.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 3,
|
||||
"path": "6mm_Ball_End.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 4,
|
||||
"path": "6mm_Bullnose.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 5,
|
||||
"path": "60degree_Vbit.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 6,
|
||||
"path": "45degree_chamfer.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 7,
|
||||
"path": "slittingsaw.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 8,
|
||||
"path": "probe.fctb"
|
||||
},
|
||||
{
|
||||
"nr": 9,
|
||||
"path": "5mm-thread-cutter.fctb"
|
||||
}
|
||||
],
|
||||
"version": 1
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import TestApp
|
||||
|
||||
from PathTests.TestPathLog import TestPathLog
|
||||
from PathTests.TestPathPreferences import TestPathPreferences
|
||||
from PathTests.TestPathPropertyBag import TestPathPropertyBag
|
||||
from PathTests.TestPathCore import TestPathCore
|
||||
#from PathTests.TestPathPost import PathPostTestCases
|
||||
from PathTests.TestPathGeom import TestPathGeom
|
||||
@@ -66,4 +67,5 @@ False if TestPathToolBit.__name__ else True
|
||||
False if TestPathVoronoi.__name__ else True
|
||||
False if TestPathThreadMilling.__name__ else True
|
||||
False if TestPathVcarve.__name__ else True
|
||||
False if TestPathPropertyBag.__name__ else True
|
||||
|
||||
|
||||
@@ -11,4 +11,4 @@
|
||||
"ShankDiameter": "6.3500 mm"
|
||||
},
|
||||
"attribute": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "3mm-thread-cutter",
|
||||
"name": "5mm-thread-cutter",
|
||||
"shape": "thread-mill.fcstd",
|
||||
"parameter": {
|
||||
"Crest": "0.10 mm",
|
||||
"Diameter": "5.00 mm",
|
||||
"Length": "50.00 mm",
|
||||
"NeckDiameter": "3.00 mm",
|
||||
"NeckHeight": "20.00 mm",
|
||||
"NeckLength": "20.00 mm",
|
||||
"ShankDiameter": "5.00 mm"
|
||||
},
|
||||
"attribute": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "Probe004",
|
||||
"name": "Probe",
|
||||
"shape": "probe.fcstd",
|
||||
"parameter": {
|
||||
"Diameter": "6.0000 mm",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
"shape": "slittingsaw.fcstd",
|
||||
"parameter": {
|
||||
"BladeThickness": "3.0000 mm",
|
||||
"BoltHeight": "3.0000 mm",
|
||||
"BoltWidth": "8.0000 mm",
|
||||
"CapHeight": "3.0000 mm",
|
||||
"CapDiameter": "8.0000 mm",
|
||||
"Diameter": "76.2000 mm",
|
||||
"Length": "50.0000 mm",
|
||||
"ShankDiameter": "19.0500 mm"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Tools
|
||||
|
||||
Each tool is stored as a JSON file which has the template's path and values for all named constraints of the template.
|
||||
Each tool is stored as a JSON file which has the shape's path and values for all attributes of the shape.
|
||||
It also includes all additional parameters and their values.
|
||||
|
||||
Storing a tool as a JSON file sounds great but eliminates the option of an accurate thumbnail. On the other hand,
|
||||
@@ -8,10 +8,10 @@ storing each tool as a `*.fcstd` file requires more space and does not allow for
|
||||
extensive tool aresenal they might want to script the generation of tools which is easily done for a `*.json` file but
|
||||
practically impossible for `*.fcstd` files.
|
||||
|
||||
When a tool is instantiated in a job the PDN body is created from the template and the constraints are set according
|
||||
to the values from the JSON file. All additional parameters are created as properties on the object. This provides the
|
||||
the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced algorithms (and
|
||||
potentially simulation).
|
||||
When a tool is instantiated in a job the PDN body is created from the shape and the attributes and constraints are set
|
||||
according to the values from the JSON file. All additional parameters are created as properties on the object. This
|
||||
provides the the correct shape and dimensions which can be used to generate a point cloud or mesh for advanced
|
||||
algorithms (and potentially simulation).
|
||||
|
||||
# Tool Libraries
|
||||
|
||||
@@ -55,33 +55,34 @@ TechDraw's templates.
|
||||
## How to create a new tool
|
||||
|
||||
1. Set the tool's Label, this will show up in the object tree
|
||||
1. Select a tool shape from the existing templates. If your tool doesn't exist, you'll have to create a new template,
|
||||
1. Select a tool shape from the existing shape files. If your tool doesn't exist, you'll have to create a new shape,
|
||||
see below for details.
|
||||
1. Each template has its own set of parameters, fill them with the tool's values.
|
||||
1. Each tool bit shape has its own set of parameters, fill them with the tool's values.
|
||||
1. Select additional parameters
|
||||
1. Save the tool under path/file that makes sense to you
|
||||
|
||||
|
||||
## How to create a new tool bit Shape
|
||||
|
||||
A tool bit template represents the physical shape of a tool. It does not completely describe the bit - for that some
|
||||
additional parameters are needed which will be added when an actual bit is parametrized from the template.
|
||||
The shape file for a tool bit is expected to contain a PD body which represents the tool as a 3d solid. The PD body
|
||||
should be parametric based on a a PropertyBag object so that, when the properties of the PropertyBag are changed the
|
||||
solid is updated to the correct representation.
|
||||
|
||||
1. Create a new FreeCAD document
|
||||
1. Open the `PartDesign` workbench, create a body and give the body a label you want to show up in the bit selection.
|
||||
1. Create a sketch in the XZ plane and draw half the profile of the bit.
|
||||
* Put the top center of the bit on the origin (0,0)
|
||||
1. For any constraint serving as a parameter for the tool (like overall Length) create a named constraint
|
||||
* The name is the label of the input field
|
||||
* Names are split at CamelCase boundaries into words in the edit dialog
|
||||
* Use a `;` in the name to add help text which will show up as the entry fields tool tip
|
||||
* If the tool is used by legacy ops it should at least have one constraint called `Diameter`
|
||||
* Use construction lines for constraints that are not directly accessible, like `Diameter` and `Angle`
|
||||
1. Any unnamed constraint will not be editable for a specific tool
|
||||
1. Once the sketch is fully constrained, close the sketch
|
||||
1. Rotate the sketch around the z-axis
|
||||
1. Open the Path workbench and (with the PD body selected) create a PropertyBag,
|
||||
menu 'Path' -> 'Utils' -> 'Property Bag'
|
||||
* this creates a PropertyBag object inside the Body (assuming it was selected)
|
||||
* add properties to which define the tool bit's shape and put those into the group 'Shape'
|
||||
* add any other properties to the bag which might be useful for the tool bit
|
||||
1. Construct the body of the tool bit and assign experssions referencing properties from the PropertyBag (in the
|
||||
`Shape` Group) for all constraints.
|
||||
* Position the tip of the tool bit on the origin (0,0)
|
||||
1. Save the document as a new file in the Shape directory
|
||||
* Before saving the document make sure you have _Save Thumbnail_ selected, and _Add program logo_ deselected in
|
||||
FreeCAD's preferences.
|
||||
* Also make sure to switch to _Front View_ and _Fit content to screen_
|
||||
* Whatever you see when saving the document will end up being the visual representation of the template
|
||||
* Whatever you see when saving the document will end up being the visual representation of tool bits with this shape
|
||||
|
||||
Not that 'Shape' is the only property group which has special meaning for tool bits. All other property groups are
|
||||
copied verbatim to the ToolBit object when one is created.
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/python3
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2021 sliptonic <[email protected]> *
|
||||
# * *
|
||||
# * 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 *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
#
|
||||
# Script for manipulating toolbit shapes in a batch. This is handy for making
|
||||
# attributes consistent over multiple or all shape file.
|
||||
#
|
||||
# Most commands are straight forward, except for --set which can be used to
|
||||
# set the actual value of a property, or, in the case of enumerations it can
|
||||
# also be used to set the enum values. This might be particularly useful when
|
||||
# adding more materials.
|
||||
#
|
||||
# The following example moves all properties from the "Extra" group into the
|
||||
# "Attributes" group. Note that the Attributes group might or might not
|
||||
# already exist. If it does exist the specified group gets merged in.
|
||||
#
|
||||
# ./toolbit-attributes.py --move 'Extra:Attributes' src/Mod/Path/Tools/Shape/*.fcstd
|
||||
#
|
||||
# This example sets the Flutes value of all Shapes to 0:
|
||||
#
|
||||
# ./toolbit-attributes.py --set Flutes=0 src/Mod/Path/Tools/Shape/*.fcstd
|
||||
#
|
||||
# Finally, this example sets the enumerations of the Material attribute:
|
||||
#
|
||||
# ./toolbit-attributes.py --set 'Material=[HSS,Carbide,Tool Steel,Titanium]' src/Mod/Path/Tools/Shape/*.fcstd
|
||||
#
|
||||
# After running this tool it might be necessary to open the shape files
|
||||
# manually and make sure they are visible and the thumbprint image is
|
||||
# saved.
|
||||
#
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
# !!! Final note: this is a dangerous tool and should not be shipped with FC. !!!!
|
||||
# !!! It's sole purpose is to make life of the developers easier and ensure !!!!
|
||||
# !!! consistent attributes across all toobit shapes. !!!!
|
||||
# !!! A single typo can ruin a lot of toolbit shapes - make sure to only use !!!!
|
||||
# !!! it if those shape files are under a version control system and you can !!!!
|
||||
# !!! back out the changes easily. !!!!
|
||||
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('path', nargs='+', help='Shape file to process')
|
||||
parser.add_argument('--move', metavar='<group1>:<group2>', help='Move attributes from group 1 into group 2')
|
||||
parser.add_argument('--delete', metavar='prop', help='Delete the given attribute')
|
||||
parser.add_argument('--set', metavar='prop=value', help='Set property value')
|
||||
parser.add_argument('--print', action='store_true', help='If set attributes are printed as discovered')
|
||||
parser.add_argument('--print-all', action='store_true', help='If set Shape attributes are also printed')
|
||||
parser.add_argument('--print-groups', action='store_true', help='If set all custom property groups are printed')
|
||||
parser.add_argument('--save-changes', action='store_true', help='Unless specified the file is not saved')
|
||||
parser.add_argument('--freecad', help='Directory FreeCAD binaries (libFreeCAD.so) if not installed')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.freecad:
|
||||
sys.path.append(args.freecad)
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathScripts.PathPropertyBag as PathPropertyBag
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
|
||||
set_var=None
|
||||
set_val=None
|
||||
|
||||
GroupMap = {}
|
||||
if args.move:
|
||||
g = args.move.split(':')
|
||||
if len(g) != 2:
|
||||
print("ERROR: {} not a valid group mapping".format(args.move))
|
||||
sys.exit(1)
|
||||
GroupMap[g[0]] = g[1]
|
||||
|
||||
if args.set:
|
||||
s = args.set.split('=')
|
||||
if len(s) != 2:
|
||||
print("ERROR: {} not a valid group mapping".format(args.move))
|
||||
sys.exit(1)
|
||||
set_var = s[0]
|
||||
set_val = s[1]
|
||||
|
||||
for i, fname in enumerate(args.path):
|
||||
#print(fname)
|
||||
doc = FreeCAD.openDocument(fname, False)
|
||||
print("{}:".format(doc.Name))
|
||||
for o in doc.Objects:
|
||||
if PathPropertyBag.IsPropertyBag(o):
|
||||
if args.print_groups:
|
||||
print(" {}: {}".format(o.Label, sorted(o.CustomPropertyGroups)))
|
||||
else:
|
||||
print(" {}:".format(o.Label))
|
||||
for p in o.Proxy.getCustomProperties():
|
||||
grp = o.getGroupOfProperty(p)
|
||||
typ = o.getTypeIdOfProperty(p)
|
||||
ttp = PathPropertyBag.getPropertyTypeName(typ)
|
||||
val = PathUtil.getProperty(o, p)
|
||||
dsc = o.getDocumentationOfProperty(p)
|
||||
enm = ''
|
||||
enum = []
|
||||
if ttp == 'Enumeration':
|
||||
enum = o.getEnumerationsOfProperty(p)
|
||||
enm = "{}".format(','.join(enum))
|
||||
if GroupMap.get(grp):
|
||||
group = GroupMap.get(grp)
|
||||
print("move: {}.{} -> {}".format(grp, p, group))
|
||||
o.removeProperty(p)
|
||||
o.Proxy.addCustomProperty(typ, p, group, dsc)
|
||||
if enum:
|
||||
print("enum {}.{}: {}".format(group, p, enum))
|
||||
setattr(o, p, enum)
|
||||
PathUtil.setProperty(o, p, val)
|
||||
if p == set_var:
|
||||
print("set {}.{} = {}".format(grp, p, set_val))
|
||||
if ttp == 'Enumeration' and set_val[0] == '[':
|
||||
enum = set_val[1:-1].split(',')
|
||||
setattr(o, p, enum)
|
||||
else:
|
||||
PathUtil.setProperty(o, p, set_val)
|
||||
if p == args.delete:
|
||||
print("delete {}.{}".format(grp, p))
|
||||
o.removeProperty(p)
|
||||
if not args.print_all and grp == 'Shape':
|
||||
continue
|
||||
if args.print or args.print_all:
|
||||
print(" {:10} {:20} {:20} {:10} {}".format(grp, p, ttp, str(val), enm))
|
||||
o.Proxy.refreshCustomPropertyGroups()
|
||||
if args.save_changes:
|
||||
doc.recompute()
|
||||
doc.save()
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
print('-done-')
|
||||
@@ -28,8 +28,6 @@
|
||||
#include <Gui/Selection.h>
|
||||
#include <boost_signals2.hpp>
|
||||
|
||||
class Ui_TaskSketcherGeneral;
|
||||
|
||||
namespace App {
|
||||
class Property;
|
||||
}
|
||||
@@ -40,6 +38,7 @@ class ViewProvider;
|
||||
|
||||
namespace SketcherGui {
|
||||
|
||||
class Ui_TaskSketcherGeneral;
|
||||
class ViewProviderSketch;
|
||||
|
||||
class SketcherGeneralWidget : public QWidget
|
||||
|
||||
@@ -6493,6 +6493,16 @@ void ViewProviderSketch::UpdateSolverInformation()
|
||||
.arg(tr("(click to select)"))
|
||||
.arg(appendRedundantMsg(getSketchObject()->getLastRedundant())));
|
||||
}
|
||||
|
||||
QString partiallyRedundantString;
|
||||
|
||||
if(hasPartiallyRedundant) {
|
||||
partiallyRedundantString = QString::fromLatin1("<br/><font color='royalblue'><span style=\"background-color: #ececec;\">%1<a href=\"#partiallyredundant\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">%2</span></a><br/>%3</span></font><br/>")
|
||||
.arg(tr("Sketch contains partially redundant constraints "))
|
||||
.arg(tr("(click to select)"))
|
||||
.arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant()));
|
||||
}
|
||||
|
||||
if (getSketchObject()->getLastSolverStatus() == 0) {
|
||||
if (dofs == 0) {
|
||||
// color the sketch as fully constrained if it has geometry (other than the axes)
|
||||
@@ -6500,31 +6510,19 @@ void ViewProviderSketch::UpdateSolverInformation()
|
||||
edit->FullyConstrained = true;
|
||||
|
||||
if (!hasRedundancies) {
|
||||
signalSetUp(QString::fromLatin1("<font color='green'><span style=\"color:#008000; background-color: #ececec;\">%1</font></span>").arg(tr("Fully constrained sketch")));
|
||||
signalSetUp(QString::fromLatin1("<font color='green'><span style=\"color:#008000; background-color: #ececec;\">%1</font></span> %2").arg(tr("Fully constrained sketch")).arg(partiallyRedundantString));
|
||||
}
|
||||
}
|
||||
else if (!hasRedundancies) {
|
||||
QString infoString;
|
||||
|
||||
if (dofs == 1)
|
||||
infoString = tr("Under-constrained sketch with <a href=\"#dofs\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">1 degree</span></a> of freedom. %1")
|
||||
.arg(hasPartiallyRedundant?
|
||||
QString::fromLatin1("<br/><font color='orangered'>%1<a href=\"#partiallyredundant\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">%2</span></a><br/>%3</font><br/>")
|
||||
.arg(tr("Sketch contains partially redundant constraints "))
|
||||
.arg(tr("(click to select)"))
|
||||
.arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant()))
|
||||
: QString());
|
||||
signalSetUp(tr("Under-constrained sketch with <a href=\"#dofs\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">1 degree</span></a> of freedom. %1")
|
||||
.arg(partiallyRedundantString));
|
||||
else
|
||||
infoString = tr("Under-constrained sketch with <a href=\"#dofs\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">%1 degrees</span></a> of freedom. %2")
|
||||
signalSetUp(tr("Under-constrained sketch with <a href=\"#dofs\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">%1 degrees</span></a> of freedom. %2")
|
||||
.arg(dofs)
|
||||
.arg(hasPartiallyRedundant?
|
||||
QString::fromLatin1("<br/><font color='orangered'>%1<a href=\"#partiallyredundant\"><span style=\" text-decoration: underline; color:#0000ff; background-color: #F8F8FF;\">%2</span></a><br/>%3</font><br/>")
|
||||
.arg(tr("Sketch contains partially redundant constraints "))
|
||||
.arg(tr("(click to select)"))
|
||||
.arg(appendPartiallyRedundantMsg(getSketchObject()->getLastPartiallyRedundant()))
|
||||
: QString());
|
||||
|
||||
signalSetUp(infoString);
|
||||
.arg(partiallyRedundantString));
|
||||
}
|
||||
|
||||
signalSolved(QString::fromLatin1("<font color='green'><span style=\"color:#008000; background-color: #ececec;\">%1</font></span>").arg(tr("Solved in %1 sec").arg(getSketchObject()->getLastSolveTime())));
|
||||
|
||||
@@ -20,6 +20,6 @@
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
import FreeCAD,FreeCADGui
|
||||
import FreeCAD
|
||||
rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start")
|
||||
rf.SetBool("AllowDownload",True)
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
import FreeCADGui,sys
|
||||
import FreeCADGui
|
||||
# MRU will be given before this script is run
|
||||
rf=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles")
|
||||
FreeCADGui.loadFile(rf.GetString("MRU"+str(MRU)))
|
||||
|
||||
@@ -31,7 +31,7 @@ function load() {
|
||||
|
||||
if (allowDownloads == 1) {
|
||||
// load latest commits
|
||||
ddiv = document.getElementById("commits");
|
||||
var ddiv = document.getElementById("commits");
|
||||
ddiv.innerHTML = "Connecting...";
|
||||
var tobj=new JSONscriptRequest('https://api.github.com/repos/FreeCAD/FreeCAD/commits?callback=printCommits');
|
||||
tobj.buildScriptTag(); // Build the script tag
|
||||
@@ -61,7 +61,7 @@ function printCommits(data) {
|
||||
|
||||
// json callback for git commits
|
||||
|
||||
ddiv = document.getElementById('commits');
|
||||
var ddiv = document.getElementById('commits');
|
||||
ddiv.innerHTML = "Received";
|
||||
var html = ['<ul>'];
|
||||
for (var i = 0; i < 25; i++) {
|
||||
@@ -76,7 +76,7 @@ function printAddons(data) {
|
||||
|
||||
// json callback for addons list
|
||||
|
||||
ddiv = document.getElementById('addons');
|
||||
var ddiv = document.getElementById('addons');
|
||||
ddiv.innerHTML = "Received";
|
||||
var html = ['<ul class="addonslist">'];
|
||||
var blacklist = ['addons_installer.FCMacro','FreeCAD-Addon-Details.md','README.md'];
|
||||
@@ -98,7 +98,7 @@ function printForum(data) {
|
||||
|
||||
// json callback for forum posts
|
||||
|
||||
ddiv = document.getElementById('forum');
|
||||
var ddiv = document.getElementById('forum');
|
||||
ddiv.innerHTML = "Received";
|
||||
var html = ['<ul>'];
|
||||
for (var i = 0; i < 25; i++) {
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
# the html code of the start page. It is built only once per FreeCAD session for now...
|
||||
|
||||
import six
|
||||
import sys,os,FreeCAD,FreeCADGui,tempfile,time,zipfile,urllib,re
|
||||
import sys,os,FreeCAD,FreeCADGui,tempfile,time,zipfile,re
|
||||
from . import TranslationTexts
|
||||
from PySide import QtCore,QtGui
|
||||
|
||||
@@ -107,7 +107,7 @@ def getInfo(filename):
|
||||
try:
|
||||
import gnome.ui
|
||||
import gnomevfs
|
||||
except:
|
||||
except Exception:
|
||||
# alternative method
|
||||
import hashlib
|
||||
fhash = hashlib.md5(("file://"+path).encode("utf8")).hexdigest()
|
||||
@@ -140,7 +140,7 @@ def getInfo(filename):
|
||||
if filename.lower().endswith(".fcstd"):
|
||||
try:
|
||||
zfile=zipfile.ZipFile(filename)
|
||||
except:
|
||||
except Exception:
|
||||
print("Cannot read file: ",filename)
|
||||
return None
|
||||
files=zfile.namelist()
|
||||
@@ -366,7 +366,6 @@ def handle():
|
||||
|
||||
# build SECTION_RECENTFILES
|
||||
|
||||
SECTION_RECENTFILES = encode("")
|
||||
rf = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles")
|
||||
rfcount = rf.GetInt("RecentFiles",0)
|
||||
SECTION_RECENTFILES = encode("<h2>"+TranslationTexts.T_RECENTFILES+"</h2>")
|
||||
@@ -493,7 +492,7 @@ def handle():
|
||||
img = os.path.join(resources_dir,"images/freecad.png")
|
||||
iconbank[wb] = img
|
||||
UL_WORKBENCHES += '<li>'
|
||||
UL_WORKBENCHES += '<img src="file:///'+iconbank[wb]+'"> '
|
||||
UL_WORKBENCHES += '<img src="file:///'+img+'"> '
|
||||
UL_WORKBENCHES += '<a href="https://www.freecadweb.org/wiki/'+wn+'_Workbench">'+wn.replace("ReverseEngineering","ReverseEng")+'</a>'
|
||||
UL_WORKBENCHES += '</li>'
|
||||
UL_WORKBENCHES += '</ul>'
|
||||
@@ -503,19 +502,19 @@ def handle():
|
||||
|
||||
try:
|
||||
import dxfLibrary
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
wblist.append("dxf-library")
|
||||
try:
|
||||
import RebarTools
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
wblist.append("reinforcement")
|
||||
try:
|
||||
import CADExchangerIO
|
||||
except:
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
wblist.append("cadexchanger")
|
||||
|
||||
@@ -171,7 +171,7 @@ CmdSurfaceGeomFillSurface::CmdSurfaceGeomFillSurface()
|
||||
sToolTipText = QT_TR_NOOP("Creates a surface from two, three or four boundary edges.");
|
||||
sWhatsThis = "Surface_GeomFillSurface";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Surface_BSplineSurface";
|
||||
sPixmap = "Surface_GeomFillSurface";
|
||||
}
|
||||
|
||||
bool CmdSurfaceGeomFillSurface::isActive(void)
|
||||
@@ -237,7 +237,7 @@ CmdSurfaceExtendFace::CmdSurfaceExtendFace()
|
||||
"with its local U and V parameters.");
|
||||
sWhatsThis = "Surface_ExtendFace";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Surface_Extend";
|
||||
sPixmap = "Surface_ExtendFace";
|
||||
}
|
||||
|
||||
void CmdSurfaceExtendFace::activated(int)
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
<file>icons/Surface_BSplineSurface.svg</file>
|
||||
<file>icons/Surface_CurveOnMesh.svg</file>
|
||||
<file>icons/Surface_Cut.svg</file>
|
||||
<file>icons/Surface_Extend.svg</file>
|
||||
<file>icons/Surface_ExtendFace.svg</file>
|
||||
<file>icons/Surface_Filling.svg</file>
|
||||
<file>icons/Surface_GeomFillSurface.svg</file>
|
||||
<file>icons/Surface_Sections.svg</file>
|
||||
<file>icons/Surface_Sewing.svg</file>
|
||||
<file>icons/Surface_Surface.svg</file>
|
||||
|
||||
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 7.4 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 187 KiB |
@@ -0,0 +1,211 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="svg2985"
|
||||
height="64px"
|
||||
width="64px">
|
||||
<title
|
||||
id="title889">Surface_ExtendFace</title>
|
||||
<defs
|
||||
id="defs2987">
|
||||
<linearGradient
|
||||
id="linearGradient4387">
|
||||
<stop
|
||||
id="stop4389"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4391"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6321">
|
||||
<stop
|
||||
id="stop6323"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop6325"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientTransform="translate(-0.23443224,0.23443198)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
id="radialGradient3692"
|
||||
xlink:href="#linearGradient3377" />
|
||||
<linearGradient
|
||||
id="linearGradient3377">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3377-3">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379-8" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381-3" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6412"
|
||||
xlink:href="#linearGradient3377-3" />
|
||||
<linearGradient
|
||||
id="linearGradient3036">
|
||||
<stop
|
||||
style="stop-color:#ef2929;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3038" />
|
||||
<stop
|
||||
style="stop-color:#a40000;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3040" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)"
|
||||
xlink:href="#linearGradient1189"
|
||||
id="linearGradient2095"
|
||||
x1="47"
|
||||
y1="9"
|
||||
x2="7"
|
||||
y2="28"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="linearGradient1189">
|
||||
<stop
|
||||
style="stop-color:#204a87;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1185" />
|
||||
<stop
|
||||
style="stop-color:#729fcf;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop1187" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Surface_ExtendFace</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[bitacovir]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Shape_from_Mesh</dc:title>
|
||||
<dc:date>2020/10/03</dc:date>
|
||||
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier />
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>surface</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<dc:description />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1">
|
||||
<path
|
||||
style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z"
|
||||
id="path3820-1-9" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 13.603223,48.657681 Z"
|
||||
id="path864" />
|
||||
<path
|
||||
id="path3820-1-9-6"
|
||||
d="M 5.3492197,24.626663 18.113409,55.643482 C 32.677175,26.678261 49.470489,63.641047 58.406281,34.501823 L 39.63819,7.4361573 C 30.967406,29.589343 16.774125,4.5857267 5.3492197,24.626663 Z"
|
||||
style="display:inline;fill:none;fill-opacity:1;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 9.4369591,17.432017 23.893747,49.402887"
|
||||
id="path858" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 40.112066,21.625636 6.018626,-7.896527"
|
||||
id="path868" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 34.776081,12.780487 54.665726,44.771608"
|
||||
id="path860" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 14.039223,26.683244 7.5864126,33.920856"
|
||||
id="path862" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 20.840834,39.937666 13.516022,48.22168"
|
||||
id="path866" />
|
||||
<path
|
||||
style="fill:none;stroke:#0b1521;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 48.04728,34.356857 6.490744,-8.440886"
|
||||
id="path870" />
|
||||
<path
|
||||
id="path856-7"
|
||||
d="m 14.039223,26.770444 6.278411,13.690423 c 11.170658,-12.42446 17.282205,3.65439 28.078446,-6.01681 L 39.937666,21.102435 c -9.271964,9.611298 -19.414292,-4.508807 -25.898443,5.668009 z"
|
||||
style="fill:none;stroke:#0b1521;stroke-width:4;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
style="fill:none;stroke:#fce94f;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 14.039223,26.770444 6.278411,13.690423 c 11.170658,-12.42446 17.282205,3.65439 28.078446,-6.01681 L 39.937666,21.102435 c -9.271964,9.611298 -19.414292,-4.508807 -25.898443,5.668009 z"
|
||||
id="path856" />
|
||||
<path
|
||||
style="fill:none;stroke:#204a87;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 42.16127,29.233848 -2.790405,-4.360007 c -3.15688,2.379363 -6.869436,2.749406 -10.703817,1.983804"
|
||||
id="path889" />
|
||||
<path
|
||||
style="fill:none;stroke:#204a87;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 22.650238,41.703469 c 4.877832,-5.031355 7.162943,-3.688493 11.706619,-2.746805"
|
||||
id="path891" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,171 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="svg2985"
|
||||
height="64px"
|
||||
width="64px">
|
||||
<title
|
||||
id="title889">Surface_GeomFillSurface</title>
|
||||
<defs
|
||||
id="defs2987">
|
||||
<linearGradient
|
||||
id="linearGradient4387">
|
||||
<stop
|
||||
id="stop4389"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop4391"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient6321">
|
||||
<stop
|
||||
id="stop6323"
|
||||
offset="0"
|
||||
style="stop-color:#71b2f8;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop6325"
|
||||
offset="1"
|
||||
style="stop-color:#002795;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
gradientTransform="translate(-0.23443224,0.23443198)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
id="radialGradient3692"
|
||||
xlink:href="#linearGradient3377" />
|
||||
<linearGradient
|
||||
id="linearGradient3377">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient3377-3">
|
||||
<stop
|
||||
style="stop-color:#faff2b;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop3379-8" />
|
||||
<stop
|
||||
style="stop-color:#ffaa00;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop3381-3" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="19.467436"
|
||||
fy="28.869568"
|
||||
fx="45.883327"
|
||||
cy="28.869568"
|
||||
cx="45.883327"
|
||||
gradientTransform="matrix(0.67067175,0,0,0.64145918,-63.380792,0.83845403)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient6412"
|
||||
xlink:href="#linearGradient3377-3" />
|
||||
<linearGradient
|
||||
id="linearGradient3036">
|
||||
<stop
|
||||
style="stop-color:#ef2929;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop3038" />
|
||||
<stop
|
||||
style="stop-color:#a40000;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop3040" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
gradientTransform="matrix(0.96812402,0,0,0.96755864,-0.72057496,-2.6783592)"
|
||||
xlink:href="#linearGradient1189"
|
||||
id="linearGradient2095"
|
||||
x1="47"
|
||||
y1="9"
|
||||
x2="7"
|
||||
y2="28"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="linearGradient1189">
|
||||
<stop
|
||||
style="stop-color:#204a87;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop1185" />
|
||||
<stop
|
||||
style="stop-color:#729fcf;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop1187" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata2990">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>Surface_GeomFillSurface</dc:title>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>[bitacovir]</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:title>Part_Shape_from_Mesh</dc:title>
|
||||
<dc:date>2020/10/03</dc:date>
|
||||
<dc:relation>http://www.freecadweb.org/wiki/index.php?title=Artwork</dc:relation>
|
||||
<dc:publisher>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:publisher>
|
||||
<dc:identifier />
|
||||
<dc:rights>
|
||||
<cc:Agent>
|
||||
<dc:title>FreeCAD LGPL2+</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:rights>
|
||||
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
|
||||
<dc:contributor>
|
||||
<cc:Agent>
|
||||
<dc:title />
|
||||
</cc:Agent>
|
||||
</dc:contributor>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>surface</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
<dc:description />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1">
|
||||
<path
|
||||
style="display:inline;fill:url(#linearGradient2095);fill-opacity:1;stroke:#302b00;stroke-width:2;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 3.1519208,24.413282 17.899691,60.320494 c 14.52186,-34.832109 31.154371,7.93994 42.771854,-25.924611 L 38.972508,3.1269928 C 30.809307,30.407148 17.749067,-0.16084869 3.1519208,24.413282 Z"
|
||||
id="path3820-1-9" />
|
||||
<path
|
||||
id="path3820-1-9-6"
|
||||
d="M 6.0274776,24.811642 18.175069,54.286966 C 33.540412,25.445065 49.03887,62.222871 57.666363,34.995102 L 39.76151,8.7926732 C 30.227488,30.267601 17.452383,4.7707061 6.0274776,24.811642 Z"
|
||||
style="display:inline;fill:none;fill-opacity:1;stroke:#fce94f;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
<path
|
||||
id="path857"
|
||||
d="M 8.3549048,24.972224 18.621264,49.266191 C 31.716699,27.213603 50.17526,55.741537 55.247193,35.269413 L 40.202199,12.856071 C 30.336022,29.759966 16.753608,10.080699 8.3549048,24.972224 Z"
|
||||
style="fill:none;stroke:#302b00;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 5.4 KiB |