Skip to content
This repository was archived by the owner on Mar 1, 2025. It is now read-only.

Commit 1d6e60f

Browse files
committed
BIM: NativeIFC 2D support - axes
1 parent a8b4fb4 commit 1d6e60f

File tree

6 files changed

+188
-29
lines changed

6 files changed

+188
-29
lines changed

src/Mod/BIM/ArchAxis.py

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,15 +95,21 @@ def execute(self,obj):
9595
pl = obj.Placement
9696
geoms = []
9797
dist = 0
98-
if obj.Distances and obj.Length.Value:
99-
if len(obj.Distances) == len(obj.Angles):
100-
for i in range(len(obj.Distances)):
98+
distances = [0]
99+
angles = [0]
100+
if hasattr(obj, "Distances"):
101+
distances = obj.Distances
102+
if hasattr(obj, "Angles"):
103+
angles = obj.Angles
104+
if distances and obj.Length.Value:
105+
if angles and len(distances) == len(angles):
106+
for i in range(len(distances)):
101107
if hasattr(obj.Length,"Value"):
102108
l = obj.Length.Value
103109
else:
104110
l = obj.Length
105-
dist += obj.Distances[i]
106-
ang = math.radians(obj.Angles[i])
111+
dist += distances[i]
112+
ang = math.radians(angles[i])
107113
p1 = Vector(dist,0,0)
108114
p2 = Vector(dist+(l/math.cos(ang))*math.sin(ang),l,0)
109115
if hasattr(obj,"Limit") and obj.Limit.Value:
@@ -274,8 +280,29 @@ def updateData(self,obj,prop):
274280
self.linecoords.point.setValues(verts)
275281
self.lineset.coordIndex.setValues(0,len(vset),vset)
276282
self.lineset.coordIndex.setNum(len(vset))
277-
self.onChanged(obj.ViewObject,"BubbleSize")
278-
self.onChanged(obj.ViewObject,"ShowLabel")
283+
elif prop in ["Placement", "Length"] and not hasattr(obj, "Distances"):
284+
# copy values from FlatLines/Wireframe nodes
285+
rn = obj.ViewObject.RootNode
286+
if rn.getNumChildren() < 3:
287+
return
288+
coords = rn.getChild(1)
289+
pts = coords.point.getValues()
290+
self.linecoords.point.setValues(pts)
291+
#self.linecoords.point.setNum(len(pts))
292+
sw = rn.getChild(2)
293+
if sw.getNumChildren() < 4:
294+
return
295+
edges = sw.getChild(sw.getNumChildren()-2)
296+
if not edges.getNumChildren():
297+
return
298+
if edges.getChild(0).getNumChildren() < 4:
299+
return
300+
eset = edges.getChild(0).getChild(3)
301+
vset = eset.coordIndex.getValues()
302+
self.lineset.coordIndex.setValues(0,len(vset),vset)
303+
self.lineset.coordIndex.setNum(len(vset))
304+
self.onChanged(obj.ViewObject,"BubbleSize")
305+
self.onChanged(obj.ViewObject,"ShowLabel")
279306

280307
def onChanged(self, vobj, prop):
281308

@@ -317,7 +344,10 @@ def onChanged(self, vobj, prop):
317344
pos = []
318345
else:
319346
pos = [vobj.BubblePosition]
320-
for i in range(len(vobj.Object.Distances)):
347+
n = 0
348+
if hasattr(vobj.Object, "Distances"):
349+
n = len(vobj.Object.Distances)
350+
for i in range(n):
321351
for p in pos:
322352
if hasattr(vobj.Object,"Limit") and vobj.Object.Limit.Value:
323353
verts = [vobj.Object.Placement.inverse().multVec(vobj.Object.Shape.Edges[i].Vertexes[0].Point),
@@ -679,7 +709,7 @@ def update(self):
679709
'fills the treewidget'
680710
self.updating = True
681711
self.tree.clear()
682-
if self.obj:
712+
if self.obj and hasattr(self.obj, "Distances"):
683713
for i in range(len(self.obj.Distances)):
684714
item = QtGui.QTreeWidgetItem(self.tree)
685715
item.setText(0,str(i+1))

src/Mod/BIM/importers/exportIFC.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,6 +2474,29 @@ def create_annotation(anno, ifcfile, context, history, preferences):
24742474
objectType = "LEADER"
24752475
elif anno.Shape.Faces:
24762476
objectType = "AREA"
2477+
elif Draft.getType(anno) == "Axis":
2478+
axdata = anno.Proxy.getAxisData(anno)
2479+
axes = []
2480+
for ax in axdata:
2481+
p1 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[0]).multiply(preferences['SCALE_FACTOR'])[:2]))
2482+
p2 = ifcbin.createIfcCartesianPoint(tuple(FreeCAD.Vector(ax[1]).multiply(preferences['SCALE_FACTOR'])[:2]))
2483+
pol = ifcbin.createIfcPolyline([p1,p2])
2484+
axis = ifcfile.createIfcGridAxis(ax[2],pol,True)
2485+
axes.append(axis)
2486+
if axes:
2487+
if len(axes) > 1:
2488+
xvc = ifcbin.createIfcDirection((1.0,0.0,0.0))
2489+
zvc = ifcbin.createIfcDirection((0.0,0.0,1.0))
2490+
ovc = ifcbin.createIfcCartesianPoint((0.0,0.0,0.0))
2491+
gpl = ifcbin.createIfcAxis2Placement3D(ovc,zvc,xvc)
2492+
plac = ifcbin.createIfcLocalPlacement(gpl)
2493+
grid = ifcfile.createIfcGrid(uid,history,name,description,None,plac,None,axes,None,None)
2494+
return grid
2495+
else:
2496+
return axes[0]
2497+
else:
2498+
print("Unable to handle object",anno.Label)
2499+
return None
24772500
else:
24782501
objectType = "LINEWORK"
24792502
sh = anno.Shape.copy()

