BIM: NativeIFC 2D support - axes
This commit is contained in:
committed by
Yorik van Havre
parent
a8b4fb485e
commit
1d6e60f558
@@ -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))
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"""
|
||||
|
||||
|
||||
@@ -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", "")
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user