Draft/BIM: change BezCurve, BSpline and Wire to Part::FeaturePython

Fixes: #7387.

See comment: https://github.com/FreeCAD/FreeCAD/issues/7387#issuecomment-2915599566

PR to change the base object of BezCurve, BSpline and Wire to `Part::FeaturePython`. This will only affect new objects. So code will have to also keep handling the old object type (`Part::Part2DObjectPython`).

The modification of BimPreflight.py needs to be verified. The steps in the old code lacked logic IMO. But I may have misunderstood.
This commit is contained in:
Roy-043
2025-05-29 11:32:23 +02:00
committed by Yorik van Havre
parent 56e68922cb
commit e4adfc63d7
17 changed files with 80 additions and 54 deletions

View File

@@ -1703,6 +1703,7 @@ def makeWindow(baseobj=None, width=None, height=None, parts=None, name=None):
1. If baseobj is not a closed shape, the tool may not create a proper solid figure.
"""
import Draft
import DraftGeomUtils
from draftutils import todo
if baseobj and Draft.getType(baseobj) == "Window" and FreeCAD.ActiveDocument:
@@ -1730,8 +1731,12 @@ def makeWindow(baseobj=None, width=None, height=None, parts=None, name=None):
window.WindowParts = parts
else:
if baseobj:
if baseobj.getLinkedObject().isDerivedFrom("Part::Part2DObject"):
# Base object is a 2D object (sketch or wire)
linked_obj = baseobj.getLinkedObject(True)
if (linked_obj.isDerivedFrom("Part::Part2DObject")
or Draft.getType(linked_obj) in ["BezCurve", "BSpline", "Wire"]) \
and DraftGeomUtils.isPlanar(baseobj.Shape):
# "BezCurve", "BSpline" and "Wire" objects created with < v1.1 are "Part::Part2DObject" objects.
# In all versions these objects need not be planar.
if baseobj.Shape.Wires:
part_type = "Frame"
if len(baseobj.Shape.Wires) == 1:

View File

@@ -768,11 +768,14 @@ def pruneIncluded(objectslist,strict=False,silent=False):
for parent in obj.InList:
if not parent.isDerivedFrom("Part::Feature"):
pass
elif Draft.getType(parent) in ["Space","Facebinder","Window","Roof","Clone","Site","Project"]:
pass
elif parent.isDerivedFrom("Part::Part2DObject"):
# don't consider 2D objects based on arch elements
pass
elif Draft.getType(parent) in [
"BezCurve", "BSpline", "Clone", "Facebinder", "Wire",
"Project", "Roof", "Site", "Space", "Window"
]:
pass
elif parent.isDerivedFrom("PartDesign::FeatureBase"):
# don't consider a PartDesign_Clone that references obj
pass

View File

@@ -577,7 +577,16 @@ def getDXF(obj):
return result
if not allOn:
objs = Draft.removeHidden(objs)
objs = [o for o in objs if ((not(Draft.getType(o) in ["Space","Dimension","Annotation"])) and (not (o.isDerivedFrom("Part::Part2DObject"))))]
objs = [
obj
for obj in objs
if (
not obj.isDerivedFrom("Part::Part2DObject")
and Draft.getType(obj) not in [
"BezCurve", "BSpline", "Wire", "Annotation", "Dimension", "Space"
]
)
]
vshapes,hshapes,sshapes,cutface,cutvolume,invcutvolume = getCutShapes(objs,cutplane,onlySolids,clip,False,showHidden)
if vshapes:
result.append(TechDraw.projectToDXF(Part.makeCompound(vshapes),direction))

View File

@@ -256,28 +256,20 @@ class BIM_Preflight_TaskPanel:
objs = FreeCADGui.Selection.getSelection()
# clean objects list of unwanted types
objs = Draft.get_group_contents(objs, walls=True, addgroups=True)
objs = [obj for obj in objs if not obj.isDerivedFrom("Part::Part2DObject")]
objs = [obj for obj in objs if not obj.isDerivedFrom("App::Annotation")]
objs = [
obj
for obj in objs
if (
hasattr(obj, "Shape")
not obj.isDerivedFrom("App::DocumentObjectGroup")
and not obj.isDerivedFrom("App::Annotation")
and not obj.isDerivedFrom("Part::Part2DObject")
and Draft.getType(obj) not in ["BezCurve", "BSpline", "Wire", "WorkingPlaneProxy"]
and hasattr(obj, "Shape")
and obj.Shape
and not (obj.Shape.Edges and (not obj.Shape.Faces))
)
]
objs = Arch.pruneIncluded(objs)
objs = [
obj for obj in objs if not obj.isDerivedFrom("App::DocumentObjectGroup")
]
objs = [
obj
for obj in objs
if Draft.getType(obj)
not in ["DraftText", "Material", "MaterialContainer", "WorkingPlaneProxy"]
]
return objs
return Arch.pruneIncluded(objs)
def getToolTip(self, test):
"gets the toolTip text from the ui file"

View File

@@ -282,9 +282,12 @@ def export(exportList, filename, colors=None, preferences=None):
annotations = []
specials = []
for obj in objectslist:
if obj.isDerivedFrom("Part::Part2DObject"):
annotations.append(obj)
elif obj.isDerivedFrom("App::Annotation") or (Draft.getType(obj) in ["DraftText","Text","Dimension","LinearDimension","AngularDimension"]):
if obj.isDerivedFrom("Part::Part2DObject") \
or obj.isDerivedFrom("App::Annotation") \
or Draft.getType(obj) in [
"BezCurve", "BSpline", "Wire",
"DraftText", "Text", "Dimension", "LinearDimension", "AngularDimension"
]:
annotations.append(obj)
elif hasattr(obj, "Proxy") and hasattr(obj.Proxy, "export_ifc"):
specials.append(obj)

View File

@@ -215,7 +215,10 @@ def is_annotation(obj):
return True
elif obj.isDerivedFrom("App::Annotation"):
return True
elif Draft.getType(obj) in ["DraftText",
elif Draft.getType(obj) in ["BezCurve",
"BSpline",
"Wire",
"DraftText",
"Text",
"Dimension",
"LinearDimension",

View File

@@ -57,7 +57,7 @@ def fuse(object1, object2):
if len(f.Wires) > 1:
holes = True
if DraftGeomUtils.isCoplanar(object1.Shape.fuse(object2.Shape).Faces) and not holes:
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython","Fusion")
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Fusion")
Wire(obj)
if App.GuiUp:
ViewProviderWire(obj.ViewObject)
@@ -65,10 +65,10 @@ def fuse(object1, object2):
obj.Tool = object2
elif holes:
# temporary hack, since Part::Fuse objects don't remove splitters
obj = App.ActiveDocument.addObject("Part::Feature","Fusion")
obj = App.ActiveDocument.addObject("Part::Feature", "Fusion")
obj.Shape = fshape
else:
obj = App.ActiveDocument.addObject("Part::Fuse","Fusion")
obj = App.ActiveDocument.addObject("Part::Fuse", "Fusion")
obj.Base = object1
obj.Tool = object2
if App.GuiUp:

View File

@@ -563,7 +563,9 @@ def upgrade(objects, delete=False, force=None):
_msg(translate("draft", "Found object with several coplanar faces: refining them"))
# only one object: if not parametric, we "draftify" it
elif len(objects) == 1 and not objects[0].isDerivedFrom("Part::Part2DObjectPython"):
elif len(objects) == 1 \
and not objects[0].isDerivedFrom("Part::Part2DObjectPython") \
and not utils.get_type(objects[0]) in ["BezCurve", "BSpline", "Wire"]:
result = _draftify(objects[0])
if result:
_msg(translate("draft", "Found 1 non-parametric object: draftifying it"))
@@ -603,7 +605,8 @@ def upgrade(objects, delete=False, force=None):
# only one object: if not parametric, we "draftify" it
elif len(objects) == 1 \
and len(edges) == 1 \
and not objects[0].isDerivedFrom("Part::Part2DObjectPython"):
and not objects[0].isDerivedFrom("Part::Part2DObjectPython") \
and not utils.get_type(objects[0]) in ["BezCurve", "BSpline", "Wire"]:
edge_type = DraftGeomUtils.geomType(objects[0].Shape.Edges[0])
# currently only support Line and Circle
if edge_type in ("Line", "Circle"):

View File

@@ -39,6 +39,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
from draftguitools import gui_base_original
from draftguitools import gui_tool_utils
from draftutils import utils
from draftutils import gui_utils
from draftutils.messages import _msg
from draftutils.translate import translate
@@ -115,10 +116,12 @@ class SubelementHighlight(gui_base_original.Modifier):
def get_editable_objects_from_selection(self):
"""Get editable Draft objects for the selection."""
for obj in Gui.Selection.getSelection():
if obj.isDerivedFrom("Part::Part2DObject"):
if (obj.isDerivedFrom("Part::Part2DObject")
or utils.get_type(obj) in ["BezCurve", "BSpline", "Wire"]):
self.editable_objects.append(obj)
elif (hasattr(obj, "Base")
and obj.Base.isDerivedFrom("Part::Part2DObject")):
and (obj.Base.isDerivedFrom("Part::Part2DObject")
or utils.get_type(obj.Base) in ["BezCurve", "BSpline", "Wire"])):
self.editable_objects.append(obj.Base)
def highlight_editable_objects(self):

View File

@@ -81,7 +81,8 @@ def make_bezcurve(pointslist,
utils.type_check([(placement,App.Placement)], "make_bezcurve")
if len(pointslist) == 2: fname = "Line"
else: fname = "BezCurve"
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
obj = App.ActiveDocument.addObject("Part::FeaturePython", fname)
obj.addExtension("Part::AttachExtensionPython")
BezCurve(obj)
obj.Points = pointslist
if degree:

View File

@@ -50,7 +50,8 @@ def make_block(objectslist):
if not App.ActiveDocument:
App.Console.PrintError("No active document. Aborting\n")
return
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython","Block")
obj = App.ActiveDocument.addObject("Part::FeaturePython", "Block")
obj.addExtension("Part::AttachExtensionPython")
Block(obj)
obj.Components = objectslist
if App.GuiUp:

View File

@@ -92,7 +92,8 @@ def make_bspline(pointslist, closed=False, placement=None, face=None, support=No
utils.type_check([(placement,App.Placement)], "make_bspline")
if len(pointslist) == 2: fname = "Line"
else: fname = "BSpline"
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython",fname)
obj = App.ActiveDocument.addObject("Part::FeaturePython", fname)
obj.addExtension("Part::AttachExtensionPython")
BSpline(obj)
obj.Closed = closed
obj.Points = pointslist

View File

@@ -67,10 +67,16 @@ def make_clone(obj, delta=None, forcedraft=False):
if not isinstance(obj,list):
obj = [obj]
if (len(obj) == 1) and obj[0].isDerivedFrom("Part::Part2DObject"):
cl = App.ActiveDocument.addObject("Part::Part2DObjectPython","Clone2D")
if len(obj) == 1 \
and obj[0].isDerivedFrom("Part::Part2DObject") \
and utils.get_type(obj[0]) not in ["BezCurve", "BSpline", "Wire"]:
# "BezCurve", "BSpline" and "Wire" objects created with < v1.1
# are "Part::Part2DObject" objects but they need not be 2D.
cl = App.ActiveDocument.addObject("Part::Part2DObjectPython", "Clone2D")
cl.Label = prefix + obj[0].Label + " (2D)"
elif (len(obj) == 1) and (hasattr(obj[0],"CloneOf") or (utils.get_type(obj[0]) == "BuildingPart")) and (not forcedraft):
elif len(obj) == 1 \
and (hasattr(obj[0], "CloneOf") or utils.get_type(obj[0]) == "BuildingPart") \
and not forcedraft:
# arch objects can be clones
try:
import Arch
@@ -106,7 +112,7 @@ def make_clone(obj, delta=None, forcedraft=False):
# fall back to Draft clone mode
if not cl:
cl = App.ActiveDocument.addObject("Part::FeaturePython","Clone")
cl = App.ActiveDocument.addObject("Part::FeaturePython", "Clone")
cl.addExtension("Part::AttachExtensionPython")
cl.Label = prefix + obj[0].Label
Clone(cl)

View File

@@ -108,7 +108,8 @@ def make_wire(pointslist, closed=False, placement=None, face=None, support=None,
else:
fname = "Wire"
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython", fname)
obj = App.ActiveDocument.addObject("Part::FeaturePython", fname)
obj.addExtension("Part::AttachExtensionPython")
Wire(obj)
obj.Points = pointslist
obj.Closed = closed

View File

@@ -66,9 +66,7 @@ class DraftObject(object):
allows distinguishing among various types of objects
derived from the same C++ class.
>>> print(A.TypeId, "->", A.Proxy.Type)
Part::Part2DObjectPython -> Wire
>>> print(B.TypeId, "->", B.Proxy.Type)
>>> print(obj.TypeId, "->", obj.Proxy.Type)
Part::Part2DObjectPython -> Circle
This class attribute is accessible through the `Proxy` object:

View File

@@ -121,8 +121,8 @@ def process(filename):
Returns
-------
Part::Part2DObject or None.
The created Draft Wire object or None if the file contains less
Part::Feature or None.
The created object or None if the file contains fewer
than 3 points.
"""
# Regex to identify data rows and throw away unused metadata

