mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c85bf90558 | |||
| 256d5a63f1 | |||
| c4605f2a8f | |||
| 4a62ffe9ca | |||
| d5e890bccd | |||
| bba11aa619 | |||
| 9f848a73e1 | |||
| 4fb8af2278 | |||
| 78653a1708 | |||
| 216092150a | |||
| ade03b171a | |||
| b5c1b81ede | |||
| 47a20f0c7c | |||
| 52d894298e | |||
| 206cd6bbe1 |
@@ -82,7 +82,15 @@ import math
|
||||
from collections.abc import Callable, Iterator
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import Any, ClassVar, Literal, Optional, Protocol, runtime_checkable
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Literal,
|
||||
Optional,
|
||||
Protocol,
|
||||
runtime_checkable,
|
||||
)
|
||||
|
||||
import blf
|
||||
import bpy
|
||||
@@ -105,6 +113,9 @@ from mathutils.kdtree import KDTree
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing.shaders import ExtrusionGuidesShader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import bmesh
|
||||
|
||||
SNAP_POINT_SIZE = 10.0
|
||||
SNAP_POINT_COLOR = (1.0, 0.5, 0.0, 1.0)
|
||||
SNAP_MAX_RADIUS = 50.0
|
||||
@@ -2035,7 +2046,9 @@ class TexturedQuadGizmoMixin(StaticTrisGizmoMixin):
|
||||
|
||||
def setup(self) -> None:
|
||||
super().setup()
|
||||
from bonsai.bim.module.drawing import gizmo_textures
|
||||
from bonsai.bim.module.drawing import (
|
||||
gizmo_textures, # ty: ignore[unresolved-import]
|
||||
)
|
||||
|
||||
self._quad_batch = batch_for_shader(
|
||||
gizmo_textures.get_shader(),
|
||||
@@ -2044,7 +2057,9 @@ class TexturedQuadGizmoMixin(StaticTrisGizmoMixin):
|
||||
)
|
||||
|
||||
def draw(self, context: bpy.types.Context) -> None:
|
||||
from bonsai.bim.module.drawing import gizmo_textures
|
||||
from bonsai.bim.module.drawing import (
|
||||
gizmo_textures, # ty: ignore[unresolved-import]
|
||||
)
|
||||
|
||||
texture = gizmo_textures.get_icon_texture(self.icon_name)
|
||||
if texture is None:
|
||||
|
||||
@@ -981,7 +981,9 @@ class CreateDrawing(bpy.types.Operator):
|
||||
# Specifically for PLAN_VIEW and REFLECTED_PLAN_VIEW, any Plan context is also prioritised.
|
||||
contexts = self.get_linework_contexts(ifc, target_view)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "body", drawing_elements, target_view, link_matrix)
|
||||
self.serialize_contexts_elements(ifc, tree, contexts, "annotation", drawing_elements, target_view, link_matrix)
|
||||
self.serialize_contexts_elements(
|
||||
ifc, tree, contexts, "annotation", drawing_elements, target_view, link_matrix
|
||||
)
|
||||
|
||||
if tool.Ifc.get() == ifc and self.camera_element not in drawing_elements:
|
||||
with profile("Camera element"):
|
||||
|
||||
@@ -27,6 +27,7 @@ import bonsai.tool as tool
|
||||
from . import (
|
||||
array,
|
||||
covering,
|
||||
decorator,
|
||||
door,
|
||||
external,
|
||||
grid,
|
||||
|
||||
@@ -38,6 +38,7 @@ import numpy as np
|
||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.root
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim.module.drawing import gizmos as gizmo
|
||||
|
||||
@@ -138,7 +138,7 @@ def update_bbim_railing_pset(element: ifcopenshell.entity_instance, railing_data
|
||||
|
||||
def generate_wall_mounted_handrail_preview(
|
||||
obj: bpy.types.Object,
|
||||
props: "BIMRailingProperties",
|
||||
props: "prop.BIMRailingProperties",
|
||||
path_data: dict[str, Any],
|
||||
si_conversion: float,
|
||||
) -> None:
|
||||
@@ -860,7 +860,9 @@ class GizmoRailingSchematic(bpy.types.GizmoGroup, gizmo.BaseSchematicGizmoGroup)
|
||||
terminal_world = anchor + billboard_rot @ view_rotation @ terminal_local
|
||||
self.terminal_gizmo.matrix_basis = gizmo.billboarded_at(terminal_world, billboard_rot, 0.18)
|
||||
|
||||
def update_editing_gizmos(self, context: bpy.types.Context, mw: "Matrix", props: "BIMRailingProperties") -> None:
|
||||
def update_editing_gizmos(
|
||||
self, context: bpy.types.Context, mw: "Matrix", props: "prop.BIMRailingProperties"
|
||||
) -> None:
|
||||
"""Hide the pen gizmo while polyline path-edit is active; reposition the cycle icon.
|
||||
|
||||
The base class shows the pen gizmo whenever ``is_editing`` is False,
|
||||
|
||||
@@ -302,23 +302,25 @@ def add_drawing(
|
||||
context=drawing.get_body_context(),
|
||||
ifc_representation_class=None,
|
||||
)
|
||||
|
||||
|
||||
drawings_parent_group = None
|
||||
for group in ifc.get().by_type("IfcGroup"):
|
||||
if group.Name == "DRAWINGS" and group.ObjectType == "DRAWINGS":
|
||||
drawings_parent_group = group
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_group:
|
||||
drawings_parent_group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"})
|
||||
|
||||
ifc.run(
|
||||
"group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"}
|
||||
)
|
||||
|
||||
group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
||||
ifc.run("group.assign_group", group=group, products=[element])
|
||||
|
||||
|
||||
ifc.run("group.assign_group", group=drawings_parent_group, products=[group])
|
||||
|
||||
|
||||
collector.assign(camera)
|
||||
pset = ifc.run("pset.add_pset", product=element, name="EPset_Drawing")
|
||||
if drawing.get_unit_system() == "METRIC":
|
||||
@@ -355,7 +357,7 @@ def add_drawing(
|
||||
if document.Name == "DRAWINGS" and document.Scope == "DRAWINGS":
|
||||
drawings_parent_document = document
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_document:
|
||||
drawings_parent_document = ifc.run("document.add_information")
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
@@ -363,7 +365,7 @@ def add_drawing(
|
||||
else:
|
||||
attributes = {"Identification": "DRAWINGS", "Name": "DRAWINGS", "Scope": "DRAWINGS"}
|
||||
ifc.run("document.edit_information", information=drawings_parent_document, attributes=attributes)
|
||||
|
||||
|
||||
information = ifc.run("document.add_information", parent=drawings_parent_document)
|
||||
uri = drawing.get_default_drawing_path(drawing_name)
|
||||
reference = ifc.run("document.add_reference", information=information)
|
||||
@@ -392,17 +394,19 @@ def duplicate_drawing(
|
||||
drawing_tool.set_name(new_drawing, drawing_name)
|
||||
group = drawing_tool.get_drawing_group(new_drawing)
|
||||
ifc.run("group.unassign_group", group=group, products=[new_drawing])
|
||||
|
||||
|
||||
drawings_parent_group = None
|
||||
for parent_group in ifc.get().by_type("IfcGroup"):
|
||||
if parent_group.Name == "DRAWINGS" and parent_group.ObjectType == "DRAWINGS":
|
||||
drawings_parent_group = parent_group
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_group:
|
||||
drawings_parent_group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"})
|
||||
|
||||
ifc.run(
|
||||
"group.edit_group", group=drawings_parent_group, attributes={"Name": "DRAWINGS", "ObjectType": "DRAWINGS"}
|
||||
)
|
||||
|
||||
new_group = ifc.run("group.add_group")
|
||||
ifc.run("group.edit_group", group=new_group, attributes={"Name": drawing_name, "ObjectType": "DRAWING"})
|
||||
ifc.run("group.assign_group", group=new_group, products=[new_drawing])
|
||||
@@ -427,7 +431,7 @@ def duplicate_drawing(
|
||||
if document.Name == "DRAWINGS" and document.Scope == "DRAWINGS":
|
||||
drawings_parent_document = document
|
||||
break
|
||||
|
||||
|
||||
if not drawings_parent_document:
|
||||
drawings_parent_document = ifc.run("document.add_information")
|
||||
if ifc.get_schema() == "IFC2X3":
|
||||
|
||||
@@ -50,14 +50,15 @@ def copy_z_rotation_to_selected(
|
||||
flip: bool = False,
|
||||
) -> int:
|
||||
"""Apply ``active``'s Z-Euler rotation to each target."""
|
||||
source_z = surveyor.get_z_rotation(active)
|
||||
source_z = surveyor.get_z_rotation(active) # ty: ignore[missing-argument]
|
||||
if flip:
|
||||
source_z += math.pi
|
||||
rotated = 0
|
||||
for obj in targets:
|
||||
if abs(_z_rotation_diff(surveyor.get_z_rotation(obj), source_z)) < Z_ROTATION_ALIGNMENT_TOLERANCE:
|
||||
target_z = surveyor.get_z_rotation(obj) # ty: ignore[missing-argument]
|
||||
if abs(_z_rotation_diff(target_z, source_z)) < Z_ROTATION_ALIGNMENT_TOLERANCE:
|
||||
continue
|
||||
surveyor.set_z_rotation(obj, source_z)
|
||||
surveyor.set_z_rotation(obj, source_z) # ty: ignore[missing-argument]
|
||||
rotated += 1
|
||||
if ifc.get_entity(obj) is not None:
|
||||
bonsai.core.geometry.edit_object_placement(ifc, geometry, surveyor, obj=obj)
|
||||
|
||||
@@ -804,7 +804,7 @@ class Profile:
|
||||
|
||||
@interface
|
||||
class Parametric:
|
||||
def get_geom_generation(cls) -> int: pass
|
||||
def get_geom_generation(cls): pass
|
||||
def refresh_post_commit(cls, operator) -> None: pass
|
||||
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ from ifcopenshell.util.shape_builder import ShapeBuilder, np_to_3d
|
||||
from mathutils import Matrix, Vector
|
||||
|
||||
import bonsai.core.geometry
|
||||
import bonsai.core.model
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
from bonsai.bim import import_ifc
|
||||
|
||||
@@ -35,6 +35,7 @@ from unittest.mock import Mock, patch
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import pytest
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -37,6 +37,8 @@ from unittest.mock import patch
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.util.element
|
||||
import pytest
|
||||
|
||||
import bonsai.tool as tool
|
||||
|
||||
@@ -146,7 +146,9 @@ def test_fit_flow_segments_with_single_segment_dispatches_obstruction():
|
||||
mep.tool.Model, "get_flow_segment_profile", return_value=segment_profile
|
||||
), patch.object(mep.MEPAddObstruction, "_execute", return_value=None) as obstruction, patch.object(
|
||||
mep.MEPAddBend, "_execute", return_value=None
|
||||
) as bend, patch.object(mep.MEPAddTransition, "_execute", return_value=None) as transition:
|
||||
) as bend, patch.object(
|
||||
mep.MEPAddTransition, "_execute", return_value=None
|
||||
) as transition:
|
||||
mep.FitFlowSegments._execute(op, context=context)
|
||||
|
||||
assert obstruction.call_count == 1
|
||||
@@ -178,7 +180,9 @@ def test_fit_flow_segments_refuses_mixed_pipe_and_duct():
|
||||
mep.tool.Model, "get_flow_segment_profile", return_value=profile
|
||||
), patch.object(mep.MEPAddObstruction, "_execute", return_value=None) as obstruction, patch.object(
|
||||
mep.MEPAddBend, "_execute", return_value=None
|
||||
) as bend, patch.object(mep.MEPAddTransition, "_execute", return_value=None) as transition:
|
||||
) as bend, patch.object(
|
||||
mep.MEPAddTransition, "_execute", return_value=None
|
||||
) as transition:
|
||||
mep.FitFlowSegments._execute(op, context=context)
|
||||
|
||||
obstruction.assert_not_called()
|
||||
|
||||
@@ -173,8 +173,9 @@ def test_gizmo_group_class_wiring(gizmo_cls_name, bl_idname, is_element_predicat
|
||||
predicate = getattr(tool.Parametric, is_element_predicate)
|
||||
fake_element = Mock()
|
||||
fake_element.is_a.return_value = True
|
||||
with patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p, patch.object(
|
||||
tool.System, "has_parametric_body", return_value=True
|
||||
with (
|
||||
patch.object(tool.Parametric, is_element_predicate, side_effect=predicate) as p,
|
||||
patch.object(tool.System, "has_parametric_body", return_value=True),
|
||||
):
|
||||
cls.is_element_type(fake_element)
|
||||
assert p.called, f"{gizmo_cls_name}.is_element_type did not delegate to Parametric.{is_element_predicate}"
|
||||
|
||||
@@ -139,6 +139,5 @@ def test_every_cancel_ops_entry_has_a_real_preview_propertygroup() -> None:
|
||||
orphaned = [attr for attr, _op in preview_base.PREVIEW_CANCEL_OPS if attr not in declared_attrs]
|
||||
assert not orphaned, (
|
||||
"PREVIEW_CANCEL_OPS contains entries whose PointerProperty child no longer "
|
||||
f"exists on {UMBRELLA_CLASS}. Drop the stale tuple(s):\n "
|
||||
+ "\n ".join(orphaned)
|
||||
f"exists on {UMBRELLA_CLASS}. Drop the stale tuple(s):\n " + "\n ".join(orphaned)
|
||||
)
|
||||
|
||||
@@ -24,6 +24,7 @@ import time
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
import pytest
|
||||
|
||||
from bonsai import tool as tool
|
||||
|
||||
@@ -23,6 +23,7 @@ import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.material
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.style
|
||||
import ifcopenshell.api.type
|
||||
|
||||
@@ -57,7 +57,8 @@ class CsvHeader(TypedDict):
|
||||
|
||||
# Formula
|
||||
Formula: NotRequired[str]
|
||||
#QuantityClass: NotRequired[str]
|
||||
# QuantityClass: NotRequired[str]
|
||||
|
||||
|
||||
# Currently we assume that if column is not part of the main header,
|
||||
# then it is a cost value category. So here we list any additional column
|
||||
@@ -97,7 +98,8 @@ class CostItem(TypedDict):
|
||||
Query: Union[str, None]
|
||||
|
||||
Formula: Union[str, None]
|
||||
#QuantityClass: Union[str, None]
|
||||
# QuantityClass: Union[str, None]
|
||||
|
||||
|
||||
class Csv2Ifc:
|
||||
# Inputs.
|
||||
@@ -420,7 +422,7 @@ class Csv2Ifc:
|
||||
products=results,
|
||||
formula=cost_item["Formula"],
|
||||
ifc_class=ifc_quantity_class,
|
||||
)
|
||||
)
|
||||
|
||||
self.create_cost_items(cost_item["children"], cost_item["ifc"])
|
||||
|
||||
|
||||
@@ -231,7 +231,7 @@ def open(
|
||||
kwargs = {"mmap": mmap}
|
||||
if logger is not None:
|
||||
kwargs["logger"] = logger
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), **kwargs) # ty: ignore[unknown-argument]
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), **kwargs)
|
||||
else:
|
||||
f = ifcopenshell_wrapper.open(str(path.absolute()), False, *((logger,) if logger is not None else ()))
|
||||
return file(f)
|
||||
|
||||
@@ -49,6 +49,7 @@ Future versions of this API may support:
|
||||
|
||||
from ._get_segment_start_point_label import register_referent_name_callback
|
||||
from .add_stationing_referent import add_stationing_referent
|
||||
from .add_positioning_referent import add_positioning_referent
|
||||
from .add_vertical_layout import add_vertical_layout
|
||||
from .add_zero_length_segment import add_zero_length_segment
|
||||
from .create import create
|
||||
@@ -94,6 +95,7 @@ from .util import *
|
||||
|
||||
__all__ = [
|
||||
"add_stationing_referent",
|
||||
"add_positioning_referent",
|
||||
"add_vertical_layout",
|
||||
"add_zero_length_segment",
|
||||
"create",
|
||||
|
||||
@@ -22,8 +22,6 @@ import numpy as np
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.ifcopenshell_wrapper as ifcopenshell_wrapper
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
|
||||
@@ -22,28 +22,11 @@ import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment import _map_alignment_cant_segment
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.alignment
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._add_segment_to_curve import _add_segment_to_curve
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_cant_segment import (
|
||||
_map_alignment_cant_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import (
|
||||
_map_alignment_horizontal_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_vertical_segment import (
|
||||
_map_alignment_vertical_segment,
|
||||
)
|
||||
|
||||
|
||||
def _add_segment_to_layout(
|
||||
|
||||
@@ -18,11 +18,7 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.alignment
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
|
||||
|
||||
def _add_zero_length_segment(file: ifcopenshell.file, layout: entity_instance) -> None:
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell.api.alignment._map_alignment_segment import _map_alignment_segment
|
||||
from typing import Union
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.guid
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_positioning_referent(
|
||||
file: ifcopenshell.file,
|
||||
name: str,
|
||||
alignment: entity_instance,
|
||||
distance_along: float,
|
||||
station: float,
|
||||
positioned_product: entity_instance,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Semantically defines the position of a product along an alignment by adding an IfcReferent to the alignment that defines the stationing system.
|
||||
|
||||
:param alignment: the alignment to receive the referent
|
||||
:param distance_along: distance along the alignment basis curve
|
||||
:param station: station value
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param positioned_product: the product whose position is informed by the referent
|
||||
:return: referent
|
||||
|
||||
Example:
|
||||
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
pier = model.by_type("IfcBridgePart")[0]
|
||||
ifcopenshell.api.alignment.add_positioning_referent(model,name="Pier 1 Sta 1+00",alignment=alignment,distance_along=0.0,station=100.0,positioned_product=pier)
|
||||
"""
|
||||
|
||||
curve = ifcopenshell.api.alignment.get_curve(alignment)
|
||||
|
||||
object_placement = None
|
||||
representation = None
|
||||
if curve and curve.is_a("IfcCompositeCurve") and 0 < len(curve.Segments):
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
DistanceAlong=file.createIfcLengthMeasure(distance_along),
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=curve,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
update_fallback_position(file, object_placement)
|
||||
else:
|
||||
object_placement = file.createIfcLocalPlacement(
|
||||
PlacementRelTo=None,
|
||||
RelativePlacement=file.createIfcAxis2Placement2D(
|
||||
Location=file.createIfcCartesianPoint(alignment.ObjectPlacement.RelativePlacement.Location.Coordinates)
|
||||
),
|
||||
)
|
||||
|
||||
# this commented out code is what you would do to add a geometric representation of the referent
|
||||
# the example is a circle. a better way would be to pass a representation into the function
|
||||
# representation = file.create_entity(
|
||||
# name="IfcCircle",
|
||||
# position=file.createIfcAxis2Placement2D(Location=file.createIfcCartesianPoint(Coordinates=(0.0, 0.0)),
|
||||
# radius=1.0)
|
||||
# )
|
||||
|
||||
# create referent for the station
|
||||
referent = file.createIfcReferent(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
OwnerHistory=None,
|
||||
Name=name,
|
||||
Description=None,
|
||||
ObjectType=None,
|
||||
ObjectPlacement=object_placement,
|
||||
Representation=representation,
|
||||
PredefinedType="POSITION",
|
||||
)
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||
|
||||
if len(referent.Positions) == 0:
|
||||
rel_positions = file.createIfcRelPositions(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatingPositioningElement=referent,
|
||||
RelatedProducts=[
|
||||
positioned_product,
|
||||
],
|
||||
)
|
||||
else:
|
||||
referent.Positions[0].RelatedProducts += (positioned_product,)
|
||||
|
||||
return referent
|
||||
@@ -16,35 +16,35 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy as np
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment.update_fallback_position import update_fallback_position
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def add_stationing_referent(
|
||||
file: ifcopenshell.file,
|
||||
name: str,
|
||||
alignment: entity_instance,
|
||||
distance_along: float,
|
||||
station: float,
|
||||
name: str,
|
||||
positioned_product: entity_instance,
|
||||
incoming_station: Optional[float] = None,
|
||||
on_basis_curve: Optional[bool] = None,
|
||||
) -> entity_instance:
|
||||
"""
|
||||
Adds an IfcReferent to the alignment with the Pset_Stationing property set.
|
||||
Adds an IfcReferent to the alignment that defines the stationing system.
|
||||
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param alignment: the alignment to receive the referent
|
||||
:param distance_along: distance along the alignment basis curve
|
||||
:param station: station value
|
||||
:param name: name to assign to IfcReferent.Name, typically a stringized version of the station value
|
||||
:param positioned_product: the product whose position is informed by the referent
|
||||
:param incoming_station: station value of the incoming segment, only set to specify a station equation
|
||||
:param on_basis_curve: whether the referent is positioned on the basis curve or the alignment curve, if None the function will default to the basis curve
|
||||
:return: referent
|
||||
|
||||
Example:
|
||||
@@ -52,14 +52,21 @@ def add_stationing_referent(
|
||||
.. code:: python
|
||||
|
||||
alignment = model.by_type("IfcAlignment")[0]
|
||||
ifcopenshell.api.alignment.add_stationing_referent(model,alignment=alignment,distance_along=0.0,station=100.0)
|
||||
ifcopenshell.api.alignment.add_stationing_referent(model,name="1+00.0",alignment=alignment,distance_along=0.0,station=100.0)
|
||||
"""
|
||||
|
||||
basis_curve = ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
if on_basis_curve is None:
|
||||
on_basis_curve = True
|
||||
|
||||
curve = (
|
||||
ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
if on_basis_curve
|
||||
else ifcopenshell.api.alignment.get_curve(alignment)
|
||||
)
|
||||
|
||||
object_placement = None
|
||||
representation = None
|
||||
if basis_curve and basis_curve.is_a("IfcCompositeCurve") and 0 < len(basis_curve.Segments):
|
||||
if curve and curve.is_a("IfcCompositeCurve") and 0 < len(curve.Segments):
|
||||
object_placement = file.createIfcLinearPlacement(
|
||||
RelativePlacement=file.createIfcAxis2PlacementLinear(
|
||||
Location=file.createIfcPointByDistanceExpression(
|
||||
@@ -67,7 +74,7 @@ def add_stationing_referent(
|
||||
OffsetLateral=None,
|
||||
OffsetVertical=None,
|
||||
OffsetLongitudinal=None,
|
||||
BasisCurve=basis_curve,
|
||||
BasisCurve=curve,
|
||||
)
|
||||
),
|
||||
)
|
||||
@@ -100,8 +107,12 @@ def add_stationing_referent(
|
||||
Representation=representation,
|
||||
PredefinedType="STATION",
|
||||
)
|
||||
properties = {"Station": station}
|
||||
if incoming_station is not None:
|
||||
properties["IncomingStation"] = incoming_station
|
||||
|
||||
pset_stationing = ifcopenshell.api.pset.add_pset(file, product=referent, name="Pset_Stationing")
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties={"Station": station})
|
||||
ifcopenshell.api.pset.edit_pset(file, pset=pset_stationing, properties=properties)
|
||||
|
||||
nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
if nest is None:
|
||||
@@ -115,15 +126,4 @@ def add_stationing_referent(
|
||||
nest.RelatedObjects, key=lambda x: ifcopenshell.util.element.get_pset(x, name="Pset_Stationing", prop="Station")
|
||||
)
|
||||
|
||||
if len(referent.Positions) == 0:
|
||||
rel_positions = file.createIfcRelPositions(
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatingPositioningElement=referent,
|
||||
RelatedProducts=[
|
||||
positioned_product,
|
||||
],
|
||||
)
|
||||
else:
|
||||
referent.Positions[0].RelatedProducts += (positioned_product,)
|
||||
|
||||
return referent
|
||||
|
||||
@@ -51,18 +51,6 @@ def _move_vertical_layout_to_child_alignment(
|
||||
# aggregate the child alignment to the parent alignment
|
||||
ifcopenshell.api.aggregate.assign_object(file, products=[child_alignment], relating_object=parent_alignment)
|
||||
|
||||
# move all referents positioning segments of the vertical layout to the referent nest of the child alignment
|
||||
child_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, child_alignment)
|
||||
parent_referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, parent_alignment)
|
||||
for referent in parent_referent_nest.RelatedObjects:
|
||||
for product in referent.Positions[0].RelatedProducts:
|
||||
if product.is_a("IfcAlignmentSegment") and product.Nests[0].RelatingObject == vertical_layout:
|
||||
# ifcopenshell.api.nest.change_nest(file,referent,child_alignment) - this doesn't work because referent is assigned to child_alignment.IsNestedBy[0].RelatedObjects
|
||||
# and it needs to be assigned to child_alignment.IsNestedBy[1].RelatedObjects
|
||||
# move the referent manually - unassign it and add it to the child alignment's referent nest
|
||||
ifcopenshell.api.nest.unassign_object(file, [referent])
|
||||
child_referent_nest.RelatedObjects += (referent,)
|
||||
|
||||
# if the parent alignment has a representation, move the Axis/Curve3D represention to the child alignment
|
||||
base_curve = ifcopenshell.api.alignment.get_basis_curve(parent_alignment)
|
||||
if base_curve:
|
||||
|
||||
@@ -23,18 +23,8 @@ import ifcopenshell.api.alignment
|
||||
from ifcopenshell.api.alignment._get_segment_endpoint import _get_segment_endpoint
|
||||
from ifcopenshell.api.alignment._update_zero_length_segment_placement import _update_zero_length_segment_placement
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.ifcopenshell_wrapper as wrapper
|
||||
import ifcopenshell.util.unit
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._get_segment_start_point_label import (
|
||||
_get_segment_start_point_label,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_horizontal_segment import (
|
||||
_map_alignment_horizontal_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._map_alignment_vertical_segment import (
|
||||
_map_alignment_vertical_segment,
|
||||
)
|
||||
from ifcopenshell.api.alignment._update_curve_segment_transition_code import (
|
||||
_update_curve_segment_transition_code,
|
||||
)
|
||||
|
||||
@@ -87,9 +87,7 @@ def create(
|
||||
_create_geometric_representation(file, alignment)
|
||||
|
||||
referent_name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, alignment, 0.0, start_station, referent_name, alignment
|
||||
)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, referent_name, alignment, 0.0, start_station)
|
||||
|
||||
for layout in alignment_layouts:
|
||||
_add_zero_length_segment(file, layout)
|
||||
|
||||
@@ -141,7 +141,7 @@ def create_as_polyline(
|
||||
|
||||
# define stationing
|
||||
name = ifcopenshell.util.alignment.station_as_string(file, start_station)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, alignment, 0.0, start_station, name, alignment)
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(file, name, alignment, 0.0, start_station)
|
||||
|
||||
# IFC 4.1.4.1.1 Alignment Aggregation To Project
|
||||
project = file.by_type("IfcProject")[0]
|
||||
|
||||
@@ -21,9 +21,7 @@ from typing import Union
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.geom
|
||||
from ifcopenshell import entity_instance, ifcopenshell_wrapper
|
||||
from ifcopenshell import entity_instance
|
||||
from ifcopenshell.api.alignment._add_segment_to_layout import _add_segment_to_layout
|
||||
|
||||
|
||||
|
||||
@@ -16,23 +16,47 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.element
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> float:
|
||||
def _distance_along_of_referent(referent: entity_instance) -> float:
|
||||
placement = referent.ObjectPlacement
|
||||
if placement.is_a("IfcLinearPlacement"):
|
||||
return placement.RelativePlacement.Location.DistanceAlong.wrappedValue
|
||||
# IfcLocalPlacement fallback (e.g. semantic-only alignment, or the placement could not yet
|
||||
# be expressed relative to a basis curve) carries no DistanceAlong; it is only ever used for
|
||||
# the starting referent, at distance 0.0.
|
||||
return 0.0
|
||||
|
||||
|
||||
def distance_along_from_station(file: ifcopenshell.file, alignment: entity_instance, station: float) -> Optional[float]:
|
||||
"""
|
||||
Given a station, returns the distance along the horizontal alignment.
|
||||
|
||||
If the alignment does not have stationing defined with an IfcReferent, the start of the alignment is assumed
|
||||
to be at station 0.0. That is, the station is the distance along.
|
||||
|
||||
.. note:: The current implementation does not account for station equations and assumes stationing is increasing along the alignment.
|
||||
Station equations (where Pset_Stationing.IncomingStation is set on a referent) are taken into account.
|
||||
For each STATION referent nested to the alignment, DistanceAlong (D) and the outgoing station (S, i.e.
|
||||
Pset_Stationing.Station) are read off, sorted by DistanceAlong. The requested station is located within
|
||||
the segment defined by the last referent whose outgoing station is less than or equal to it, and the
|
||||
distance along is computed as D + (station - S) for that referent.
|
||||
|
||||
If the station falls within a gap introduced by a forward (gap) station equation - that is, it was skipped
|
||||
over by the equation - there is no distance along that corresponds to it, and None is returned.
|
||||
|
||||
Note that an overlap (backward) station equation causes a range of stations to correspond to two distinct
|
||||
distances along the alignment, one on either side of the equation. This implementation returns the distance
|
||||
along in the segment following the equation (i.e. the outgoing side).
|
||||
|
||||
:param alignment: the alignment
|
||||
:param station: station value
|
||||
:return: distance along the horizontal alignment
|
||||
:return: distance along the horizontal alignment, or None if the station falls inside a station equation gap
|
||||
|
||||
Example:
|
||||
|
||||
@@ -43,6 +67,36 @@ def distance_along_from_station(file: ifcopenshell.file, alignment: entity_insta
|
||||
print(dist_along) # 100.00
|
||||
"""
|
||||
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||
dist_along = station - start_station
|
||||
return dist_along
|
||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
if referent_nest is None:
|
||||
start_station = ifcopenshell.api.alignment.get_alignment_start_station(file, alignment)
|
||||
return station - start_station
|
||||
|
||||
stations = [
|
||||
(
|
||||
_distance_along_of_referent(referent),
|
||||
ifcopenshell.util.element.get_pset(referent, name="Pset_Stationing", prop="Station"),
|
||||
)
|
||||
for referent in referent_nest.RelatedObjects
|
||||
]
|
||||
stations.sort(key=lambda entry: entry[0])
|
||||
|
||||
index = None
|
||||
for i, (distance_along, outgoing_station) in enumerate(stations):
|
||||
if outgoing_station <= station:
|
||||
index = i
|
||||
|
||||
if index is None:
|
||||
# station precedes the alignment's starting station; extrapolate from the first referent
|
||||
distance_along, outgoing_station = stations[0]
|
||||
return distance_along + (station - outgoing_station)
|
||||
|
||||
distance_along, outgoing_station = stations[index]
|
||||
|
||||
if index + 1 < len(stations):
|
||||
next_distance_along, _ = stations[index + 1]
|
||||
if station - outgoing_station > next_distance_along - distance_along:
|
||||
# the station was skipped over by a forward (gap) station equation
|
||||
return None
|
||||
|
||||
return distance_along + (station - outgoing_station)
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import numpy as np
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.util.placement
|
||||
from ifcopenshell import entity_instance
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@ def assign_cost_item_quantity(
|
||||
"products": products or [],
|
||||
"prop_name": prop_name,
|
||||
"formula": formula,
|
||||
"ifc_class" : ifc_class
|
||||
"ifc_class": ifc_class,
|
||||
}
|
||||
return usecase.execute()
|
||||
|
||||
@@ -134,7 +134,7 @@ class Usecase:
|
||||
continue
|
||||
self.assign_cost_control(related_object=product, cost_item=self.settings["cost_item"])
|
||||
if self.settings["formula"]:
|
||||
tree = ast.parse(self.settings["formula"], mode = "eval")
|
||||
tree = ast.parse(self.settings["formula"], mode="eval")
|
||||
collector = VariableExtractor()
|
||||
collector.visit(tree)
|
||||
variables = collector.variables
|
||||
@@ -144,10 +144,10 @@ class Usecase:
|
||||
value = getter(product, variable)
|
||||
|
||||
if value is None:
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
f"is missing (None). Check Pset/Qset or property name."
|
||||
)
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
f"is missing (None). Check Pset/Qset or property name."
|
||||
)
|
||||
elif value == 0:
|
||||
print(
|
||||
f"WARNING: Variable '{variable}' in product '{product.Name}' "
|
||||
@@ -159,7 +159,9 @@ class Usecase:
|
||||
|
||||
new_quantity = None
|
||||
for quantity in self.quantities:
|
||||
if quantity.Formula == self.settings["formula"] and len(self.settings["products"]) == 1: #Todo improve it
|
||||
if (
|
||||
quantity.Formula == self.settings["formula"] and len(self.settings["products"]) == 1
|
||||
): # Todo improve it
|
||||
new_quantity = quantity
|
||||
self.settings["ifc_class"] = quantity.is_a()
|
||||
continue
|
||||
@@ -184,23 +186,23 @@ class Usecase:
|
||||
self.update_cost_item_count()
|
||||
|
||||
def get_value_from_pset(
|
||||
self,
|
||||
product:ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
self,
|
||||
product: ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
) -> float:
|
||||
pset_name = v.split(".")[0]
|
||||
pset = ifcopenshell.util.element.get_pset(product, pset_name)
|
||||
pset_property_name = v.split(".")[1]
|
||||
return (pset or {}).get(pset_property_name,None)
|
||||
return (pset or {}).get(pset_property_name, None)
|
||||
|
||||
def get_value_from_qset(
|
||||
self,
|
||||
product:ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
self,
|
||||
product: ifcopenshell.entity_instance,
|
||||
v: str,
|
||||
) -> float:
|
||||
qtos = ifcopenshell.util.element.get_psets(product, qtos_only = True)
|
||||
qtos = ifcopenshell.util.element.get_psets(product, qtos_only=True)
|
||||
quantities = next(iter(qtos.values()), {})
|
||||
return (quantities or {}).get(v,None)
|
||||
return (quantities or {}).get(v, None)
|
||||
|
||||
def assign_cost_control(
|
||||
self, related_object: ifcopenshell.entity_instance, cost_item: ifcopenshell.entity_instance
|
||||
@@ -243,6 +245,7 @@ class Usecase:
|
||||
count += 1
|
||||
quantity[3] = count
|
||||
|
||||
|
||||
OPERATORS = {
|
||||
ast.Add: operator.add,
|
||||
ast.Sub: operator.sub,
|
||||
@@ -252,18 +255,20 @@ OPERATORS = {
|
||||
ast.USub: operator.neg,
|
||||
}
|
||||
|
||||
|
||||
def build_full_name(node):
|
||||
#used for variables with dots
|
||||
# used for variables with dots
|
||||
parts = []
|
||||
while isinstance(node, ast.Attribute):
|
||||
parts.append(node.attr)
|
||||
node = node.value
|
||||
parts.append(node.attr)
|
||||
node = node.value
|
||||
|
||||
if isinstance(node, ast.Name):
|
||||
parts.append(node.id)
|
||||
|
||||
return ".".join(reversed(parts))
|
||||
|
||||
|
||||
class VariableExtractor(ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.variables = set()
|
||||
@@ -274,6 +279,7 @@ class VariableExtractor(ast.NodeVisitor):
|
||||
def visit_Attribute(self, node):
|
||||
self.variables.add(build_full_name(node))
|
||||
|
||||
|
||||
class FormulaEvaluator(ast.NodeVisitor):
|
||||
def __init__(self, values):
|
||||
self.values = values
|
||||
@@ -281,7 +287,7 @@ class FormulaEvaluator(ast.NodeVisitor):
|
||||
def visit_BinOp(self, node):
|
||||
left = self.visit(node.left)
|
||||
right = self.visit(node.right)
|
||||
return OPERATORS[type(node.op)](left, right)
|
||||
return OPERATORS[type(node.op)](left, right) # ty: ignore[too-many-positional-arguments]
|
||||
|
||||
def visit_Name(self, node):
|
||||
return self.values[node.id]
|
||||
|
||||
@@ -221,8 +221,7 @@ for id in to_emit:
|
||||
statements.append("%s << %s" % (id, stmt))
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(
|
||||
r"""
|
||||
print(r"""
|
||||
# This file is generated by IfcOpenShell ifcexpressparser bootstrap.py
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -261,6 +260,4 @@ if __name__ == "__main__":
|
||||
mdl = importlib.import_module(output)
|
||||
mdl.Generator(m).emit()
|
||||
sys.stdout.write(m.schema.name)
|
||||
"""
|
||||
% ("\n ".join(statements))
|
||||
)
|
||||
""" % ("\n ".join(statements)))
|
||||
|
||||
@@ -695,6 +695,7 @@ codegen_rule("MOD", lambda context: "%")
|
||||
codegen_rule("TRUE", lambda context: "True")
|
||||
codegen_rule("FALSE", lambda context: "False")
|
||||
|
||||
|
||||
def _dotted_name(node: ast.AST):
|
||||
"""Return dotted name for Name/Attribute chains, else None."""
|
||||
if isinstance(node, ast.Name):
|
||||
@@ -704,6 +705,7 @@ def _dotted_name(node: ast.AST):
|
||||
return f"{base}.{node.attr}" if base else node.attr
|
||||
return None
|
||||
|
||||
|
||||
class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
def visit_Attribute(self, node):
|
||||
parents = []
|
||||
@@ -720,7 +722,7 @@ class AttributeGetattrTransformer(ast.NodeTransformer):
|
||||
if isinstance(node.ctx, ast.Store):
|
||||
return node
|
||||
|
||||
if _dotted_name(node) in ('ifcopenshell.create_entity', 'str.lower'):
|
||||
if _dotted_name(node) in ("ifcopenshell.create_entity", "str.lower"):
|
||||
return node
|
||||
|
||||
if node.attr.startswith("__"):
|
||||
|
||||
@@ -363,24 +363,18 @@ class EarlyBoundCodeWriter:
|
||||
)
|
||||
)
|
||||
|
||||
self.statements[self.statements.index("{factory_placeholder}")] = (
|
||||
"""
|
||||
self.statements[self.statements.index("{factory_placeholder}")] = """
|
||||
class %(schema_name)s_instance_factory : public IfcParse::instance_factory {
|
||||
virtual IfcUtil::IfcBaseClass* operator()(const IfcParse::declaration* decl, IfcEntityInstanceData&& data) const {
|
||||
%(instance_mapping)s
|
||||
}
|
||||
};
|
||||
"""
|
||||
% locals()
|
||||
)
|
||||
""" % locals()
|
||||
|
||||
""
|
||||
self.statements[self.statements.index("{string_pool_placeholder}")] = (
|
||||
"""
|
||||
self.statements[self.statements.index("{string_pool_placeholder}")] = """
|
||||
const std::string strings[] = {%s};
|
||||
"""
|
||||
% ",".join(map(lambda s: '"%s"s' % s, self.strings))
|
||||
)
|
||||
""" % ",".join(map(lambda s: '"%s"s' % s, self.strings))
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join(self.statements)
|
||||
|
||||
@@ -145,8 +145,7 @@ class configuration:
|
||||
config.set(
|
||||
"snippets",
|
||||
"print all wall ids",
|
||||
self.config_encode(
|
||||
"""
|
||||
self.config_encode("""
|
||||
###########################################################################
|
||||
# A simple script that iterates over all walls in the current model #
|
||||
# and prints their Globally unique IDs (GUIDS) to the console window #
|
||||
@@ -154,15 +153,13 @@ class configuration:
|
||||
|
||||
for wall in model.by_type("IfcWall"):
|
||||
print ("wall with global id: "+str(wall.GlobalId))
|
||||
""".lstrip()
|
||||
),
|
||||
""".lstrip()),
|
||||
)
|
||||
|
||||
config.set(
|
||||
"snippets",
|
||||
"print properties of current selection",
|
||||
self.config_encode(
|
||||
"""
|
||||
self.config_encode("""
|
||||
###########################################################################
|
||||
# A simple script that iterates over all IfcPropertySets of the currently #
|
||||
# selected object and prints them to the console #
|
||||
@@ -180,8 +177,7 @@ if selection:
|
||||
for prop in relDefinesByProperties.RelatingPropertyDefinition.HasProperties:
|
||||
print ("{:<20} :{}".format(prop.Name,prop.NominalValue.wrappedValue))
|
||||
print ("\\n")
|
||||
""".lstrip()
|
||||
),
|
||||
""".lstrip()),
|
||||
)
|
||||
with open(conf_file, "w") as configfile:
|
||||
config.write(configfile)
|
||||
|
||||
@@ -1697,10 +1697,16 @@ class uninitialized_tag: ...
|
||||
|
||||
def arrange_polygons(settings, polygons): ...
|
||||
def clear_schemas(): ...
|
||||
def construct_iterator(geometry_library, settings, file, num_threads): ...
|
||||
def construct_iterator_with_include_exclude(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def construct_iterator_with_include_exclude_globalid(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def construct_iterator_with_include_exclude_id(geometry_library, settings, file, elems, include, num_threads): ...
|
||||
def construct_iterator(geometry_library, settings, file, num_threads, logger=...): ...
|
||||
def construct_iterator_with_include_exclude(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def construct_iterator_with_include_exclude_globalid(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def construct_iterator_with_include_exclude_id(
|
||||
geometry_library, settings, file, elems, include, num_threads, logger=...
|
||||
): ...
|
||||
def convert_loop_to_function_item(loop): ...
|
||||
def create_box(*args): ...
|
||||
def create_epeck(*args): ...
|
||||
@@ -1717,8 +1723,8 @@ def line_segments_to_polygons(s, eps, segments): ...
|
||||
def map_shape(settings, instance): ...
|
||||
def nary_union(sequence): ...
|
||||
def new_IfcBaseClass(schema_identifier: str, name: str) -> entity_instance: ...
|
||||
def open(fn: str, readonly: bool = False) -> file: ...
|
||||
def parse_ifcxml(filename): ...
|
||||
def open(fn: str, readonly: bool = False, logger=...) -> file: ...
|
||||
def parse_ifcxml(filename, logger=...): ...
|
||||
def polygons_to_svg(*args): ...
|
||||
def read(data): ...
|
||||
def register_schema(arg1): ...
|
||||
|
||||
@@ -56,7 +56,7 @@ def append_zero_length_segments(file: ifcopenshell.file) -> ifcopenshell.file:
|
||||
for alignment in alignments:
|
||||
layouts = ifcopenshell.api.alignment.get_alignment_layouts(alignment)
|
||||
for layout in layouts:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, layout, include_referent=False)
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, layout)
|
||||
curve = ifcopenshell.api.alignment.get_layout_curve(layout)
|
||||
if curve:
|
||||
ifcopenshell.api.alignment.add_zero_length_segment(patched_file, curve)
|
||||
|
||||
@@ -355,8 +355,7 @@ def get_cost_rate(
|
||||
|
||||
class CostValueUnserialiser:
|
||||
def parse(self, formula: str):
|
||||
l = lark.Lark(
|
||||
"""start: formula
|
||||
l = lark.Lark("""start: formula
|
||||
formula: operand (operator operand)*
|
||||
operand: value | category "(" formula ")"
|
||||
value: NUMBER?
|
||||
@@ -393,8 +392,7 @@ class CostValueUnserialiser:
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
start = l.parse(formula)
|
||||
return self.get_formula(start.children[0])
|
||||
|
||||
|
||||
@@ -39,8 +39,7 @@ import ifcopenshell.util.shape
|
||||
import ifcopenshell.util.system
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
filter_elements_grammar = lark.Lark(
|
||||
"""start: filter_group
|
||||
filter_elements_grammar = lark.Lark("""start: filter_group
|
||||
filter_group: facet_list ("+" facet_list)*
|
||||
facet_list: facet ("," facet)*
|
||||
|
||||
@@ -111,11 +110,9 @@ filter_elements_grammar = lark.Lark(
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
get_element_grammar = lark.Lark(
|
||||
"""start: keys
|
||||
get_element_grammar = lark.Lark("""start: keys
|
||||
|
||||
keys: key ("." key)*
|
||||
key: quoted_string | regex_string | unquoted_string
|
||||
@@ -130,11 +127,9 @@ get_element_grammar = lark.Lark(
|
||||
WS: /[ \\t\\f\\r\\n]/+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
format_grammar = lark.Lark(
|
||||
"""start: expression
|
||||
format_grammar = lark.Lark("""start: expression
|
||||
|
||||
?expression: add_sub
|
||||
?add_sub: mul_div
|
||||
@@ -193,8 +188,7 @@ format_grammar = lark.Lark(
|
||||
NEWLINE: (CR? LF)+
|
||||
|
||||
%ignore WS // Disregard spaces in text
|
||||
"""
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
class FormatTransformer(lark.Transformer):
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def test_add_positioning_referent():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
||||
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||
)
|
||||
|
||||
assert referent.is_a("IfcReferent")
|
||||
assert referent.PredefinedType == "POSITION"
|
||||
assert referent.Name == "P.C."
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
assert len(referent.Positions) == 1
|
||||
rel_positions = referent.Positions[0]
|
||||
assert rel_positions.is_a("IfcRelPositions")
|
||||
assert rel_positions.RelatingPositioningElement == referent
|
||||
assert rel_positions.RelatedProducts == (segment,)
|
||||
|
||||
|
||||
def test_add_positioning_referent_creates_separate_referent_per_call():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", start_station=2000.0)
|
||||
|
||||
horizontal_layout = ifcopenshell.api.alignment.get_horizontal_layout(alignment)
|
||||
segment = ifcopenshell.api.alignment.get_layout_segments(horizontal_layout)[0]
|
||||
|
||||
first_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=segment
|
||||
)
|
||||
|
||||
other_product = file.createIfcBuildingElementProxy(GlobalId=ifcopenshell.guid.new(), Name="Sign")
|
||||
second_referent = ifcopenshell.api.alignment.add_positioning_referent(
|
||||
file, "P.C.", alignment, distance_along=0.0, station=2000.0, positioned_product=other_product
|
||||
)
|
||||
|
||||
# each call creates its own IfcReferent, each with its own IfcRelPositions to the product passed in
|
||||
assert first_referent != second_referent
|
||||
assert len(first_referent.Positions) == 1
|
||||
assert first_referent.Positions[0].RelatedProducts == (segment,)
|
||||
assert len(second_referent.Positions) == 1
|
||||
assert second_referent.Positions[0].RelatedProducts == (other_product,)
|
||||
|
||||
|
||||
test_add_positioning_referent()
|
||||
test_add_positioning_referent_creates_separate_referent_per_call()
|
||||
@@ -0,0 +1,115 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
|
||||
#
|
||||
# This file is part of IfcOpenShell.
|
||||
#
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# IfcOpenShell is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.unit
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
def _create_test_file():
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_si_unit(file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
return file
|
||||
|
||||
|
||||
def _create_test_alignment_with_vertical(file):
|
||||
# include_vertical=True so that get_curve() (IfcGradientCurve, on the "Axis" representation)
|
||||
# and get_basis_curve() (IfcCompositeCurve, on the "FootPrint" representation) are different
|
||||
# entities, letting the on_basis_curve option be observed.
|
||||
alignment = ifcopenshell.api.alignment.create(file, "TestAlignment", include_vertical=True, start_station=0.0)
|
||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment).is_a("IfcCompositeCurve")
|
||||
assert ifcopenshell.api.alignment.get_curve(alignment).is_a("IfcGradientCurve")
|
||||
assert ifcopenshell.api.alignment.get_basis_curve(alignment) != ifcopenshell.api.alignment.get_curve(alignment)
|
||||
return alignment
|
||||
|
||||
|
||||
def _assert_common_referent_asserts(referent, name, station):
|
||||
assert referent.is_a("IfcReferent")
|
||||
assert referent.PredefinedType == "STATION"
|
||||
assert referent.Name == name
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == station
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve():
|
||||
# on_basis_curve=None should behave the same as on_basis_curve=True
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=None
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
assert referent.ObjectPlacement.RelativePlacement.Location.BasisCurve == ifcopenshell.api.alignment.get_basis_curve(
|
||||
alignment
|
||||
)
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_true():
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=True
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
assert referent.ObjectPlacement.RelativePlacement.Location.BasisCurve == ifcopenshell.api.alignment.get_basis_curve(
|
||||
alignment
|
||||
)
|
||||
|
||||
|
||||
def test_add_stationing_referent_on_basis_curve_false():
|
||||
# with a vertical layout present, on_basis_curve=False positions the referent on the
|
||||
# alignment curve (IfcGradientCurve) rather than on the basis curve (IfcCompositeCurve).
|
||||
file = _create_test_file()
|
||||
alignment = _create_test_alignment_with_vertical(file)
|
||||
|
||||
referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "1+00.000", alignment, distance_along=100.0, station=100.0, on_basis_curve=False
|
||||
)
|
||||
|
||||
_assert_common_referent_asserts(referent, "1+00.000", 100.0)
|
||||
|
||||
assert referent.ObjectPlacement.is_a("IfcLinearPlacement")
|
||||
basis_curve = referent.ObjectPlacement.RelativePlacement.Location.BasisCurve
|
||||
assert basis_curve == ifcopenshell.api.alignment.get_curve(alignment)
|
||||
assert basis_curve != ifcopenshell.api.alignment.get_basis_curve(alignment)
|
||||
|
||||
|
||||
test_add_stationing_referent_on_basis_curve_none_defaults_to_basis_curve()
|
||||
test_add_stationing_referent_on_basis_curve_true()
|
||||
test_add_stationing_referent_on_basis_curve_false()
|
||||
@@ -48,5 +48,26 @@ def test_add_stationing_to_alignment():
|
||||
assert ifcopenshell.util.element.get_pset(element=referent, name="Pset_Stationing", prop="Station") == 2000.0
|
||||
assert referent.ObjectPlacement != None
|
||||
|
||||
# add a station equation at 1000 distance along. this is station 3+000 in coming and 4+000 outgoing.
|
||||
# this is a gap equation.
|
||||
second_referent = ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "4+000.000", alignment, distance_along=1000.0, station=4000.0, incoming_station=3000.0
|
||||
)
|
||||
|
||||
referent_nest = ifcopenshell.api.alignment.get_referent_nest(file, alignment)
|
||||
assert len(referent_nest.RelatedObjects) == 2
|
||||
|
||||
assert second_referent == referent_nest.RelatedObjects[1]
|
||||
|
||||
assert second_referent.PredefinedType == "STATION"
|
||||
assert second_referent.Name == "4+000.000"
|
||||
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing")
|
||||
assert ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="Station") == 4000.0
|
||||
assert (
|
||||
ifcopenshell.util.element.get_pset(element=second_referent, name="Pset_Stationing", prop="IncomingStation")
|
||||
== 3000.0
|
||||
)
|
||||
assert second_referent.ObjectPlacement != None
|
||||
|
||||
|
||||
test_add_stationing_to_alignment()
|
||||
|
||||
@@ -21,9 +21,12 @@ import math
|
||||
|
||||
import pytest
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.alignment
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.api.unit
|
||||
import numpy as np
|
||||
import ifcopenshell.util.unit
|
||||
|
||||
|
||||
def test_create_representation():
|
||||
|
||||
@@ -53,4 +53,56 @@ def test_distance_along_from_station():
|
||||
assert ifcopenshell.api.alignment.distance_along_from_station(file, alignment, 17525.36) == pytest.approx(7525.36)
|
||||
|
||||
|
||||
def test_distance_along_from_station_with_station_equations():
|
||||
# Reproduces the worked example from the IFC Alignment Geometry Implementation Guide, chapter 9.2.6:
|
||||
# a gap equation (P3: incoming 14+00.00, outgoing 17+00.00) and an overlap equation
|
||||
# (P4: incoming 19+00.00, outgoing 18+50.00).
|
||||
file = ifcopenshell.file(schema="IFC4X3")
|
||||
project = file.createIfcProject(GlobalId=ifcopenshell.guid.new(), Name="Test")
|
||||
length = ifcopenshell.api.unit.add_conversion_based_unit(file, name="foot")
|
||||
ifcopenshell.api.unit.assign_unit(file, units=[length])
|
||||
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
|
||||
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
|
||||
file,
|
||||
context_type="Model",
|
||||
context_identifier="Axis",
|
||||
target_view="MODEL_VIEW",
|
||||
parent=geometric_representation_context,
|
||||
)
|
||||
|
||||
coordinates = [(500.0, 2500.0), (3340.0, 660.0), (4340.0, 5000.0), (7600.0, 4560.0), (8480.0, 2010.0)]
|
||||
radii = [(1000.0), (1250.0), (950.0)]
|
||||
vpoints = [(0.0, 100.0), (2000.0, 135.0), (5000.0, 105.0), (7400.0, 153.0), (9800.0, 105.0), (12800.0, 90.0)]
|
||||
lengths = [(1600.0), (1200.0), (2000.0), (800.0)]
|
||||
|
||||
alignment = ifcopenshell.api.alignment.create_by_pi_method(
|
||||
file, "TestAlignment", coordinates, radii, vpoints, lengths, start_station=1000.0
|
||||
)
|
||||
|
||||
ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "P3", alignment, distance_along=400.0, station=1700.0, incoming_station=1400.0
|
||||
)
|
||||
ifcopenshell.api.alignment.add_stationing_referent(
|
||||
file, "P4", alignment, distance_along=600.0, station=1850.0, incoming_station=1900.0
|
||||
)
|
||||
|
||||
distance_along_from_station = ifcopenshell.api.alignment.distance_along_from_station
|
||||
|
||||
# between P2 and P3: Sta. 13+00.00
|
||||
assert distance_along_from_station(file, alignment, 1300.0) == pytest.approx(300.0)
|
||||
|
||||
# between P3 and P4: Sta. 18+00.00
|
||||
assert distance_along_from_station(file, alignment, 1800.0) == pytest.approx(500.0)
|
||||
|
||||
# between P4 and P5: Sta. 19+25.00
|
||||
assert distance_along_from_station(file, alignment, 1925.0) == pytest.approx(675.0)
|
||||
|
||||
# Sta. 15+00.00 falls inside the gap opened by the equation at P3 and has no corresponding distance along
|
||||
assert distance_along_from_station(file, alignment, 1500.0) is None
|
||||
|
||||
# Sta. 18+75.00 falls inside the overlap zone at P4; the post-equation (outgoing) match is returned
|
||||
assert distance_along_from_station(file, alignment, 1875.0) == pytest.approx(625.0)
|
||||
|
||||
|
||||
test_distance_along_from_station()
|
||||
test_distance_along_from_station_with_station_equations()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def test_skip_over_non_entity_instance():
|
||||
data = """
|
||||
ISO-10303-21;
|
||||
|
||||
@@ -46,4 +46,4 @@ def test_file(filename):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main(["-sx", __file__, '--import-mode=importlib'])
|
||||
pytest.main(["-sx", __file__, "--import-mode=importlib"])
|
||||
|
||||
@@ -111,7 +111,7 @@ class Patcher(ifcpatch.BasePatcher):
|
||||
if element.is_a("IfcProject"):
|
||||
proj = self.new.add(element)
|
||||
for ctx in element.RepresentationContexts or ():
|
||||
for coop in getattr(ctx, 'HasCoordinateOperation', ()):
|
||||
for coop in getattr(ctx, "HasCoordinateOperation", ()):
|
||||
self.new.add(coop)
|
||||
return proj
|
||||
return ifcopenshell.api.project.append_asset(
|
||||
|
||||
@@ -33,9 +33,7 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
Points=point_list,
|
||||
Segments=segments,
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
return curve
|
||||
|
||||
def test_run_without_segments(self):
|
||||
@@ -80,9 +78,7 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
Points=point_list,
|
||||
Segments=[self.file.createIfcLineIndex((1, 2, 3, 4, 1))],
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
ifcpatch.execute(
|
||||
{"input": "input.ifc", "file": self.file, "recipe": "DowngradeIndexedPolyCurve", "arguments": []}
|
||||
)
|
||||
@@ -110,9 +106,7 @@ class TestDowngradeIndexedPolyCurve(test.bootstrap.IFC4):
|
||||
self.file.createIfcLineIndex((3, 4)),
|
||||
],
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve
|
||||
)
|
||||
self.file.create_entity("IfcArbitraryClosedProfileDef", ProfileType="AREA", OuterCurve=curve)
|
||||
ifcpatch.execute(
|
||||
{"input": "input.ifc", "file": self.file, "recipe": "DowngradeIndexedPolyCurve", "arguments": []}
|
||||
)
|
||||
|
||||
@@ -977,12 +977,14 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
|
||||
if (item == nullptr) {
|
||||
throw IfcParse::IfcException("Failed to convert placement");
|
||||
}
|
||||
/*
|
||||
if (st.get<ifcopenshell::geometry::settings::ConvertBackUnits>().get()) {
|
||||
// we pass the settings to the Transformation object, but access the data just offloads to the
|
||||
// generic cartesian_base<Matrix4> so there's no time to apply the settings to the translation part.
|
||||
item = ifcopenshell::geometry::taxonomy::matrix4::ptr(item->clone_());
|
||||
item->components().col(3).head<3>() /= kernel.settings().get<ifcopenshell::geometry::settings::LengthUnit>().get();
|
||||
}
|
||||
*/
|
||||
return new IfcGeom::Transformation(kernel.settings(), item);
|
||||
} else {
|
||||
if (!representation) {
|
||||
|
||||
@@ -33,6 +33,7 @@ WaveFrontOBJSerializer::WaveFrontOBJSerializer(const stream_or_filename& obj_fil
|
||||
, mtl_stream(mtl_filename)
|
||||
, vcount_total(1)
|
||||
, ncount_total(1)
|
||||
, uvcount_total(1)
|
||||
{
|
||||
obj_stream.stream << std::setprecision(settings.get<ifcopenshell::geometry::settings::FloatingPointDigits>().get());
|
||||
mtl_stream.stream << std::setprecision(settings.get<ifcopenshell::geometry::settings::FloatingPointDigits>().get());
|
||||
@@ -96,6 +97,7 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o)
|
||||
|
||||
size_t vcount = mesh.verts().size() / 3;
|
||||
size_t ncount = mesh.normals().size() / 3;
|
||||
size_t uvcount = mesh.uvs().size() / 2;
|
||||
|
||||
for (auto it = mesh.verts().begin(); it != mesh.verts().end();) {
|
||||
const double x = *(it++);
|
||||
@@ -151,9 +153,12 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o)
|
||||
const int n3 = v3 - vcount_total + ncount_total;
|
||||
|
||||
if (has_normals && has_uvs) {
|
||||
obj_stream.stream << "f " << v1 << "/" << n1 << "/" << n1 << " "
|
||||
<< v2 << "/" << n2 << "/" << n2 << " "
|
||||
<< v3 << "/" << n3 << "/" << n3 << "\n";
|
||||
const int t1 = v1 - vcount_total + uvcount_total;
|
||||
const int t2 = v2 - vcount_total + uvcount_total;
|
||||
const int t3 = v3 - vcount_total + uvcount_total;
|
||||
obj_stream.stream << "f " << v1 << "/" << t1 << "/" << n1 << " "
|
||||
<< v2 << "/" << t2 << "/" << n2 << " "
|
||||
<< v3 << "/" << t3 << "/" << n3 << "\n";
|
||||
} else if (has_normals) {
|
||||
obj_stream.stream << "f " << v1 << "//" << n1 << " "
|
||||
<< v2 << "//" << n2 << " "
|
||||
@@ -196,4 +201,5 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o)
|
||||
|
||||
vcount_total += vcount;
|
||||
ncount_total += ncount;
|
||||
uvcount_total += uvcount;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class SERIALIZERS_API WaveFrontOBJSerializer : public WriteOnlyGeometrySerialize
|
||||
private:
|
||||
stream_or_filename obj_stream;
|
||||
stream_or_filename mtl_stream;
|
||||
size_t vcount_total, ncount_total;
|
||||
size_t vcount_total, ncount_total, uvcount_total;
|
||||
std::set<std::string> materials;
|
||||
public:
|
||||
WaveFrontOBJSerializer(const stream_or_filename& obj_filename, const stream_or_filename& mtl_filename, const ifcopenshell::geometry::Settings& geometry_settings, const ifcopenshell::geometry::SerializerSettings& settings, Logger& logger = Logger::Root());
|
||||
|
||||
Reference in New Issue
Block a user