BIM: NativeIFC 2D support - axes

This commit is contained in:
Yorik van Havre
2024-11-14 10:35:16 +01:00
committed by Yorik van Havre
parent a8b4fb485e
commit 1d6e60f558
6 changed files with 188 additions and 29 deletions

View File

@@ -95,15 +95,21 @@ class _Axis:
pl = obj.Placement
geoms = []
dist = 0
if obj.Distances and obj.Length.Value:
if len(obj.Distances) == len(obj.Angles):
for i in range(len(obj.Distances)):
distances = [0]
angles = [0]
if hasattr(obj, "Distances"):
distances = obj.Distances
if hasattr(obj, "Angles"):
angles = obj.Angles
if distances and obj.Length.Value:
if angles and len(distances) == len(angles):
for i in range(len(distances)):
if hasattr(obj.Length,"Value"):
l = obj.Length.Value
else:
l = obj.Length
dist += obj.Distances[i]
ang = math.radians(obj.Angles[i])
dist += distances[i]
ang = math.radians(angles[i])
p1 = Vector(dist,0,0)
p2 = Vector(dist+(l/math.cos(ang))*math.sin(ang),l,0)
if hasattr(obj,"Limit") and obj.Limit.Value:
@@ -274,8 +280,29 @@ class _ViewProviderAxis:
self.linecoords.point.setValues(verts)
self.lineset.coordIndex.setValues(0,len(vset),vset)
self.lineset.coordIndex.setNum(len(vset))
self.onChanged(obj.ViewObject,"BubbleSize")
self.onChanged(obj.ViewObject,"ShowLabel")
elif prop in ["Placement", "Length"] and not hasattr(obj, "Distances"):
# copy values from FlatLines/Wireframe nodes
rn = obj.ViewObject.RootNode
if rn.getNumChildren() < 3:
return
coords = rn.getChild(1)
pts = coords.point.getValues()
self.linecoords.point.setValues(pts)
#self.linecoords.point.setNum(len(pts))
sw = rn.getChild(2)
if sw.getNumChildren() < 4:
return
edges = sw.getChild(sw.getNumChildren()-2)
if not edges.getNumChildren():
return
if edges.getChild(0).getNumChildren() < 4:
return
eset = edges.getChild(0).getChild(3)
vset = eset.coordIndex.getValues()
self.lineset.coordIndex.setValues(0,len(vset),vset)
self.lineset.coordIndex.setNum(len(vset))
self.onChanged(obj.ViewObject,"BubbleSize")
self.onChanged(obj.ViewObject,"ShowLabel")
def onChanged(self, vobj, prop):
@@ -317,7 +344,10 @@ class _ViewProviderAxis:
pos = []
else:
pos = [vobj.BubblePosition]
for i in range(len(vobj.Object.Distances)):
n = 0
if hasattr(vobj.Object, "Distances"):
n = len(vobj.Object.Distances)
for i in range(n):
for p in pos:
if hasattr(vobj.Object,"Limit") and vobj.Object.Limit.Value:
verts = [vobj.Object.Placement.inverse().multVec(vobj.Object.Shape.Edges[i].Vertexes[0].Point),
@@ -679,7 +709,7 @@ class _AxisTaskPanel:
'fills the treewidget'
self.updating = True
self.tree.clear()
if self.obj:
if self.obj and hasattr(self.obj, "Distances"):
for i in range(len(self.obj.Distances)):
item = QtGui.QTreeWidgetItem(self.tree)
item.setText(0,str(i+1))

View File

@@ -2474,6 +2474,29 @@ def create_annotation(anno, ifcfile, context, history, preferences):
objectType = "LEADER"
elif anno.Shape.Faces:
objectType = "AREA"
elif Draft.getType(anno) == "Axis":
axdata = anno.Proxy.getAxisData(anno)
axes = []
for ax in axdata:
p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(preferences['SCALE_FACTOR'])[:2]))
p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(preferences['SCALE_FACTOR'])[:2]))
pol = ifcbin.createIfcPolyline([p1,p2])
axis = ifcfile.createIfcGridAxis(ax[2],pol,True)
axes.append(axis)
if axes:
if len(axes) > 1:
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
plac = ifcbin.createIfcLocalPlacement(gpl)
grid = ifcfile.createIfcGrid(uid,history,name,description,None,plac,None,axes,None,None)
return grid
else:
return axes[0]
else:
print("Unable to handle object",anno.Label)
return None
else:
objectType = "LINEWORK"
sh = anno.Shape.copy()

View File

