This commit is contained in:
Thomas Krijnen
2021-06-05 09:49:19 +02:00
parent 5c1d26ac91
commit ece6e6c8ca
@@ -1,3 +1,5 @@
import operator
from dataclasses import dataclass from dataclasses import dataclass
import numpy import numpy
@@ -14,14 +16,17 @@ import ifcopenshell.transition_curve
# does it make handling the variety of distance expressions and # does it make handling the variety of distance expressions and
# interpolation harder? # interpolation harder?
@dataclass @dataclass
class line: class line:
start_point: numpy.ndarray start_point: numpy.ndarray
direction_vector: numpy.ndarray direction_vector: numpy.ndarray
def __call__(self, u): def __call__(self, u):
return self.start_point + \ p = numpy.ndarray((3,))
self.direction_vector * u p[0:2] = self.start_point + self.direction_vector * u
p[2] = numpy.nan
return p
@dataclass @dataclass
@@ -29,10 +34,9 @@ class circle:
radius: numpy.ndarray radius: numpy.ndarray
def __call__(self, u): def __call__(self, u):
return numpy.array([ return numpy.array(
self.radius * numpy.cos(u), [self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan]
self.radius * numpy.sin(u) )
])
def place(matrix, func): def place(matrix, func):
@@ -40,16 +44,62 @@ def place(matrix, func):
Higher order function for application of a 3x3 matrix Higher order function for application of a 3x3 matrix
to a 2D point. Assumes a functor such as line or circle. to a 2D point. Assumes a functor such as line or circle.
""" """
def inner(*args): def inner(*args):
v = func(*args) v = func(*args)
# homogenize # homogenize
v = numpy.insert(v, v.shape[-1], 1, axis=-1) v = numpy.insert(v[0:2], v[0:2].shape, 1, axis=-1)
return (matrix @ v)[0:2] p = numpy.ndarray((3,))
p[0:2] = (matrix @ v)[0:2]
p[2] = numpy.nan
return p
return inner return inner
# primitives for manipulating and joining curve functor domains
def reparametrized_curve(fn, a, b):
return lambda u: fn(a * u + b)
def normalized_curve(fn):
return lambda u: fn(u / fn.length)
class trimmed_curve:
def __init__(self, fn, length):
self.fn = fn
self.length = length
def __call__(self, u):
assert u >= 0.0 and u <= self.length
return self.fn(u)
class piecewise:
# takes a set of functors and returns a function f(u) that delegates to the correct segment
def __init__(self, fns):
self.fns = fns
self.length = sum(map(operator.attrgetter("length"), fns))
def __call__(self, u):
# this is silly, assuming `u` is monotonically increases we should not always start
# searching from the first segment or at least binary search into the segment
# lengths
u0 = 0
for fn in self.fns:
u1 = u0 + fn.length
if u >= u0 and u <= u1:
return fn(u - u0)
u0 = u1
# mapping functions from IFC entities # mapping functions from IFC entities
def map_inst(inst): def map_inst(inst):
""" """
Looks up one of the implementation functions below in the global namespace Looks up one of the implementation functions below in the global namespace
@@ -60,14 +110,12 @@ def map_inst(inst):
def impl_IfcLine(inst): def impl_IfcLine(inst):
return line( return line(
numpy.array(inst.Pnt.Coordinates), numpy.array(inst.Pnt.Coordinates),
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude,
) )
def impl_IfcCircle(inst): def impl_IfcCircle(inst):
return place(map_inst(inst.Position), circle( return place(map_inst(inst.Position), circle(inst.Radius))
inst.Radius
))
def impl_IfcClothoid(inst): def impl_IfcClothoid(inst):
@@ -83,7 +131,7 @@ def impl_IfcClothoid(inst):
# StartRadius = # StartRadius =
# EndRadius = # EndRadius =
# ) # )
return lambda *args: numpy.array((0.,0.)) return lambda *args: numpy.array((0.0, 0.0))
def impl_IfcAxis2Placement2D(inst): def impl_IfcAxis2Placement2D(inst):
@@ -99,13 +147,14 @@ def impl_IfcAxis2Placement2D(inst):
arr.T[0, 0:2] = inst.RefDirection.DirectionRatios arr.T[0, 0:2] = inst.RefDirection.DirectionRatios
arr.T[0, 0:2] /= numpy.linalg.norm(arr.T[0, 0:2]) 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] arr.T[1, 0:2] = -arr.T[0, 1], arr.T[0, 0]
return arr return arr
# conversion functions for semantic design parameters (not used atm) # conversion functions for semantic design parameters (not used atm)
def convert(inst): def convert(inst):
""" """
Looks up one of the conversion functions below in the global namespace Looks up one of the conversion functions below in the global namespace
@@ -116,14 +165,13 @@ def convert(inst):
def convert_IfcAlignmentHorizontalSegment_LINE(data): def convert_IfcAlignmentHorizontalSegment_LINE(data):
xy = numpy.array(data.StartPoint.Coordinates) xy = numpy.array(data.StartPoint.Coordinates)
yield xy yield xy
di = numpy.array([ di = numpy.array([numpy.cos(data.StartDirection), numpy.sin(data.StartDirection)])
numpy.cos(data.StartDirection),
numpy.sin(data.StartDirection)
])
yield xy + di * data.SegmentLength yield xy + di * data.SegmentLength
# Two approaches, either DesignParameters or Representation # Two approaches, either DesignParameters or Representation
def interpret_linear_element_semantics(settings, crv): def interpret_linear_element_semantics(settings, crv):
# traverse decomposition # traverse decomposition
for rel in crv.IsNestedBy: for rel in crv.IsNestedBy:
@@ -136,28 +184,39 @@ def interpret_linear_element_semantics(settings, crv):
yield from convert(dp) yield from convert(dp)
def evaluate_segment(segment):
# print(segment)
# print(segment.ParentCurve)
# print()
func = place(map_inst(segment.Placement), map_inst(segment.ParentCurve))
# reparam so domain starts at zero
reparam = reparametrized_curve(func, 1.0, -segment.SegmentStart[0])
# embed curve length (doesn't do much, just make length recoverable)
trimmed = trimmed_curve(reparam, segment.SegmentLength[0])
return trimmed
def interpret_linear_element_geometry(settings, crv): def interpret_linear_element_geometry(settings, crv):
for segment in crv.Representation.Representations[0].Items[0].Segments: func = piecewise(
list(
print(segment) map(
print(segment.ParentCurve) evaluate_segment,
print() crv.Representation.Representations[0].Items[0].Segments,
)
func = place(
map_inst(segment.Placement),
map_inst(segment.ParentCurve)
) )
)
for u in numpy.linspace( for u in numpy.linspace(0, func.length, num=int(numpy.ceil(func.length / 0.05))):
segment.SegmentStart[0], yield func(u)
segment.SegmentStart[0] + segment.SegmentLength[0],
num=32
):
yield func(u)
interpret_linear_element = interpret_linear_element_geometry interpret_linear_element = interpret_linear_element_geometry
def create_shape(settings, elem): def create_shape(settings, elem):
if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"): if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"):
return numpy.row_stack(list(interpret_linear_element(settings, elem))) return numpy.row_stack(list(interpret_linear_element(settings, elem)))
@@ -172,7 +231,7 @@ def print_structure(alignment, indent=0):
print(" " * indent, str(alignment)[0:100]) print(" " * indent, str(alignment)[0:100])
for rel in alignment.IsNestedBy: for rel in alignment.IsNestedBy:
for child in rel.RelatedObjects: for child in rel.RelatedObjects:
print_structure(child, indent+2) print_structure(child, indent + 2)
if __name__ == "__main__": if __name__ == "__main__":