src/Mod/BIM/nativeifc/ifc_export.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,15 @@ def get_object_type(ifcentity, objecttype=None):
179179
objecttype = "dimension"
180180
elif get_text(ifcentity):
181181
objecttype = "text"
182+
elif ifcentity.is_a("IfcGridAxis"):
183+
objecttype = "axis"
182184
return objecttype
183185

184186

185187
def is_annotation(obj):
186188
"""Determines if the given FreeCAD object should be saved as an IfcAnnotation"""
187189

188-
if getattr(obj, "IfcClass", None) == "IfcAnnotation":
190+
if getattr(obj, "IfcClass", None) in ["IfcAnnotation", "IfcGridAxis"]:
189191
return True
190192
if getattr(obj, "IfcType", None) == "Annotation":
191193
return True
@@ -270,6 +272,30 @@ def get_sectionplane(annotation):
270272
return None
271273

272274

275+
def get_axis(obj):
276+
"""Determines if a given IFC entity is an IfcGridAxis. Returns a tuple
277+
containing a Placement, a length value in millimeters, and a tag"""
278+
279+
if obj.is_a("IfcGridAxis"):
280+
tag = obj.AxisTag
281+
s = ifcopenshell.util.unit.calculate_unit_scale(obj.file) * 1000
282+
shape = importIFCHelper.get2DShape(obj.AxisCurve, s, notext=True)
283+
if shape:
284+
edge = shape[0].Edges[0] # we suppose here the axis shape is a single straight line
285+
if obj.SameSense:
286+
p0 = edge.Vertexes[0].Point
287+
p1 = edge.Vertexes[-1].Point
288+
else:
289+
p0 = edge.Vertexes[-1].Point
290+
p1 = edge.Vertexes[0].Point
291+
length = edge.Length
292+
placement = FreeCAD.Placement()
293+
placement.Base = p0
294+
placement.Rotation = FreeCAD.Rotation(FreeCAD.Vector(0,1,0), p1.sub(p0))
295+
return (placement, length, tag)
296+
return None
297+
298+
273299
def create_annotation(obj, ifcfile):
274300
"""Adds an IfcAnnotation from the given object to the given IFC file"""
275301

src/Mod/BIM/nativeifc/ifc_generator.py

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -465,21 +465,39 @@ def set_representation(vobj, node):
465465
"""Sets the correct coin nodes for the given Part object"""
466466