@@ -179,13 +179,15 @@ def get_object_type(ifcentity, objecttype=None):
objecttype = "dimension"
elif get_text(ifcentity):
objecttype = "text"
elif ifcentity.is_a("IfcGridAxis"):
objecttype = "axis"
return objecttype
def is_annotation(obj):
"""Determines if the given FreeCAD object should be saved as an IfcAnnotation"""
if getattr(obj, "IfcClass", None) == "IfcAnnotation":
if getattr(obj, "IfcClass", None) in ["IfcAnnotation", "IfcGridAxis"]:
return True
if getattr(obj, "IfcType", None) == "Annotation":
return True
@@ -270,6 +272,30 @@ def get_sectionplane(annotation):
return None
def get_axis(obj):
"""Determines if a given IFC entity is an IfcGridAxis. Returns a tuple
containing a Placement, a length value in millimeters, and a tag"""
if obj.is_a("IfcGridAxis"):
tag = obj.AxisTag
s = ifcopenshell.util.unit.calculate_unit_scale(obj.file) * 1000
shape = importIFCHelper.get2DShape(obj.AxisCurve, s, notext=True)
if shape:
edge = shape[0].Edges[0] # we suppose here the axis shape is a single straight line
if obj.SameSense:
p0 = edge.Vertexes[0].Point
p1 = edge.Vertexes[-1].Point
else:
p0 = edge.Vertexes[-1].Point
p1 = edge.Vertexes[0].Point
length = edge.Length
placement = FreeCAD.Placement()
placement.Base = p0
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0,1,0), p1.sub(p0))
return (placement, length, tag)
return None
def create_annotation(obj, ifcfile):
"""Adds an IfcAnnotation from the given object to the given IFC file"""

View File

@@ -465,21 +465,39 @@ def set_representation(vobj, node):
"""Sets the correct coin nodes for the given Part object"""
# node = [colors, verts, faces, edges, parts]
if not vobj.RootNode:
return
if vobj.RootNode.getNumChildren() < 3:
return
coords = vobj.RootNode.getChild(1) # SoCoordinate3
fset = vobj.RootNode.getChild(2).getChild(1).getChild(6) # SoBrepFaceSet
eset = (
vobj.RootNode.getChild(2).getChild(2).getChild(0).getChild(3)
) # SoBrepEdgeSet
switch = vobj.RootNode.getChild(2)
num_modes = switch.getNumChildren()
if num_modes < 3:
return
# the number of display modes under switch can vary.
# the last 4 ones are the ones that are defined for
# Part features
faces = switch.getChild(num_modes-3)
edges = switch.getChild(num_modes-2)
fset = None
if faces.getNumChildren() >= 7:
fset = faces.getChild(6) # SoBrepFaceSet
eset = None
if edges.getNumChildren() >= 1:
if edges.getChild(0).getNumChildren() >= 4:
eset = edges.getChild(0).getChild(3) # SoBrepEdgeSet
# reset faces and edges
fset.coordIndex.deleteValues(0)
eset.coordIndex.deleteValues(0)
if fset:
fset.coordIndex.deleteValues(0)
if eset:
eset.coordIndex.deleteValues(0)
coords.point.deleteValues(0)
if not node:
return
if node[1] and node[3]:
if node[1] and node[3] and eset:
coords.point.setValues(node[1])
eset.coordIndex.setValues(node[3])
if node[2] and node[4]:
if node[2] and node[4] and fset:
fset.coordIndex.setValues(node[2])
fset.partIndex.setValues(node[4])
@@ -553,7 +571,9 @@ def delete_ghost(document):
def get_annotation_shape(annotation, ifcfile, coin=False):
"""Returns a shape or a coin node form an IFC annotation"""
"""Returns a shape or a coin node form an IFC annotation.
Returns [colors, verts, faces, edges], colors and faces
being normally None for 2D shapes."""
import Part
from importers import importIFCHelper
@@ -562,14 +582,21 @@ def get_annotation_shape(annotation, ifcfile, coin=False):
placement = None
ifcscale = importIFCHelper.getScaling(ifcfile)
shapes2d = []
for rep in annotation.Representation.Representations:
if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]:
sh = importIFCHelper.get2DShape(rep, ifcscale, notext=True)
if sh:
shapes2d.extend(sh)
if hasattr(annotation, "Representation"):
for rep in annotation.Representation.Representations:
if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]:
sh = importIFCHelper.get2DShape(rep, ifcscale, notext=True)
if sh:
shapes2d.extend(sh)
elif hasattr(annotation, "AxisCurve"):
sh = importIFCHelper.get2DShape(annotation.AxisCurve, ifcscale, notext=True)
shapes2d.extend(sh)
if shapes2d:
shape = Part.makeCompound(shapes2d)
placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale)
if hasattr(annotation, "ObjectPlacement"):
placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale)
else:
placement = None
if coin:
iv = shape.writeInventor()
iv = iv.replace("\n", "")

View File

