moving code to shape builder

This commit is contained in:
Andrej730
2023-07-21 11:00:12 +05:00
parent 288cc698bd
commit bb3e978bba
4 changed files with 213 additions and 330 deletions
File diff suppressed because one or more lines are too long
@@ -20,195 +20,8 @@ import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
from math import cos, tan, pi from math import cos, tan, pi
from pathlib import Path from pathlib import Path
from mathutils import Vector
from itertools import chain from itertools import chain
from ifcopenshell.util.shape_builder import ShapeBuilder, V
from ifcopenshell.util.shape_builder import ShapeBuilder
V = lambda *x: Vector([float(i) for i in x])
def get_simple_2dcurve_data(coords, fillets=[], fillet_radius=[], closed=True, ifc_file=None):
"""
Creates simple 2D curve from set of 2d coords and list of points with fillets.
Simple curve means that all fillets are based on 90 degree angle.
> coords: list of 2d coords. Example: ((x0,y0), (x1,y1), (x2, y2))
> fillets: list of points from `coords` to base fillet on. Example: (1,)
> fillet_radius: list of fillet radius for each of corresponding point form `fillets`. Example: (5.,)
Note: filler_radius could be just 1 float value if it's the same for all fillets.
Optional arguments:
> closed: boolean whether curve should be closed (whether last point connected to first one). Default: True
> ifc_file: ifc file to create IfcIndexedPolyCurve for the function output
< returns (points, segments, ifc_curve) for the created simple curve
if both points in e are equally far from pt, then v1 is returned."""
# option to use same fillet radius for all fillets
if isinstance(fillet_radius, float):
fillet_radius = [fillet_radius] * len(fillets)
fillets = dict(zip(fillets, fillet_radius))
segments = []
points = []
for co_i, co in enumerate(coords, 0):
current_point = len(points)
if co_i in fillets:
r = fillets[co_i]
rsb = r * cos(pi / 4) # radius shift big
rss = r - rsb # radius shift small
next_co = coords[(co_i + 1) % len(coords)]
previous_co = coords[co_i - 1]
# identify fillet type (1 of 4 possible types)
x_direction = 1 if coords[co_i][0] < previous_co[0] or coords[co_i][0] < next_co[0] else -1
y_direction = 1 if coords[co_i][1] < previous_co[1] or coords[co_i][1] < next_co[1] else -1
xshift_point = (co[0] + r * x_direction, co[1])
middle_point = (co[0] + rss * x_direction, co[1] + rss * y_direction)
yshift_point = (co[0], co[1] + r * y_direction)
# identify fillet direction
if co[1] == previous_co[1]:
points.extend((xshift_point, middle_point, yshift_point))
else:
points.extend((yshift_point, middle_point, xshift_point))
segments.append([current_point - 1, current_point])
segments.append([current_point, current_point + 1, current_point + 2])
else:
points.append(co)
if co_i != 0:
segments.append([current_point - 1, current_point])
if closed:
segments.append([len(points) - 1, 0])
# replace negative index
if segments[0][0] == -1:
segments[0][0] = len(points) - 1
# sometime fillet points could match previous or next points in line
# I remove them at the end to avoid making fillet algorithm even less readable
points, segments = remove_redundant_points(points, segments)
ifc_curve = None
if ifc_file:
ifc_points = ifc_file.createIfcCartesianPointList2D(points)
ifc_segments = []
for segment in segments:
segment = [i + 1 for i in segment]
if len(segment) == 2:
ifc_segments.append(ifc_file.createIfcLineIndex(segment))
elif len(segment) == 3:
ifc_segments.append(ifc_file.createIfcArcIndex(segment))
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return (points, segments, ifc_curve)
def remove_redundant_points(points, segments):
# prevent mutating
points = [tuple(p) for p in points]
segments = segments.copy()
# find duplicate points, reindex them in segments
# and mark them to delete later
points_to_remove = []
prev_point = 0
for i, p in enumerate(points[1:], 1):
if p != points[prev_point]:
prev_point = i
continue
valid_segments = []
for s in segments:
s = [ps if ps != i else prev_point for ps in s]
valid_segments.append(s)
segments = valid_segments
points_to_remove.append(i)
# remove duplicate segments
valid_segments = [segment for segment in segments if len(set(segment)) != 1]
points = [point for i, point in enumerate(points) if i not in points_to_remove]
# correct the order in segments
unique_points = sorted(set(chain(*valid_segments)))
unique_points_translation = {prev: i for i, prev in enumerate(unique_points)}
valid_segments = [[unique_points_translation[p] for p in s] for s in valid_segments]
return points, valid_segments
def create_z_profile_lips_curve(
ifc_file, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius
):
x1 = FirstFlangeWidth
x2 = SecondFlangeWidth
y = Depth / 2
g = Girth
t = WallThickness
r = FilletRadius
coords = (
(-t / 2, y),
(x2, y),
(x2, y - g),
(x2 - t, y - g),
(x2 - t, y - t),
(t / 2, y - t),
(t / 2, -y),
(-x1, -y),
(-x1, -y + g),
(-x1 + t, -y + g),
(-x1 + t, -y + t),
(-t / 2, -y + t),
)
# no additional thickness in outer radius option
# points, segments, ifc_curve = create_curve_from_coords(coords, fillets = (0, 1, 4, 5, 6, 7, 10, 11), fillet_radius=r, closed=True, ifc_file=ifc_file)
points, segments, ifc_curve = get_simple_2dcurve_data(
coords,
fillets=(0, 1, 4, 5, 6, 7, 10, 11),
fillet_radius=(r + t, r + t, r, r, r + t, r + t, r, r),
closed=True,
ifc_file=ifc_file,
)
return ifc_curve
def create_transition_arc_ifc(width, height, ifc_file=None):
# create an arc in the rectangle with specified width and height
# if it's not possible to make a complete arc
# it will create arc with longest radius possible
# and straight segment in the middle
fillet_size = (width / 2) / height
if fillet_size <= 1:
fillet_radius = height * fillet_size
curve_coords = [
(0.0, 0.0),
(0.0, height),
(width * 0.5, height),
(width, height),
(width, 0.0),
]
fillets = (1, 3)
else:
fillet_radius = height
curve_coords = [
(0.0, 0.0),
(0.0, height),
(fillet_radius, height),
(width - fillet_radius, height),
(width, height),
(width, 0.0),
]
fillets = (1, 4)
points, segments, transition_arc = get_simple_2dcurve_data(
curve_coords, fillets, fillet_radius, closed=False, ifc_file=ifc_file
)
return points, segments, transition_arc
class LibraryGenerator: class LibraryGenerator:
@@ -289,12 +102,12 @@ class LibraryGenerator:
if position: if position:
kwargs["position"] = position kwargs["position"] = position
_, _, rectangle = get_simple_2dcurve_data( _, _, rectangle = builder.get_simple_2dcurve_data(
coords=builder.get_rectangle_coords(**kwargs), coords=builder.get_rectangle_coords(**kwargs),
fillets=(0, 1, 2, 3), fillets=(0, 1, 2, 3),
fillet_radius=fillet_radius, fillet_radius=fillet_radius,
closed=True, closed=True,
ifc_file=self.file, create_ifc_curve=True,
) )
return rectangle return rectangle
@@ -431,9 +244,9 @@ class LibraryGenerator:
second_arc_depth = depth * (1 - 0.10) second_arc_depth = depth * (1 - 0.10)
second_arc_width = width - (depth - second_arc_depth) * 2 second_arc_width = width - (depth - second_arc_depth) * 2
polyline = builder.polyline((V(0, 0), V(width, 0))) polyline = builder.polyline((V(0, 0), V(width, 0)))
_, _, first_semicircle = create_transition_arc_ifc(width, depth, ifc_file=self.file) _, _, first_semicircle = builder.create_transition_arc_ifc(width, depth, create_ifc_curve=True)
_, _, second_semicircle = create_transition_arc_ifc(second_arc_width, second_arc_depth, ifc_file=self.file) _, _, second_semicircle = builder.create_transition_arc_ifc(second_arc_width, second_arc_depth, create_ifc_curve=True)
builder.translate(second_semicircle, V((width - second_arc_width) / 2, 0)) builder.translate(second_semicircle, V((width - second_arc_width) / 2, 0))
builder.translate([polyline, first_semicircle, second_semicircle], shift_to_center) builder.translate([polyline, first_semicircle, second_semicircle], shift_to_center)
@@ -1105,8 +918,8 @@ class LibraryGenerator:
fillets_radius = [fillets_radius * 5] * 2 + [fillets_radius] * 2 fillets_radius = [fillets_radius * 5] * 2 + [fillets_radius] * 2
else: else:
fillets_radius = [fillets_radius] * 4 fillets_radius = [fillets_radius] * 4
_, _, fillet_rectangle = get_simple_2dcurve_data( _, _, fillet_rectangle = builder.get_simple_2dcurve_data(
coords=coords, fillets=(0, 1, 2, 3), fillet_radius=fillets_radius, closed=True, ifc_file=self.file coords=coords, fillets=(0, 1, 2, 3), fillet_radius=fillets_radius, closed=True, create_ifc_curve=True
) )
circle_position = V( circle_position = V(
@@ -1182,8 +995,8 @@ class LibraryGenerator:
] ]
fillets = (0, 1, 6, 7) fillets = (0, 1, 6, 7)
fillet_radius = (width / 2, width / 5, width / 5, width / 2) fillet_radius = (width / 2, width / 5, width / 5, width / 2)
_, _, seat = get_simple_2dcurve_data( _, _, seat = builder.get_simple_2dcurve_data(
coords, fillets, fillet_radius=fillet_radius, closed=True, ifc_file=self.file coords, fillets, fillet_radius=fillet_radius, closed=True, create_ifc_curve=True
) )
seat_first_part = ifcopenshell.util.element.copy_deep(self.file, seat) seat_first_part = ifcopenshell.util.element.copy_deep(self.file, seat)
@@ -1251,8 +1064,8 @@ class LibraryGenerator:
size=V(width, depth - cistern_depth), position=V(0, cistern_depth) + shift_to_center size=V(width, depth - cistern_depth), position=V(0, cistern_depth) + shift_to_center
) )
fillet_radius = min(width / 2, (depth - cistern_depth) / 2) fillet_radius = min(width / 2, (depth - cistern_depth) / 2)
_, _, seat_main_curve = get_simple_2dcurve_data( _, _, seat_main_curve = builder.get_simple_2dcurve_data(
seat_main_curve_points, fillets=(2, 3), fillet_radius=fillet_radius, closed=True, ifc_file=self.file seat_main_curve_points, fillets=(2, 3), fillet_radius=fillet_radius, closed=True, create_ifc_curve=True
) )
seat_main_curve_mask = builder.circle(center=V(0, depth - fillet_radius), radius=fillet_radius * 0.75) seat_main_curve_mask = builder.circle(center=V(0, depth - fillet_radius), radius=fillet_radius * 0.75)
seat_main_curve_profile = builder.profile(seat_main_curve, inner_curves=seat_main_curve_mask) seat_main_curve_profile = builder.profile(seat_main_curve, inner_curves=seat_main_curve_mask)
@@ -24,119 +24,8 @@ import ifcopenshell.api
import boltspy as bolts import boltspy as bolts
from math import cos, pi from math import cos, pi
from pathlib import Path from pathlib import Path
from mathutils import Vector from ifcopenshell.util.shape_builder import ShapeBuilder, V
V = lambda *x: Vector([float(i) for i in x])
def create_simple_2dcurve(coords, fillets, fillet_radius, closed=True, ifc_file=None):
"""
Creates simple 2D curve from set of 2d coords and list of points with fillets.
Simple curve means that all fillets are based on 90 degree angle.
> coords: list of 2d coords. Example: ((x0,y0), (x1,y1), (x2, y2))
> fillets: list of points from `coords` to base fillet on. Example: (1,)
> fillet_radius: list of fillet radius for each of corresponding point form `fillets`. Example: (5.,)
Note: filler_radius could be just 1 float value if it's the same for all fillets.
Optional arguments:
> closed: boolean whether curve should be closed (whether last point connected to first one). Default: True
> ifc_file: ifc file to create IfcIndexedPolyCurve for the function output
< returns (points, segments, ifc_curve) for the created simple curve
if both points in e are equally far from pt, then v1 is returned."""
# option to use same fillet radius for all fillets
if isinstance(fillet_radius, float):
fillet_radius = [fillet_radius] * len(fillets)
fillets = dict(zip(fillets, fillet_radius))
segments = []
points = []
for co_i, co in enumerate(coords, 0):
current_point = len(points)
if co_i in fillets:
r = fillets[co_i]
rsb = r * cos(pi/4) # radius shift big
rss = r - rsb # radius shift small
next_co = coords[(co_i+1) % len(coords)]
previous_co = coords[co_i-1]
# identify fillet type (1 of 4 possible types)
x_direction = 1 if coords[co_i][0] < previous_co[0] or coords[co_i][0] < next_co[0] else -1
y_direction = 1 if coords[co_i][1] < previous_co[1] or coords[co_i][1] < next_co[1] else -1
xshift_point = (co[0] + r * x_direction, co[1])
middle_point = (co[0] + rss * x_direction, co[1] + rss * y_direction)
yshift_point = (co[0], co[1] + r * y_direction)
# identify fillet direction
if co[1] == previous_co[1]:
points.extend( (xshift_point, middle_point, yshift_point))
else:
points.extend( (yshift_point, middle_point, xshift_point))
segments.append( [current_point-1, current_point] )
segments.append( [current_point, current_point+1, current_point+2] )
else:
points.append( co )
if co_i != 0:
segments.append( [current_point-1, current_point] )
if closed:
segments.append( [0, len(points)-1] )
# replace negative index
if segments[0][0] == -1:
segments[0][0] = len(points) - 1
ifc_curve = None
if ifc_file:
ifc_points = ifc_file.createIfcCartesianPointList2D(points)
ifc_segments = []
for segment in segments:
segment = [i+1 for i in segment]
if len(segment) == 2:
ifc_segments.append( ifc_file.createIfcLineIndex( segment ))
elif len(segment) == 3:
ifc_segments.append( ifc_file.createIfcArcIndex( segment ))
ifc_curve = ifc_file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return (points, segments, ifc_curve)
def create_z_profile_lips_curve(ifc_file, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius):
x1 = FirstFlangeWidth
x2 = SecondFlangeWidth
y = Depth / 2
g = Girth
t = WallThickness
r = FilletRadius
coords = (
(-t/2, y),
(x2, y),
(x2, y-g),
(x2-t, y-g),
(x2-t, y-t),
(t/2, y-t),
(t/2, -y),
(-x1, -y),
(-x1, -y+g),
(-x1+t, -y+g),
(-x1+t, -y+t),
(-t/2, -y+t)
)
# no additional thickness in outer radius option
# points, segments, ifc_curve = create_curve_from_coords(coords, fillets = (0, 1, 4, 5, 6, 7, 10, 11), fillet_radius=r, closed=True, ifc_file=ifc_file)
points, segments, ifc_curve = create_simple_2dcurve(coords,
fillets = (0, 1, 4, 5, 6, 7, 10, 11),
fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r),
closed=True, ifc_file=ifc_file)
return ifc_curve
class LibraryGenerator: class LibraryGenerator:
def generate(self, parse_profiles_type="EU", output_filename="IFC4 EU Steel.ifc"): def generate(self, parse_profiles_type="EU", output_filename="IFC4 EU Steel.ifc"):
@@ -158,6 +47,7 @@ class LibraryGenerator:
) )
dim_exponents = self.file.createIfcDimensionalExponents(0, 0, 0, 0, 0, 0, 0) dim_exponents = self.file.createIfcDimensionalExponents(0, 0, 0, 0, 0, 0, 0)
length_unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI") length_unit = ifcopenshell.api.run("unit.add_si_unit", self.file, unit_type="LENGTHUNIT", prefix="MILLI")
builder = ShapeBuilder(self.file)
# define angle unit to use degrees for IfcPlaneAngleMeasure: # define angle unit to use degrees for IfcPlaneAngleMeasure:
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcPlaneAngleMeasure.htm # https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/lexical/IfcPlaneAngleMeasure.htm
@@ -258,7 +148,7 @@ class LibraryGenerator:
# by default bolts provides diameter, so we need to convert it to radius # by default bolts provides diameter, so we need to convert it to radius
ifc_params["Radius"] /= 2 ifc_params["Radius"] /= 2
elif prof_type == "profile_z_lips": elif prof_type == "profile_z_lips":
ifc_curve = create_z_profile_lips_curve(self.file, **ifc_params) ifc_curve = builder.create_z_profile_lips_curve(**ifc_params)
ifc_params = {"OuterCurve": ifc_curve} ifc_params = {"OuterCurve": ifc_curve}
elif prof_type == "profile_l*lbeam_2l": elif prof_type == "profile_l*lbeam_2l":
profiles_gap = ifc_params["ProfilesGap"] profiles_gap = ifc_params["ProfilesGap"]
@@ -21,6 +21,7 @@ import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
from math import cos, sin, pi from math import cos, sin, pi
from mathutils import Vector, Matrix from mathutils import Vector, Matrix
from itertools import chain
V = lambda *x: Vector([float(i) for i in x]) V = lambda *x: Vector([float(i) for i in x])
sign = lambda x: x and (1, -1)[x < 0] sign = lambda x: x and (1, -1)[x < 0]
@@ -635,3 +636,182 @@ class ShapeBuilder:
elif polyline.is_a("IfcPolyline"): elif polyline.is_a("IfcPolyline"):
for i, co in enumerate(coords): for i, co in enumerate(coords):
polyline.Points[i].Coordinates = co polyline.Points[i].Coordinates = co
def get_simple_2dcurve_data(self, coords, fillets=[], fillet_radius=[], closed=True, create_ifc_curve=None):
"""
Creates simple 2D curve from set of 2d coords and list of points with fillets.
Simple curve means that all fillets are based on 90 degree angle.
> coords: list of 2d coords. Example: ((x0,y0), (x1,y1), (x2, y2))
> fillets: list of points from `coords` to base fillet on. Example: (1,)
> fillet_radius: list of fillet radius for each of corresponding point form `fillets`. Example: (5.,)
Note: filler_radius could be just 1 float value if it's the same for all fillets.
Optional arguments:
> closed: boolean whether curve should be closed (whether last point connected to first one). Default: True
> create_ifc_curve: create IfcIndexedPolyCurve or just return the data. Default: False
< returns (points, segments, ifc_curve) for the created simple curve
if both points in e are equally far from pt, then v1 is returned."""
def remove_redundant_points(points, segments):
# prevent mutating
points = [tuple(p) for p in points]
segments = segments.copy()
# find duplicate points, reindex them in segments
# and mark them to delete later
points_to_remove = []
prev_point = 0
for i, p in enumerate(points[1:], 1):
if p != points[prev_point]:
prev_point = i
continue
valid_segments = []
for s in segments:
s = [ps if ps != i else prev_point for ps in s]
valid_segments.append(s)
segments = valid_segments
points_to_remove.append(i)
# remove duplicate segments
valid_segments = [segment for segment in segments if len(set(segment)) != 1]
points = [point for i, point in enumerate(points) if i not in points_to_remove]
# correct the order in segments
unique_points = sorted(set(chain(*valid_segments)))
unique_points_translation = {prev: i for i, prev in enumerate(unique_points)}
valid_segments = [[unique_points_translation[p] for p in s] for s in valid_segments]
return points, valid_segments
# option to use same fillet radius for all fillets
if isinstance(fillet_radius, float):
fillet_radius = [fillet_radius] * len(fillets)
fillets = dict(zip(fillets, fillet_radius))
segments = []
points = []
for co_i, co in enumerate(coords, 0):
current_point = len(points)
if co_i in fillets:
r = fillets[co_i]
rsb = r * cos(pi / 4) # radius shift big
rss = r - rsb # radius shift small
next_co = coords[(co_i + 1) % len(coords)]
previous_co = coords[co_i - 1]
# identify fillet type (1 of 4 possible types)
x_direction = 1 if coords[co_i][0] < previous_co[0] or coords[co_i][0] < next_co[0] else -1
y_direction = 1 if coords[co_i][1] < previous_co[1] or coords[co_i][1] < next_co[1] else -1
xshift_point = (co[0] + r * x_direction, co[1])
middle_point = (co[0] + rss * x_direction, co[1] + rss * y_direction)
yshift_point = (co[0], co[1] + r * y_direction)
# identify fillet direction
if co[1] == previous_co[1]:
points.extend((xshift_point, middle_point, yshift_point))
else:
points.extend((yshift_point, middle_point, xshift_point))
segments.append([current_point - 1, current_point])
segments.append([current_point, current_point + 1, current_point + 2])
else:
points.append(co)
if co_i != 0:
segments.append([current_point - 1, current_point])
if closed:
segments.append([len(points) - 1, 0])
# replace negative index
if segments[0][0] == -1:
segments[0][0] = len(points) - 1
# sometime fillet points could match previous or next points in line
# I remove them at the end to avoid making fillet algorithm even less readable
points, segments = remove_redundant_points(points, segments)
ifc_curve = None
if create_ifc_curve:
ifc_points = self.file.createIfcCartesianPointList2D(points)
ifc_segments = []
for segment in segments:
segment = [i + 1 for i in segment]
if len(segment) == 2:
ifc_segments.append(self.file.createIfcLineIndex(segment))
elif len(segment) == 3:
ifc_segments.append(self.file.createIfcArcIndex(segment))
ifc_curve = self.file.createIfcIndexedPolyCurve(Points=ifc_points, Segments=ifc_segments)
return (points, segments, ifc_curve)
def create_z_profile_lips_curve(self, FirstFlangeWidth, SecondFlangeWidth, Depth, Girth, WallThickness, FilletRadius):
x1 = FirstFlangeWidth
x2 = SecondFlangeWidth
y = Depth / 2
g = Girth
t = WallThickness
r = FilletRadius
# fmt: off
coords = (
(-t/2, y),
(x2, y),
(x2, y-g),
(x2-t, y-g),
(x2-t, y-t),
(t/2, y-t),
(t/2, -y),
(-x1, -y),
(-x1, -y+g),
(-x1+t, -y+g),
(-x1+t, -y+t),
(-t/2, -y+t)
)
# fmt: on
# option for no additional thickness in outer radius:
# points, segments, ifc_curve = create_curve_from_coords(
# coords, fillets = (0, 1, 4, 5, 6, 7, 10, 11), fillet_radius=r, closed=True, ifc_file=ifc_file
# )
points, segments, ifc_curve = self.get_simple_2dcurve_data(coords,
fillets = (0, 1, 4, 5, 6, 7, 10, 11),
fillet_radius=(r+t, r+t, r, r, r+t, r+t, r, r),
closed=True, create_ifc_curve=True)
return ifc_curve
def create_transition_arc_ifc(self, width, height, create_ifc_curve=False):
# create an arc in the rectangle with specified width and height
# if it's not possible to make a complete arc
# it will create arc with longest radius possible
# and straight segment in the middle
fillet_size = (width / 2) / height
if fillet_size <= 1:
fillet_radius = height * fillet_size
curve_coords = [
(0.0, 0.0),
(0.0, height),
(width * 0.5, height),
(width, height),
(width, 0.0),
]
fillets = (1, 3)
else:
fillet_radius = height
curve_coords = [
(0.0, 0.0),
(0.0, height),
(fillet_radius, height),
(width - fillet_radius, height),
(width, height),
(width, 0.0),
]
fillets = (1, 4)
points, segments, transition_arc = self.get_simple_2dcurve_data(
curve_coords, fillets, fillet_radius, closed=False, create_ifc_curve=create_ifc_curve
)
return points, segments, transition_arc