[Feature idea] Sketcher Ctrl-C ctrl-V between projects

About the development of the Part Design module/workbench. PLEASE DO NOT POST HELP REQUESTS HERE!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

[Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by paddle »

Hey guys,

Something that has been annoying me a lot is the inability to copy/paste geometries in the sketcher.

I know there's the copy/clone commands, but ctrl-c ctrl-v is much more intuitive and fast to use.

Beside something that would be really cool is to be able to copy/paste geometries between sketches and between project :
You copy in project A sketch001, then open project B, open sketch006 and paste your selected geometries.

My question is, in what scope should this be integrated in order to be cross sketches/projects ?
If it's integrated inside ViewProviderSketch::keyPressed then will the data be lost once the sketch is exited?

My guess is that it requires a vector of geometries, but where to declare it such that it is 'program wide' ?

Cheers
chrisb
Veteran
Posts: 54150
Joined: Tue Mar 17, 2015 9:14 am

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by chrisb »

I remember having read that copying of complex geometries up to a whole model is done by using the system wide keyboard buffer on the textual xml representation.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

An

Post by paddle »

Ok so the plan is :
Get the selected geometries.
Get the constraints that are on those geometries.
Put all that on a ByteArray
Put the byteArray in the QclipBoard. This way the copy/paste can even be done between instances of FreeCAD. Even potentially posted as text in the forum, which could make sharing geometry on the forum easy (for bug demo or other) without having to save and attach the freecad file.

Sounds easy. But I'm stuck at first step. I can't find how to get the list of selected geometries. I basically want a

Code: Select all

std::vector<Part::Geometry*> geoSelected;
First issue is that it seems that to get the geometries you start with a vector of string "vertex 1" "edge 4"... Ok the code is here already.

Code: Select all

// get the selection
    std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();

    // only one sketch with its subelements are allowed to be selected
    if (selection.size() != 1) {
        QMessageBox::warning(Gui::getMainWindow(),
                             QObject::tr("Wrong selection"),
                             QObject::tr("Select elements from a single sketch."));
        return;
    }

    // get the needed lists and objects
    const std::vector<std::string> &SubNames = selection[0].getSubNames();
    if (SubNames.empty()) {
        QMessageBox::warning(Gui::getMainWindow(),
                             QObject::tr("Wrong selection"),
                             QObject::tr("Select elements from a single sketch."));
        return;
    }

    Sketcher::SketchObject* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
    getSelection().clearSelection();

    int LastGeoId = 0;
    Sketcher::PointPos LastPointPos = Sketcher::PointPos::none;
    const Part::Geometry *LastGeo = 0;

    // create python command with list of elements
    std::stringstream stream;
    int geoids = 0;
    for (std::vector<std::string>::const_iterator it=SubNames.begin(); it != SubNames.end(); ++it) {
        // only handle non-external edges
        if (it->size() > 4 && it->substr(0,4) == "Edge") {
            LastGeoId = std::atoi(it->substr(4,4000).c_str()) - 1;
            LastPointPos = Sketcher::PointPos::none;
            LastGeo = Obj->getGeometry(LastGeoId);
            // lines to copy
            if (LastGeoId >= 0) {
                geoids++;
                stream << LastGeoId << ",";
            }
        }
        else if (it->size() > 6 && it->substr(0,6) == "Vertex") {
            // only if it is a GeomPoint
            int VtId = std::atoi(it->substr(6,4000).c_str()) - 1;
            int GeoId;
            Sketcher::PointPos PosId;
            Obj->getGeoVertexIndex(VtId, GeoId, PosId);
            if (Obj->getGeometry(GeoId)->getTypeId() == Part::GeomPoint::getClassTypeId()) {
                LastGeoId = GeoId;
                LastPointPos = Sketcher::PointPos::start;
                // points to copy
                if (LastGeoId >= 0) {
                    geoids++;
                    stream << LastGeoId << ",";
                }
            }
        }
    }
Now my problem is that I can't find how to create an empty std::vector<Part::Geometry*> and push_back the geometries in the for loop.

I try the simple :

Code: Select all

std::vector<Part::Geometry*> geovals;

                for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end(); ++it) {
                    if (it->size() > 4 && it->substr(0, 4) == "Edge") {
                        LastGeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
                        LastGeo = Obj->getGeometry(LastGeoId);
                        if (LastGeoId >= 0) {
                            geovals.push_back(LastGeo);
                        }
                    }
But VS tells me it's wrong because of something I can't get :

Code: Select all

C++ no instance of overloaded function matches the argument list            argument types are: (const Part::Geometry *)            object type is: std::vector<Part::Geometry *, std::allocator<Part::Geometry *>>
Another issue is that this is a vector of pointers. So my bytearray will have useless pointers stored and not the actual data...

Any hint?
User avatar
onekk
Veteran
Posts: 6197
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: An

Post by onekk »

paddle wrote: Wed Jan 26, 2022 3:18 pm This way the copy/paste can even be done between instances of FreeCAD. Even potentially posted as text in the forum, which could make sharing geometry on the forum easy (for bug demo or other) without having to save and attach the freecad file.

Yes and No, Sketch is not only a bunch of geometries, so, you could have some difficulties to export it.
In Curves WB where you have a menu item that permit to past the code that is generating the Curve or the Surface in "Python Console".

Using it you will have this printed in "Python Console"

Code: Select all

>>> ### Begin command Curves_bspline_to_console
>>> import FreeCAD
>>> from FreeCAD import Vector
>>> import Part
>>> 
>>> poles0 = [Vector (-186.17474365234375, 73.06820678710938, 40.0), Vector (-466.6299222984221, 100.91031895516615, 40.0), Vector (-560.541579553814, 246.90657028186783, 39.999999999999986), Vector (-39.8610101313747, 384.96656062446516, 40.000000000000014), Vector (-15.280454727937686, 200.65344883087087, 40.0), Vector (-135.01138305664062, 56.912776947021484, 40.0)]
>>> weights0 = [1.0, 1.0, 1.0, 1.0, 1.0, 1.0]
>>> knots0 = [0.0, 307.9455593123799, 684.7652291154752, 936.3089571815935]
>>> mults0 = [4, 1, 1, 4]
>>> periodic0 = False
>>> degree0 = 3
>>> rational0 = False
>>> bs0 = Part.BSplineCurve()
>>> bs0.buildFromPolesMultsKnots(poles0, mults0, knots0, periodic0, degree0, weights0, rational0)
>>> obj0 = FreeCAD.ActiveDocument.addObject("Part::Spline","BSplineCurve0")
>>> obj0.Shape = bs0.toShape()
>>> 
Something similar would be very useful to have even for generic Part objects, maybe for Sketch it will be more complicated, but, maybe with some work it could be done.

Just an idea.

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

Re: An

Post by paddle »

onekk wrote: Thu Jan 27, 2022 5:58 pm Yes and No, Sketch is not only a bunch of geometries, so, you could have some difficulties to export it.
In Curves WB where you have a menu item that permit to past the code that is generating the Curve or the Surface in "Python Console".

Using it you will have this printed in "Python Console"

Something similar would be very useful to have even for generic Part objects, maybe for Sketch it will be more complicated, but, maybe with some work it could be done.

Just an idea.

Regards

Carlo D.
I start to have the feeling that thie geometry class is indeed quite complex and exporting it as a bytearray is a no-go.

But the fact is that sketch geometries are not particularly complex. It should basically be a vector of geometries, including the different points coordinate.

So yes new plan:
- Export the geometry in text format (something formated similarly to the example you gave).
- copy the text to the clipboard.
- Import from text to geometry and constraint classes when ctrlV

It makes more work but it feels like it's going to be easier than running around trying to export the geometry class directly.
User avatar
onekk
Veteran
Posts: 6197
Joined: Sat Jan 17, 2015 7:48 am
Contact:

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by onekk »

if you make thing using scripting, sketch is not the most immediate way, you could take care of constraints in code and use only Part and maybe Draft functions.

If you use vector math is not difficult to calculate "ending points" and similar things.

EDIT: sorry for mistyping, but on mobile it happens. (I've corrected post on PC)

Regards

Carlo D.
GitHub page: https://github.com/onekk/freecad-doc.
- In deep articles on FreeCAD.
- Learning how to model with scripting.
- Various other stuffs.

Blog: https://okkmkblog.wordpress.com/
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by paddle »

So I just started to work a bit on this again and copying to the clipboard is surprinsigly easy. I can easily copy the geoid of selected objects then print them anywhere.

Now I only need to format the string to something like :

Code: Select all

Geometries:  
 {newGeoId = 0; GeoType = xx; p1(x,y);p2(x,y)... } 
 {newGeoId = 1; GeoType = xx; p1(x,y);p2(x,y)... }
Constraints: 
 {newCstrId = 0; CstrType = xx; First = x; FirstPos = x; Second = x; SecondPos = x; Third = x; ThirdPos = x}
 {newCstrId = 0; CstrType = xx; First = x; FirstPos = x; Second = x; SecondPos = x; Third = x; ThirdPos = x}
Then work on the CtrlV part: create geometries from this string.

Should be pretty straight forward!

Do you have any comment regarding the format of the string? Maybe there's already something existing in FreeCAD which is similar? Maybe it could reuse some python format or something rather than this dumb format I used? (I never used python scripting with FreeCAD so consider my knowledge = 0 on that)
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by paddle »

I found that there are

Code: Select all

Save (Writer &writer) 
Restore(XMLReader &reader)
functions which are doing just what I need : exporting the geometries and constraints as xml formated string.
The thing is that the string is put in a Writer &writer object that I'm having trouble dealing with.

I'm trying to make an new instance of that writer class in order to use the existing save functions. Though I can't get it to work.

Code: Select all

                    std::stringstream stream;
                    Base::Writer *writer;
                    const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
                    for (std::vector< Sketcher::Constraint* >::const_iterator it = vals.begin(); it != vals.end(); ++it) {
                        
                        (*it)->Save(*writer);
                        
                    }
                    stream << writer->Stream().rdbuf();

                    //Copy to clipboard
                    QClipboard* clipboard = QGuiApplication::clipboard();
                    clipboard->setText(QString::fromStdString(stream.str()));
Any idea how to do it properly?
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by paddle »

Ok we haev to use StringWriter class :

Code: Select all

                    Base::StringWriter writer;
                    const std::vector< Sketcher::Constraint* >& vals = Obj->Constraints.getValues();
                    for (std::vector< Sketcher::Constraint* >::const_iterator it = vals.begin();
                        it != vals.end(); ++it) {
                        for (int i = 0; i < listOfGeoId.size(); i++) {
                            if ( (*it)->First == listOfGeoId[i] || (*it)->Second == listOfGeoId[i] || (*it)->Third == listOfGeoId[i] ) {
                                (*it)->Save(writer);
                            }
                        }
                    }
                    //Copy to clipboard
                    QClipboard* clipboard = QGuiApplication::clipboard();
                    clipboard->setText(QString::fromStdString(writer.getString()));
Working as expected now.
User avatar
paddle
Veteran
Posts: 1412
Joined: Mon Feb 03, 2020 4:47 pm

Re: [Feature idea] Sketcher Ctrl-C ctrl-V between projects

Post by paddle »

So using the existing save and restore functions, we can do what I planed to do.
Now the only thing is that construction lines are copied as normal lines.

Somehow my restore is failing to restore the geometryModeFlags parameter which set lines as construction.

Code: Select all

<GeoExtensions count="1">
        <GeoExtension type="Sketcher::SketchGeometryExtension" internalGeometryType="0" geometryModeFlags="00000000000000000000000000000010" geometryLayer="0"/>
    </GeoExtensions>
Other than that this feature will benefit a lot when group dragging will be implemented (which is currently pending review/debugging).
Post Reply