View File

@@ -795,7 +795,7 @@ def drawLine(line, forceShape=False):
Returns
-------
Part::Part2DObject or Part::TopoShape ('Edge')
Part::Feature or Part::TopoShape ('Edge')
The returned object is normally a `Wire`, if the global
variables `dxfCreateDraft` or `dxfCreateSketch` are set,
and `forceShape` is `False`.
@@ -847,7 +847,7 @@ def drawPolyline(polyline, forceShape=False, num=None):
Returns
-------
Part::Part2DObject or Part::TopoShape ('Wire', 'Face', 'Shell')
Part::Feature or Part::TopoShape ('Wire', 'Face', 'Shell')
It returns `None` if it fails producing a shape.
If the polyline has a `width` and the global variable
@@ -1292,7 +1292,7 @@ def drawSplineIterpolation(verts, closed=False, forceShape=False,
Returns
-------
Part::Part2DObject or Part::TopoShape ('Edge', 'Face')
Part::Feature or Part::TopoShape ('Edge', 'Face')
The returned object is normally a `Draft Wire` or `Draft BSpline`,
if the global variables `dxfCreateDraft` or `dxfCreateSketch` are set,
and `forceShape` is `False`.
@@ -1350,7 +1350,7 @@ def drawSplineOld(spline, forceShape=False):
Returns
-------
Part::Part2DObject or Part::TopoShape ('Edge', 'Face')
Part::Feature or Part::TopoShape ('Edge', 'Face')
The returned object is normally a `Draft Wire` or `Draft BSpline`
as returned from `drawSplineIterpolation()`.
@@ -1409,7 +1409,7 @@ def drawSpline(spline, forceShape=False):
Returns
-------
Part::Part2DObject or Part::TopoShape ('Edge', 'Face')
Part::Feature or Part::TopoShape ('Edge', 'Face')
The returned object is normally a `Draft BezCurve`
created with `Draft.make_bezcurve(controlpoints, degree=degree)`,
if `forceShape` is `False` and there are no weights.
@@ -1813,7 +1813,7 @@ def drawLayerBlock(objlist, name="LayerBlock"):
Returns
-------
Part::Part2DObject or Part::TopoShape ('Compound')
Part::Feature or Part::TopoShape ('Compound')
If the global variables `dxfCreateDraft` or `dxfCreateSketch` are set,
and no element in `objlist` is a `Part.Shape`,
it will try to return a `Draft Block`.
@@ -1910,9 +1910,8 @@ def addObject(shape, name="Shape", layer=None):
-------
Part::Feature or Part::Part2DObject
If the `shape` is a simple `Part.Shape`, it will be encapsulated
inside a `Part::Feature` object and this will be returned.
Otherwise, it is assumed it is already a Draft object
(`Part::Part2DObject`) and will just return this.
inside a `Part::Feature` object and this will be returned. Otherwise,
it is assumed it is already a Draft object which will just be returned.
It applies the text and line color by calling `formatObject()`
before returning the new object.
@@ -1941,8 +1940,6 @@ def addObject(shape, name="Shape", layer=None):
l = layerObjects[lay]
l.append(newob)
formatObject(newob)
return newob