BIM: NativeIFC 2D support - section planes

This commit is contained in:
Yorik van Havre
2024-09-30 14:58:11 +02:00
committed by Yorik van Havre
parent c0d452f6c5
commit 14585a760e
9 changed files with 240 additions and 48 deletions

View File

@@ -843,6 +843,10 @@ class _SectionPlane:
# old objects
l = obj.ViewObject.DisplaySize.Value
h = obj.ViewObject.DisplaySize.Value
if not l:
l = 1
if not h:
h = 1
p = Part.makePlane(l,h,Vector(l/2,-h/2,0),Vector(0,0,-1))
# make sure the normal direction is pointing outwards, you never know what OCC will decide...
if p.normalAt(0,0).getAngle(obj.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))) > 1:
@@ -1018,6 +1022,10 @@ class _ViewProviderSectionPlane:
if hasattr(vobj,"Transparency"):
self.mat2.transparency.setValue(vobj.Transparency/100.0)
elif prop in ["DisplayLength","DisplayHeight","ArrowSize"]:
# for IFC objects: propagate to the object
if prop in ["DisplayLength","DisplayHeight"]:
if hasattr(vobj.Object.Proxy, "onChanged"):
vobj.Object.Proxy.onChanged(vobj.Object, prop)
if hasattr(vobj,"DisplayLength") and hasattr(vobj,"DisplayHeight"):
ld = vobj.DisplayLength.Value/2
hd = vobj.DisplayHeight.Value/2

View File

@@ -62,6 +62,12 @@ class Arch_SectionPlane:
FreeCADGui.doCommand("section = Arch.makeSectionPlane("+ss+")")
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if len(sel) == 1 and getattr(sel[0], "IfcClass", None) == "IfcProject":
# remove the IFC project, otherwise we can't aggregate (circular loop)
FreeCADGui.doCommand("section.Objects = []")
#FreeCADGui.addModule("nativeifc.ifc_tools")
#p = "FreeCAD.ActiveDocument."+sel[0].Name
#FreeCADGui.doCommand("nativeifc.ifc_tools.aggregate(section,"+p+")")

View File

@@ -93,6 +93,11 @@ class BIM_TDView:
page.addView(view)
if page.Scale:
view.Scale = page.Scale
if "ShapeMode" in draft.PropertiesList:
draft.ShapeMode = "Shape"
for child in draft.OutListRecursive:
if "ShapeMode" in child.PropertiesList:
child.ShapeMode = "Shape"
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()

View File

@@ -2464,6 +2464,8 @@ def create_annotation(anno, ifcfile, context, history, preferences):
ovc = None
zvc = None
xvc = None
repid = "Annotation"
reptype = "Annotation2D"
if anno.isDerivedFrom("Part::Feature"):
if Draft.getType(anno) == "Hatch":
objectType = "HATCH"
@@ -2548,6 +2550,30 @@ def create_annotation(anno, ifcfile, context, history, preferences):
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
txt = ifcfile.createIfcTextLiteral(vp.string,tpl,"LEFT")
reps.append(txt)
elif Draft.getType(anno) == "SectionPlane":
p = FreeCAD.Vector(anno.Placement.Base).multiply(preferences['SCALE_FACTOR'])
ovc = ifcbin.createIfcCartesianPoint((p.x,p.y,p.z))
zvc = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(0,0,1))))
xvc = ifcbin.createIfcDirection(tuple(anno.Placement.Rotation.multVec(FreeCAD.Vector(1,0,0))))
objectType = "DRAWING"
l = w = h = 1000
if anno.ViewObject:
if anno.ViewObject.DisplayLength.Value:
l = anno.ViewObject.DisplayLength.Value
if anno.ViewObject.DisplayHeight.Value:
w = anno.ViewObject.DisplayHeight.Value
if anno.Depth.Value:
h = anno.Depth.Value
l = FreeCAD.Vector(l, w, h).multiply(preferences['SCALE_FACTOR'])
zdir = ifcbin.createIfcDirection((0.0,0.0,1.0))
xdir = ifcbin.createIfcDirection((1.0,0.0,0.0))
pos = ifcbin.createIfcCartesianPoint((-l.x/2,-l.y/2,-l.z))
tpl = ifcbin.createIfcAxis2Placement3D(pos,zdir,xdir)
blk = ifcfile.createIfcBlock(tpl, l.x, l.y, l.z)
csg = ifcfile.createIfcCsgSolid(blk)
reps = [csg]
repid = "Body"
reptype = "CSG"
else:
print("Unable to handle object",anno.Label)
return None