@@ -26,7 +26,8 @@ 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", "SectionPlane", "PhysicalProperties"]
NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension",
"SectionPlane", "Axis", "PhysicalProperties"]
class ifc_object:
"""Base class for all IFC-based objects"""

View File

@@ -253,7 +253,7 @@ def create_object(ifcentity, document, ifcfile, shapemode=0, objecttype=None):
if exobj:
return exobj
s = "IFC: Created #{}: {}, '{}'\n".format(
ifcentity.id(), ifcentity.is_a(), ifcentity.Name
ifcentity.id(), ifcentity.is_a(), getattr(ifcentity, "Name", "")
)
objecttype = ifc_export.get_object_type(ifcentity, objecttype)
FreeCAD.Console.PrintLog(s)
@@ -494,6 +494,7 @@ def add_object(document, otype=None, oname="IfcObject"):
'text',
'dimension',
'sectionplane',
'axis',
or anything else for a standard IFC object"""
if not document:
@@ -501,6 +502,15 @@ def add_object(document, otype=None, oname="IfcObject"):
if otype == "sectionplane":
obj = Arch.makeSectionPlane()
obj.Proxy = ifc_objects.ifc_object(otype)
elif otype == "axis":
obj = Arch.makeAxis()
obj.Proxy = ifc_objects.ifc_object(otype)
obj.removeProperty("Angles")
obj.removeProperty("Distances")
obj.removeProperty("Labels")
obj.removeProperty("Limit")
if obj.ViewObject:
obj.ViewObject.DisplayMode = "Flat Lines"
elif otype == "dimension":
obj = Draft.make_dimension(FreeCAD.Vector(), FreeCAD.Vector(1,0,0))
obj.Proxy = ifc_objects.ifc_object(otype)
@@ -666,7 +676,18 @@ def add_properties(
if value is not None:
setattr(obj, attr, str(value))
# annotation properties
if ifcentity.is_a("IfcAnnotation"):
if ifcentity.is_a("IfcGridAxis"):
axisdata = ifc_export.get_axis(ifcentity)
if axisdata:
if "Placement" not in obj.PropertiesList:
obj.addProperty("App::PropertyPlacement", "Placement", "Base")
if "CustomText" in obj.PropertiesList:
obj.setPropertyStatus("CustomText", "Hidden")
obj.setExpression("CustomText", "AxisTag")
if "Length" not in obj.PropertiesList:
obj.addProperty("App::PropertyLength","Length","Axis")
obj.Length = axisdata[1]
elif ifcentity.is_a("IfcAnnotation"):
sectionplane = ifc_export.get_sectionplane(ifcentity)
if sectionplane:
if "Placement" not in obj.PropertiesList:
@@ -1028,6 +1049,13 @@ def set_placement(obj):
if obj.Class in ["IfcProject", "IfcProjectLibrary"]:
return
element = get_ifc_element(obj)
if not hasattr(element, "ObjectPlacement"):
# special case: this is a grid axis, it has no placement
if element.is_a("IfcGridAxis"):
return set_axis_points(obj, element, ifcfile)
# other cases of objects without ObjectPlacement?
print("DEBUG: object without ObjectPlacement",element)
return False
placement = FreeCAD.Placement(obj.Placement)
placement.Base = FreeCAD.Vector(placement.Base).multiply(get_scale(ifcfile))
new_matrix = get_ios_matrix(placement)
@@ -1053,6 +1081,29 @@ def set_placement(obj):
return False
def set_axis_points(obj, element, ifcfile):
"""Sets the points of an axis from placement and length"""
if element.AxisCurve.is_a("IfcPolyline"):
p1 = obj.Placement.Base
p2 = obj.Placement.multVec(FreeCAD.Vector(0, obj.Length.Value, 0))
api_run(
"attribute.edit_attributes",
ifcfile,
product=element.AxisCurve.Points[0],
attributes={"Coordinates": tuple(p1)},
)
api_run(
"attribute.edit_attributes",
ifcfile,
product=element.AxisCurve.Points[-1],
attributes={"Coordinates": tuple(p2)},
)
return True
print("DEBUG: unhandled axis type:",element.AxisCurve.is_a())
return False
def save_ifc(obj, filepath=None):
"""Saves the linked IFC file of a project, but does not mark it as saved"""
@@ -1218,6 +1269,7 @@ def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None):
parent_element = get_ifc_element(parent)
else:
parent_element = parent
uprel = None
# case 4: anything inside group
if parent_element.is_a("IfcGroup"):
# special case: adding a section plane to a grouo turns it into a drawing
@@ -1355,7 +1407,7 @@ def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None):
"void.add_opening", ifcfile, opening=element, element=parent_element
)
# case 3: element aggregated inside other element
else:
elif element.is_a("IfcProduct"):
try:
api_run("aggregate.unassign_object", ifcfile, products=[element])
except: