freepyscad: update the script windows to a mini-ide with auto-completion.

Also automatically download latest version of the script.
This commit is contained in:
supermerill 2020-06-09 21:53:06 +02:00 committed by supermerill
parent 2b04548ecb
commit 186d7ce508
7 changed files with 702 additions and 2511 deletions

View File

@ -1,3 +0,0 @@
# FreeCAD init script of the pyscad module
# print("Init from ", __name__)

View File

@ -1,136 +0,0 @@
# FreePySCAD
You like OpenSCAD but you hate it at the same time?
You can't work in FreeCAD because don't like wasting your time moving the mouse and clicking?
FreePySCAD is for you!
note: it's in an alpha stage right now. You can use it but some things may not work as advertised. Tested against 0.17.
## How it work
FreePySCAD is a python library for FreeCAD to let user write their code in a text editor and see the result after a "compilation" process, like OpenSCAD but in FreeCAD.
To install the library, clone the github repository into the "FreeCAD X.xx/mod" directory
To write your code, you can open the FreeCAD macro editor and beginning your macro with "from FreePySCAD.FreePySCAD import *"
You can also type in the python console "execfile('path_to/my_pycad.py')", this has the advantage to show the errors.
The geometry passed inside the scene().redraw(...) function will be added inside the current document, replacing everything.
## what's different
The braces are replaced with parenthesis
The ';' are replaced with ',' and you also have to place it after ')' if no other ')' are directly after that to respect the python syntax.
You can't let the modifiers like translate, rotate... be unattached: use the parenthesis or a dot (see below)
OpenSCAD: difference(){ translate([1,1,0]) cube(2); rotate([0,0,45]) cube(2); }
FreePySCAD: difference()( translate([1,1,0]).cube(2), rotate([0,0,45])(cube(2)),)
resize, minkowski and hull aren't implemented.
You can also wrote a more concise code with FreePySCAD if you want (i was tired of writing "translate([ ])" over an over)
cut()( move(1,1).cube(2), cube(2).rotate(0,0,60) , rotate(z=30)(cube(2)) )
You can now use functions with real variables that can be changed!
Here is a working ugly example:
from Pyscad.pyscad import *
def make_T(l,h):
big = cube(l,w,h)
l = l/3.0
h = 2.0*h/3.0
return cut("T")(
big,
cube(l,w,h),
cube(l,w,h).move(l*2),
)
w=10
T_10cube = make_T(10,10)
w=3
T_3cube = make_T(10,10)
scene().redraw(
T_10cube,
T_3cube.move(12),
)
You also have to pass your objects inside the scene.redraw() function to put it into the FreeCAD environment.
You can see and execute some complex exemples in the exemple directory
## FreePySCAD cheatsheet:
#### 1D:
* line([x1,y1,z1],[x2,y2,z2])
* arc([x1,y1,z1],[x2,y2,z2],[x3,y3,z3])
* helix(r,p,h) # p = pitch = height between the begin and the end of a single loop
#### 1D | 2D:
* circle(r)
* ellipse(r,l)
* polygon([points],closed)
* bspline([points],closed)
* bezier([points],closed)
#### 2D:
* square(size)
* square([width,height]) | square(width,height) | rectangle([width,height])
* poly_reg(r|d,nb,inscr)
* text(text,size)
* gear(nb, mod, angle, external, high_precision)
#### transformation 1D to 2D to 3D:
* create_wire(closed)(...1D) #create a new wire from many edges, can be extruded if they are connected, you can check that by putting closed to True
* offset2D(length,fillet,fusion)(...2D)
* linear_extrude(height,twist,taper)(obj_2D)
* extrude(x,y,z,taper)(obj_2D)
* rotate_extrude(angle)(obj_2D) #rotate over the Z axis
* path_extrude(frenet,transition)(path_1D, patron_2D)
note: most of these transformations can only work on a single object, as these can't be unionized before.
#### 3D:
* sphere(r|d,fn)
* cube(size)
* cube(x,y,z) | cube([width,depth,height]) | box(x,y,z)
* triangle(x,y,z) | triangle([width,depth,height])
* cylinder(r|d,h,fn,angle) #will call poly_ext if fn >0
* cone(r1|d1,r2|d2,h,fn) | cylinder(r1|d1,r2|d2,h,fn)
* torus(r1,r2,h)
* poly_ext(r,nb,h) # r = radius, nb = nb vertex (min 3)
* poly_int(a,nb,h) # a = apothem, nb = nb vertex (min 3)
* polyhedron(points, faces) # for debugging use polyhedron_wip : it creates a group of points & faces instead of a 3D solid mesh
* solid_slices(points, centers) #new way to create not-so complicated shells, see below. centers are optional. Much simpler than polyhedron. May not work with not-convex shapes.
* thread(r,p,nb,r2, pattern,fn,center) # implementation of a way to create threads, with pattern (2D array of points). It creates a new 3D object from triangles (vertexes & faces).
* iso_thread(d,p,h,internal,offset,fn) # usage of thread method with an iso pattern.
#### 3D Boolean operations:
* union()(...3D) | union().add(...3D) # can work with 2D
* intersection()(...3D)
* difference()(...3D) | cut()(...3D)
#### Transformations:
* mirror(x,y,z)(...) | mirror([x,y,z])(...)
* offset(length,fillet,fusion)(...3D)
wip, don't work, use the gui for now:
* chamfer().setEdges(radius,edge_id...)(...3D)
* fillet().setEdges(radius,edge_id...)(...3D)
#### Modifiers:
* .x/y/z() | .center()
* translate/move(x,y,z)(...) | move([x,y,z])(...) | .move(x,y,z) | .move([x,y,z]) | move(x,y,z).stdfuncXXX(
* rotate(x,y,z)(...) | rotate([x,y,z])(...) | .rotate(x,y,z) | .rotate([x,y,z]) | rotate(x,y,z).stdfuncXXX(
* scale(x,y,z)(...) | scale([x,y,z])(...) | .scale(x,y,z) | .scale([x,y,z]) | scale(x,y,z).stdfuncXXX(
* .color("colorname") | .color(r,g,b,a) | .color([r,g,b,a]) | color(something)(...) | color(something).stdfuncXXX(
* .multmatrix(m)
#### Other:
* scene().draw(...3D) | scene().redraw(...3D) #redraw() erase everything in the document before rebuilding the object tree. Draw() try to update when possible and don't erase everything, but sometimes it fail to detect a change.
* importSvg(filepath,ids) #ids is an optional array of index to say which one have to be imported
* importStl(filepath,ids)
* group()(...) # a group of nodes (1D, 2D & 3D can be mixed), for viewing purpose only as it can't be used by anything, although you can use the modifiers.
All python syntax and standard library can be used
### notes:
* ...3D represent a list (possibly empty) of 3D node
* You can replace )(...) by ).add(...) for union, difference and
* Center: on almost every object, you can set as parameter, center=True or center=center_x, center=center_yz, ...
you can also use the transformation .center() or .x(), .yz(), .xyz() ....
* Label: on almost everything, you can set the "name" parameter to whatever you want, it will be shown in the FreeCAD object hierarchy.
* The notation move(2).box(1) should be used only when it's very convenient, it's here mainly to make conversion from OpenSCAD to FreePySCAD more easy, but it can led to strange behaviors, see the two points below.
* Order of execution: move(6)(move(3).move(2).cube(1).move(4).move(5)) => it begin at the object then move away from it.
* The move(2).box(1) work but you cannot do move(1).myfunc() because myfunc isn't in the list of functions that is available to the "move object". In this case, you have to use move(1)(myfunc()) or myfunc().move(1)
* When a part fail to compile, it creates a sphere of size _default_size. you can change the variable _default_size, it's used as a default value when 0.0 will create an impossible object. Example: circle() == circle(_default_size).
* solid_slices : need a double-array of points. Each array of points is a slice. It creates triangles to join one slice to the next. The last point of each slice have to be approximately aligned ( = don't put them 180° apart), because it's used as the first edge. The middle point (mean of all points if not given via the centers argument) is used to choose the next point to draw triangle and for closing the shell at the bottom layer and top layer. The line from the center of a slice to the center of the next one must be inside the slice and the next slice.

View File

@ -1,33 +0,0 @@
# ***************************************************************************
# * *
# * Copyright (c) 2016 - Victor Titov (DeepSOIC) <vv.titov@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 *
# * *
# ***************************************************************************
__title__ = "Pyscad"
__author__ = "supermerill"
__url__ = "http://www.freecadweb.org"
__doc__ = "Dafuk"
## @package CompoundTools
# \ingroup PART
# \brief CompoundTools Package for Part workbench
# import pyscad

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "GUI_ObjectList.hpp"
#include "slic3r/Utils/Http.hpp"
#include "AppConfig.hpp"
#include "Tab.hpp"
#include <wx/scrolwin.h>
@ -14,9 +15,11 @@
//C++11
#include <ctime>
#include <stdio.h>
#include <stdlib.h>
#include <boost/process.hpp>
#include <regex>
#include <boost/locale.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/tee.hpp>
@ -34,6 +37,62 @@ static wxSize get_screen_size(wxWindow* window)
namespace Slic3r {
namespace GUI {
//TODO: auto tab
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
bool get_file_from_web(const std::string &url, const boost::filesystem::path &target_path)
{
bool res = false;
boost::filesystem::path tmp_path = target_path;
tmp_path += (boost::format(".%1%%2%") % get_current_pid() % ".download").str();
BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`")
% url
% target_path.string()
% tmp_path.string();
Slic3r::Http::get(url)
.on_progress([](Http::Progress, bool &cancel) {
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% url
% http_status
% error;
})
.on_complete([&](std::string body, unsigned /* http_status */) {
boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
boost::filesystem::rename(tmp_path, target_path);
res = true;
})
.perform_sync();
return res;
}
// Downloads a file (http get operation). Cancels if the Updater is being destroyed.
void get_string_from_web_async(const std::string &url, FreeCADDialog* free, std::function<void(FreeCADDialog*, std::string)> listener)
{
Slic3r::Http::get(url)
.on_progress([](Http::Progress, bool &cancel) {
})
.on_error([&url](std::string body, std::string error, unsigned http_status) {
(void)body;
BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%")
% url
% http_status
% error;
})
.on_complete([free, listener](std::string body, unsigned http_status ) {
listener(free, body);
})
.perform();
}
std::string create_help_text() {
std::stringstream ss;
@ -75,7 +134,7 @@ std::string create_help_text() {
}
FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe)
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("FreeCAD script engine")),
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("FreePySCAD : script engine for FreeCAD")),
#if ENABLE_SCROLLABLE
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#else
@ -87,13 +146,76 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe)
this->main_frame = mainframe;
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
//def
PyCommand py1{ "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" };
PyCommand py2 = { "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" };
commands.emplace_back(PyCommand{ "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" });
commands.emplace_back(PyCommand{"cylinder", PyCommandType::pctOBJECT, { "r","h","fn=","angle=","d=","r1=","r2=","d1=","d2=" },
"cylinder(r,h)\ncylinder(d=,h=,[fn=,angle=])\ncylinder(r1=,r2=,h=)\ncylinder(d1=,d2=,h=)"});
commands.emplace_back(PyCommand{"move", PyCommandType::pctMODIFIER, { "x","y","z" }, "move(x,y,z)"});
commands.emplace_back(PyCommand{"rotate", PyCommandType::pctMODIFIER, { "x","y","z" }, "rotate(x,y,z)"});
commands.emplace_back(PyCommand{"cut", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER,"cut()(...obj)"});
commands.emplace_back(PyCommand{"union", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "union()(...obj)"});
commands.emplace_back(PyCommand{"intersection", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "intersection()(...obj)"});
commands.emplace_back(PyCommand{"linear_extrude", PyCommandType::pctOPERATION,"linear_extrude(height,[twist=,taper=,slices=,convexity=])(2D_obj)"});
commands.emplace_back(PyCommand{"rotate_extrude", PyCommandType::pctOPERATION, "rotate_extrude(angle,[convexity])(2D_obj)"});
commands.emplace_back(PyCommand{"path_extrude", PyCommandType::pctOPERATION, "path_extrude(frenet,transition)(2D_obj)"});
commands.emplace_back(PyCommand{"mirror", PyCommandType::pctOPERATION, { "x","y","z" }, "mirror(x,y,z)(obj)"});
commands.emplace_back(PyCommand{"offset", PyCommandType::pctOPERATION, { "length","fillet" }, "offset(length,fillet)(...obj)"});
commands.emplace_back(PyCommand{"chamfer", PyCommandType::pctOPERATION, { "l" }, "chamfer(l)(...obj)"});
commands.emplace_back(PyCommand{"fillet", PyCommandType::pctOPERATION, { "l" }, "fillet(l)(...obj)"});
commands.emplace_back(PyCommand{"poly_ext", PyCommandType::pctOBJECT, {"r", "nb", "h", "d="}, "poly_ext(r,nb,h)\npoly_ext(d=,nb=,h=)"});
commands.emplace_back(PyCommand{"poly_int", PyCommandType::pctOBJECT, { "a", "nb", "h", "d=" }, "poly_int(a,nb,h)\npoly_int(d=,nb=,h=)"});
commands.emplace_back(PyCommand{"triangle", PyCommandType::pctOBJECT, { "x","y","z" }, "triangle(x,y,z)"});
commands.emplace_back(PyCommand{ "iso_thread", PyCommandType::pctOBJECT, {"d","p","h","internal","offset","fn="},
"iso_thread(d,p,h,internal, offset,[fn=])\nm3 screw: iso_thread(3,0.5,10,False,0)\nm3 nut: cut()(...,iso_thread(3,0.5,3,True,0.15))" });
commands.emplace_back(PyCommand{"text", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"gear", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"importStl", PyCommandType::pctOBJECT, "importStl(filename,ids)"});
commands.emplace_back(PyCommand{"solid_slices", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"create_wire", PyCommandType::pctOPERATION, { "closed" }, "create_wire(closed)(...1D_obj)"});
commands.emplace_back(PyCommand{"line", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"arc", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"circle", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"polygon", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"bezier", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"square", PyCommandType::pctOBJECT});
commands.emplace_back(PyCommand{"importSvg", PyCommandType::pctOBJECT, "importSvg(filename,ids)"});
commands.emplace_back(PyCommand{"xy", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"z", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"center", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"x", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"y", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"xz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"yz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
commands.emplace_back(PyCommand{"xyz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER});
// alias
commands.emplace_back(PyCommand{"difference", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "difference()(...obj)"});
commands.emplace_back(PyCommand{"translate", PyCommandType::pctMODIFIER, "translate(x,y,z)"});
commands.emplace_back(PyCommand{"extrude", PyCommandType::pctOPERATION, "extrude(x,y,z,taper,[convexity=])"});
//redraw
commands.emplace_back(PyCommand{"redraw", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER,
"redraw(...obj3D)\nEvery object inside this command\nwill be added into SuperSlicer.\n"});
// beta / buggy
commands.emplace_back(PyCommand{"scale", PyCommandType::pctMODIFIER | PyCommandType::pctDO_NOT_SHOW});
word_regex = std::regex("[a-z]+");
// fonts
const wxFont& font = wxGetApp().normal_font();
const wxFont& bold_font = wxGetApp().bold_font();
SetFont(font);
wxIcon *freecad_icon = new wxIcon();
freecad_icon->LoadFile( (boost::filesystem::path(Slic3r::resources_dir()) / "icons" / "Freecad.svg").generic_string());
//sadly, this do nothing in dialog
//this->SetIcon(freecad_icon);
auto main_sizer = new wxGridBagSizer(5,5); //(int rows, int cols, int vgap, int hgap)
auto main_sizer = new wxGridBagSizer(1,1); //(int vgap, int hgap)
// | |_icon_|
// |editor_| help |
// |_err___|______|
// |__bts_________|
//main view
createSTC();
@ -106,20 +228,35 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe)
wxDefaultPosition, wxSize(200, 500), wxTE_MULTILINE);
m_help->SetEditable(false);
//wxBoxSizer *m_icons = new wxBoxSizer(wxHORIZONTAL);
//m_icons->Add(16,16,0,0,0,freecad_icon);
//wxSizerItem* Add(wxSizer * sizer, const wxGBPosition & pos, const wxGBSpan & span = wxDefaultSpan, int flag = 0, int border = 0, wxObject * userData = NULL)
//wxSizerItem* Add(wxSizer * sizer, int proportion = 0, int flag = 0, int border = 0, wxObject * userData = NULL)
main_sizer->Add(m_text, wxGBPosition(1,1), wxGBSpan(1,1), wxEXPAND | wxALL, 5);
main_sizer->Add(m_help, wxGBPosition(1, 2), wxGBSpan(2, 1), wxEXPAND | wxVERTICAL, 5);
main_sizer->Add(m_errors, wxGBPosition(2, 1), wxGBSpan(1, 1), 0, 5);
/*main_sizer->Add(m_text, 1, wxEXPAND | wxALL, 5);
main_sizer->Add(m_errors, 1, wxALL, 5);
main_sizer->Add(m_help, 1, wxALL, 5);*/
main_sizer->Add(m_text, wxGBPosition(1,1), wxGBSpan(2,1), wxEXPAND | wxALL, 2);
//main_sizer->Add(m_icons, wxGBPosition(1, 2), wxGBSpan(1, 1), 0, 2);
//main_sizer->Add(m_help, wxGBPosition(2, 2), wxGBSpan(2, 1), wxEXPAND | wxVERTICAL, 2);
main_sizer->Add(m_help, wxGBPosition(1, 2), wxGBSpan(3, 1), wxEXPAND | wxVERTICAL, 2);
main_sizer->Add(m_errors, wxGBPosition(3, 1), wxGBSpan(1, 1), 0, 2);
wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer();
wxButton* bt = new wxButton(this, wxID_FILE1, _(L("Generate")));
bt->Bind(wxEVT_BUTTON, &FreeCADDialog::create_geometry, this);
buttons->Add(bt);
wxButton* bt_create_geometry = new wxButton(this, wxID_FILE1, _(L("Generate")));
bt_create_geometry->Bind(wxEVT_BUTTON, &FreeCADDialog::create_geometry, this);
buttons->Add(bt_create_geometry);
wxButton* bt_new = new wxButton(this, wxID_FILE3, _(L("New")));
bt_new->Bind(wxEVT_BUTTON, &FreeCADDialog::new_script, this);
buttons->Add(bt_new);
wxButton* bt_load = new wxButton(this, wxID_FILE2, _(L("Load")));
bt_load->Bind(wxEVT_BUTTON, &FreeCADDialog::load_script, this);
buttons->Add(bt_load);
wxButton* bt_save = new wxButton(this, wxID_FILE3, _(L("Save")));
bt_save->Bind(wxEVT_BUTTON, &FreeCADDialog::save_script, this);
buttons->Add(bt_save);
wxButton* bt_quick_save = new wxButton(this, wxID_FILE4, _(L("Quick Save")));
bt_quick_save->Bind(wxEVT_BUTTON, &FreeCADDialog::quick_save, this);
bt_quick_save->Hide();
buttons->Add(bt_quick_save);
wxButton* close = new wxButton(this, wxID_CLOSE, _(L("Close")));
close->Bind(wxEVT_BUTTON, &FreeCADDialog::closeMe, this);
@ -128,19 +265,318 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe)
close->SetFocus();
SetAffirmativeId(wxID_CLOSE);
buttons->Realize();
main_sizer->Add(buttons, wxGBPosition(3, 1), wxGBSpan(1, 2), wxEXPAND | wxALL, 5);
main_sizer->Add(buttons, wxGBPosition(4, 1), wxGBSpan(1, 2), wxEXPAND | wxALL, 5);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
//set keyboard shortcut
wxAcceleratorEntry entries[2];
//entries[0].Set(wxACCEL_CTRL, (int) 'X', bt_create_geometry->GetId());
//entries[2].Set(wxACCEL_SHIFT, (int) 'W', wxID_FILE1);
entries[0].Set(wxACCEL_NORMAL, WXK_ESCAPE, wxID_CLOSE);
entries[1].Set(wxACCEL_NORMAL, WXK_F5, bt_create_geometry->GetId()); // wxID_FILE1);
entries[1].Set(wxACCEL_CTRL, (int) 'N', bt_new->GetId());
entries[1].Set(wxACCEL_CTRL | wxACCEL_SHIFT, (int) 'S', bt_save->GetId());
entries[1].Set(wxACCEL_CTRL, (int) 'S', bt_quick_save->GetId());
this->SetAcceleratorTable(wxAcceleratorTable(2, entries));
}
void FreeCADDialog::closeMe(wxCommandEvent& event_args) {
bool ok = this->write_text_in_file(m_text->GetText(), boost::filesystem::path(Slic3r::data_dir()) / "temp" / "current_pyscad.py");
this->gui_app->change_calibration_dialog(this, nullptr);
this->Destroy();
}
void FreeCADDialog::load_script(wxCommandEvent& event_args) {
wxFileDialog dialog(this,
_(L("Choose one file (py):")),
gui_app->app_config->get_last_dir(),
"",
"FreePySCAD files (*.py)|*.py",
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK) {
opened_file = boost::filesystem::path(dialog.GetPath().wx_str());
load_text_from_file(opened_file);
}
}
bool FreeCADDialog::load_text_from_file(const boost::filesystem::path &path) {
if (boost::filesystem::exists(path)) {
try {
std::locale loc = boost::locale::generator()("en_US.UTF-8");
// Open the stream to 'lock' the file.
boost::filesystem::ifstream in;
in.imbue(loc);
in.open(path);
// Obtain the size of the file.
const uintmax_t sz = boost::filesystem::file_size(path);
// Create a buffer.
std::string result(sz, '\0');
// Read the whole file into the buffer.
in.read(result.data(), sz);
m_text->SetTextRaw(result.c_str());
in.close();
}
catch (std::exception ex) {
//TODO: emit error meessage on std / boost
return false;
}
return true;
}
return false;
}
void FreeCADDialog::save_script(wxCommandEvent& event_args) {
wxFileDialog dialog(this,
_(L("Choose one file (py):")),
gui_app->app_config->get_last_dir(),
"",
"FreePySCAD files (*.py)|*.py",
wxFD_SAVE);
if (dialog.ShowModal() == wxID_OK) {
opened_file = boost::filesystem::path(dialog.GetPath().wx_str());
write_text_in_file(m_text->GetText(), opened_file);
}
}
void FreeCADDialog::quick_save(wxCommandEvent& event_args) {
if (boost::filesystem::exists(opened_file) ){
write_text_in_file(m_text->GetText(), opened_file);
}
}
bool FreeCADDialog::write_text_in_file(const wxString &towrite, const boost::filesystem::path &file) {
try {
//add text if the saved file exist
boost::filesystem::create_directories(file.parent_path());
std::locale loc = boost::locale::generator()("en_US.UTF-8");
// Open the stream to 'lock' the file.
boost::filesystem::ofstream out;
out.imbue(loc);
out.open(file);
out << towrite;
out.close();
}
catch (std::exception ex) {
return false;
}
return true;
}
const PyCommand* FreeCADDialog::get_command(const wxString &str) const {
const PyCommand *command = nullptr;
for (const PyCommand &cmd : commands) {
if (cmd.name == str) {
command = &cmd;
break;
}
}
return command;
}
void FreeCADDialog::on_autocomp_complete(wxStyledTextEvent& event) {
//ad extra characters after autocomplete
const PyCommand *command = get_command(event.GetString());
if (command == nullptr)
return;
wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject();
int currentPos = stc->GetCurrentPos();
bool has_already_parenthese = stc->GetCharAt(currentPos) == '(';
if ( ((command->type & PyCommandType::pctOPERATION) != 0) && !has_already_parenthese) {
stc->InsertText(currentPos, "()(),");
if(((command->type & PyCommandType::pctNO_PARAMETER) != 0))
stc->GotoPos(currentPos + 3);
else
stc->GotoPos(currentPos + 1);
} else if (((command->type & PyCommandType::pctOBJECT) != 0) && !has_already_parenthese) {
stc->InsertText(currentPos, "(),");
if (((command->type & PyCommandType::pctNO_PARAMETER) != 0))
stc->GotoPos(currentPos + 2);
else
stc->GotoPos(currentPos + 1);
} else if (((command->type & PyCommandType::pctMODIFIER) != 0) && !has_already_parenthese) {
stc->InsertText(currentPos, "()");
if (((command->type & PyCommandType::pctNO_PARAMETER) != 0))
stc->GotoPos(currentPos + 2);
else
stc->GotoPos(currentPos + 1);
//TODO: check if it's a object.modif(), a modif()(object) a modif().object ?
}
if (!command->tooltip.empty())
stc->CallTipShow(currentPos, command->tooltip);
}
void FreeCADDialog::on_word_change_for_autocomplete(wxStyledTextEvent& event) {
wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject();
// Find the word start
int current_pos = stc->GetCurrentPos();
int word_start_pos = stc->WordStartPosition(current_pos, true);
int len_entered = current_pos - word_start_pos;
const wxString str = stc->GetTextRange(word_start_pos, current_pos + 1);
const wxString event_string = event.GetString();
if ((event.GetModificationType() & (wxSTC_MOD_INSERTTEXT | wxSTC_PERFORMED_USER)) != (wxSTC_MOD_INSERTTEXT | wxSTC_PERFORMED_USER)) {
return; // not our event
}
//std::cout << "word_change "<<event.GetModificationType()<<" typed: " << (int)stc->GetCharAt(current_pos) << " with len_entered " << len_entered
//<< ", event_string='"<< event_string << "' (last='"<< (event_string.empty()?-1:event_string.Last().GetValue()) <<"') ; Str is '" << str << "' with length " << str.length()
// << "' chars are (c-1)='" << stc->GetCharAt(current_pos - 1) << "' : (c)='" << stc->GetCharAt(current_pos)
//<< "', text length=" << stc->GetTextLength() << ", currentPos=" << current_pos << " , " << int('\n') << "\n";
//std::cout << "test: " << (!(stc->GetCharAt(current_pos - 1) <= '\n')) << ", 2=" << (len_entered >= 0) << ", 3=" << (!str.empty())
// << ", 4=" << (std::regex_match(str.ToStdString(), word_regex))
// <<", Mod5="<<((event.GetModificationType() & wxSTC_STARTACTION) != 0)
// <<", 6="<< (current_pos <= 1 || str != ".")<<", 6b="<< (str == ".")
// << "\n";
if ((event.GetModificationType() & wxSTC_STARTACTION) != 0 && (str.empty() || str.Last() != '.'))
return;
//if (!event_string.empty() && !str.empty() && int(str[str.length() - 1]) != event_string.Last().GetValue()) std::cout << "removecall?\n";
if (len_entered >= 0 && !str.empty() && std::regex_match(str.ToStdString(), word_regex)) {
//check for possible words
//todo: check for '.' to filter for modifiers
int nb_words = 0;
wxString possible;
for (const PyCommand &cmd : commands) {
if (cmd.name == str) return;
if (cmd.name.StartsWith(str) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0) ) {
nb_words++; possible += possible.empty() ? cmd.name : (" " + cmd.name);
}
}
// Display the autocompletion list
if (nb_words >= 1)
stc->AutoCompShow(len_entered, possible);
} else if (!str.empty() && str.Last() == '.') {
//wxString possible;
//for(const wxString &str : modif_words)
// possible += possible.empty() ? str : (" " + str);
wxString possible;
for (const PyCommand &cmd : commands) {
if (((cmd.type & PyCommandType::pctMODIFIER) != 0) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) {
possible += possible.empty() ? cmd.name : (" " + cmd.name);
}
}
//std::cout << "autocomplete: modifier: '"<< possible.ToStdString() <<"'\n";
if (possible.length() >= 1)
stc->AutoCompShow(0, possible);
}
}
void FreeCADDialog::on_char_add(wxStyledTextEvent& event) {
//if (event.GetUpdated() != wxSTC_UPDATE_CONTENT) return;
wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject();
// Find the word start
int current_pos = stc->GetCurrentPos();
int word_start_pos = stc->WordStartPosition(current_pos, true);
int len_entered = current_pos - word_start_pos;
const wxString str = stc->GetTextRange(word_start_pos, current_pos + 1);
//if(current_pos>1)
// std::cout << "char typed: " << (char)stc->GetCharAt(current_pos)<<" with length "<< len_entered
// << ", str is "<< str << "chars are '"<< stc->GetCharAt(current_pos - 1) << "' : '" << stc->GetCharAt(current_pos)
// <<"', text length="<< stc->GetTextLength()<<", currentPos="<< current_pos<<" , "<<int('\n')<<"\n";
if(current_pos > 2 && stc->GetCharAt(current_pos-1) == '\n'){
//TODO: check that we are really in a freepyscad section.
int lastpos = current_pos - 2;
if (stc->GetCharAt(lastpos) == '\r')
lastpos--;
// do we need to move the ',' ?
if (stc->GetCharAt(current_pos) == ',') {
stc->SetTargetStart(current_pos);
stc->SetTargetEnd(current_pos + 1);
stc->ReplaceTarget("");
}
//do we need to add the ','?
if (stc->GetCharAt(lastpos) == ')')
stc->InsertText(lastpos + 1, ",");
} else if (stc->GetTextLength() > current_pos && event.GetKey() == int(',') && stc->GetCharAt(current_pos - 1) == ',' && stc->GetCharAt(current_pos) == ',') {
stc->SetTargetStart(current_pos);
stc->SetTargetEnd(current_pos + 1);
stc->ReplaceTarget("");
} else if (stc->GetTextLength() > current_pos && event.GetKey() == int(')') && stc->GetCharAt(current_pos - 1) == ')' && stc->GetCharAt(current_pos) == ')') {
stc->SetTargetStart(current_pos);
stc->SetTargetEnd(current_pos + 1);
stc->ReplaceTarget("");
} else if (stc->GetTextLength() > current_pos && event.GetKey() == int('"') && stc->GetCharAt(current_pos - 1) == '"' && stc->GetCharAt(current_pos) == '"') {
stc->SetTargetStart(current_pos);
stc->SetTargetEnd(current_pos + 1);
stc->ReplaceTarget("");
} else if (stc->GetTextLength() > current_pos && event.GetKey() == int('"') && stc->GetCharAt(current_pos - 1) == '"'
&& (stc->GetCharAt(current_pos) == ')' || stc->GetCharAt(current_pos) == ',') ) {
stc->InsertText(current_pos, "\"");
}
}
// note: this works on KEY, not on CHAR, so be sure the key is the right one for all keyboard layout.
// space, back, del are ok but no ascii char
void FreeCADDialog::on_key_type(wxKeyEvent& event)
{
if (event.GetKeyCode() == WXK_SPACE && event.GetModifiers() == wxMOD_CONTROL)
{
//get word, if any
int current_pos = m_text->GetCurrentPos();
int word_start_pos = m_text->WordStartPosition(current_pos, true);
const wxString str = m_text->GetTextRange(word_start_pos, current_pos);
//std::cout << "ctrl-space! " << event.GetEventType() << " '" << str.ToStdString() << "' " << int(m_text->GetCharAt(current_pos - 1)) << "\n";
if (current_pos > 0 && m_text->GetCharAt(current_pos - 1) == '.') {
//only modifiers
wxString possible;
for (const PyCommand &cmd : commands) {
if (((cmd.type & PyCommandType::pctMODIFIER) != 0) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) {
possible += possible.empty() ? cmd.name : (" " + cmd.name);
}
}
m_text->AutoCompShow(0, possible);
return;
}
//propose words
int nb_words = 0;
wxString possible;
for (const PyCommand &cmd : commands) {
if (str.IsEmpty() || cmd.name.StartsWith(str) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) {
nb_words++; possible += possible.empty() ? cmd.name : (" " + cmd.name);
}
}
//std::cout << "space autocomplete: find " << nb_words << " forstring '" << str << "'\n";
// Display the autocompletion list
if (nb_words >= 1)
m_text->AutoCompShow(str.length(), possible);
}else if (event.GetKeyCode() == WXK_BACK && event.GetModifiers() == wxMOD_NONE)
{
//check if we can delete the whole word in one go
//see if previous word is a command
int current_pos = m_text->GetCurrentPos();
if (m_text->GetCharAt(current_pos - 1) == '(' && m_text->GetCharAt(current_pos) == ')')
current_pos--;
while (m_text->GetCharAt(current_pos - 2) == '(' && m_text->GetCharAt(current_pos -1) == ')')
current_pos -= 2;
int word_start_pos = m_text->WordStartPosition(current_pos, true);
wxString str = m_text->GetTextRange(word_start_pos, current_pos);
if (str.length() > 2) {
int del_more = 0;
if (m_text->GetCharAt(current_pos) == '(' && m_text->GetCharAt(current_pos + 1) == ')') {
del_more += 2;
}
if (m_text->GetCharAt(current_pos+2) == '(' && m_text->GetCharAt(current_pos + 3) == ')') {
del_more += 2;
}
const PyCommand *cmd = get_command(str);
if (cmd != nullptr) {
//delete the whole word
m_text->SetTargetStart(current_pos - str.length());
m_text->SetTargetEnd(current_pos + del_more);
m_text->ReplaceTarget("");
return; // don't use the backdel event, it's already del
}
}
event.Skip(true);
} else {
event.Skip(true);
}
//event.SetWillBeProcessedAgain();
}
void FreeCADDialog::createSTC()
{
m_text = new wxStyledTextCtrl(this, wxID_ANY,
@ -151,59 +587,51 @@ void FreeCADDialog::createSTC()
m_text->StyleSetBackground(wxSTC_STYLE_LINENUMBER, wxColour(220, 220, 220));
//m_text->SetMarginType(MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER);
m_text->SetTabWidth(4);
m_text->SetIndent(4);
m_text->SetUseTabs(true);
m_text->SetIndentationGuides(wxSTC_IV_LOOKFORWARD);
m_text->SetBackSpaceUnIndents(true);
m_text->SetTabIndents(true);
m_text->SetWrapMode(wxSTC_WRAP_WORD);
m_text->StyleClearAll();
m_text->SetLexer(wxSTC_LEX_PYTHON);
/*
//m_text->Bind(wxEVT_STC_CHANGE, &FreeCADDialog::on_word_change_for_autocomplete, this);
m_text->Bind(wxEVT_STC_MODIFIED, &FreeCADDialog::on_word_change_for_autocomplete, this);
m_text->Bind(wxEVT_STC_CHARADDED, &FreeCADDialog::on_char_add, this);
m_text->Bind(wxEVT_KEY_DOWN, &FreeCADDialog::on_key_type, this);
m_text->Bind(wxEVT_STC_AUTOCOMP_COMPLETED, &FreeCADDialog::on_autocomp_complete, this);
m_text->Connect(wxID_ANY,
wxEVT_KEY_DOWN,
wxKeyEventHandler(FreeCADDialog::on_key_type),
(wxObject*)NULL,
this);
#define wxSTC_P_DEFAULT 0
#define wxSTC_P_COMMENTLINE 1
#define wxSTC_P_NUMBER 2
#define wxSTC_P_STRING 3
#define wxSTC_P_CHARACTER 4
#define wxSTC_P_WORD 5
#define wxSTC_P_TRIPLE 6
#define wxSTC_P_TRIPLEDOUBLE 7
#define wxSTC_P_CLASSNAME 8
#define wxSTC_P_DEFNAME 9
#define wxSTC_P_OPERATOR 10
#define wxSTC_P_IDENTIFIER 11
#define wxSTC_P_COMMENTBLOCK 12
#define wxSTC_P_STRINGEOL 13
#define wxSTC_P_WORD2 14
#define wxSTC_P_DECORATOR 15
*/
m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(255, 0, 0));
m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(255, 0, 0));
m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(255, 0, 0));
m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(0, 150, 0));
m_text->StyleSetForeground(wxSTC_H_TAGUNKNOWN, wxColour(0, 150, 0));
m_text->StyleSetForeground(wxSTC_H_ATTRIBUTE, wxColour(0, 0, 150));
m_text->StyleSetForeground(wxSTC_H_ATTRIBUTEUNKNOWN, wxColour(0, 0, 150));
m_text->StyleSetForeground(wxSTC_H_COMMENT, wxColour(150, 150, 150));
m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(128, 128, 128));
m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(0, 128, 0));
m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(0, 128, 128));
m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(128, 0, 128));
m_text->StyleSetItalic(wxSTC_P_STRING,true),
m_text->StyleSetForeground(wxSTC_P_CHARACTER, wxColour(128, 0, 128));
m_text->StyleSetItalic(wxSTC_P_CHARACTER, true),
m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(0, 0, 0));
m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(128, 255, 128)); // comment, grennsish
m_text->StyleSetForeground(wxSTC_P_COMMENTBLOCK, wxColour(128, 255, 128)); // comment, grennsish
m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(255, 128, 0)); // number red-orange
m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(128, 256, 0)); // string, light green
m_text->StyleSetBackground(wxSTC_P_STRINGEOL, wxColour(255, 0, 0)); // End of line where string is not closed
m_text->StyleSetForeground(wxSTC_P_CHARACTER, wxColour(128, 256, 0));
m_text->StyleSetForeground(wxSTC_P_WORD, wxColour(0, 0, 128));
m_text->StyleSetBold(wxSTC_P_WORD, true),
m_text->StyleSetForeground(wxSTC_P_TRIPLE, wxColour(128, 0, 0));
m_text->StyleSetForeground(wxSTC_P_TRIPLEDOUBLE, wxColour(128, 0, 0));
m_text->StyleSetForeground(wxSTC_P_CLASSNAME, wxColour(0, 0, 255));
m_text->StyleSetBold(wxSTC_P_CLASSNAME, true),
m_text->StyleSetForeground(wxSTC_P_DEFNAME, wxColour(0, 128, 128));
m_text->StyleSetForeground(wxSTC_P_WORD2, wxColour(0, 0, 128));
m_text->StyleSetForeground(wxSTC_P_TRIPLE, wxColour(128, 0, 0)); // triple quote
m_text->StyleSetForeground(wxSTC_P_TRIPLEDOUBLE, wxColour(128, 0, 0)); //triple double quote
m_text->StyleSetForeground(wxSTC_P_DEFNAME, wxColour(0, 128, 128)); // Function or method name definition
m_text->StyleSetBold(wxSTC_P_DEFNAME, true),
m_text->StyleSetForeground(wxSTC_P_OPERATOR, wxColour(0, 0, 0));
m_text->StyleSetForeground(wxSTC_P_OPERATOR, wxColour(255, 0, 0));
m_text->StyleSetBold(wxSTC_P_OPERATOR, true),
m_text->StyleSetForeground(wxSTC_P_IDENTIFIER, wxColour(128, 128, 128));
m_text->StyleSetForeground(wxSTC_P_COMMENTBLOCK, wxColour(128, 128, 128));
m_text->StyleSetForeground(wxSTC_P_STRINGEOL, wxColour(0, 0, 0));
m_text->StyleSetBackground(wxSTC_P_STRINGEOL, wxColour(224, 192, 224));
m_text->StyleSetForeground(wxSTC_P_IDENTIFIER, wxColour(255, 64, 255)); // function call and almost all defined words in the language, violet
//add text if the saved file exist
boost::filesystem::path temp_file(Slic3r::data_dir());
temp_file = temp_file / "temp" / "current_pyscad.py";
load_text_from_file(temp_file);
}
@ -215,63 +643,177 @@ void FreeCADDialog::on_dpi_changed(const wxRect& suggested_rect)
Fit();
Refresh();
}
time_t parse_iso_time(std::string &str) {
int y, M, d, h, m;
float s;
sscanf(str.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s);
tm time;
time.tm_year = y - 1900; // Year since 1900
time.tm_mon = M - 1; // 0-11
time.tm_mday = d; // 1-31
time.tm_hour = h; // 0-23
time.tm_min = m; // 0-59
time.tm_sec = (int)s; // 0-61 (0-60 in C++11)
return mktime(&time);
}
void FreeCADDialog::test_update_script_file(std::string &json) {
//check if it's more recent
std::stringstream ss;
ss << json;
boost::property_tree::ptree root;
boost::property_tree::read_json(ss, root);
const boost::filesystem::path pyscad_path(boost::filesystem::path(Slic3r::data_dir()) / "scripts" / "FreePySCAD");
try {
std::string str_date;
for (const auto &entry : root.get_child("commit.committer")) {
if (entry.first == "date") {
str_date = entry.second.data();
break;
}
}
using namespace boost::locale;
boost::locale::generator gen;
std::locale loc = gen.generate(""); // or "C", "en_US.UTF-8" etc.
std::locale::global(loc);
std::cout.imbue(loc);
std::cout << "root.commit.committer.date=" << str_date << "\n";
std::time_t commit_time = parse_iso_time(str_date);
std::cout << "github time_t = "<<commit_time<<"\n";
std::time_t last_modif = boost::filesystem::last_write_time(pyscad_path / "freepyscad.py");
std::cout << "pyscad_path time_t = " << commit_time << "\n";
if (commit_time > last_modif) {
std::cout << "have to update!!\n";
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/__init__.py", pyscad_path / "__init__.py");
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/Init.py", pyscad_path / "Init.py");
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/freepyscad.py", pyscad_path / "freepyscad.py");
}
}
catch (std::exception ex) {
std::cerr << "Error, cannot parse https://api.github.com/repos/supermerill/FreePySCAD/commits/master: " << ex.what() << "\n";
}
}
bool FreeCADDialog::init_start_python() {
//reinit
exec_var.reset(new ExecVar());
// Get the freecad path (python path)
boost::filesystem::path pythonpath(gui_app->app_config->get("freecad_path"));
pythonpath = pythonpath / "python.exe";
if (!exists(pythonpath)) {
m_errors->AppendText("Error, cannot find the freecad (version 0.19 or higher) python at '" + pythonpath.string() + "', please update your freecad bin directory in the preferences.");
return false;
}
const boost::filesystem::path scripts_path(boost::filesystem::path(Slic3r::data_dir()) / "scripts");
boost::filesystem::create_directories(scripts_path / "FreePySCAD");
if (!boost::filesystem::exists(scripts_path / "FreePySCAD" / "freepyscad.py")) {
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/__init__.py", scripts_path / "FreePySCAD" / "__init__.py");
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/Init.py", scripts_path / "FreePySCAD" / "Init.py");
get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/freepyscad.py", scripts_path / "FreePySCAD" / "freepyscad.py");
}else if (!update_done){
update_done = true;
//try to check last version on website
//it's async so maybe you won't update it in time, but it's not the end of the world.
const boost::filesystem::path pyscad_path = scripts_path / "FreePySCAD";
std::function<void(FreeCADDialog*, std::string&)> truc = &FreeCADDialog::test_update_script_file;
get_string_from_web_async("https://api.github.com/repos/supermerill/FreePySCAD/commits/master", this, &FreeCADDialog::test_update_script_file);
}
exec_var->process.reset(new boost::process::child(pythonpath.string() + " -u -i", boost::process::std_in < exec_var->pyin,
boost::process::std_out > exec_var->data_out, boost::process::std_err > exec_var->data_err, exec_var->ios));
exec_var->pyin << "import FreeCAD" << std::endl;
exec_var->pyin << "import Part" << std::endl;
exec_var->pyin << "import Draft" << std::endl;
exec_var->pyin << "import sys" << std::endl;
exec_var->pyin << "sys.path.append('" << scripts_path.generic_string() << "')" << std::endl;
//std::cout << "sys.path.append('" << pyscad_path.generic_string() << "')" << std::endl;
exec_var->pyin << "from FreePySCAD.freepyscad import *" << std::endl;
//std::cout << "from FreePySCAD.freepyscad import *" << std::endl;
exec_var->pyin << "App.newDocument(\"document\")" << std::endl;
#ifdef __WINDOWS__
exec_var->pyin << "set_font_dir(\"C:/Windows/Fonts/\")" << std::endl;
#endif
#ifdef __APPLE__
exec_var->pyin << "set_font_dir([\"/System/Library/Fonts/\", \"~/Library/Fonts/\"])" << std::endl;
#endif
#ifdef __linux__
exec_var->pyin << "set_font_dir([\"/usr/share/fonts/\",\"~/.fonts/\"])" << std::endl;
// also add
#endif
return true;
}
bool FreeCADDialog::end_python() {
exec_var->pyin << "quit()" << std::endl;
exec_var->process->wait();
exec_var->ios.run();
return true;
}
void FreeCADDialog::create_geometry(wxCommandEvent& event_args) {
//Create the script
std::string program = "C:/Program Files/FreeCAD 0.18/bin/python.exe";
// cleaning
boost::filesystem::path object_path(Slic3r::resources_dir());
object_path = object_path / "generation" / "freecad" / "temp.amf";
boost::filesystem::path object_path(Slic3r::data_dir());
object_path = object_path / "temp" / "temp.amf";
m_errors->Clear();
if (exists(object_path)) {
remove(object_path);
}
//create synchronous pipe streams
boost::process::opstream pyin;
//boost::process::ipstream pyout;
//boost::process::ipstream pyerr;
boost::asio::io_service ios;
std::future<std::string> dataOut;
std::future<std::string> dataErr;
if (!init_start_python()) return;
boost::filesystem::path pythonpath(gui_app->app_config->get("freecad_path"));
pythonpath = pythonpath / "python.exe";
if (!exists(pythonpath)) {
m_errors->AppendText("Error, cannot find the freecad python at '"+ pythonpath.string()+"', please update your freecad bin directory in the preferences.");
//create file
//search for scene().redraw(...), add it if not present (without defs)
boost::filesystem::path temp_file(Slic3r::data_dir());
temp_file = temp_file / "temp";
boost::filesystem::create_directories(temp_file);
temp_file = temp_file / "exec_temp.py";
wxString text = m_text->GetText();
if (text.find("scene().redraw(") == std::string::npos) {
int redraw_pos = text.find("redraw()");
if (redraw_pos == std::string::npos) {
text = "scene().redraw(\n" + text + "\n)";
} else {
text = text.substr(0, redraw_pos) + "scene()." + text.substr(redraw_pos);
}
}
if (!this->write_text_in_file(text, temp_file)) {
m_errors->AppendText("Error, cannot write into "+ temp_file.string() + "!");
return;
}
boost::filesystem::path pyscad_path(Slic3r::resources_dir());
pyscad_path = pyscad_path / "generation" / "freecad";
boost::process::child c(pythonpath.string() +" -u -i", boost::process::std_in < pyin, boost::process::std_out > dataOut, boost::process::std_err > dataErr, ios);
pyin << "import FreeCAD" << std::endl;
pyin << "import Part" << std::endl;
pyin << "import Draft" << std::endl;
pyin << "import sys" << std::endl;
pyin << "sys.path.append('"<< pyscad_path.generic_string() <<"')"<<std::endl;
pyin << "from Pyscad.pyscad import *" << std::endl;
pyin << "App.newDocument(\"document\")" << std::endl;
pyin << "scene().redraw("<< m_text->GetText() <<")" << std::endl;
pyin << "Mesh.export(App.ActiveDocument.RootObjects, u\"" << object_path.generic_string() << "\")" << std::endl;
pyin << "print('exported!')" << std::endl;
pyin << "quit()" << std::endl;
c.wait();
ios.run();
std::cout << "exit with code " << c.exit_code() << "\n";
//std::cout<< "scene().redraw(" << boost::replace_all_copy(boost::replace_all_copy(m_text->GetText(), "\r", ""), "\n", "") << ")" << std::endl;
//exec_var->pyin << "scene().redraw("<< boost::replace_all_copy(boost::replace_all_copy(m_text->GetText(), "\r", ""), "\n", "") <<")" << std::endl;
exec_var->pyin << ("exec(open('" + temp_file.generic_string() + "').read())\n");
exec_var->pyin << "Mesh.export(App.ActiveDocument.RootObjects, u\"" << object_path.generic_string() << "\")" << std::endl;
exec_var->pyin << "print('exported!')" << std::endl;
exec_var->pyin << "App.ActiveDocument.RootObjects" << std::endl;
end_python();
std::string pyout_str_hello;
std::cout << "==cout==\n";
std::cout << dataOut.get();
std::cout << exec_var->data_out.get();
std::cout << "==cerr==\n";
std::string errStr = dataErr.get();
std::string errStr = exec_var->data_err.get();
std::cout << errStr << "\n";
std::string cleaned = boost::replace_all_copy(boost::replace_all_copy(errStr, ">>> ", ""),"\r","");
boost::replace_all(cleaned, "QWaitCondition: Destroyed while threads are still waiting\n", "");
boost::replace_all(cleaned, "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n", "");
boost::replace_all(cleaned, "\n\n", "\n");
m_errors->AppendText(cleaned);
std::cout << std::count(cleaned.begin(), cleaned.end(), '\n') << "\n";
std::cout << std::count(cleaned.begin(), cleaned.end(), '\r') << "\n";
if (!exists(object_path)) {
m_errors->AppendText("\nError, no object generated.");
@ -281,7 +823,7 @@ void FreeCADDialog::create_geometry(wxCommandEvent& event_args) {
Plater* plat = this->main_frame->plater();
Model& model = plat->model();
plat->reset();
std::vector<size_t> objs_idx = plat->load_files(std::vector<std::string>{ object_path.generic_string() }, true, false);
std::vector<size_t> objs_idx = plat->load_files(std::vector<std::string>{ object_path.generic_string() }, true, false, false);
if (objs_idx.empty()) return;
/// --- translate ---
const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config();

View File

@ -12,10 +12,35 @@
#include <wx/stc/stc.h>
#include <wx/html/htmlwin.h>
#include <wx/textctrl.h>
#include <boost/process.hpp>
namespace Slic3r {
namespace GUI {
enum PyCommandType : uint16_t {
pctNONE = 0x0,
pctOPERATION = 0x1 << 0,
pctOBJECT = 0x1 << 1,
pctMODIFIER = 0x1 << 2,
pctNO_PARAMETER = 0x1 << 3,
pctDO_NOT_SHOW = 0x1 << 4
};
class PyCommand {
public:
wxString name;
PyCommandType type;
wxString tooltip;
std::vector<std::string> args;
PyCommand(wxString lbl, PyCommandType modifier) : name(lbl), type(modifier) { auto lol = { "Z","z" }; }
PyCommand(wxString lbl, uint16_t modifier) : name(lbl), type(PyCommandType(modifier)) {}
PyCommand(wxString lbl, PyCommandType modifier, std::string tooltip) : name(lbl), type(modifier), tooltip(tooltip) {}
PyCommand(wxString lbl, uint16_t modifier, std::string tooltip) : name(lbl), type(PyCommandType(modifier)), tooltip(tooltip) {}
PyCommand(wxString lbl, PyCommandType modifier, std::initializer_list<const char*> args, std::string tooltip) : name(lbl), type(modifier), tooltip(tooltip) { for(const char* arg: args) this->args.emplace_back(arg); }
PyCommand(wxString lbl, uint16_t modifier, std::initializer_list<const char*> args, std::string tooltip) : name(lbl), type(PyCommandType(modifier)), tooltip(tooltip) { for (const char* arg : args) this->args.emplace_back(arg); }
};
class FreeCADDialog : public DPIDialog
{
@ -26,14 +51,50 @@ public:
protected:
void closeMe(wxCommandEvent& event_args);
void createSTC();
bool init_start_python();
void create_geometry(wxCommandEvent& event_args);
bool end_python();
void new_script(wxCommandEvent& event_args) { m_text->ClearAll(); }
void load_script(wxCommandEvent& event_args);
void save_script(wxCommandEvent& event_args);
void quick_save(wxCommandEvent& event_args);
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_word_change_for_autocomplete(wxStyledTextEvent& event);
void on_char_add(wxStyledTextEvent& event);
void on_key_type(wxKeyEvent& event);
void on_autocomp_complete(wxStyledTextEvent& event);
bool write_text_in_file(const wxString &towrite, const boost::filesystem::path &file);
bool load_text_from_file(const boost::filesystem::path &file);
void test_update_script_file(std::string &json);
wxStyledTextCtrl* m_text;
wxTextCtrl* m_errors;
wxTextCtrl* m_help;
MainFrame* main_frame;
GUI_App* gui_app;
std::vector<PyCommand> commands;
std::regex word_regex;
bool update_done = false;
boost::filesystem::path opened_file;
struct ExecVar {
boost::process::opstream pyin;
boost::asio::io_service ios;
std::future<std::string> data_out;
std::future<std::string> data_err;
std::unique_ptr<boost::process::child> process;
};
std::unique_ptr<ExecVar> exec_var;
bool ready = false;
protected:
const PyCommand* get_command(const wxString &name) const;
};
} // namespace GUI

View File

@ -300,12 +300,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors)
const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME;
const std::string idx_path = (cache_path / (vendor.id + ".idx")).string();
const std::string idx_path_temp = idx_path + "-update";
//check if idx_url is leading to our site
if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/"))
{
BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url;
continue;
}
//check if idx_url is leading to a safe site
//if (! boost::starts_with(idx_url, "http://files.my_company.com/wp-content/uploads/repository/"))
//{
// BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url;
// continue;
//}
if (!get_file(idx_url, idx_path_temp)) { continue; }
if (cancel) { return; }