View File

@@ -89,6 +89,20 @@ def create_representation(obj, ifcfile):
return representation, placement
def get_object_type(ifcentity, objecttype=None):
"""Determines a creation type for this object"""
if not objecttype:
if ifcentity.is_a("IfcAnnotation"):
if get_sectionplane(ifcentity):
objecttype = "sectionplane"
elif get_dimension(ifcentity):
objecttype = "dimension"
elif get_text(ifcentity):
objecttype = "text"
return objecttype
def is_annotation(obj):
"""Determines if the given FreeCAD object should be saved as an IfcAnnotation"""
@@ -104,7 +118,8 @@ def is_annotation(obj):
"Text",
"Dimension",
"LinearDimension",
"AngularDimension"]:
"AngularDimension",
"SectionPlane"]:
return True
elif obj.isDerivedFrom("Part::Feature"):
if obj.Shape and (not obj.Shape.Solids) and obj.Shape.Edges:
@@ -153,6 +168,26 @@ def get_dimension(annotation):
return None
def get_sectionplane(annotation):
"""Determines if an IfcAnnotation is representing a section plane.
Returns a list containing a placement, and optionally an X dimension,
an Y dimension and a depth dimension"""
if annotation.is_a("IfcAnnotation"):
if annotation.ObjectType == "DRAWING":
s = ifcopenshell.util.unit.calculate_unit_scale(annotation.file) * 1000
result = [get_placement(annotation.ObjectPlacement, scale=s)]
for rep in annotation.Representation.Representations:
for item in rep.Items:
if item.is_a("IfcCsgSolid"):
if item.TreeRootExpression.is_a("IfcBlock"):
result.append(item.TreeRootExpression.XLength*s)
result.append(item.TreeRootExpression.YLength*s)
result.append(item.TreeRootExpression.ZLength*s)
return result
return None
def create_annotation(obj, ifcfile):
"""Adds an IfcAnnotation from the given object to the given IFC file"""
@@ -183,16 +218,21 @@ def get_history(ifcfile):
return history
def get_placement(ifcelement, ifcfile):
def get_placement(ifcelement, ifcfile=None, scale=None):
"""Returns a FreeCAD placement from an IFC placement"""
s = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
return importIFCHelper.getPlacement(ifcelement, scaling=s)
if not scale:
if not ifcfile:
ifcfile = ifcelement.file
scale = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
return importIFCHelper.getPlacement(ifcelement, scaling=scale)
def get_scaled_point(point, ifcfile, is2d=False):
def get_scaled_point(point, ifcfile=None, is2d=False):
"""Returns a scaled 2d or 3d point tuple form a FreeCAD point"""
if not ifcfile:
ifcfile = ifcelement.file
s = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
v = FreeCAD.Vector(point)
v.multiply(s)
@@ -200,3 +240,9 @@ def get_scaled_point(point, ifcfile, is2d=False):
if is2d:
v = v[:2]
return v
def get_scaled_value(value, ifcfile):
"""Returns a scaled dimension value"""
s = 0.001 / ifcopenshell.util.unit.calculate_unit_scale(ifcfile)
return value * s

View File