467467
# node = [colors, verts, faces, edges, parts]
468+
if not vobj.RootNode:
469+
return
470+
if vobj.RootNode.getNumChildren() < 3:
471+
return
468472
coords = vobj.RootNode.getChild(1) # SoCoordinate3
469-
fset = vobj.RootNode.getChild(2).getChild(1).getChild(6) # SoBrepFaceSet
470-
eset = (
471-
vobj.RootNode.getChild(2).getChild(2).getChild(0).getChild(3)
472-
) # SoBrepEdgeSet
473+
switch = vobj.RootNode.getChild(2)
474+
num_modes = switch.getNumChildren()
475+
if num_modes < 3:
476+
return
477+
# the number of display modes under switch can vary.
478+
# the last 4 ones are the ones that are defined for
479+
# Part features
480+
faces = switch.getChild(num_modes-3)
481+
edges = switch.getChild(num_modes-2)
482+
fset = None
483+
if faces.getNumChildren() >= 7:
484+
fset = faces.getChild(6) # SoBrepFaceSet
485+
eset = None
486+
if edges.getNumChildren() >= 1:
487+
if edges.getChild(0).getNumChildren() >= 4:
488+
eset = edges.getChild(0).getChild(3) # SoBrepEdgeSet
473489
# reset faces and edges
474-
fset.coordIndex.deleteValues(0)
475-
eset.coordIndex.deleteValues(0)
490+
if fset:
491+
fset.coordIndex.deleteValues(0)
492+
if eset:
493+
eset.coordIndex.deleteValues(0)
476494
coords.point.deleteValues(0)
477495
if not node:
478496
return
479-
if node[1] and node[3]:
497+
if node[1] and node[3] and eset:
480498
coords.point.setValues(node[1])
481499
eset.coordIndex.setValues(node[3])
482-
if node[2] and node[4]:
500+
if node[2] and node[4] and fset:
483501
fset.coordIndex.setValues(node[2])
484502
fset.partIndex.setValues(node[4])
485503

@@ -553,7 +571,9 @@ def delete_ghost(document):
553571

554572

555573
def get_annotation_shape(annotation, ifcfile, coin=False):
556-
"""Returns a shape or a coin node form an IFC annotation"""
574+
"""Returns a shape or a coin node form an IFC annotation.
575+
Returns [colors, verts, faces, edges], colors and faces
576+
being normally None for 2D shapes."""
557577

558578
import Part
559579
from importers import importIFCHelper
@@ -562,14 +582,21 @@ def get_annotation_shape(annotation, ifcfile, coin=False):
562582
placement = None
563583
ifcscale = importIFCHelper.getScaling(ifcfile)
564584
shapes2d = []
565-
for rep in annotation.Representation.Representations:
566-
if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]:
567-
sh = importIFCHelper.get2DShape(rep, ifcscale, notext=True)
568-
if sh:
569-
shapes2d.extend(sh)
585+
if hasattr(annotation, "Representation"):
586+
for rep in annotation.Representation.Representations:
587+
if rep.RepresentationIdentifier in ["Annotation", "FootPrint", "Axis"]:
588+
sh = importIFCHelper.get2DShape(rep, ifcscale, notext=True)
589+
if sh:
590+
shapes2d.extend(sh)
591+
elif hasattr(annotation, "AxisCurve"):
592+
sh = importIFCHelper.get2DShape(annotation.AxisCurve, ifcscale, notext=True)
593+
shapes2d.extend(sh)
570594
if shapes2d:
571595
shape = Part.makeCompound(shapes2d)
572-
placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale)
596+
if hasattr(annotation, "ObjectPlacement"):
597+
placement = importIFCHelper.getPlacement(annotation.ObjectPlacement, ifcscale)
598+
else:
599+
placement = None
573600
if coin:
574601
iv = shape.writeInventor()
575602
iv = iv.replace("\n", "")

src/Mod/BIM/nativeifc/ifc_objects.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
translate = FreeCAD.Qt.translate
2727

2828
# the property groups below should not be treated as psets
29-
NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension", "SectionPlane", "PhysicalProperties"]
29+
NON_PSETS = ["Base", "IFC", "", "Geometry", "Dimension", "Linear/radial dimension",
30+
"SectionPlane", "Axis", "PhysicalProperties"]
3031

3132
class ifc_object:
3233
"""Base class for all IFC-based objects"""

