New tool: Draft_Fillet, prototype implementation

A forum dedicated to the Draft, Arch and BIM workbenches development.
vocx
Posts: 921
Joined: Thu Oct 18, 2018 9:18 pm

New tool: Draft_Fillet, prototype implementation

Postby vocx » Wed Aug 21, 2019 6:56 am

Inspired by this thread Part.Line has no StartPoint and no EndPoint in 0.17, I decided to go ahead and create a "Draft Fillet" tool. See pull request #2441.

It is not fully functional, but I wanted to put the code out there so other users can take a look at it, and finish the implementation.

You must import the module before even choosing Draft.

Code: Select all

import DraftFillet
Why? Because I didn't want to pollute Draft.py and DraftTools.py with even more code. I'm still not sure how to register the command from the beginning without messing with the circular dependencies due to calling Draft, DraftTools, DraftGui, etc. This requires a major refactoring, so I'm still thinking on the best way of including external tools from their own modules.

The command basically takes two Draft Wires, then extracts their edges and uses

Code: Select all

edges = DraftGeomUtils.fillet([edge1, edge2], radius)
The resulting edges is a list of three edges: two straight segments, and the arc that ties them. Then they are fused with

Code: Select all

new_wire = Part.Wire(edges)
The new_wire is the new topological shape, which can be assigned to a new "Fillet" scripted object. This object is pretty simple, it only indicates the starting point, ending point, and radius. Currently, the radius of curvature cannot be changed graphically. Or if you change it, it does nothing. Ideally, you should be able to change the radius and see the curvature changing in real time.

From the terminal you can create the desired radius, but not from the interface

Code: Select all

new = DraftFillet.makeFillet([wire1, wire2], radius=300)
And this is the thing, does it really make sense to have a Draft Fillet at all? The normal Draft Wire can already show a fillet by changing its "Fillet Radius" property.

So, maybe this tool should just wrap around the _Wire class. It could just fuse two straight edges, turn that into a _Wire, and then adjust the radius accordingly. I didn't test this, but I guess it could work, and it would be simpler because all properties would be easily inherited. Still, editing the radius graphically would be nice.

I guess a tool like this only makes sense if you have many individual Draft Lines, because otherwise Draft Wire is already pretty complete.

And just for completeness, the Draft_Fillet.svg icon is the same as the Sketcher icon, just with the Draft colors (yellow).
carlopav
Posts: 212
Joined: Mon Dec 31, 2018 1:49 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby carlopav » Thu Aug 22, 2019 4:05 pm

Great! Thanks vox, I was too busy with edit to try to do it.
Maybe you can use the same splitting technique that draft edit uses?
I'll try it as soon as I can. Probably next weekend
vocx
Posts: 921
Joined: Thu Oct 18, 2018 9:18 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby vocx » Thu Aug 22, 2019 5:11 pm

carlopav wrote:
Thu Aug 22, 2019 4:05 pm
...
Maybe you can use the same splitting technique that draft edit uses?
...
It's already basically like that, however there is a difference in behavior. Draft Edit is a purely graphical command, meaning that you need to have the interface loaded. It doesn't run from the terminal.

Draft Fillet, just like Draft Line, Draft Wire, Draft Arc, and other creation tools, creates an object by running a creation function. It defines makeFillet(), which can be run without interface; and if the interface is loaded, a CommandFillet() class just calls this function. This means that the module needs to first define the terminal command, before the graphical command, but this seems to cause the circular dependency.

As it is currently, the terminal commands are defined in Draft.py and the graphical commands in DraftTools.py. Draft Edit only needs to be imported in the later, but a tool like Draft Wire, and Draft Fillet, needs to have code in both Draft.py and DraftTools.py because it's both terminal and graphical.

A solution could be perhaps to have a DraftFilletNoGUI.py to include in Draft.py, and a DraftFilletGUI.py to include in DraftTools.py. But this means that all tools should be split into two files, which creates a lot of files. It's a solution, but I don't think it's the best one. I feel that the best thing would be to refactor parts of Draft.py, so that the base classes are in a separate file, DraftBaseObjects.py; then these could be imported by many tools without having to cause a circular dependency.
Last edited by vocx on Fri Aug 30, 2019 6:47 am, edited 1 time in total.
carlopav
Posts: 212
Joined: Mon Dec 31, 2018 1:49 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby carlopav » Thu Aug 22, 2019 8:45 pm

Ok i didn't consider this... From this point of view it's a good testing for further splitting
User avatar
dimitar
Posts: 144
Joined: Thu Jun 13, 2019 6:10 am
Location: UK
Contact:

Re: New tool: Draft_Fillet, prototype implementation

Postby dimitar » Wed Aug 28, 2019 6:22 pm

This is probably outside of the scope, but I am curious why there's such a discrepancy between the sketcher toolset and the draft toolset. Ie. fillet in sketcher and offset in draft. I imagine they are being developed with different scopes in mind?
vocx
Posts: 921
Joined: Thu Oct 18, 2018 9:18 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby vocx » Thu Aug 29, 2019 2:19 am

dimitar wrote:
Wed Aug 28, 2019 6:22 pm
This is probably outside of the scope, but I am curious why there's such a discrepancy between the sketcher toolset and the draft toolset. Ie. fillet in sketcher and offset in draft. I imagine they are being developed with different scopes in mind?
You are under the impression that FreeCAD is a well planed software. It is not. One dude, let's call him Jürgen, decided to implement some OCCT functions in a program in order to have a free CAD system, and he did, and that's how FreeCAD and the Part workbench were born. Then, years later, Yorik decided to add a bunch of code for 2D drawing in order to be usable for architectural plans and thus Draft was born. The Sketcher and Draft have different scopes because they were developed by different people with different objectives. The sketcher relies on constraints and a solver to produce planar geometrical shapes that could be padded to produce complex solids, while Draft was more intended for simple geometry creation (no constraints) to produce blueprints, like those architects need.

Draft has basically developed on its own, using Python wrappings around the complex C++ code that is Part. Nobody thought about some master plan and making the tools behave the same; everybody just added code on their own to produce the results they wanted.
User avatar
bitacovir
Posts: 670
Joined: Sat Apr 19, 2014 6:23 am
Contact:

Re: New tool: Draft_Fillet, prototype implementation

Postby bitacovir » Thu Aug 29, 2019 11:57 am

vocx wrote:
Thu Aug 29, 2019 2:19 am
Nobody thought about some master plan and making the tools behave the same; everybody just added code on their own to produce the results they wanted.
FreeCAD is a delicious anarchy.
;)
::bitacovir::
===================================
One must be absolutely modern.
Arthur Rimbaud (A Season in Hell -1873)

My Blog
Mini Airflow Tunnel Project
User avatar
yorik
Site Admin
Posts: 11458
Joined: Tue Feb 17, 2009 9:16 pm
Location: São Paulo, Brazil
Contact:

Re: New tool: Draft_Fillet, prototype implementation

Postby yorik » Thu Aug 29, 2019 2:39 pm

I would add that very often, this "anarchy" allowed for people to experiment things that would have been "forbidden" or discouraged if we had a strict roadmap. The good side is that you always have many different ways to reach a certain goal. And very, very often, one WB ended up incorporating successful experiments from others. All in all, I think it's not a bad development model...

Of course this all doesn't turn FreeCAD very easy to learn, that's obviously the bad aspect of it. But that all can be bettered. Better start having the best and most powerful tools as we are able to make, then see about making it all more user-friendly, than the contrary.
vocx
Posts: 921
Joined: Thu Oct 18, 2018 9:18 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby vocx » Fri Aug 30, 2019 7:52 am

Updated code. It's in pull request #2472.

Now makeFillet() has an optional argument to delete the original objects, or keep them. The default is to keep them.

Code: Select all

Fillet = DraftFillet.makeFillet([L1, L2], radius=200)
Fillet = DraftFillet.makeFillet([L1, L2], radius=200, delete=True)
I don't have circular dependencies anymore. Things just work. I'm not sure if this is the best way to do it, but I'm going to keep looking into it.

In InitGui.py the new module is added. This is required so the workbench correctly picks up the tool, and adds it to the workbench's toolbar (the button) when the user graphically switches to Draft.

Code: Select all

import os, Draft_rc, DraftTools, DraftGui, DraftFillet
In the implementation of the graphical tool, we actually call the command line tool.

Code: Select all

        name = translate("draft", "Create fillet")

        args = _wires + ', radius=' + str(rad)
        if delete:
            args += ', delete=' + str(delete)
        func = ['arc = DraftFillet.makeFillet(' + args + ')']
        func.append('Draft.autogroup(arc)')
        func.append('FreeCAD.ActiveDocument.recompute()')
        self.commit(name, func)
The command is DraftFillet.makeFillet(), and it is in its own module DraftFillet.py. Originally, I said that this module needed to be imported before calling the function, otherwise it wouldn't work. However, I missed the important instruction

Code: Select all

FreeCADGui.addModule("Draft")
This is normally added before the self.commit() code above so that functions like Draft.makeLine() would work. So I just load the appropriate module.

Code: Select all

FreeCADGui.addModule("DraftFillet")
About the graphical interface. Now when pressing the button, a Task panel appears that allows setting the value of the radius of the fillet, and toggle whether to delete the original objects or not.

What I would think would be nice is to show a "tracker", that is, the preview of the new shape, which would be updated when the value of the radius changes. I haven't investigated how this works exactly, so any help is welcome.

I will further investigate separating other tools into their own modules. However, there is still the issue of leaving a reference to the object in the main Draft.py file. Since a fillet is created from the code in DraftFillet.py, there is no class in Draft.py of this type. But for all other objects, a class definitely needs to be in Draft.py, or old files will fail to load.

Also, I just realized that DraftGui.py basically defines a huge DraftToolBar class with basically the Task panel of every single Draft tool. I think it is also important to separate this into smaller classes, so they are more manageable. Each tool should probably have its own .ui file as well.

----------

I just added the chamfer option as well. Since the underlying function DraftGeomUtil.fillet() supports it, it was straight-forward. Also to the Task panel interface.

Code: Select all

Fillet = DraftFillet.makeFillet([L1, L2], radius=200)
Fillet = DraftFillet.makeFillet([L1, L2], radius=300, delete=True)
Fillet = DraftFillet.makeFillet([L1, L2], radius=500, chamfer=True, delete=True)
carlopav
Posts: 212
Joined: Mon Dec 31, 2018 1:49 pm

Re: New tool: Draft_Fillet, prototype implementation

Postby carlopav » Fri Aug 30, 2019 10:21 pm

Thanks for going on with this, also is nicely documented, I'll try to bring some of your documenting style to Edit too!
vocx wrote:
Fri Aug 30, 2019 7:52 am
What I would think would be nice is to show a "tracker", that is, the preview of the new shape, which would be updated when the value of the radius changes. I haven't investigated how this works exactly, so any help is welcome.
My 2 cents [also to check if i learned something :)]:
- initialize a DraftTrackers.arcTracker() when the first edge is selected
- when mouse is moved, look for an object under the mouse with getObjectInfo(CursorPos)
- if an object is found, process the fillet -> arcTracker.on() and arcTracker.update(with the parameters of the fillet)
- else arcTracker.off()
- finishing the command arcTracker.finalize() to remove the tracker.

I think entity trackers are often referred as ghosts in the code.
hope someone can confirm :)

PS, this should work also with different flows of the command (when selecting 2 edges and running fillet, waiting for user to input radius).
PS2, dont know if already implemented, but would be nice to be able to set a radius=0 and to have the 2 edges joined together