Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

first rough partial draft of alignment handling in python #1480

Merged
merged 1 commit into from May 23, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
191 changes: 191 additions & 0 deletions src/ifcopenshell-python/ifcopenshell/alignment.py
@@ -0,0 +1,191 @@
from dataclasses import dataclass

import numpy

import ifcopenshell
import ifcopenshell.geom
import ifcopenshell.express
import ifcopenshell.transition_curve

# geometric primitives

# @notes
# - not sure if the separation of geometric primitives make sense
# does it make handling the variety of distance expressions and
# interpolation harder?

@dataclass
class line:
start_point: numpy.ndarray
direction_vector: numpy.ndarray

def __call__(self, u):
return self.start_point + \
self.direction_vector * u


@dataclass
class circle:
radius: numpy.ndarray

def __call__(self, u):
return numpy.array([
self.radius * numpy.cos(u),
self.radius * numpy.sin(u)
])


def place(matrix, func):
"""
Higher order function for application of a 3x3 matrix
to a 2D point. Assumes a functor such as line or circle.
"""
def inner(*args):
v = func(*args)
# homogenize
v = numpy.insert(v, v.shape[-1], 1, axis=-1)
return (matrix @ v)[0:2]
return inner


# mapping functions from IFC entities

def map_inst(inst):
"""
Looks up one of the implementation functions below in the global namespace
"""
return globals()[f"impl_{inst.is_a()}"](inst)


def impl_IfcLine(inst):
return line(
numpy.array(inst.Pnt.Coordinates),
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude
)


def impl_IfcCircle(inst):
return place(map_inst(inst.Position), circle(
inst.Radius
))


def impl_IfcClothoid(inst):
# @todo
# place = map_inst(inst.Position)
# ifcopenshell.transition_curve.TransitionCurve(
# StartPoint = place.T[2]
# StartDirection = numpy.arctan2(place.T[0][1], place.T[0][0]),
# SegmentLength =
# IsStartRadiusCCW =
# IsEndRadiusCCW =
# TransitionCurveType =
# StartRadius =
# EndRadius =
# )
return lambda *args: numpy.array((0.,0.))


def impl_IfcAxis2Placement2D(inst):
arr = numpy.eye(3)

if inst is None:
return arr

arr.T[2, 0:2] = inst.Location.Coordinates

if inst.RefDirection is None:
return arr

arr.T[0, 0:2] = inst.RefDirection.DirectionRatios
arr.T[0, 0:2] /= numpy.linalg.norm(arr.T[0, 0:2])
arr.T[1, 0:2] = -arr.T[0,1], arr.T[0,0]

return arr


# conversion functions for semantic design parameters (not used atm)

def convert(inst):
"""
Looks up one of the conversion functions below in the global namespace
"""
yield from globals()[f"convert_{inst.is_a()}_{inst.PredefinedType}"](inst)


def convert_IfcAlignmentHorizontalSegment_LINE(data):
xy = numpy.array(data.StartPoint.Coordinates)
yield xy
di = numpy.array([
numpy.cos(data.StartDirection),
numpy.sin(data.StartDirection)
])
yield xy + di * data.SegmentLength

# Two approaches, either DesignParameters or Representation

def interpret_linear_element_semantics(settings, crv):
# traverse decomposition
for rel in crv.IsNestedBy:
for obj in rel.RelatedObjects:
yield from interpret_linear_element_semantics(settings, obj)

# lookup design parameters and dispatch to conversion function
if crv.is_a("IfcAlignmentSegment"):
dp = crv.DesignParameters
yield from convert(dp)


def interpret_linear_element_geometry(settings, crv):
for segment in crv.Representation.Representations[0].Items[0].Segments:

print(segment)
print(segment.ParentCurve)
print()

func = place(
map_inst(segment.Placement),
map_inst(segment.ParentCurve)
)

for u in numpy.linspace(
segment.SegmentStart[0],
segment.SegmentStart[0] + segment.SegmentLength[0],
num=32
):
yield func(u)


interpret_linear_element = interpret_linear_element_geometry

def create_shape(settings, elem):
if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"):
return numpy.row_stack(list(interpret_linear_element(settings, elem)))
else:
return ifcopenshell.geom.create_shape(settings, elem)


def print_structure(alignment, indent=0):
"""
Debugging function to print alignment decomposition
"""
print(" " * indent, str(alignment)[0:100])
for rel in alignment.IsNestedBy:
for child in rel.RelatedObjects:
print_structure(child, indent+2)


if __name__ == "__main__":
import sys
from matplotlib import pyplot as plt

s = ifcopenshell.express.parse("IFC4x3_RC3.exp")
ifcopenshell.register_schema(s)
f = ifcopenshell.open(sys.argv[1])
print_structure(f.by_type("IfcAlignment")[0])

al_hor = f.by_type("IfcAlignmentHorizontal")[0]
xy = create_shape({}, al_hor)

plt.plot(xy.T[0], xy.T[1])
plt.savefig("horizontal_alignment.png")