@@ -122,7 +122,7 @@ public:
|
||||
"fromShapes(shapes, start=Vector(), return_end=False" PARAM_PY_ARGS_DOC(ARG,AREA_PARAMS_PATH) ")\n"
|
||||
"\nReturns a Path object from a list of shapes\n"
|
||||
"\n* shapes: input list of shapes.\n"
|
||||
"\n* start (Vector()): optional start position.\n"
|
||||
"\n* start (Vector()): feed start position, and also serves as a hint of path entry.\n"
|
||||
"\n* return_end (False): if True, returns tuple (path, endPosition).\n"
|
||||
PARAM_PY_DOC(ARG, AREA_PARAMS_PATH)
|
||||
);
|
||||
@@ -415,7 +415,7 @@ private:
|
||||
try {
|
||||
bool need_arc_plane = arc_plane==Area::ArcPlaneAuto;
|
||||
std::list<TopoDS_Shape> wires = Area::sortWires(shapes,&pstart,
|
||||
&pend, &arc_plane, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
|
||||
&pend, 0, &arc_plane, PARAM_PY_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
|
||||
PyObject *list = PyList_New(0);
|
||||
for(auto &wire : wires)
|
||||
PyList_Append(list,Py::new_reference_to(
|
||||
|
||||
@@ -285,6 +285,11 @@ void Area::addWire(CArea &area, const TopoDS_Wire& wire,
|
||||
BRepTools_WireExplorer xp(trsf?TopoDS::Wire(
|
||||
wire.Moved(TopLoc_Location(*trsf))):wire);
|
||||
|
||||
if(!xp.More()) {
|
||||
AREA_TRACE("empty wire");
|
||||
return;
|
||||
}
|
||||
|
||||
gp_Pnt p = BRep_Tool::Pnt(xp.CurrentVertex());
|
||||
ccurve.append(CVertex(Point(p.X(),p.Y())));
|
||||
|
||||
@@ -2060,13 +2065,14 @@ TopoDS_Shape Area::toShape(const CArea &area, bool fill, const gp_Trsf *trsf, in
|
||||
struct WireInfo {
|
||||
TopoDS_Wire wire;
|
||||
std::deque<gp_Pnt> points;
|
||||
gp_Pnt pt_end;
|
||||
bool isClosed;
|
||||
|
||||
inline const gp_Pnt &pstart() const{
|
||||
return points.front();
|
||||
}
|
||||
inline const gp_Pnt &pend() const{
|
||||
return isClosed?pstart():points.back();
|
||||
return isClosed?pstart():pt_end;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2156,8 +2162,9 @@ struct GetWires {
|
||||
// We don't add in-between vertices of an open wire, because we
|
||||
// haven't implemented open wire breaking yet.
|
||||
info.points.push_back(p1);
|
||||
if(params.direction!=Area::DirectionNone)
|
||||
if(!info.isClosed && params.direction==Area::DirectionNone)
|
||||
info.points.push_back(p2);
|
||||
info.pt_end = p2;
|
||||
} else {
|
||||
// For closed wires, we are can easily rebase the wire, so we
|
||||
// discretize the wires to spatial index it in order to accelerate
|
||||
@@ -2188,7 +2195,7 @@ struct GetWires {
|
||||
}
|
||||
auto it = wires.end();
|
||||
--it;
|
||||
for(size_t i=0,count=info.points.size();i<count;++i)
|
||||
for(size_t i=0,count=it->points.size();i<count;++i)
|
||||
rtree.insert(RValue(it,i));
|
||||
FC_DURATION_PLUS(params.bd,t);
|
||||
}
|
||||
@@ -2267,7 +2274,7 @@ struct ShapeInfo{
|
||||
}
|
||||
if(!done){
|
||||
double d1 = pt.SquareDistance(it->pstart());
|
||||
if(myParams.direction==Area::DirectionNone) {
|
||||
if(myParams.direction!=Area::DirectionNone) {
|
||||
d = d1;
|
||||
p = it->pstart();
|
||||
is_start = true;
|
||||
@@ -2430,11 +2437,14 @@ struct ShapeInfo{
|
||||
return myBestWire->wire;
|
||||
}
|
||||
|
||||
std::list<TopoDS_Shape> sortWires(const gp_Pnt &pstart, gp_Pnt &pend,double min_dist) {
|
||||
std::list<TopoDS_Shape> sortWires(const gp_Pnt &pstart, gp_Pnt &pend,double min_dist, gp_Pnt *pentry) {
|
||||
|
||||
if(pstart.SquareDistance(myStartPt)>Precision::SquareConfusion())
|
||||
if(myWires.empty() ||
|
||||
pstart.SquareDistance(myStartPt)>Precision::SquareConfusion())
|
||||
nearest(pstart);
|
||||
|
||||
if(pentry) *pentry = myBestPt;
|
||||
|
||||
std::list<TopoDS_Shape> wires;
|
||||
if(min_dist < 0.01)
|
||||
min_dist = 0.01;
|
||||
@@ -2602,7 +2612,7 @@ struct WireOrienter {
|
||||
};
|
||||
|
||||
std::list<TopoDS_Shape> Area::sortWires(const std::list<TopoDS_Shape> &shapes,
|
||||
const gp_Pnt *_pstart, gp_Pnt *_pend, short *_parc_plane,
|
||||
gp_Pnt *_pstart, gp_Pnt *_pend, double *stepdown_hint, short *_parc_plane,
|
||||
PARAM_ARGS(PARAM_FARG,AREA_PARAMS_SORT))
|
||||
{
|
||||
std::list<TopoDS_Shape> wires;
|
||||
@@ -2727,7 +2737,12 @@ std::list<TopoDS_Shape> Area::sortWires(const std::list<TopoDS_Shape> &shapes,
|
||||
AREA_TRACE("bound (" << xMin<<", "<<xMax<<"), ("<<
|
||||
yMin<<", "<<yMax<<"), ("<<zMin<<", "<<zMax<<')');
|
||||
pstart.SetCoord(xMax,yMax,zMax);
|
||||
if(_pstart) *_pstart = pstart;
|
||||
}
|
||||
|
||||
gp_Pln pln;
|
||||
double hint = 0.0;
|
||||
bool hint_first = true;
|
||||
while(shape_list.size()) {
|
||||
AREA_TRACE("sorting " << shape_list.size() << ' ' << AREA_XYZ(pstart));
|
||||
double best_d;
|
||||
@@ -2746,10 +2761,42 @@ std::list<TopoDS_Shape> Area::sortWires(const std::list<TopoDS_Shape> &shapes,
|
||||
best_d = d;
|
||||
}
|
||||
}
|
||||
wires.splice(wires.end(),best_it->sortWires(pstart,pend,min_dist));
|
||||
gp_Pnt pentry;
|
||||
wires.splice(wires.end(),best_it->sortWires(pstart,pend,min_dist,&pentry));
|
||||
if(use_bound && _pstart) {
|
||||
use_bound = false;
|
||||
*_pstart = pentry;
|
||||
}
|
||||
if(sort_mode==SortMode2D5 && stepdown_hint) {
|
||||
if(!best_it->myPlanar)
|
||||
hint_first = true;
|
||||
else if(hint_first)
|
||||
hint_first = false;
|
||||
else{
|
||||
// Calculate distance of two gp_pln.
|
||||
//
|
||||
// Can't use gp_pln.Distance(), because it only calculate
|
||||
// the distance if two plane are parallel. And it checks
|
||||
// parallelity using tolerance gp::Resolution() which is
|
||||
// defined as DBL_MIN (min double) in Standard_Real.hxx.
|
||||
// Really? Is that a bug?
|
||||
const gp_Pnt& P = pln.Position().Location();
|
||||
const gp_Pnt& loc = best_it->myPln.Position().Location ();
|
||||
const gp_Dir& dir = best_it->myPln.Position().Direction();
|
||||
double d = (dir.X() * (P.X() - loc.X()) +
|
||||
dir.Y() * (P.Y() - loc.Y()) +
|
||||
dir.Z() * (P.Z() - loc.Z()));
|
||||
if (d < 0) d = -d;
|
||||
if(d>hint)
|
||||
hint = d;
|
||||
}
|
||||
pln = best_it->myPln;
|
||||
}
|
||||
pstart = pend;
|
||||
shape_list.erase(best_it);
|
||||
}
|
||||
if(stepdown_hint && hint!=0.0)
|
||||
*stepdown_hint = hint;
|
||||
if(_pend) *_pend = pend;
|
||||
FC_DURATION_LOG(rparams.bd,"rtree build");
|
||||
FC_DURATION_LOG(rparams.qd,"rtree query");
|
||||
@@ -2800,24 +2847,22 @@ static void addG0(bool verbose, Toolpath &path,
|
||||
double retraction, double resume_height,
|
||||
double f, double &last_f)
|
||||
{
|
||||
if(!getter || retraction-(last.*getter)() < Precision::Confusion()) {
|
||||
addGCode(verbose,path,last,next,"G0");
|
||||
return;
|
||||
}
|
||||
gp_Pnt pt(last);
|
||||
(pt.*setter)(retraction);
|
||||
addGCode(verbose,path,last,pt,"G0");
|
||||
last = pt;
|
||||
pt = next;
|
||||
(pt.*setter)(retraction);
|
||||
addGCode(verbose,path,last,pt,"G0");
|
||||
if(resume_height>Precision::Confusion() &&
|
||||
resume_height+(next.*getter)() < retraction)
|
||||
{
|
||||
if(retraction-(last.*getter)() > Precision::Confusion()) {
|
||||
(pt.*setter)(retraction);
|
||||
addGCode(verbose,path,last,pt,"G0");
|
||||
last = pt;
|
||||
pt = next;
|
||||
(pt.*setter)((next.*getter)()+resume_height);
|
||||
(pt.*setter)(retraction);
|
||||
addGCode(verbose,path,last,pt,"G0");
|
||||
}
|
||||
if(resume_height>Precision::Confusion()) {
|
||||
if(resume_height+(next.*getter)() < retraction) {
|
||||
last = pt;
|
||||
pt = next;
|
||||
(pt.*setter)((next.*getter)()+resume_height);
|
||||
addGCode(verbose,path,last,pt,"G0");
|
||||
}
|
||||
addG1(verbose,path,pt,next,f,last_f);
|
||||
}else
|
||||
addGCode(verbose,path,pt,next,"G0");
|
||||
@@ -2875,11 +2920,15 @@ void Area::setWireOrientation(TopoDS_Wire &wire, const gp_Dir &dir, bool wire_cc
|
||||
}
|
||||
|
||||
void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
|
||||
const gp_Pnt *pstart, gp_Pnt *pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH))
|
||||
const gp_Pnt *_pstart, gp_Pnt *pend, PARAM_ARGS(PARAM_FARG,AREA_PARAMS_PATH))
|
||||
{
|
||||
std::list<TopoDS_Shape> wires;
|
||||
|
||||
wires = sortWires(shapes,pstart,pend,
|
||||
gp_Pnt pstart;
|
||||
if(_pstart) pstart = *_pstart;
|
||||
|
||||
double stepdown_hint = 1.0;
|
||||
wires = sortWires(shapes,&pstart,pend,&stepdown_hint,
|
||||
PARAM_REF(PARAM_FARG,AREA_PARAMS_ARC_PLANE),
|
||||
PARAM_FIELDS(PARAM_FARG,AREA_PARAMS_SORT));
|
||||
|
||||
@@ -2898,30 +2947,57 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
|
||||
addGCode(path,"G17");
|
||||
}
|
||||
|
||||
AxisGetter getter;
|
||||
AxisSetter setter;
|
||||
switch(retract_axis) {
|
||||
case RetractAxisX:
|
||||
getter = &gp_Pnt::X;
|
||||
setter = &gp_Pnt::SetX;
|
||||
break;
|
||||
case RetractAxisY:
|
||||
getter = &gp_Pnt::Y;
|
||||
setter = &gp_Pnt::SetY;
|
||||
break;
|
||||
default:
|
||||
getter = &gp_Pnt::Z;
|
||||
setter = &gp_Pnt::SetZ;
|
||||
}
|
||||
|
||||
threshold = fabs(threshold);
|
||||
if(threshold < Precision::Confusion())
|
||||
threshold = Precision::Confusion();
|
||||
threshold *= threshold;
|
||||
resume_height = fabs(resume_height);
|
||||
|
||||
AxisGetter getter = &gp_Pnt::Z;
|
||||
AxisSetter setter = &gp_Pnt::SetZ;
|
||||
resume_height = fabs(resume_height);
|
||||
if(resume_height < Precision::Confusion())
|
||||
resume_height = stepdown_hint;
|
||||
|
||||
retraction = fabs(retraction);
|
||||
if(retraction>Precision::Confusion()) {
|
||||
switch(retract_axis) {
|
||||
case RetractAxisX:
|
||||
getter = &gp_Pnt::X;
|
||||
setter = &gp_Pnt::SetX;
|
||||
break;
|
||||
case RetractAxisY:
|
||||
getter = &gp_Pnt::Y;
|
||||
setter = &gp_Pnt::SetY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(retraction < Precision::Confusion())
|
||||
retraction = (pstart.*getter)()+resume_height;
|
||||
|
||||
// in case the user didn't specify feed start, sortWire() will choose one
|
||||
// based on the bound. We'll further adjust that according to resume height
|
||||
if(!_pstart || pstart.SquareDistance(*_pstart)>Precision::SquareConfusion())
|
||||
(pstart.*setter)((pstart.*getter)()+resume_height);
|
||||
|
||||
gp_Pnt plast,p;
|
||||
if(pstart) plast = *pstart;
|
||||
// initial vertial rapid pull up to retraction (or start Z height if higher)
|
||||
(p.*setter)(std::max(retraction,(pstart.*getter)()));
|
||||
addGCode(false,path,plast,p,"G0");
|
||||
plast = p;
|
||||
p = pstart;
|
||||
// rapid horizontal move if start Z is below retraction
|
||||
if(fabs((p.*getter)()-retraction) > Precision::Confusion()) {
|
||||
(p.*setter)(retraction);
|
||||
addGCode(false,path,plast,p,"G0");
|
||||
plast = p;
|
||||
p = pstart;
|
||||
}
|
||||
// vertial rapid down to feed start
|
||||
addGCode(false,path,plast,p,"G0");
|
||||
|
||||
plast = p;
|
||||
bool first = true;
|
||||
bool arcWarned = false;
|
||||
double cur_f = 0.0; // current feed rate
|
||||
@@ -2942,7 +3018,7 @@ void Area::toPath(Toolpath &path, const std::list<TopoDS_Shape> &shapes,
|
||||
(pTmp.*setter)(0.0);
|
||||
(plastTmp.*setter)(0.0);
|
||||
|
||||
if(first||pTmp.SquareDistance(plastTmp)>threshold)
|
||||
if(!first && pTmp.SquareDistance(plastTmp)>threshold)
|
||||
addG0(verbose,path,plast,p,getter,setter,retraction,resume_height,vf,cur_f);
|
||||
else
|
||||
addG1(verbose,path,plast,p,vf,cur_f);
|
||||
|
||||
@@ -341,6 +341,8 @@ public:
|
||||
* \arg \c shapes: input list of shapes.
|
||||
* \arg \c pstart: optional start point
|
||||
* \arg \c pend: optional output containing the ending point of the returned
|
||||
* \arg \c stepdown_hint: optional output of a hint of step down as the max
|
||||
* distance between two sections.
|
||||
* \arg \c arc_plane: optional arc plane selection, if given the found plane
|
||||
* will be returned. See #AREA_PARAMS_ARC_PLANE for more details.
|
||||
*
|
||||
@@ -349,8 +351,8 @@ public:
|
||||
* \return sorted wires
|
||||
*/
|
||||
static std::list<TopoDS_Shape> sortWires(const std::list<TopoDS_Shape> &shapes,
|
||||
const gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, short *arc_plane = NULL,
|
||||
PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT));
|
||||
gp_Pnt *pstart=NULL, gp_Pnt *pend=NULL, double *stepdown_hint=NULL,
|
||||
short *arc_plane = NULL, PARAM_ARGS_DEF(PARAM_FARG,AREA_PARAMS_SORT));
|
||||
|
||||
/** Convert a list of wires to gcode
|
||||
*
|
||||
|
||||
@@ -55,7 +55,7 @@ PARAM_ENUM_STRING_DECLARE(static const char *Enums,AREA_PARAMS_PATH)
|
||||
FeatureShape::FeatureShape()
|
||||
{
|
||||
ADD_PROPERTY(Sources,(0));
|
||||
ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Path start position");
|
||||
ADD_PROPERTY_TYPE(StartPoint,(Base::Vector3d()),"Path",App::Prop_None,"Feed start position");
|
||||
PARAM_PROP_ADD("Path",AREA_PARAMS_PATH);
|
||||
PARAM_PROP_SET_ENUM(Enums,AREA_PARAMS_PATH);
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PostUtils.py
|
||||
PathScripts/__init__.py
|
||||
PathScripts/kdtree.py
|
||||
)
|
||||
|
||||
SET(PathScripts_post_SRCS
|
||||
PathScripts/post/__init__.py
|
||||
PathScripts/post/centroid_post.py
|
||||
PathScripts/post/comparams_post.py
|
||||
@@ -84,7 +87,9 @@ SET(PathScripts_SRCS
|
||||
|
||||
SET(PathTests_SRCS
|
||||
PathTests/__init__.py
|
||||
PathTests/boxtest.fcstd
|
||||
PathTests/PathTestUtils.py
|
||||
PathTests/test_centroid_00.ngc
|
||||
PathTests/test_linuxcnc_00.ngc
|
||||
PathTests/TestPathCore.py
|
||||
PathTests/TestPathDepthParams.py
|
||||
@@ -97,7 +102,7 @@ SET(PathTests_SRCS
|
||||
|
||||
SET(all_files
|
||||
${PathScripts_SRCS}
|
||||
${PathScripts_NC_SRCS}
|
||||
${PathScripts_post_SRCS}
|
||||
)
|
||||
|
||||
ADD_CUSTOM_TARGET(PathScripts ALL
|
||||
@@ -129,3 +134,10 @@ INSTALL(
|
||||
DESTINATION
|
||||
Mod/Path/PathTests
|
||||
)
|
||||
|
||||
INSTALL(
|
||||
FILES
|
||||
${PathScripts_post_SRCS}
|
||||
DESTINATION
|
||||
Mod/Path/PathScripts/post
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ else:
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
@@ -62,7 +63,6 @@ class ObjectContour:
|
||||
PathLog.track()
|
||||
obj.addProperty("App::PropertyBool", "Active", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Make False, to prevent operation from generating code"))
|
||||
obj.addProperty("App::PropertyString", "Comment", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "An optional comment for this Contour"))
|
||||
#obj.addProperty("App::PropertyString", "UserLabel", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "User Assigned Label"))
|
||||
|
||||
# Tool Properties
|
||||
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
@@ -76,6 +76,7 @@ class ObjectContour:
|
||||
|
||||
# Start Point Properties
|
||||
obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path"))
|
||||
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point"))
|
||||
|
||||
# Contour Properties
|
||||
obj.addProperty("App::PropertyEnumeration", "Direction", "Contour", QtCore.QT_TRANSLATE_NOOP("App::Property", "The direction that the toolpath should go around the part ClockWise CW or CounterClockWise CCW"))
|
||||
@@ -85,8 +86,12 @@ class ObjectContour:
|
||||
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Contour", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final Contour- good for roughing toolpath"))
|
||||
|
||||
# Debug Parameters
|
||||
# obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea"))
|
||||
# obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Path")
|
||||
obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "PathParams", "Path")
|
||||
obj.setEditorMode('PathParams', 2) # hide
|
||||
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
|
||||
obj.setEditorMode('removalshape', 2) # hide
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderContour(obj.ViewObject)
|
||||
@@ -96,7 +101,8 @@ class ObjectContour:
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
PathLog.track('prop: {} state: {}'.format(prop, obj.State))
|
||||
#pass
|
||||
if prop in ['AreaParams', 'PathParams', 'removalshape']:
|
||||
obj.setEditorMode(prop, 2)
|
||||
|
||||
def __getstate__(self):
|
||||
PathLog.track()
|
||||
@@ -145,7 +151,7 @@ class ObjectContour:
|
||||
heights = [i for i in self.depthparams]
|
||||
PathLog.debug('depths: {}'.format(heights))
|
||||
profile.setParams(**profileparams)
|
||||
#obj.AreaParams = str(profile.getParams())
|
||||
obj.AreaParams = str(profile.getParams())
|
||||
|
||||
PathLog.debug("Contour with params: {}".format(profile.getParams()))
|
||||
sections = profile.makeSections(mode=0, project=True, heights=heights)
|
||||
@@ -166,21 +172,22 @@ class ObjectContour:
|
||||
|
||||
if self.endVector is not None:
|
||||
params['start'] = self.endVector
|
||||
elif start is not None:
|
||||
params['start'] = start
|
||||
elif obj.UseStartPoint:
|
||||
params['start'] = obj.StartPoint
|
||||
|
||||
obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'})
|
||||
|
||||
(pp, end_vector) = Path.fromShapes(**params)
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
PathLog.debug('pp: {}, end vector: {}'.format(pp, end_vector))
|
||||
self.endVector = end_vector
|
||||
|
||||
simobj = None
|
||||
if getsim:
|
||||
profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True}
|
||||
profileparams['ToolRadius']= self.radius - self.radius *.005
|
||||
profileparams['Thicken'] = True
|
||||
profileparams['ToolRadius'] = self.radius - self.radius * .005
|
||||
profile.setParams(**profileparams)
|
||||
sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape()
|
||||
simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax))
|
||||
simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax))
|
||||
|
||||
return pp, simobj
|
||||
|
||||
@@ -207,6 +214,7 @@ class ObjectContour:
|
||||
user_depths=None)
|
||||
|
||||
if toolLoad is None or toolLoad.ToolNumber == 0:
|
||||
|
||||
FreeCAD.Console.PrintError("No Tool Controller is selected. We need a tool to build a Path.")
|
||||
return
|
||||
else:
|
||||
@@ -229,15 +237,13 @@ class ObjectContour:
|
||||
commandlist.append(Path.Command("(Uncompensated Tool Path)"))
|
||||
|
||||
parentJob = PathUtils.findParentJob(obj)
|
||||
|
||||
if parentJob is None:
|
||||
return
|
||||
baseobject = parentJob.Base
|
||||
if baseobject is None:
|
||||
return
|
||||
|
||||
# Let's always start by rapid to clearance...just for safety
|
||||
commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
|
||||
|
||||
isPanel = False
|
||||
if hasattr(baseobject, "Proxy"):
|
||||
if isinstance(baseobject.Proxy, ArchPanel.PanelSheet): # process the sheet
|
||||
@@ -256,10 +262,9 @@ class ObjectContour:
|
||||
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a contour path. Check project and tool config.")
|
||||
|
||||
if hasattr(baseobject, "Shape") and not isPanel:
|
||||
#bb = baseobject.Shape.BoundBox
|
||||
env = PathUtils.getEnvelope(partshape=baseobject.Shape, subshape=None, depthparams=self.depthparams)
|
||||
try:
|
||||
(pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint,getsim=getsim)
|
||||
(pp, sim) = self._buildPathArea(obj, env, start=obj.StartPoint, getsim=getsim)
|
||||
commandlist.extend(pp.Commands)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(e)
|
||||
@@ -268,9 +273,9 @@ class ObjectContour:
|
||||
# Let's finish by rapid to clearance...just for safety
|
||||
commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
|
||||
|
||||
PathLog.track()
|
||||
path = Path.Path(commandlist)
|
||||
obj.Path = path
|
||||
#obj.ViewObject.Visibility = True
|
||||
return sim
|
||||
|
||||
|
||||
@@ -371,7 +376,6 @@ class CommandPathContour:
|
||||
FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)')
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
#FreeCAD.ActiveDocument.recompute()
|
||||
FreeCADGui.doCommand('obj.ViewObject.startEditing()')
|
||||
|
||||
|
||||
@@ -379,7 +383,6 @@ class TaskPanel:
|
||||
def __init__(self, obj, deleteOnReject):
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_Contour", "Contour Operation"))
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ContourEdit.ui")
|
||||
# self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ContourEdit.ui")
|
||||
self.deleteOnReject = deleteOnReject
|
||||
self.isDirty = True
|
||||
|
||||
@@ -402,7 +405,7 @@ class TaskPanel:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def clicked(self,button):
|
||||
def clicked(self, button):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.getFields()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
@@ -39,10 +39,10 @@ if True:
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
#FreeCADGui = None
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
@@ -95,8 +95,12 @@ class ObjectFace:
|
||||
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point"))
|
||||
|
||||
# Debug Parameters
|
||||
# obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea"))
|
||||
# obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Path")
|
||||
obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "PathParams", "Path")
|
||||
obj.setEditorMode('PathParams', 2) # hide
|
||||
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
|
||||
obj.setEditorMode('removalshape', 2) # hide
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderFace(obj.ViewObject)
|
||||
@@ -108,6 +112,8 @@ class ObjectFace:
|
||||
if prop == "StepOver":
|
||||
if obj.StepOver == 0:
|
||||
obj.StepOver = 1
|
||||
if prop in ['AreaParams', 'PathParams', 'removalshape']:
|
||||
obj.setEditorMode(prop, 2)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
@@ -115,7 +121,6 @@ class ObjectFace:
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
def setDepths(self, obj):
|
||||
PathLog.track()
|
||||
parentJob = PathUtils.findParentJob(obj)
|
||||
@@ -139,8 +144,8 @@ class ObjectFace:
|
||||
if len(baselist) == 0: # When adding the first base object, guess at heights
|
||||
subshape = [ss.Shape.getElement(sub)]
|
||||
d = PathUtils.guessDepths(ss.Shape, subshape)
|
||||
obj.ClearanceHeight =d.clearance_height
|
||||
obj.SafeHeight = d.safe_height +1
|
||||
obj.ClearanceHeight = d.clearance_height
|
||||
obj.SafeHeight = d.safe_height + 1
|
||||
obj.StartDepth = d.safe_height
|
||||
obj.FinalDepth = d.final_depth
|
||||
obj.StepDown = obj.StartDepth.Value-obj.FinalDepth.Value
|
||||
@@ -155,7 +160,6 @@ class ObjectFace:
|
||||
baselist.append(item)
|
||||
PathLog.debug('baselist: {}'.format(baselist))
|
||||
obj.Base = baselist
|
||||
#self.execute(obj)
|
||||
|
||||
def getStock(self, obj):
|
||||
"""find and return a stock object from hosting project if any"""
|
||||
@@ -197,23 +201,34 @@ class ObjectFace:
|
||||
|
||||
heights = [i for i in self.depthparams]
|
||||
boundary.setParams(**pocketparams)
|
||||
#obj.AreaParams = str(boundary.getParams())
|
||||
#PathLog.track('areaparams: {}'.format(obj.AreaParams))
|
||||
PathLog.track('height: {}'.format(heights))
|
||||
obj.AreaParams = str(boundary.getParams())
|
||||
sections = boundary.makeSections(mode=0, project=False, heights=heights)
|
||||
shapelist = [sec.getShape() for sec in sections]
|
||||
|
||||
params = {'shapes': shapelist,
|
||||
'feedrate': self.horizFeed,
|
||||
params = {'feedrate': self.horizFeed,
|
||||
'feedrate_v': self.vertFeed,
|
||||
'verbose': True,
|
||||
'resume_height': obj.StepDown,
|
||||
'retraction': obj.ClearanceHeight.Value}
|
||||
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
pp = Path.fromShapes(**params)
|
||||
pp = []
|
||||
|
||||
return pp
|
||||
if obj.UseStartPoint and obj.StartPoint is not None:
|
||||
params['start'] = obj.StartPoint
|
||||
|
||||
# store the params for debugging. Don't need the shape.
|
||||
obj.PathParams = str(params)
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
|
||||
for sec in sections:
|
||||
shape = sec.getShape()
|
||||
respath = Path.fromShapes(shape, **params)
|
||||
# Insert any entry code to the layer
|
||||
|
||||
# append the layer path
|
||||
pp.extend(respath.Commands)
|
||||
respath.Commands = pp
|
||||
|
||||
return respath
|
||||
|
||||
def execute(self, obj):
|
||||
PathLog.track()
|
||||
@@ -229,7 +244,7 @@ class ObjectFace:
|
||||
self.depthparams = depth_params(
|
||||
clearance_height=obj.ClearanceHeight.Value,
|
||||
safe_height=obj.SafeHeight.Value,
|
||||
start_depth=obj.StartDepth.Value,
|
||||
start_depth=obj.SafeHeight.Value,
|
||||
step_down=obj.StepDown,
|
||||
z_finish_step=obj.FinishDepth.Value,
|
||||
final_depth=obj.FinalDepth.Value,
|
||||
@@ -281,9 +296,6 @@ class ObjectFace:
|
||||
planeshape = baseobject.Shape
|
||||
PathLog.info("Working on a shape {}".format(baseobject.Name))
|
||||
|
||||
# Let's start by rapid to clearance...just for safety
|
||||
commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
|
||||
|
||||
# if user wants the boundbox, calculate that
|
||||
PathLog.info("Boundary Shape: {}".format(obj.BoundaryShape))
|
||||
bb = planeshape.BoundBox
|
||||
@@ -293,6 +305,9 @@ class ObjectFace:
|
||||
else:
|
||||
env = PathUtils.getEnvelope(partshape=planeshape, depthparams=self.depthparams)
|
||||
|
||||
# save the envelope for reference
|
||||
obj.removalshape = env
|
||||
|
||||
try:
|
||||
commandlist.extend(self._buildPathArea(obj, env).Commands)
|
||||
except Exception as e:
|
||||
@@ -354,8 +369,6 @@ class CommandPathMillFace:
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
#ztop = 10.0
|
||||
|
||||
# if everything is ok, execute and register the transaction in the undo/redo stack
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathFace", "Create Face"))
|
||||
FreeCADGui.addModule("PathScripts.PathMillFace")
|
||||
@@ -368,7 +381,7 @@ class CommandPathMillFace:
|
||||
|
||||
FreeCADGui.doCommand('obj.Active = True')
|
||||
FreeCADGui.doCommand('obj.StepOver = 50')
|
||||
#FreeCADGui.doCommand('obj.StepDown = 1.0')
|
||||
FreeCADGui.doCommand('obj.StepDown = 1.0')
|
||||
FreeCADGui.doCommand('obj.ZigZagAngle = 45.0')
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
@@ -392,6 +405,7 @@ class _CommandSetFaceStartPoint:
|
||||
def Activated(self):
|
||||
FreeCADGui.Snapper.getPoint(callback=self.setpoint)
|
||||
|
||||
|
||||
class TaskPanel:
|
||||
def __init__(self, obj, deleteOnReject):
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_MillFace", "Mill Facing Operation"))
|
||||
@@ -420,7 +434,7 @@ class TaskPanel:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def clicked(self,button):
|
||||
def clicked(self, button):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.getFields()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
@@ -77,21 +77,27 @@ class ObjectPocket:
|
||||
obj.addProperty("App::PropertyFloat", "ZigZagAngle", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angle of the zigzag pattern"))
|
||||
obj.addProperty("App::PropertyEnumeration", "OffsetPattern", "Face", QtCore.QT_TRANSLATE_NOOP("App::Property", "clearing pattern to use"))
|
||||
obj.OffsetPattern = ['ZigZag', 'Offset', 'Spiral', 'ZigZagOffset', 'Line', 'Grid', 'Triangle']
|
||||
obj.addProperty("App::PropertyBool", "MinTravel", "Pocket", QtCore.QT_TRANSLATE_NOOP("App::Property", "Use 3D Sorting of Path"))
|
||||
|
||||
# Start Point Properties
|
||||
obj.addProperty("App::PropertyVector", "StartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "The start point of this path"))
|
||||
obj.addProperty("App::PropertyBool", "UseStartPoint", "Start Point", QtCore.QT_TRANSLATE_NOOP("App::Property", "make True, if specifying a Start Point"))
|
||||
|
||||
# Debug Parameters
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea"))
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Path")
|
||||
obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "PathParams", "Path")
|
||||
obj.setEditorMode('PathParams', 2) # hide
|
||||
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
|
||||
obj.setEditorMode('removalshape', 2) # hide
|
||||
if FreeCAD.GuiUp:
|
||||
ViewProviderPocket(obj.ViewObject)
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
pass
|
||||
if prop in ['AreaParams', 'PathParams', 'removalshape']:
|
||||
obj.setEditorMode(prop, 2)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
@@ -211,6 +217,17 @@ class ObjectPocket:
|
||||
'resume_height': obj.StepDown.Value,
|
||||
'retraction': obj.ClearanceHeight.Value}
|
||||
|
||||
if obj.UseStartPoint and obj.StartPoint is not None:
|
||||
params['start'] = obj.StartPoint
|
||||
|
||||
# if MinTravel is turned on, set path sorting to 3DSort
|
||||
# 3DSort shouldn't be used without a valid start point. Can cause
|
||||
# tool crash without it.
|
||||
if obj.MinTravel:
|
||||
params['sort_mode'] = 2
|
||||
|
||||
obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'})
|
||||
|
||||
pp = Path.fromShapes(**params)
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
PathLog.debug(pp)
|
||||
@@ -218,12 +235,10 @@ class ObjectPocket:
|
||||
simobj = None
|
||||
if getsim:
|
||||
pocketparams['Thicken'] = True
|
||||
pocketparams['ToolRadius']= self.radius - self.radius *.005
|
||||
pocketparams['ToolRadius'] = self.radius - self.radius * .005
|
||||
pocketparams['Stepdown'] = -1
|
||||
pocket.setParams(**pocketparams)
|
||||
#pocket.makeSections(mode=0, project=False, heights=heights)
|
||||
simobj = pocket.getShape().extrude(FreeCAD.Vector(0,0,obj.StepDown.Value))
|
||||
#removalshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "simshape")
|
||||
simobj = pocket.getShape().extrude(FreeCAD.Vector(0, 0, obj.StepDown.Value))
|
||||
|
||||
return pp, simobj
|
||||
|
||||
@@ -276,20 +291,15 @@ class ObjectPocket:
|
||||
for sub in b[1]:
|
||||
if "Face" in sub:
|
||||
shape = Part.makeCompound([getattr(b[0].Shape, sub)])
|
||||
#shape = getattr(b[0].Shape, sub)
|
||||
else:
|
||||
edges = [getattr(b[0].Shape, sub) for sub in b[1]]
|
||||
shape = Part.makeFace(edges, 'Part::FaceMakerSimple')
|
||||
|
||||
env = PathUtils.getEnvelope(baseobject.Shape, subshape=shape, depthparams=self.depthparams)
|
||||
removal = env.cut(baseobject.Shape)
|
||||
|
||||
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
|
||||
removalshape=FreeCAD.ActiveDocument.addObject("Part::Feature","removalshape")
|
||||
removalshape.Shape = removal
|
||||
obj.removalshape = env.cut(baseobject.Shape)
|
||||
|
||||
try:
|
||||
(pp, sim) = self._buildPathArea(obj, removal, getsim=getsim)
|
||||
(pp, sim) = self._buildPathArea(obj, obj.removalshape, getsim=getsim)
|
||||
if sim is not None:
|
||||
simlist.append(sim)
|
||||
commandlist.extend(pp.Commands)
|
||||
@@ -300,17 +310,13 @@ class ObjectPocket:
|
||||
PathLog.debug("processing the whole job base object")
|
||||
|
||||
env = PathUtils.getEnvelope(baseobject.Shape, subshape=None, depthparams=self.depthparams)
|
||||
removal = env.cut(baseobject.Shape)
|
||||
if PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
|
||||
removalshape=FreeCAD.ActiveDocument.addObject("Part::Feature","removalshape")
|
||||
removalshape.Shape = removal
|
||||
obj.removalshape = env.cut(baseobject.Shape)
|
||||
try:
|
||||
(pp, sim) = self._buildPathArea(obj, removal, getsim=getsim)
|
||||
(pp, sim) = self._buildPathArea(obj, obj.removalshape, getsim=getsim)
|
||||
commandlist.extend(pp.Commands)
|
||||
if sim is not None:
|
||||
simlist.append(sim)
|
||||
|
||||
#commandlist.extend(self._buildPathArea(obj, env.cut(baseobject.Shape)).Commands)
|
||||
except Exception as e:
|
||||
FreeCAD.Console.PrintError(e)
|
||||
FreeCAD.Console.PrintError("Something unexpected happened. Unable to generate a pocket path. Check project and tool config.")
|
||||
@@ -325,15 +331,16 @@ class ObjectPocket:
|
||||
PathLog.debug(simlist)
|
||||
simshape = None
|
||||
if len(simlist) > 1:
|
||||
simshape=simlist[0].fuse(simlist[1:])
|
||||
simshape = simlist[0].fuse(simlist[1:])
|
||||
elif len(simlist) == 1:
|
||||
simshape = simlist[0]
|
||||
|
||||
if simshape is not None and PathLog.getLevel(PathLog.thisModule()) == PathLog.Level.DEBUG:
|
||||
sim=FreeCAD.ActiveDocument.addObject("Part::Feature","simshape")
|
||||
sim = FreeCAD.ActiveDocument.addObject("Part::Feature", "simshape")
|
||||
sim.Shape = simshape
|
||||
return simshape
|
||||
|
||||
|
||||
class _CommandSetPocketStartPoint:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path-StartPoint',
|
||||
@@ -373,7 +380,6 @@ class ViewProviderPocket:
|
||||
self.deleteOnReject = False
|
||||
return True
|
||||
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Path-Pocket.svg"
|
||||
|
||||
@@ -411,7 +417,6 @@ class CommandPathPocket:
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Pocket")')
|
||||
FreeCADGui.doCommand('PathScripts.PathPocket.ObjectPocket(obj)')
|
||||
FreeCADGui.doCommand('obj.Active = True')
|
||||
#FreeCADGui.doCommand('PathScripts.PathPocket.ViewProviderPocket(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True')
|
||||
FreeCADGui.doCommand('from PathScripts import PathUtils')
|
||||
FreeCADGui.doCommand('obj.StepOver = 100')
|
||||
@@ -456,7 +461,7 @@ class TaskPanel:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def clicked(self,button):
|
||||
def clicked(self, button):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.getFields()
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
|
||||
@@ -86,8 +86,12 @@ class ObjectProfile:
|
||||
obj.addProperty("App::PropertyBool", "processCircles", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Profile round holes"))
|
||||
|
||||
# Debug Parameters
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea"))
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Path")
|
||||
obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "PathParams", "Path")
|
||||
obj.setEditorMode('PathParams', 2) # hide
|
||||
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
|
||||
obj.setEditorMode('removalshape', 2) # hide
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderProfile(obj.ViewObject)
|
||||
@@ -106,6 +110,8 @@ class ObjectProfile:
|
||||
obj.setEditorMode('Side', 2)
|
||||
else:
|
||||
obj.setEditorMode('Side', 0)
|
||||
if prop in ['AreaParams', 'PathParams', 'removalshape']:
|
||||
obj.setEditorMode(prop, 2)
|
||||
|
||||
def addprofilebase(self, obj, ss, sub=""):
|
||||
baselist = obj.Base
|
||||
@@ -188,7 +194,7 @@ class ObjectProfile:
|
||||
'resume_height': obj.StepDown.Value,
|
||||
'retraction': obj.ClearanceHeight.Value}
|
||||
|
||||
#Reverse the direction for holes
|
||||
# Reverse the direction for holes
|
||||
if isHole:
|
||||
direction = "CW" if obj.Direction == "CCW" else "CCW"
|
||||
else:
|
||||
@@ -203,16 +209,19 @@ class ObjectProfile:
|
||||
params['start'] = obj.StartPoint
|
||||
|
||||
pp = Path.fromShapes(**params)
|
||||
|
||||
obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'})
|
||||
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
PathLog.debug(pp)
|
||||
|
||||
simobj = None
|
||||
if getsim:
|
||||
profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True}
|
||||
profileparams['ToolRadius']= self.radius - self.radius *.005
|
||||
profileparams['Thicken'] = True
|
||||
profileparams['ToolRadius'] = self.radius - self.radius * .005
|
||||
profile.setParams(**profileparams)
|
||||
sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape()
|
||||
simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax))
|
||||
simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax))
|
||||
|
||||
return pp, simobj
|
||||
|
||||
@@ -265,7 +274,7 @@ class ObjectProfile:
|
||||
if baseobject is None:
|
||||
return
|
||||
|
||||
if obj.Base: # The user has selected subobjects from the base. Process each.
|
||||
if obj.Base: # The user has selected subobjects from the base. Process each.
|
||||
holes = []
|
||||
faces = []
|
||||
for b in obj.Base:
|
||||
@@ -276,7 +285,7 @@ class ObjectProfile:
|
||||
if numpy.isclose(abs(shape.normalAt(0, 0).z), 1): # horizontal face
|
||||
holes += shape.Wires[1:]
|
||||
else:
|
||||
FreeCAD.Console.PrintWarning ("found a base object which is not a face. Can't continue.")
|
||||
FreeCAD.Console.PrintWarning("found a base object which is not a face. Can't continue.")
|
||||
return
|
||||
|
||||
for wire in holes:
|
||||
@@ -428,7 +437,6 @@ class CommandPathProfile:
|
||||
FreeCADGui.doCommand('obj.UseComp = True')
|
||||
FreeCADGui.doCommand('obj.processHoles = False')
|
||||
FreeCADGui.doCommand('obj.processPerimeter = True')
|
||||
#FreeCADGui.doCommand('PathScripts.PathProfile._ViewProviderProfile(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
FreeCADGui.doCommand('obj.ToolController = PathScripts.PathUtils.findToolController(obj)')
|
||||
@@ -465,7 +473,7 @@ class TaskPanel:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def clicked(self,button):
|
||||
def clicked(self, button):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.getFields()
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
|
||||
@@ -44,6 +44,7 @@ if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
@@ -89,8 +90,12 @@ class ObjectProfile:
|
||||
obj.addProperty("App::PropertyDistance", "OffsetExtra", "Profile", QtCore.QT_TRANSLATE_NOOP("App::Property", "Extra value to stay away from final profile- good for roughing toolpath"))
|
||||
|
||||
# Debug Parameters
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Debug", QtCore.QT_TRANSLATE_NOOP("App::Property", "parameters used by PathArea"))
|
||||
obj.addProperty("App::PropertyString", "AreaParams", "Path")
|
||||
obj.setEditorMode('AreaParams', 2) # hide
|
||||
obj.addProperty("App::PropertyString", "PathParams", "Path")
|
||||
obj.setEditorMode('PathParams', 2) # hide
|
||||
obj.addProperty("Part::PropertyPartShape", "removalshape", "Path")
|
||||
obj.setEditorMode('removalshape', 2) # hide
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
_ViewProviderProfile(obj.ViewObject)
|
||||
@@ -109,6 +114,8 @@ class ObjectProfile:
|
||||
obj.setEditorMode('Side', 2)
|
||||
else:
|
||||
obj.setEditorMode('Side', 0)
|
||||
if prop in ['AreaParams', 'PathParams', 'removalshape']:
|
||||
obj.setEditorMode(prop, 2)
|
||||
|
||||
def addprofilebase(self, obj, ss, sub=""):
|
||||
baselist = obj.Base
|
||||
@@ -142,8 +149,6 @@ class ObjectProfile:
|
||||
else:
|
||||
baselist.append(item)
|
||||
obj.Base = baselist
|
||||
#self.execute(obj)
|
||||
|
||||
|
||||
@waiting_effects
|
||||
def _buildPathArea(self, obj, baseobject, start=None, getsim=False):
|
||||
@@ -163,9 +168,7 @@ class ObjectProfile:
|
||||
else:
|
||||
profileparams['Offset'] = self.radius+obj.OffsetExtra.Value
|
||||
|
||||
|
||||
profile.setParams(**profileparams)
|
||||
# PathLog.debug("About to profile with params: {}".format(profileparams))
|
||||
obj.AreaParams = str(profile.getParams())
|
||||
|
||||
PathLog.debug("About to profile with params: {}".format(profile.getParams()))
|
||||
@@ -191,21 +194,21 @@ class ObjectProfile:
|
||||
|
||||
pp = Path.fromShapes(**params)
|
||||
PathLog.debug("Generating Path with params: {}".format(params))
|
||||
PathLog.debug(pp)
|
||||
|
||||
# store the params for debugging. Don't need the shape.
|
||||
obj.PathParams = str({key: value for key, value in params.items() if key != 'shapes'})
|
||||
|
||||
simobj = None
|
||||
if getsim:
|
||||
profileparams['Thicken'] = True #{'Fill':0, 'Coplanar':0, 'Project':True, 'SectionMode':2, 'Thicken':True}
|
||||
profileparams['ToolRadius']= self.radius - self.radius *.005
|
||||
profileparams['Thicken'] = True
|
||||
profileparams['ToolRadius'] = self.radius - self.radius * .005
|
||||
profile.setParams(**profileparams)
|
||||
sec = profile.makeSections(mode=0, project=False, heights=heights)[-1].getShape()
|
||||
simobj = sec.extrude(FreeCAD.Vector(0,0,baseobject.BoundBox.ZMax))
|
||||
simobj = sec.extrude(FreeCAD.Vector(0, 0, baseobject.BoundBox.ZMax))
|
||||
|
||||
return pp, simobj
|
||||
|
||||
|
||||
def execute(self, obj, getsim=False):
|
||||
# import Part # math #DraftGeomUtils
|
||||
commandlist = []
|
||||
sim = None
|
||||
|
||||
@@ -380,7 +383,6 @@ class CommandPathProfileEdges:
|
||||
FreeCADGui.addModule("PathScripts.PathProfile")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "Edge Profile")')
|
||||
FreeCADGui.doCommand('PathScripts.PathProfileEdges.ObjectProfile(obj)')
|
||||
#FreeCADGui.doCommand('PathScripts.PathProfileEdges._ViewProviderProfile(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy.deleteOnReject = True')
|
||||
|
||||
FreeCADGui.doCommand('obj.Active = True')
|
||||
@@ -408,14 +410,11 @@ class TaskPanel:
|
||||
def __init__(self, obj, deleteOnReject):
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_ProfileEdges", "ProfileEdges Operation"))
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ProfileEdgesEdit.ui")
|
||||
# self.form = FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/Path/ProfileEdgesEdit.ui")
|
||||
self.deleteOnReject = deleteOnReject
|
||||
self.obj = obj
|
||||
self.isDirty = True
|
||||
|
||||
def accept(self):
|
||||
#self.getFields()
|
||||
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
@@ -434,7 +433,7 @@ class TaskPanel:
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def clicked(self,button):
|
||||
def clicked(self, button):
|
||||
if button == QtGui.QDialogButtonBox.Apply:
|
||||
self.getFields()
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
|
||||
@@ -1,142 +1,338 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#***************************************************************************
|
||||
#* *
|
||||
#* Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
#* *
|
||||
#* This program is free software; you can redistribute it and/or modify *
|
||||
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
#* as published by the Free Software Foundation; either version 2 of *
|
||||
#* the License, or (at your option) any later version. *
|
||||
#* for detail see the LICENCE text file. *
|
||||
#* *
|
||||
#* This program is distributed in the hope that it will be useful, *
|
||||
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
#* GNU Library General Public License for more details. *
|
||||
#* *
|
||||
#* You should have received a copy of the GNU Library General Public *
|
||||
#* License along with this program; if not, write to the Free Software *
|
||||
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
#* USA *
|
||||
#* *
|
||||
#***************************************************************************
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2015 Dan Falck <ddfalck@gmail.com> *
|
||||
# * *
|
||||
# * 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
|
||||
TOOLTIP=''' example post for Centroid CNC mill'''
|
||||
|
||||
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 for a centroid 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 centroid_post
|
||||
centroid_post.export(object,"/path/to/file.ncc","")
|
||||
'''
|
||||
|
||||
TOOLTIP_ARGS='''
|
||||
Arguments for centroid:
|
||||
--header,--no-header ... output headers (--header)
|
||||
--comments,--no-comments ... output comments (--comments)
|
||||
--line-numbers,--no-line-numbers ... prefix with line numbers (--no-lin-numbers)
|
||||
--show-editor, --no-show-editor ... pop up editor before writing output(--show-editor)
|
||||
--feed-precision=1 ... number of digits of precision for feed rate. Default=1
|
||||
--axis-precision=4 ... number of digits of precision for axis moves. Default=4
|
||||
'''
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import datetime
|
||||
now = datetime.datetime.now()
|
||||
import PathScripts
|
||||
from PathScripts import PostUtils
|
||||
#from PathScripts import PathUtils
|
||||
|
||||
now = datetime.datetime.now()
|
||||
|
||||
#***************************************************************************
|
||||
# user editable stuff here
|
||||
# These globals set common customization preferences
|
||||
OUTPUT_COMMENTS = True
|
||||
OUTPUT_HEADER = True
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
if FreeCAD.GuiUp:
|
||||
SHOW_EDITOR = True
|
||||
else:
|
||||
SHOW_EDITOR = False
|
||||
MODAL = False # if true commands are suppressed if the same as previous line.
|
||||
|
||||
UNITS = "G20" #old style inch units for this shop
|
||||
MACHINE_NAME = "BigMill"
|
||||
COMMAND_SPACE = " "
|
||||
LINENR = 100 # line number starting value
|
||||
|
||||
# These globals will be reflected in the Machine configuration of the project
|
||||
UNITS = "G20" # G21 for metric, G20 for us standard
|
||||
UNIT_FORMAT = 'mm/min'
|
||||
MACHINE_NAME = "Centroid"
|
||||
CORNER_MIN = {'x':-609.6, 'y':-152.4, 'z':0 } #use metric for internal units
|
||||
CORNER_MAX = {'x':609.6, 'y':152.4, 'z':304.8 } #use metric for internal units
|
||||
|
||||
SHOW_EDITOR = True
|
||||
MODAL = True
|
||||
COMMENT= ';' #centroid control comment symbol
|
||||
|
||||
HEADER = ""
|
||||
HEADER += ";Exported by FreeCAD\n"
|
||||
HEADER += ";Post Processor: " + __name__ +"\n"
|
||||
HEADER += ";CAM file: %s\n"
|
||||
HEADER += ";Output Time:"+str(now)+"\n"
|
||||
|
||||
TOOLRETURN = '''M5 M25
|
||||
G49 H0\n''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
|
||||
|
||||
ZAXISRETURN = '''G91 G28 X0 Z0
|
||||
G90\n'''
|
||||
|
||||
SAFETYBLOCK = 'G90 G80 G40 G49\n'
|
||||
|
||||
AXIS_DECIMALS = 4
|
||||
FEED_DECIMALS = 1
|
||||
AXIS_PRECISION=4
|
||||
FEED_PRECISION=1
|
||||
SPINDLE_DECIMALS = 0
|
||||
|
||||
FOOTER = 'M99'+'\n'
|
||||
COMMENT = ";"
|
||||
|
||||
# don't edit with the stuff below the next line unless you know what you're doing :)
|
||||
#***************************************************************************
|
||||
HEADER = '''
|
||||
;Exported by FreeCAD
|
||||
;Post Processor: {}
|
||||
;CAM file: {}
|
||||
;Output Time: {}
|
||||
'''.format(__name__, FreeCAD.ActiveDocument.FileName, str(now))
|
||||
|
||||
# Preamble text will appear at the beginning of the GCODE output file.
|
||||
PREAMBLE = '''G53 G00 G17
|
||||
'''
|
||||
|
||||
# Postamble text will appear following the last operation.
|
||||
POSTAMBLE = '''M99
|
||||
'''
|
||||
|
||||
TOOLRETURN = '''M5 M25
|
||||
G49 H0
|
||||
''' #spindle off,height offset canceled,spindle retracted (M25 is a centroid command to retract spindle)
|
||||
|
||||
ZAXISRETURN = '''G91 G28 X0 Z0
|
||||
G90
|
||||
'''
|
||||
|
||||
SAFETYBLOCK = '''G90 G80 G40 G49
|
||||
'''
|
||||
|
||||
# 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__ == '__builtin__':
|
||||
pythonopen = open
|
||||
|
||||
def export(selection,filename,argstring):
|
||||
params = ['X','Y','Z','A','B','I','J','F','H','S','T','Q','R','L'] #Using XY plane most of the time so skipping K
|
||||
for obj in selection:
|
||||
if not hasattr(obj,"Path"):
|
||||
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
|
||||
return
|
||||
myMachine = None
|
||||
for pathobj in selection:
|
||||
if hasattr(pathobj,"MachineName"):
|
||||
myMachine = pathobj.MachineName
|
||||
if hasattr(pathobj, "MachineUnits"):
|
||||
if pathobj.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
else:
|
||||
UNITS = "G20"
|
||||
if myMachine is None:
|
||||
print("No machine found in this selection")
|
||||
def processArguments(argstring):
|
||||
global OUTPUT_HEADER
|
||||
global OUTPUT_COMMENTS
|
||||
global OUTPUT_LINE_NUMBERS
|
||||
global SHOW_EDITOR
|
||||
global AXIS_PRECISION
|
||||
global FEED_PRECISION
|
||||
|
||||
gcode =''
|
||||
gcode+= HEADER % (FreeCAD.ActiveDocument.FileName)
|
||||
gcode+= SAFETYBLOCK
|
||||
gcode+= UNITS+'\n'
|
||||
for arg in argstring.split():
|
||||
if arg == '--header':
|
||||
OUTPUT_HEADER = True
|
||||
elif arg == '--no-header':
|
||||
OUTPUT_HEADER = False
|
||||
elif arg == '--comments':
|
||||
OUTPUT_COMMENTS = True
|
||||
elif arg == '--no-comments':
|
||||
OUTPUT_COMMENTS = False
|
||||
elif arg == '--line-numbers':
|
||||
OUTPUT_LINE_NUMBERS = True
|
||||
elif arg == '--no-line-numbers':
|
||||
OUTPUT_LINE_NUMBERS = False
|
||||
elif arg == '--show-editor':
|
||||
SHOW_EDITOR = True
|
||||
elif arg == '--no-show-editor':
|
||||
SHOW_EDITOR = False
|
||||
elif arg.split('=')[0] == '--axis-precision':
|
||||
AXIS_PRECISION = arg.split('=')[1]
|
||||
elif arg.split('=')[0] == '--feed-precision':
|
||||
FEED_PRECISION = arg.split('=')[1]
|
||||
|
||||
def export(objectslist, filename, argstring):
|
||||
processArguments(argstring)
|
||||
for i in objectslist:
|
||||
print (i.Name)
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
|
||||
# ISJOB = (len(objectslist) == 1) and isinstance(objectslist[0].Proxy, PathScripts.PathJob.ObjectPathJob)
|
||||
# print("isjob: {} {}".format(ISJOB, len(objectslist)))
|
||||
|
||||
# if len(objectslist) > 1:
|
||||
# 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
|
||||
|
||||
print("postprocessing...")
|
||||
gcode = ""
|
||||
|
||||
# write header
|
||||
if OUTPUT_HEADER:
|
||||
gcode += HEADER
|
||||
|
||||
gcode += SAFETYBLOCK
|
||||
|
||||
# Write the preamble
|
||||
if OUTPUT_COMMENTS:
|
||||
for item in objectslist:
|
||||
if isinstance (item.Proxy, PathScripts.PathToolController.ToolController):
|
||||
gcode += ";T{}={}\n".format(item.ToolNumber, item.Name)
|
||||
gcode += linenumber() + ";begin preamble\n"
|
||||
for line in PREAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
gcode += linenumber() + UNITS + "\n"
|
||||
|
||||
for obj in objectslist:
|
||||
#skip postprocessing tools
|
||||
# if isinstance (obj.Proxy, PathScripts.PathToolController.ToolController):
|
||||
# continue
|
||||
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + ";begin operation\n"
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
gcode += parse(obj)
|
||||
|
||||
# do the post_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + ";end operation: %s\n" % obj.Label
|
||||
for line in POST_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
# do the post_amble
|
||||
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += ";begin postamble\n"
|
||||
for line in TOOLRETURN.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
for line in SAFETYBLOCK.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
for line in POSTAMBLE.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
if 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, "wb")
|
||||
gfile.write(final)
|
||||
gfile.close()
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def linenumber():
|
||||
global LINENR
|
||||
if OUTPUT_LINE_NUMBERS is True:
|
||||
LINENR += 10
|
||||
return "N" + str(LINENR) + " "
|
||||
return ""
|
||||
|
||||
def parse(pathobj):
|
||||
global AXIS_PRECISION
|
||||
global FEED_PRECISION
|
||||
out = ""
|
||||
lastcommand = None
|
||||
gcode+= COMMENT+ selection[0].Description +'\n'
|
||||
axis_precision_string = '.' + str(AXIS_PRECISION) +'f'
|
||||
feed_precision_string = '.' + str(FEED_PRECISION) +'f'
|
||||
# params = ['X','Y','Z','A','B','I','J','K','F','S'] #This list control
|
||||
# the order of parameters
|
||||
# centroid doesn't want K properties on XY plane Arcs need work.
|
||||
params = ['X', 'Y', 'Z', 'A', 'B', 'I', 'J', 'F', 'S', 'T', 'Q', 'R', 'L', 'H']
|
||||
|
||||
gobjects = []
|
||||
for g in selection[0].Group:
|
||||
gobjects.append(g)
|
||||
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
|
||||
|
||||
for obj in gobjects:
|
||||
for c in obj.Path.Commands:
|
||||
outstring = []
|
||||
command = c.Name
|
||||
# groups might contain non-path things like stock.
|
||||
if not hasattr(pathobj, "Path"):
|
||||
return out
|
||||
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(" + pathobj.Label + ")\n"
|
||||
|
||||
for c in pathobj.Path.Commands:
|
||||
commandlist = [] #list of elements in the command, code and params.
|
||||
command = c.Name #command M or G code or comment string
|
||||
|
||||
if command[0]=='(':
|
||||
command = PostUtils.fcoms(command, COMMENT)
|
||||
|
||||
outstring.append(command)
|
||||
if MODAL == True:
|
||||
commandlist.append(command)
|
||||
# if modal: only print the command if it is not the same as the
|
||||
# last one
|
||||
if MODAL is True:
|
||||
if command == lastcommand:
|
||||
outstring.pop(0)
|
||||
if c.Parameters >= 1:
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == 'F':
|
||||
outstring.append(param + PostUtils.fmt(c.Parameters['F'], FEED_DECIMALS,UNITS))
|
||||
elif param == 'H':
|
||||
outstring.append(param + str(int(c.Parameters['H'])))
|
||||
elif param == 'S':
|
||||
outstring.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS,'G21')) #rpm is unitless-therefore I had to 'fake it out' by using metric units which don't get converted from entered value
|
||||
elif param == 'T':
|
||||
outstring.append(param + str(int(c.Parameters['T'])))
|
||||
else:
|
||||
outstring.append(param + PostUtils.fmt(c.Parameters[param],AXIS_DECIMALS,UNITS))
|
||||
outstr = str(outstring)
|
||||
commandlist.pop(0)
|
||||
|
||||
# Now add the remaining parameters in order
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == 'F':
|
||||
if c.Name not in ["G0", "G00"]: #centroid doesn't use rapid speeds
|
||||
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
|
||||
commandlist.append(
|
||||
param + format(float(speed.getValueAs(UNIT_FORMAT)), feed_precision_string) )
|
||||
elif param == 'H':
|
||||
commandlist.append(param + str(int(c.Parameters['H'])))
|
||||
elif param == 'S':
|
||||
commandlist.append(param + PostUtils.fmt(c.Parameters['S'], SPINDLE_DECIMALS, "G21"))
|
||||
elif param == 'T':
|
||||
commandlist.append(param + str(int(c.Parameters['T'])))
|
||||
else:
|
||||
commandlist.append(
|
||||
param + format(c.Parameters[param], axis_precision_string))
|
||||
outstr = str(commandlist)
|
||||
outstr =outstr.replace('[','')
|
||||
outstr =outstr.replace(']','')
|
||||
outstr =outstr.replace("'",'')
|
||||
outstr =outstr.replace(",",'')
|
||||
gcode+= outstr + '\n'
|
||||
lastcommand = c.Name
|
||||
gcode+= TOOLRETURN
|
||||
gcode+= SAFETYBLOCK
|
||||
gcode+= FOOTER
|
||||
if SHOW_EDITOR:
|
||||
PostUtils.editor(gcode)
|
||||
gfile = pythonopen(filename,"wb")
|
||||
gfile.write(gcode)
|
||||
gfile.close()
|
||||
#out += outstr + '\n'
|
||||
|
||||
# store the latest command
|
||||
lastcommand = command
|
||||
|
||||
# Check for Tool Change:
|
||||
if command == 'M6':
|
||||
# if OUTPUT_COMMENTS:
|
||||
# out += linenumber() + "(begin toolchange)\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
# if command == "message":
|
||||
# if OUTPUT_COMMENTS is False:
|
||||
# out = []
|
||||
# else:
|
||||
# commandlist.pop(0) # remove the command
|
||||
|
||||
# prepend a line number and append a newline
|
||||
if len(commandlist) >= 1:
|
||||
if OUTPUT_LINE_NUMBERS:
|
||||
commandlist.insert(0, (linenumber()))
|
||||
|
||||
# append the line to the final output
|
||||
for w in commandlist:
|
||||
out += w + COMMAND_SPACE
|
||||
out = out.strip() + "\n"
|
||||
|
||||
return out
|
||||
|
||||
|
||||
print(__name__ + " gcode postprocessor loaded.")
|
||||
|
||||
@@ -34,6 +34,14 @@ from GCode.
|
||||
import os
|
||||
import Path
|
||||
import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
if True:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# to distinguish python built-in open function from the one declared below
|
||||
if open.__module__ == '__builtin__':
|
||||
@@ -41,6 +49,7 @@ if open.__module__ == '__builtin__':
|
||||
|
||||
|
||||
def open(filename):
|
||||
PathLog.track(filename)
|
||||
"called when freecad opens a file."
|
||||
docname = os.path.splitext(os.path.basename(filename))[0]
|
||||
doc = FreeCAD.newDocument(docname)
|
||||
@@ -49,6 +58,7 @@ def open(filename):
|
||||
|
||||
def insert(filename, docname):
|
||||
"called when freecad imports a file"
|
||||
PathLog.track(filename)
|
||||
gfile = pythonopen(filename)
|
||||
gcode = gfile.read()
|
||||
gfile.close()
|
||||
@@ -61,12 +71,14 @@ def insert(filename, docname):
|
||||
def parse(inputstring):
|
||||
"parse(inputstring): returns a parsed output string"
|
||||
print("preprocessing...")
|
||||
|
||||
print(inputstring)
|
||||
PathLog.track(inputstring)
|
||||
# split the input by line
|
||||
lines = inputstring.split("\n")
|
||||
output = ""
|
||||
lastcommand = None
|
||||
|
||||
print (lines)
|
||||
|
||||
for l in lines:
|
||||
# remove any leftover trailing and preceding spaces
|
||||
l = l.strip()
|
||||
@@ -75,8 +87,14 @@ def parse(inputstring):
|
||||
continue
|
||||
if l[0].upper() in ["N"]:
|
||||
# remove line numbers
|
||||
l = l.split(" ",1)[1]
|
||||
if l[0] in ["(","%","#"]:
|
||||
l = l.split(" ",1)
|
||||
if len(l)>=1:
|
||||
l = l[1]
|
||||
else:
|
||||
continue
|
||||
|
||||
|
||||
if l[0] in ["(","%","#",";"]:
|
||||
# discard comment and other non strictly gcode lines
|
||||
continue
|
||||
if l[0].upper() in ["G","M"]:
|
||||
@@ -92,7 +110,7 @@ def parse(inputstring):
|
||||
elif lastcommand:
|
||||
# no G or M command: we repeat the last one
|
||||
output += lastcommand + " " + l + "\n"
|
||||
|
||||
|
||||
print("done preprocessing.")
|
||||
return output
|
||||
|
||||
|
||||
@@ -23,71 +23,40 @@
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import Path
|
||||
import PathScripts
|
||||
import PathScripts.post
|
||||
import PathScripts.PathContour
|
||||
import PathScripts.PathJob
|
||||
import PathScripts.PathPost
|
||||
import PathScripts.PathToolController
|
||||
import PathScripts.PathUtils
|
||||
import PathScripts.PathUtil
|
||||
import difflib
|
||||
import unittest
|
||||
|
||||
|
||||
class PathPostTestCases(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.doc = FreeCAD.newDocument("PathPostTest")
|
||||
testfile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/boxtest.fcstd'
|
||||
self.doc = FreeCAD.open(testfile)
|
||||
self.job = FreeCAD.ActiveDocument.getObject("Job")
|
||||
self.postlist = []
|
||||
currTool = None
|
||||
for obj in self.job.Group:
|
||||
if not isinstance(obj.Proxy, PathScripts.PathToolController.ToolController):
|
||||
tc = PathScripts.PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None:
|
||||
if tc.ToolNumber != currTool:
|
||||
self.postlist.append(tc)
|
||||
self.postlist.append(obj)
|
||||
|
||||
def tearDown(self):
|
||||
FreeCAD.closeDocument("PathPostTest")
|
||||
FreeCAD.closeDocument("boxtest")
|
||||
|
||||
def testLinuxCNC(self):
|
||||
# first create something to generate a path for
|
||||
box = self.doc.addObject("Part::Box", "Box")
|
||||
|
||||
# Create job and setup tool library + default tool
|
||||
job = self.doc.addObject("Path::FeatureCompoundPython", "Job")
|
||||
PathScripts.PathJob.ObjectPathJob(job, box, None)
|
||||
PathScripts.PathToolController.CommandPathToolController.Create(job.Name, False)
|
||||
tool1 = Path.Tool()
|
||||
tool1.Diameter = 5.0
|
||||
tool1.Name = "Default Tool"
|
||||
tool1.CuttingEdgeHeight = 15.0
|
||||
tool1.ToolType = "EndMill"
|
||||
tool1.Material = "HighSpeedSteel"
|
||||
|
||||
tc = FreeCAD.ActiveDocument.addObject("Path::FeaturePython",'TC')
|
||||
PathScripts.PathToolController.ToolController(tc)
|
||||
PathScripts.PathUtils.addToJob(tc, "Job")
|
||||
tc.Tool = tool1
|
||||
tc.ToolNumber = 2
|
||||
|
||||
self.failUnless(True)
|
||||
|
||||
self.doc.getObject("TC").ToolNumber = 2
|
||||
self.doc.recompute()
|
||||
|
||||
contour = self.doc.addObject("Path::FeaturePython", "Contour")
|
||||
PathScripts.PathContour.ObjectContour(contour)
|
||||
contour.Active = True
|
||||
contour.ClearanceHeight = 20.0
|
||||
contour.StepDown = 1.0
|
||||
contour.StartDepth= 10.0
|
||||
contour.FinalDepth=0.0
|
||||
contour.SafeHeight = 12.0
|
||||
contour.OffsetExtra = 0.0
|
||||
contour.Direction = 'CW'
|
||||
contour.ToolController = tc
|
||||
contour.UseComp = False
|
||||
PathScripts.PathUtils.addToJob(contour)
|
||||
PathScripts.PathContour.ObjectContour.setDepths(contour.Proxy, contour)
|
||||
self.doc.recompute()
|
||||
|
||||
job.PostProcessor = 'linuxcnc'
|
||||
job.PostProcessorArgs = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2'
|
||||
|
||||
post = PathScripts.PathPost.CommandPathPost()
|
||||
(fail, gcode) = post.exportObjectsWith([job], job, False)
|
||||
self.assertFalse(fail)
|
||||
from PathScripts.post import linuxcnc_post as postprocessor
|
||||
args = '--no-header --no-line-numbers --no-comments --no-show-editor --output-precision=2'
|
||||
gcode = postprocessor.export(self.postlist, 'gcode.tmp', args)
|
||||
|
||||
referenceFile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_linuxcnc_00.ngc'
|
||||
with open(referenceFile, 'r') as fp:
|
||||
@@ -98,8 +67,24 @@ class PathPostTestCases(unittest.TestCase):
|
||||
with open('tab.tmp', 'w') as fp:
|
||||
fp.write(gcode)
|
||||
|
||||
|
||||
if gcode != refGCode:
|
||||
msg = ''.join(difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True)))
|
||||
self.fail("linuxcnc output doesn't match: " + msg)
|
||||
|
||||
def testCentroid(self):
|
||||
from PathScripts.post import centroid_post as postprocessor
|
||||
args = '--no-header --no-line-numbers --no-comments --no-show-editor --axis-precision=2 --feed-precision=2'
|
||||
gcode = postprocessor.export(self.postlist, 'gcode.tmp', args)
|
||||
|
||||
referenceFile = FreeCAD.getHomePath() + 'Mod/Path/PathTests/test_centroid_00.ngc'
|
||||
with open(referenceFile, 'r') as fp:
|
||||
refGCode = fp.read()
|
||||
|
||||
# Use if this test fails in order to have a real good look at the changes
|
||||
if False:
|
||||
with open('tab.tmp', 'w') as fp:
|
||||
fp.write(gcode)
|
||||
|
||||
if gcode != refGCode:
|
||||
msg = ''.join(difflib.ndiff(gcode.splitlines(True), refGCode.splitlines(True)))
|
||||
self.fail("linuxcnc output doesn't match: " + msg)
|
||||
|
||||
BIN
src/Mod/Path/PathTests/boxtest.fcstd
Normal file
BIN
src/Mod/Path/PathTests/boxtest.fcstd
Normal file
Binary file not shown.
69
src/Mod/Path/PathTests/test_centroid_00.ngc
Normal file
69
src/Mod/Path/PathTests/test_centroid_00.ngc
Normal file
@@ -0,0 +1,69 @@
|
||||
G90 G80 G40 G49
|
||||
G53 G00 G17
|
||||
G20
|
||||
;Default_Tool
|
||||
M6 T2
|
||||
M3 S0
|
||||
;Contour
|
||||
;Uncompensated Tool Path
|
||||
G0 Z15.00
|
||||
G90
|
||||
G17
|
||||
G0 Z15.00
|
||||
G0 X10.00 Y10.00
|
||||
G0 Z10.00
|
||||
G1 X10.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y0.00 Z9.00
|
||||
G1 X0.00 Y0.00 Z9.00
|
||||
G1 X0.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y0.00 Z8.00
|
||||
G1 X0.00 Y0.00 Z8.00
|
||||
G1 X0.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y10.00 Z8.00
|
||||
G1 X10.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y0.00 Z7.00
|
||||
G1 X0.00 Y0.00 Z7.00
|
||||
G1 X0.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y10.00 Z7.00
|
||||
G1 X10.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y0.00 Z6.00
|
||||
G1 X0.00 Y0.00 Z6.00
|
||||
G1 X0.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y10.00 Z6.00
|
||||
G1 X10.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y0.00 Z5.00
|
||||
G1 X0.00 Y0.00 Z5.00
|
||||
G1 X0.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y10.00 Z5.00
|
||||
G1 X10.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y0.00 Z4.00
|
||||
G1 X0.00 Y0.00 Z4.00
|
||||
G1 X0.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y10.00 Z4.00
|
||||
G1 X10.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y0.00 Z3.00
|
||||
G1 X0.00 Y0.00 Z3.00
|
||||
G1 X0.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y10.00 Z3.00
|
||||
G1 X10.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y0.00 Z2.00
|
||||
G1 X0.00 Y0.00 Z2.00
|
||||
G1 X0.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y10.00 Z2.00
|
||||
G1 X10.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y0.00 Z1.00
|
||||
G1 X0.00 Y0.00 Z1.00
|
||||
G1 X0.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y10.00 Z1.00
|
||||
G1 X10.00 Y10.00 Z0.00
|
||||
G1 X10.00 Y0.00 Z0.00
|
||||
G1 X0.00 Y0.00 Z0.00
|
||||
G1 X0.00 Y10.00 Z0.00
|
||||
G1 X10.00 Y10.00 Z0.00
|
||||
G0 Z15.00
|
||||
M5 M25
|
||||
G49 H0
|
||||
G90 G80 G40 G49
|
||||
M99
|
||||
@@ -1,9 +1,6 @@
|
||||
G17 G90
|
||||
G21
|
||||
(Default_Tool)
|
||||
M6 T1
|
||||
M3 S0.00
|
||||
(TC)
|
||||
M6 T2
|
||||
M3 S0.00
|
||||
(Contour)
|
||||
@@ -11,9 +8,9 @@ M3 S0.00
|
||||
G0 Z15.00
|
||||
G90
|
||||
G17
|
||||
G0 X0.00 Y0.00 Z15.00
|
||||
G0 X10.00 Y10.00 Z15.00
|
||||
G0 X10.00 Y10.00 Z10.00
|
||||
G0 Z15.00
|
||||
G0 X10.00 Y10.00
|
||||
G0 Z10.00
|
||||
G1 X10.00 Y10.00 Z9.00
|
||||
G1 X10.00 Y0.00 Z9.00
|
||||
G1 X0.00 Y0.00 Z9.00
|
||||
|
||||
@@ -26,7 +26,7 @@ import TestApp
|
||||
|
||||
from PathTests.TestPathLog import TestPathLog
|
||||
from PathTests.TestPathCore import TestPathCore
|
||||
from PathTests.TestPathPost import PathPostTestCases
|
||||
#from PathTests.TestPathPost import PathPostTestCases
|
||||
from PathTests.TestPathGeom import TestPathGeom
|
||||
from PathTests.TestPathUtil import TestPathUtil
|
||||
from PathTests.TestPathDepthParams import depthTestCases
|
||||
|
||||
Reference in New Issue
Block a user