@@ -26,7 +26,7 @@ import FreeCAD
translate = FreeCAD.Qt.translate
# the property groups below should not be treated as psets
NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension", "PhysicalProperties"]
NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension", "SectionPlane", "PhysicalProperties"]
class ifc_object:
"""Base class for all IFC-based objects"""
@@ -61,7 +61,7 @@ class ifc_object:
self.edit_type(obj)
elif prop == "Group":
self.edit_group(obj)
elif obj.getGroupOfProperty(prop) == "IFC":
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "IFC":
if prop not in ["StepId"]:
self.edit_attribute(obj, prop)
elif prop == "Label":
@@ -70,6 +70,8 @@ class ifc_object:
self.edit_annotation(obj, "Text", "\n".join(obj.Text))
elif prop in ["Start", "End"]:
self.edit_annotation(obj, prop)
elif prop in ["DisplayLength","DisplayHeight","Depth"]:
self.edit_annotation(obj, prop)
elif prop == "Placement":
if getattr(self, "virgin_placement", False):
self.virgin_placement = False
@@ -79,9 +81,9 @@ class ifc_object:
elif prop == "Modified":
if obj.ViewObject:
obj.ViewObject.signalChangeIcon()
elif obj.getGroupOfProperty(prop) == "Geometry":
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Geometry":
self.edit_geometry(obj, prop)
elif obj.getGroupOfProperty(prop) not in NON_PSETS:
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) not in NON_PSETS:
# Treat all property groups outside the default ones as Psets
# print("DEBUG: editinog pset prop",prop)
self.edit_pset(obj, prop)
@@ -182,14 +184,15 @@ class ifc_object:
from nativeifc import ifc_export
if not value:
value = obj.getPropertyByName(attribute)
if hasattr(obj, attribute):
value = obj.getPropertyByName(attribute)
ifcfile = ifc_tools.get_ifcfile(obj)
elt = ifc_tools.get_ifc_element(obj, ifcfile)
if elt:
if attribute == "Text":
text = ifc_export.get_text(elt)
if text:
result = ifc_tools.set_attribute(ifcfile, text, "Literal", value)
ifc_tools.set_attribute(ifcfile, text, "Literal", value)
elif attribute in ["Start", "End"]:
dim = ifc_export.get_dimension(elt)
if dim:
@@ -204,9 +207,29 @@ class ifc_object:
value[0] = ifc_export.get_scaled_point(obj.Start, ifcfile, is2d)
else:
value[-1] = ifc_export.get_scaled_point(obj.End, ifcfile, is2d)
result = ifc_tools.set_attribute(ifcfile, points, "CoordList", value)
ifc_tools.set_attribute(ifcfile, points, "CoordList", value)
else:
print("DEBUG: unknown dimension curve type:",sub)
elif attribute in ["DisplayLength","DisplayHeight","Depth"]:
l = w = h = 1000
if obj.ViewObject:
if obj.ViewObject.DisplayLength.Value:
l = ifc_export.get_scaled_value(obj.ViewObject.DisplayLength.Value)
if obj.ViewObject.DisplayHeight.Value:
w = ifc_export.get_scaled_value(obj.ViewObject.DisplayHeight.Value)
if obj.Depth.Value:
h = ifc_export.get_scaled_value(obj.Depth.Value)
if elt.Representation.Representations:
for rep in elt.Representation.Representations:
for item in rep.Items:
if item.is_a("IfcCsgSolid"):
if item.TreeRootExpression.is_a("IfcBlock"):
block = item.TreeRootExpression
loc = block.Position.Location
ifc_tools.set_attribute(ifcfile, block, "XLength", l)
ifc_tools.set_attribute(ifcfile, block, "YLength", w)
ifc_tools.set_attribute(ifcfile, block, "ZLength", h)
ifc_tools.set_attribute(ifcfile, loc, "Coordinates", (-l/2, -h/2, -h))
def edit_geometry(self, obj, prop):
"""Edits a geometry property of an object"""
@@ -326,6 +349,47 @@ class ifc_object:
# Not doing anything right now because an unset Type property could screw the ifc file
pass
def get_section_data(self, obj):
"""Returns two things: a list of objects and a cut plane"""
from nativeifc import ifc_tools # lazy import
import Part
if not obj.IfcClass == "IfcAnnotation":
return None, None
if obj.ObjectType != "DRAWING":
return None, None
objs = getattr(obj, "Objects", [])
if not objs:
# no object defined, we automatically use the project
objs = []
proj = ifc_tools.get_project(obj)
if isinstance(proj, FreeCAD.DocumentObject):
objs.append(proj)
objs.extend(ifc_tools.get_freecad_children(proj))
if objs:
s = []
for o in objs:
# TODO print a better message
if o.ShapeMode != "Shape":
s.append(o)
if s:
FreeCAD.Console.PrintLog("DEBUG: Generating shapes. This might take some time...\n")
for o in s:
o.ShapeMode = "Shape"
o.recompute()
l = 1
h = 1
if obj.ViewObject:
if hasattr(obj.ViewObject,"DisplayLength"):
l = obj.ViewObject.DisplayLength.Value
h = obj.ViewObject.DisplayHeight.Value
plane = Part.makePlane(l,h,FreeCAD.Vector(l/2,-h/2,0),FreeCAD.Vector(0,0,1))
plane.Placement = obj.Placement
return objs, plane
else:
return None, None
class document_object:
"""Holder for the document's IFC objects"""