src/Mod/BIM/nativeifc/ifc_tools.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def create_object(ifcentity, document, ifcfile, shapemode=0, objecttype=None):
253253
if exobj:
254254
return exobj
255255
s = "IFC: Created #{}: {}, '{}'\n".format(
256-
ifcentity.id(), ifcentity.is_a(), ifcentity.Name
256+
ifcentity.id(), ifcentity.is_a(), getattr(ifcentity, "Name", "")
257257
)
258258
objecttype = ifc_export.get_object_type(ifcentity, objecttype)
259259
FreeCAD.Console.PrintLog(s)
@@ -494,13 +494,23 @@ def add_object(document, otype=None, oname="IfcObject"):
494494
'text',
495495
'dimension',
496496
'sectionplane',
497+
'axis',
497498
or anything else for a standard IFC object"""
498499

499500
if not document:
500501
return None
501502
if otype == "sectionplane":
502503
obj = Arch.makeSectionPlane()
503504
obj.Proxy = ifc_objects.ifc_object(otype)
505+
elif otype == "axis":
506+
obj = Arch.makeAxis()
507+
obj.Proxy = ifc_objects.ifc_object(otype)
508+
obj.removeProperty("Angles")
509+
obj.removeProperty("Distances")
510+
obj.removeProperty("Labels")
511+
obj.removeProperty("Limit")
512+
if obj.ViewObject:
513+
obj.ViewObject.DisplayMode = "Flat Lines"
504514
elif otype == "dimension":
505515
obj = Draft.make_dimension(FreeCAD.Vector(), FreeCAD.Vector(1,0,0))
506516
obj.Proxy = ifc_objects.ifc_object(otype)
@@ -666,7 +676,18 @@ def add_properties(
666676
if value is not None:
667677
setattr(obj, attr, str(value))
668678
# annotation properties
669-
if ifcentity.is_a("IfcAnnotation"):
679+
if ifcentity.is_a("IfcGridAxis"):
680+
axisdata = ifc_export.get_axis(ifcentity)
681+
if axisdata:
682+
if "Placement" not in obj.PropertiesList:
683+
obj.addProperty("App::PropertyPlacement", "Placement", "Base")
684+
if "CustomText" in obj.PropertiesList:
685+
obj.setPropertyStatus("CustomText", "Hidden")
686+
obj.setExpression("CustomText", "AxisTag")
687+
if "Length" not in obj.PropertiesList:
688+
obj.addProperty("App::PropertyLength","Length","Axis")
689+
obj.Length = axisdata[1]
690+
elif ifcentity.is_a("IfcAnnotation"):
670691
sectionplane = ifc_export.get_sectionplane(ifcentity)
671692
if sectionplane:
672693
if "Placement" not in obj.PropertiesList:
@@ -1028,6 +1049,13 @@ def set_placement(obj):
10281049
if obj.Class in ["IfcProject", "IfcProjectLibrary"]:
10291050
return
10301051
element = get_ifc_element(obj)
1052+
if not hasattr(element, "ObjectPlacement"):
1053+
# special case: this is a grid axis, it has no placement
1054+
if element.is_a("IfcGridAxis"):
1055+
return set_axis_points(obj, element, ifcfile)
1056+
# other cases of objects without ObjectPlacement?
1057+
print("DEBUG: object without ObjectPlacement",element)
1058+
return False
10311059
placement = FreeCAD.Placement(obj.Placement)
10321060
placement.Base = FreeCAD.Vector(placement.Base).multiply(get_scale(ifcfile))
10331061
new_matrix = get_ios_matrix(placement)
@@ -1053,6 +1081,29 @@ def set_placement(obj):
10531081
return False
10541082

10551083

1084+
def set_axis_points(obj, element, ifcfile):
1085+
"""Sets the points of an axis from placement and length"""
1086+
1087+
if element.AxisCurve.is_a("IfcPolyline"):
1088+
p1 = obj.Placement.Base
1089+
p2 = obj.Placement.multVec(FreeCAD.Vector(0, obj.Length.Value, 0))
1090+
api_run(
1091+
"attribute.edit_attributes",
1092+
ifcfile,
1093+
product=element.AxisCurve.Points[0],
1094+
attributes={"Coordinates": tuple(p1)},
1095+
)
1096+
api_run(
1097+
"attribute.edit_attributes",
1098+
ifcfile,
1099+
product=element.AxisCurve.Points[-1],
1100+
attributes={"Coordinates": tuple(p2)},
1101+
)
1102+
return True
1103+
print("DEBUG: unhandled axis type:",element.AxisCurve.is_a())
1104+
return False
1105+
1106+
10561107
def save_ifc(obj, filepath=None):
10571108
"""Saves the linked IFC file of a project, but does not mark it as saved"""
10581109

@@ -1218,6 +1269,7 @@ def create_relationship(old_obj, obj, parent, element, ifcfile, mode=None):
12181269
parent_element = get_ifc_element(parent)
12191270
else:
12201271
parent_element = parent
1272+
uprel = None
12211273
# case 4: anything inside group
12221274
if parent_element.is_a("IfcGroup"):
12231275
# 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):
13551407
"void.add_opening", ifcfile, opening=element, element=parent_element
13561408
)
13571409
# case 3: element aggregated inside other element
1358-
else:
1410+
elif element.is_a("IfcProduct"):
13591411
try:
13601412
api_run("aggregate.unassign_object", ifcfile, products=[element])
13611413
except:

0 commit comments

Comments
 (0)