Arch: Handle rectangle-and circle-based profiles in IFC import/export
This commit is contained in:
@@ -1256,6 +1256,7 @@ def getExtrusionData(shape,sortmethod="area"):
|
||||
|
||||
"area" = Of the faces with the smallest area, the one with the lowest z coordinate.
|
||||
"z" = The face with the lowest z coordinate.
|
||||
a 3D vector = the face which center is closest to the given 3D point
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -1314,9 +1315,11 @@ def getExtrusionData(shape,sortmethod="area"):
|
||||
if valids:
|
||||
if sortmethod == "z":
|
||||
valids.sort(key=lambda v: v[0].CenterOfMass.z)
|
||||
else:
|
||||
elif sortmethod == "area":
|
||||
# sort by smallest area
|
||||
valids.sort(key=lambda v: v[0].Area)
|
||||
else:
|
||||
valids.sort(key=lambda v: (v[0].CenterOfMass.sub(sortmethod)).Length)
|
||||
return valids[0]
|
||||
return None
|
||||
|
||||
|
||||
@@ -1746,16 +1746,18 @@ def getProfile(ifcfile,p):
|
||||
pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc)
|
||||
if isinstance(p.Edges[0].Curve,Part.Circle):
|
||||
# extruded circle
|
||||
profile = ifcfile.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius)
|
||||
profile = ifcbin.createIfcCircleProfileDef("AREA",None,pt,p.Edges[0].Curve.Radius)
|
||||
elif isinstance(p.Edges[0].Curve,Part.Ellipse):
|
||||
# extruded ellipse
|
||||
profile = ifcfile.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius)
|
||||
profile = ifcbin.createIfcEllipseProfileDef("AREA",None,pt,p.Edges[0].Curve.MajorRadius,p.Edges[0].Curve.MinorRadius)
|
||||
elif (checkRectangle(p.Edges)):
|
||||
# arbitrarily use the first edge as the rectangle orientation
|
||||
d = vec(p.Edges[0])
|
||||
d.normalize()
|
||||
pxvc = ifcbin.createIfcDirection(tuple(d)[:2])
|
||||
povc = ifcbin.createIfcCartesianPoint(tuple(p.CenterOfMass[:2]))
|
||||
povc = ifcbin.createIfcCartesianPoint((0.0,0.0))
|
||||
# profile must be located at (0,0) because placement gets added later
|
||||
#povc = ifcbin.createIfcCartesianPoint(tuple(p.CenterOfMass[:2]))
|
||||
pt = ifcbin.createIfcAxis2Placement2D(povc,pxvc)
|
||||
#semiPerimeter = p.Length/2
|
||||
#diff = math.sqrt(semiPerimeter**2 - 4*p.Area)
|
||||
@@ -1763,7 +1765,7 @@ def getProfile(ifcfile,p):
|
||||
#h = min(abs((semiPerimeter + diff)/2),abs((semiPerimeter - diff)/2))
|
||||
b = p.Edges[0].Length
|
||||
h = p.Edges[1].Length
|
||||
profile = ifcfile.createIfcRectangleProfileDef("AREA",'rectangular',pt,b,h)
|
||||
profile = ifcbin.createIfcRectangleProfileDef("AREA",'rectangular',pt,b,h)
|
||||
elif (len(p.Faces) == 1) and (len(p.Wires) > 1):
|
||||
# face with holes
|
||||
f = p.Faces[0]
|
||||
|
||||
@@ -208,6 +208,7 @@ class recycler:
|
||||
|
||||
self.ifcfile = ifcfile
|
||||
self.compress = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcCompress",True)
|
||||
self.mergeProfiles = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch").GetBool("ifcMergeProfiles",False)
|
||||
self.cartesianpoints = {(0,0,0):self.ifcfile[8]} # from template
|
||||
self.directions = {(1,0,0):self.ifcfile[6],(0,0,1):self.ifcfile[7],(0,1,0):self.ifcfile[10]} # from template
|
||||
self.polylines = {}
|
||||
@@ -222,6 +223,7 @@ class recycler:
|
||||
self.transformationoperators = {}
|
||||
self.psas = {}
|
||||
self.spared = 0
|
||||
self.profiledefs = {}
|
||||
|
||||
def createIfcCartesianPoint(self,points):
|
||||
if self.compress and points in self.cartesianpoints:
|
||||
@@ -387,3 +389,33 @@ class recycler:
|
||||
if self.compress:
|
||||
self.psas[key] = c
|
||||
return c
|
||||
|
||||
def createIfcRectangleProfileDef(self,name,mode,pt,b,h):
|
||||
key = "RECT"+str(name)+str(mode)+str(pt)+str(b)+str(h)
|
||||
if self.compress and self.mergeProfiles and key in self.profiledefs:
|
||||
return self.profiledefs[key]
|
||||
else:
|
||||
c = self.ifcfile.createIfcRectangleProfileDef(name,mode,pt,b,h)
|
||||
if self.compress and self.mergeProfiles:
|
||||
self.profiledefs[key] = c
|
||||
return c
|
||||
|
||||
def createIfcCircleProfileDef(self,name,mode,pt,r):
|
||||
key = "CIRC"+str(name)+str(mode)+str(pt)+str(r)
|
||||
if self.compress and self.mergeProfiles and key in self.profiledefs:
|
||||
return self.profiledefs[key]
|
||||
else:
|
||||
c = self.ifcfile.createIfcCircleProfileDef(name,mode,pt,r)
|
||||
if self.compress and self.mergeProfiles:
|
||||
self.profiledefs[key] = c
|
||||
return c
|
||||
|
||||
def createIfcEllipseProfileDef(self,name,mode,pt,majr,minr):
|
||||
key = "ELLI"+str(name)+str(mode)+str(pt)+str(majr)+str(minr)
|
||||
if self.compress and self.mergeProfiles and key in self.profiledefs:
|
||||
return self.profiledefs[key]
|
||||
else:
|
||||
c = self.ifcfile.createIfcEllipseProfileDef(name,mode,pt,majr,minr)
|
||||
if self.compress and self.mergeProfiles:
|
||||
self.profiledefs[key] = c
|
||||
return c
|
||||
|
||||
@@ -139,7 +139,14 @@ structuralifcobjects = (
|
||||
# ********** get the prefs, available in import and export ****************
|
||||
def getPreferences():
|
||||
|
||||
"""retrieves IFC preferences"""
|
||||
"""retrieves IFC preferences.
|
||||
|
||||
MERGE_MODE_ARCH:
|
||||
0 = parametric arch objects
|
||||
1 = non-parametric arch objects
|
||||
2 = Part shapes
|
||||
3 = One compound per storey
|
||||
"""
|
||||
|
||||
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Arch")
|
||||
|
||||
@@ -274,6 +281,7 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None):
|
||||
subtractions = importIFCHelper.buildRelSubtractions(ifcfile)
|
||||
mattable = importIFCHelper.buildRelMattable(ifcfile)
|
||||
colors = importIFCHelper.buildRelProductColors(ifcfile, prodrepr)
|
||||
colordict = {} # { objname:color tuple } for non-GUI use
|
||||
if preferences['DEBUG']: print("done.")
|
||||
|
||||
# only import a list of IDs and their children, if defined
|
||||
@@ -485,24 +493,29 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None):
|
||||
else:
|
||||
if preferences['GET_EXTRUSIONS'] and (preferences['MERGE_MODE_ARCH'] != 1):
|
||||
|
||||
# get IFC profile
|
||||
profileid = None
|
||||
sortmethod = None
|
||||
if product.Representation:
|
||||
if product.Representation.Representations:
|
||||
if product.Representation.Representations[0].is_a("IfcShapeRepresentation"):
|
||||
if product.Representation.Representations[0].Items:
|
||||
if product.Representation.Representations[0].Items[0].is_a("IfcExtrudedAreaSolid"):
|
||||
profileid = product.Representation.Representations[0].Items[0].SweptArea.id()
|
||||
sortmethod = importIFCHelper.getProfileCenterPoint(product.Representation.Representations[0].Items[0])
|
||||
|
||||
# recompose extrusions from a shape
|
||||
if ptype in ["IfcWall","IfcWallStandardCase","IfcSpace"]:
|
||||
sortmethod = "z"
|
||||
else:
|
||||
sortmethod = "area"
|
||||
if not sortmethod:
|
||||
if ptype in ["IfcWall","IfcWallStandardCase","IfcSpace"]:
|
||||
sortmethod = "z"
|
||||
else:
|
||||
sortmethod = "area"
|
||||
ex = Arch.getExtrusionData(shape,sortmethod) # is this an extrusion?
|
||||
if ex:
|
||||
|
||||
# check for extrusion profile
|
||||
baseface = None
|
||||
profileid = None
|
||||
addplacement = None
|
||||
if product.Representation:
|
||||
if product.Representation.Representations:
|
||||
if product.Representation.Representations[0].is_a("IfcShapeRepresentation"):
|
||||
if product.Representation.Representations[0].Items:
|
||||
if product.Representation.Representations[0].Items[0].is_a("IfcExtrudedAreaSolid"):
|
||||
profileid = product.Representation.Representations[0].Items[0].SweptArea.id()
|
||||
if profileid and (profileid in profiles):
|
||||
|
||||
# reuse existing profile if existing
|
||||
@@ -526,23 +539,34 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None):
|
||||
print("extrusion ",end="")
|
||||
import DraftGeomUtils
|
||||
if DraftGeomUtils.hasCurves(ex[0]) or len(ex[0].Wires) != 1:
|
||||
# curves or holes? We just make a Part face
|
||||
baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint")
|
||||
# bug/feature in ifcopenshell? Some faces of a shell may have non-null placement
|
||||
# workaround to remove the bad placement: exporting/reimporting as step
|
||||
if not ex[0].Placement.isNull():
|
||||
import tempfile
|
||||
fd, tf = tempfile.mkstemp(suffix=".stp")
|
||||
ex[0].exportStep(tf)
|
||||
f = Part.read(tf)
|
||||
os.close(fd)
|
||||
os.remove(tf)
|
||||
# is this a circle?
|
||||
if (len(ex[0].Edges) == 1) and isinstance(ex[0].Edges[0].Curve,Part.Circle):
|
||||
baseface = Draft.makeCircle(ex[0].Edges[0])
|
||||
else:
|
||||
f = ex[0]
|
||||
baseface.Shape = f
|
||||
# curves or holes? We just make a Part face
|
||||
baseface = FreeCAD.ActiveDocument.addObject("Part::Feature",name+"_footprint")
|
||||
# bug/feature in ifcopenshell? Some faces of a shell may have non-null placement
|
||||
# workaround to remove the bad placement: exporting/reimporting as step
|
||||
if not ex[0].Placement.isNull():
|
||||
import tempfile
|
||||
fd, tf = tempfile.mkstemp(suffix=".stp")
|
||||
ex[0].exportStep(tf)
|
||||
f = Part.read(tf)
|
||||
os.close(fd)
|
||||
os.remove(tf)
|
||||
else:
|
||||
f = ex[0]
|
||||
baseface.Shape = f
|
||||
else:
|
||||
# no hole and no curves, we make a Draft Wire instead
|
||||
baseface = Draft.makeWire([v.Point for v in ex[0].Wires[0].OrderedVertexes],closed=True)
|
||||
# no curve and no hole, we can make a draft object
|
||||
verts = [v.Point for v in ex[0].Wires[0].OrderedVertexes]
|
||||
# TODO verts are different if shape is made of RectangleProfileDef or not
|
||||
# is this a rectangle?
|
||||
if importIFCHelper.isRectangle(verts):
|
||||
baseface = Draft.makeRectangle(verts,face=True)
|
||||
else:
|
||||
# no hole and no curves, we make a Draft Wire instead
|
||||
baseface = Draft.makeWire(verts,closed=True)
|
||||
if profileid:
|
||||
# store for possible shared use
|
||||
profiles[profileid] = baseface
|
||||
@@ -818,12 +842,15 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None):
|
||||
|
||||
# color
|
||||
|
||||
if FreeCAD.GuiUp and (pid in colors) and colors[pid]:
|
||||
# if preferences['DEBUG']: print(" setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255))
|
||||
if hasattr(obj.ViewObject,"ShapeColor"):
|
||||
obj.ViewObject.ShapeColor = tuple(colors[pid][0:3])
|
||||
if hasattr(obj.ViewObject,"Transparency"):
|
||||
obj.ViewObject.Transparency = colors[pid][3]
|
||||
if (pid in colors) and colors[pid]:
|
||||
colordict[obj.Name] = colors[pid]
|
||||
if FreeCAD.GuiUp:
|
||||
# if preferences['DEBUG']: print(" setting color: ",int(colors[pid][0]*255),"/",int(colors[pid][1]*255),"/",int(colors[pid][2]*255))
|
||||
if hasattr(obj.ViewObject,"ShapeColor"):
|
||||
obj.ViewObject.ShapeColor = tuple(colors[pid][0:3])
|
||||
if hasattr(obj.ViewObject,"Transparency"):
|
||||
obj.ViewObject.Transparency = colors[pid][3]
|
||||
|
||||
|
||||
# if preferences['DEBUG'] is on, recompute after each shape
|
||||
if preferences['DEBUG']: FreeCAD.ActiveDocument.recompute()
|
||||
@@ -1248,6 +1275,13 @@ def insert(filename,docname,skip=[],only=[],root=None,preferences=None):
|
||||
if not obj.InList:
|
||||
rootgroup.addObject(obj)
|
||||
|
||||
# Save colordict in non-GUI mode
|
||||
if colordict and not FreeCAD.GuiUp:
|
||||
import json
|
||||
d = doc.Meta
|
||||
d["colordict"] = json.dumps(colordict)
|
||||
doc.Meta = d
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
if ZOOMOUT and FreeCAD.GuiUp:
|
||||
@@ -1344,3 +1378,24 @@ def createFromProperties(propsets,ifcfile):
|
||||
else:
|
||||
print("Unhandled FreeCAD property:",name," of type:",ptype)
|
||||
return obj
|
||||
|
||||
|
||||
def applyColorDict(doc,colordict=None):
|
||||
|
||||
"""applies the contents of a color dict to the objects in the given doc.
|
||||
If no colordict is given, the doc Meta property is searched for a "colordict" entry."""
|
||||
|
||||
if not colordict:
|
||||
if "colordict" in doc.Meta:
|
||||
import json
|
||||
colordict = json.loads(doc.Meta["colordict"])
|
||||
if colordict:
|
||||
for obj in doc.Objects:
|
||||
if obj.Name in colordict:
|
||||
color = colordict[obj.Name]
|
||||
if hasattr(obj.ViewObject,"ShapeColor"):
|
||||
obj.ViewObject.ShapeColor = tuple(color[0:3])
|
||||
if hasattr(obj.ViewObject,"Transparency") and (len(color) >= 4):
|
||||
obj.ViewObject.Transparency = color[3]
|
||||
else:
|
||||
print("No valid color dict to apply")
|
||||
|
||||
@@ -607,6 +607,9 @@ def get2DShape(representation,scaling=1000):
|
||||
pts.append(c)
|
||||
return Part.makePolygon(pts)
|
||||
|
||||
def getRectangle(ent):
|
||||
return Part.makePlane(ent.XDim,ent.YDim)
|
||||
|
||||
def getLine(ent):
|
||||
pts = []
|
||||
p1 = getVector(ent.Pnt)
|
||||
@@ -629,11 +632,13 @@ def get2DShape(representation,scaling=1000):
|
||||
result = []
|
||||
if ent.is_a() in ["IfcGeometricCurveSet","IfcGeometricSet"]:
|
||||
elts = ent.Elements
|
||||
elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
|
||||
elif ent.is_a() in ["IfcLine","IfcPolyline","IfcCircle","IfcTrimmedCurve","IfcRectangleProfileDef"]:
|
||||
elts = [ent]
|
||||
for el in elts:
|
||||
if el.is_a("IfcPolyline"):
|
||||
result.append(getPolyline(el))
|
||||
if el.is_a("IfcRectangleProfileDef"):
|
||||
result.append(getRectangle(el))
|
||||
elif el.is_a("IfcLine"):
|
||||
result.append(getLine(el))
|
||||
elif el.is_a("IfcCircle"):
|
||||
@@ -691,6 +696,40 @@ def get2DShape(representation,scaling=1000):
|
||||
elif item.is_a("IfcTextLiteral"):
|
||||
t = Draft.makeText([item.Literal],point=getPlacement(item.Placement,scaling).Base)
|
||||
return t # dirty hack... Object creation should not be done here
|
||||
elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve"]:
|
||||
elif representation.is_a() in ["IfcPolyline","IfcCircle","IfcTrimmedCurve","IfcRectangleProfileDef"]:
|
||||
result = getCurveSet(representation)
|
||||
return result
|
||||
|
||||
|
||||
def getProfileCenterPoint(sweptsolid):
|
||||
"""returns the center point of the profile of an extrusion"""
|
||||
v = FreeCAD.Vector(0,0,0)
|
||||
if hasattr(sweptsolid,"SweptArea"):
|
||||
profile = get2DShape(sweptsolid.SweptArea)
|
||||
if profile:
|
||||
profile = profile[0]
|
||||
if hasattr(profile,"CenterOfMass"):
|
||||
v = profile.CenterOfMass
|
||||
elif hasattr(profile,"BoundBox"):
|
||||
v = profile.BoundBox.Center
|
||||
if hasattr(sweptsolid,"Position"):
|
||||
pos = getPlacement(sweptsolid.Position)
|
||||
v = pos.multVec(v)
|
||||
return v
|
||||
|
||||
|
||||
def isRectangle(verts):
|
||||
"""returns True if the given 4 vertices form a rectangle"""
|
||||
if len(verts) != 4:
|
||||
return False
|
||||
v1 = verts[1].sub(verts[0])
|
||||
v2 = verts[2].sub(verts[1])
|
||||
v3 = verts[3].sub(verts[2])
|
||||
v4 = verts[0].sub(verts[3])
|
||||
if abs(v2.getAngle(v1)-math.pi/2) > 0.01:
|
||||
return False
|
||||
if abs(v3.getAngle(v2)-math.pi/2) > 0.01:
|
||||
return False
|
||||
if abs(v4.getAngle(v3)-math.pi/2) > 0.01:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -38,7 +38,7 @@ if App.GuiUp:
|
||||
from draftviewproviders.view_rectangle import ViewProviderRectangle
|
||||
|
||||
|
||||
def make_rectangle(length, height, placement=None, face=None, support=None):
|
||||
def make_rectangle(length, height=0, placement=None, face=None, support=None):
|
||||
"""makeRectangle(length, width, [placement], [face])
|
||||
|
||||
Creates a Rectangle object with length in X direction and height in Y
|
||||
@@ -54,12 +54,27 @@ def make_rectangle(length, height, placement=None, face=None, support=None):
|
||||
face : Bool
|
||||
If face is False, the rectangle is shown as a wireframe,
|
||||
otherwise as a face.
|
||||
|
||||
Rectangles can also be constructed by giving them a list of four vertices
|
||||
as first argument: makeRectangle(list_of_vertices,face=...)
|
||||
but you are responsible to check yourself that these 4 vertices are ordered
|
||||
and actually form a rectangle, otherwise the result might be wrong. Placement
|
||||
is ignored when constructing a rectangle this way (face argument is kept).
|
||||
"""
|
||||
|
||||
if not App.ActiveDocument:
|
||||
App.Console.PrintError("No active document. Aborting\n")
|
||||
return
|
||||
|
||||
if isinstance(length,(list,tuple)) and (len(length) == 4):
|
||||
verts = length
|
||||
xv = verts[1].sub(verts[0])
|
||||
yv = verts[3].sub(verts[0])
|
||||
zv = xv.cross(yv)
|
||||
rr = App.Rotation(xv,yv,zv,"XYZ")
|
||||
rp = App.Placement(verts[0],rr)
|
||||
return makeRectangle(xv.Length,yv.Length,rp,face,support)
|
||||
|
||||
if placement: type_check([(placement,App.Placement)], "make_rectangle")
|
||||
|
||||
obj = App.ActiveDocument.addObject("Part::Part2DObjectPython","Rectangle")
|
||||
|
||||
Reference in New Issue
Block a user