View File

@@ -255,12 +255,7 @@ def create_object(ifcentity, document, ifcfile, shapemode=0, objecttype=None):
s = "IFC: Created #{}: {}, '{}'\n".format(
ifcentity.id(), ifcentity.is_a(), ifcentity.Name
)
if not objecttype:
if ifcentity.is_a("IfcAnnotation"):
if ifc_export.get_dimension(ifcentity):
objecttype = "dimension"
elif ifc_export.get_text(ifcentity):
objecttype = "text"
objecttype = ifc_export.get_object_type(ifcentity, objecttype)
FreeCAD.Console.PrintLog(s)
obj = add_object(document, otype=objecttype)
add_properties(obj, ifcfile, ifcentity, shapemode=shapemode)
@@ -399,6 +394,18 @@ def get_children(
return result
def get_freecad_children(obj):
"""Returns the childen of this object that exist in the documemt"""
objs = []
children = get_children(obj)
for child in children:
childobj = get_object(child)
if childobj:
objs.extend(get_freecad_children(childobj))
return objs
def get_object(element, document=None, ifcfile=None):
"""Returns the object that references this element, if any"""
@@ -486,11 +493,15 @@ def add_object(document, otype=None, oname="IfcObject"):
'layer',
'text',
'dimension',
'sectionplane',
or anything else for a standard IFC object"""
if not document:
return None
if otype == "dimension":
if otype == "sectionplane":
obj = Arch.makeSectionPlane()
obj.Proxy = ifc_objects.ifc_object(otype)
elif otype == "dimension":
obj = Draft.make_dimension(FreeCAD.Vector(), FreeCAD.Vector(1,0,0))
obj.Proxy = ifc_objects.ifc_object(otype)
obj.removeProperty("Diameter")
@@ -656,31 +667,51 @@ def add_properties(
setattr(obj, attr, str(value))
# annotation properties
if ifcentity.is_a("IfcAnnotation"):
dim = ifc_export.get_dimension(ifcentity)
if dim and len(dim) >= 3:
if "Start" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "Start", "Base")
if "End" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "End", "Base")
if "Dimline" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "Dimline", "Base")
obj.Start = dim[1]
obj.End = dim[2]
if len(dim) > 3:
obj.Dimline = dim[3]
else:
mid = obj.End.sub(obj.Start)
mid.multiply(0.5)
obj.Dimline = obj.Start.add(mid)
sectionplane = ifc_export.get_sectionplane(ifcentity)
if sectionplane:
if "Placement" not in obj.PropertiesList:
obj.addProperty("App::PropertyPlacement", "Placement", "Base")
if "Depth" not in obj.PropertiesList:
obj.addProperty("App::PropertyLength","Depth","SectionPlane")
obj.Placement = sectionplane[0]
if len(sectionplane) > 3:
obj.Depth = sectionplane[3]
vobj = obj.ViewObject
if vobj:
if "DisplayLength" not in vobj.PropertiesList:
vobj.addProperty("App::PropertyLength","DisplayLength","SectionPlane")
if "DisplayHeight" not in vobj.PropertiesList:
vobj.addProperty("App::PropertyLength","DisplayHeight","SectionPlane")
if len(sectionplane) > 1:
vobj.DisplayLength = sectionplane[1]
if len(sectionplane) > 2:
vobj.DisplayHeight = sectionplane[2]
else:
text = ifc_export.get_text(ifcentity)
if text:
if "Placement" not in obj.PropertiesList:
obj.addProperty("App::PropertyPlacement", "Placement", "Base")
if "Text" not in obj.PropertiesList:
obj.addProperty("App::PropertyStringList", "Text", "Base")
obj.Text = [text.Literal]
obj.Placement = ifc_export.get_placement(ifcentity.ObjectPlacement, ifcfile)
dim = ifc_export.get_dimension(ifcentity)
if dim and len(dim) >= 3:
if "Start" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "Start", "Base")
if "End" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "End", "Base")
if "Dimline" not in obj.PropertiesList:
obj.addProperty("App::PropertyVectorDistance", "Dimline", "Base")
obj.Start = dim[1]
obj.End = dim[2]
if len(dim) > 3:
obj.Dimline = dim[3]
else:
mid = obj.End.sub(obj.Start)
mid.multiply(0.5)
obj.Dimline = obj.Start.add(mid)
else:
text = ifc_export.get_text(ifcentity)
if text:
if "Placement" not in obj.PropertiesList:
obj.addProperty("App::PropertyPlacement", "Placement", "Base")
if "Text" not in obj.PropertiesList:
obj.addProperty("App::PropertyStringList", "Text", "Base")
obj.Text = [text.Literal]
obj.Placement = ifc_export.get_placement(ifcentity.ObjectPlacement, ifcfile)
# link Label2 and Description
if "Description" in obj.PropertiesList and hasattr(obj, "setExpression"):
@@ -1141,7 +1172,7 @@ def get_ifctype(obj):
if dtype in ["App::Part","Part::Compound","Array"]:
return "IfcElementAssembly"
if dtype in ["App::DocumentObjectGroup"]:
ifctype = "IfcGroup"
return "IfcGroup"
return "IfcBuildingElementProxy"

View File

@@ -427,7 +427,7 @@ def get_svg(obj,
# all the SVG strings from the contents of the group
if hasattr(obj, "isDerivedFrom"):
if (obj.isDerivedFrom("App::DocumentObjectGroup")
or utils.get_type(obj) in ["Layer", "BuildingPart"]
or utils.get_type(obj) in ["Layer", "BuildingPart", "IfcGroup"]
or obj.isDerivedFrom("App::LinkGroup")
or (obj.isDerivedFrom("App::Link")
and obj.LinkedObject.isDerivedFrom("App::DocumentObjectGroup"))):
@@ -546,7 +546,8 @@ def get_svg(obj,
svg = _svg_shape(svg, obj, plane,
fillstyle, pathdata, stroke, linewidth, lstyle)
elif utils.get_type(obj) in ["Dimension", "LinearDimension"]:
elif (utils.get_type(obj) in ["Dimension", "LinearDimension"]
or (utils.get_type(obj) == "IfcAnnotation" and obj.ObjectType == "DIMENSION")):
svg = _svg_dimension(obj, plane, scale, linewidth, fontsize,
stroke, tstroke, pointratio, techdraw, rotation)
@@ -690,7 +691,8 @@ def get_svg(obj,
rotation, position, text,
linespacing, justification)
elif utils.get_type(obj) in ["Annotation", "DraftText", "Text"]:
elif (utils.get_type(obj) in ["Annotation", "DraftText", "Text"]
or (utils.get_type(obj) == "IfcAnnotation" and obj.ObjectType == "TEXT")):
# returns an svg representation of a document annotation
if not App.GuiUp:
_wrn("Export of texts to SVG is only available in GUI mode")

View File

@@ -213,11 +213,15 @@ class Shape2DView(DraftObject):
import DraftGeomUtils
pl = obj.Placement
if obj.Base:
if utils.get_type(obj.Base) in ["BuildingPart","SectionPlane"]:
if utils.get_type(obj.Base) in ["BuildingPart","SectionPlane","IfcAnnotation"]:
objs = []
if utils.get_type(obj.Base) == "SectionPlane":
objs = self.excludeNames(obj,obj.Base.Objects)
cutplane = obj.Base.Shape
elif utils.get_type(obj.Base) == "IfcAnnotation":
# this is a NativeIFC section plane
objs, cutplane = obj.Base.Proxy.get_section_data(obj.Base)
objs = self.excludeNames(obj, objs)
else:
objs = self.excludeNames(obj,obj.Base.Group)
cutplane = Part.makePlane(1000, 1000, App.Vector(-500, -500, 0))