mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-15 18:14:08 +00:00
Merge branch 'v0.8.0' into ifcmax/initial-refresh
This commit is contained in:
+2
-2
@@ -17,8 +17,8 @@
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
SHELL := sh
|
||||
PYTHON:=python3.11
|
||||
PIP:=pip3.11
|
||||
PYTHON:=python3
|
||||
PIP:=pip3
|
||||
PATCH:=patch
|
||||
SED:=sed -i
|
||||
VENV_ACTIVATE:=bin/activate
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
Copyright (c) 2011-2012, Nikita Volchenkov (<nikitavolchenkov@gmail.com>),
|
||||
with Reserved Font Name OpenGost Type B.
|
||||
|
||||
Copyright (c) 2012, Valek Filippov (<frob@gnome.org>).
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -118,6 +118,19 @@ def update_shader_graph(self: Union["Texture", "BIMStylesProperties"], context:
|
||||
tool.Loader.create_surface_style_with_textures(material, shading_data, textures_data)
|
||||
|
||||
|
||||
def _make_clear_null_updater(null_prop: str):
|
||||
def _update(self: "BIMStylesProperties", context: bpy.types.Context) -> None:
|
||||
self[null_prop] = False
|
||||
update_shader_graph(self, context)
|
||||
|
||||
return _update
|
||||
|
||||
|
||||
update_diffuse_colour = _make_clear_null_updater("is_diffuse_colour_null")
|
||||
update_specular_colour = _make_clear_null_updater("is_specular_colour_null")
|
||||
update_specular_highlight_value = _make_clear_null_updater("is_specular_highlight_null")
|
||||
|
||||
|
||||
UV_MODES = [
|
||||
("UV", "UV", _("Actual UV data presented on the geometry")),
|
||||
("Generated", "Generated", _("Automatically-generated UV from the vertex positions of the mesh")),
|
||||
@@ -221,24 +234,29 @@ class BIMStylesProperties(PropertyGroup):
|
||||
transparency: bpy.props.FloatProperty(
|
||||
name="Transparency", default=0.0, min=0.0, max=1.0, update=update_shader_graph
|
||||
)
|
||||
# TODO: do something on null?
|
||||
is_diffuse_colour_null: BoolProperty(name="Is Null")
|
||||
is_diffuse_colour_null: BoolProperty(name="Is Null", update=update_shader_graph)
|
||||
diffuse_colour_class: EnumProperty(
|
||||
items=[(x, x, "") for x in get_args(ColourClass)],
|
||||
name="Diffuse Colour Class",
|
||||
update=update_shader_graph,
|
||||
update=update_diffuse_colour,
|
||||
)
|
||||
diffuse_colour: bpy.props.FloatVectorProperty(
|
||||
name="Diffuse Colour", subtype="COLOR", default=(1, 1, 1), min=0.0, max=1.0, size=3, update=update_shader_graph
|
||||
name="Diffuse Colour",
|
||||
subtype="COLOR",
|
||||
default=(1, 1, 1),
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3,
|
||||
update=update_diffuse_colour,
|
||||
)
|
||||
diffuse_colour_ratio: bpy.props.FloatProperty(
|
||||
name="Diffuse Ratio", default=0.0, min=0.0, max=1.0, update=update_shader_graph
|
||||
name="Diffuse Ratio", default=0.0, min=0.0, max=1.0, update=update_diffuse_colour
|
||||
)
|
||||
is_specular_colour_null: BoolProperty(name="Is Null")
|
||||
is_specular_colour_null: BoolProperty(name="Is Null", update=update_shader_graph)
|
||||
specular_colour_class: EnumProperty(
|
||||
items=[(x, x, "") for x in get_args(ColourClass)],
|
||||
name="Specular Colour Class",
|
||||
update=update_shader_graph,
|
||||
update=update_specular_colour,
|
||||
default="IfcNormalisedRatioMeasure",
|
||||
)
|
||||
specular_colour: bpy.props.FloatVectorProperty(
|
||||
@@ -248,7 +266,7 @@ class BIMStylesProperties(PropertyGroup):
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
size=3,
|
||||
update=update_shader_graph,
|
||||
update=update_specular_colour,
|
||||
)
|
||||
specular_colour_ratio: bpy.props.FloatProperty(
|
||||
name="Specular Ratio",
|
||||
@@ -256,16 +274,16 @@ class BIMStylesProperties(PropertyGroup):
|
||||
default=0.0,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
update=update_shader_graph,
|
||||
update=update_specular_colour,
|
||||
)
|
||||
is_specular_highlight_null: BoolProperty(name="Is Null")
|
||||
is_specular_highlight_null: BoolProperty(name="Is Null", update=update_shader_graph)
|
||||
specular_highlight: bpy.props.FloatProperty(
|
||||
name="Specular Highlight",
|
||||
description="Used as Roughness value in PHYSICAL Reflectance Method",
|
||||
default=0.0,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
update=update_shader_graph,
|
||||
update=update_specular_highlight_value,
|
||||
)
|
||||
reflectance_method: EnumProperty(
|
||||
name="Reflectance Method",
|
||||
|
||||
@@ -987,7 +987,8 @@ class Cost(bonsai.core.tool.Cost):
|
||||
def disable_editing_cost_item_parent(cls) -> None:
|
||||
props = cls.get_cost_props()
|
||||
props.active_cost_item_id = 0
|
||||
props.change_cost_item_parent = False
|
||||
if props.change_cost_item_parent == True:
|
||||
props.change_cost_item_parent = False
|
||||
|
||||
@classmethod
|
||||
def load_cost_item_quantities(cls, cost_item: Optional[ifcopenshell.entity_instance] = None) -> None:
|
||||
|
||||
@@ -203,6 +203,11 @@ class Style(bonsai.core.tool.Style):
|
||||
|
||||
available_props = props.bl_rna.properties.keys()
|
||||
for prop_blender, prop_ifc in STYLE_PROPS_MAP.items():
|
||||
null_prop_name = f"is_{prop_blender}_null"
|
||||
if null_prop_name in available_props and getattr(props, null_prop_name):
|
||||
surface_style_data[prop_ifc] = None
|
||||
continue
|
||||
|
||||
class_prop_name = f"{prop_blender}_class"
|
||||
|
||||
# get detailed color properties if available
|
||||
|
||||
@@ -61,7 +61,62 @@ When Blender ships with a new Python version:
|
||||
- What to update
|
||||
* - ``.github/workflows/ci-lint.yaml``
|
||||
- ``MIN_BLENDER_PY_VERSION``
|
||||
* - ``.github/scripts/publish-bonsai-releases.py``
|
||||
- ``CURRENT_PYTHON_VERSION``
|
||||
* - ``src/bonsai/Makefile``
|
||||
- ``SUPPORTED_PYVERSIONS``
|
||||
* - ``src/bonsai/scripts/dev_environment.py``
|
||||
- ``PYTHON_VERSION`` mapping (Blender version, bundled Python version)
|
||||
|
||||
Release
|
||||
-------
|
||||
|
||||
Notes:
|
||||
|
||||
- Typically all packages are released at once using the same version schema
|
||||
- The ``README.md`` badges can serve as a visual reference for what versions have been released
|
||||
- Corrective Release (if needed after a standard release):
|
||||
|
||||
- Create a new branch from the release tag (e.g., from the ``ifcopenshell-0.8.5`` tag)
|
||||
- Update ``VERSION`` with the ``-post1`` suffix (e.g., ``0.8.5-post1``, **not** ``.post1``)
|
||||
- The hyphen is required for semantic versioning compliance; Blender will not process ``.post1`` suffixes correctly
|
||||
- Follow the standard release process for the corrective version
|
||||
|
||||
- Multiple Blender Python Versions:
|
||||
|
||||
- Blender does not allow multiple builds for the same platform with different Python versions (e.g., cannot have both ``bonsai_py311-0.8.5-windows-x64.zip`` and ``bonsai_py313-0.8.5-windows-x64.zip``)
|
||||
- Workaround: publish different Python versions as different extension versions (e.g., py313 as ``0.8.5`` and py311 as ``0.8.5-post1``)
|
||||
- Set the maximum Blender version on the Blender extensions platform UI to prevent conflicts (e.g., set max version ``5.1.0`` for ``0.8.5-post1``, which restricts it to versions below 5.1.0)
|
||||
|
||||
Things to update:
|
||||
|
||||
- ``.github/workflows/ci-bcf-pypi.yml`` - release `bcf-client <https://pypi.org/project/bcf-client/>`_ to PyPI
|
||||
- ``.github/workflows/ci-bonsai.yml`` - release bonsai in GitHub releases
|
||||
- ``.github/workflows/ci-bsdd-pypi.yaml`` - release `bsdd <https://pypi.org/project/bsdd/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifc4d-pypi.yaml`` - release `ifc4d <https://pypi.org/project/ifc4d/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifc5d-pypi.yaml`` - release `ifc5d <https://pypi.org/project/ifc5d/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcclash-pypi.yaml`` - release `ifcclash <https://pypi.org/project/ifcclash/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcconvert.yml`` - release ifcconvert binaries in GitHub releases
|
||||
- ``.github/workflows/ci-ifccsv-pypi.yaml`` - release `ifccsv <https://pypi.org/project/ifccsv/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcdiff-pypi.yaml`` - release `ifcdiff <https://pypi.org/project/ifcdiff/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcedit-pypi.yaml`` - release `ifcedit <https://pypi.org/project/ifcedit/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcfm-pypi.yaml`` - release `ifcfm <https://pypi.org/project/ifcfm/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifccityjson-pypi.yaml`` - release `ifccityjson <https://pypi.org/project/ifccityjson/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcmcp-pypi.yaml`` - release `ifcopenshell-mcp <https://pypi.org/project/ifcopenshell-mcp/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcopenshell-python.yml`` - release ifcopenshell-python binaries in GitHub releases
|
||||
- ``.github/workflows/ci-ifcopenshell-python-pypi.yml`` - release `ifcopenshell <https://pypi.org/project/ifcopenshell/>`_ wheels to PyPI
|
||||
- ``.github/workflows/ci-ifcpatch-pypi.yaml`` - release `ifcpatch <https://pypi.org/project/ifcpatch/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcquery-pypi.yaml`` - release `ifcquery <https://pypi.org/project/ifcquery/>`_ to PyPI
|
||||
- ``.github/workflows/ci-ifcsverchok.yml`` - release ifcsverchok Blender add-on in GitHub releases
|
||||
- ``.github/workflows/ci-ifctester-pypi.yml`` - release `ifctester <https://pypi.org/project/ifctester/>`_ to PyPI
|
||||
- ``.github/workflows/ci-pyodide-wasm-release.yml`` - release pyodide wasm wheel to `wasm-wheels <https://github.com/IfcOpenShell/wasm-wheels>`_
|
||||
- ``.github/workflows/publish-bonsai-releases.yml`` - publish Bonsai Blender extension to `Blender extensions platform <https://extensions.blender.org/add-ons/bonsai/>`_
|
||||
|
||||
- ❗ Requires ``BLENDER_EXTENSIONS_TOKEN`` secret to be set - ❗ not yet configured
|
||||
|
||||
- Publishing documentation and websites (see `website <https://github.com/IfcOpenShell/website>`_ repository):
|
||||
|
||||
- `ifcopenshell-docs.yml` - builds and publishes IfcOpenShell documentation to `docs.ifcopenshell.org <https://docs.ifcopenshell.org>`_ (`ifcopenshell_org_docs <https://github.com/IfcOpenShell/ifcopenshell_org_docs>`_ repo)
|
||||
- `bonsai-docs.yml` - builds and publishes Bonsai documentation to `docs.bonsaibim.org <https://docs.bonsaibim.org>`_ (`bonsaibim_org_docs <https://github.com/IfcOpenShell/bonsaibim_org_docs>`_ repo)
|
||||
- `publish-websites.yml` - publishes `bonsaibim.org <https://bonsaibim.org>`_ (`bonsaibim_org_static_html <https://github.com/IfcOpenShell/bonsaibim_org_static_html>`_ repo) and `ifcopenshell.org <https://ifcopenshell.org>`_ (`ifcopenshell_org_static_html <https://github.com/IfcOpenShell/ifcopenshell_org_static_html>`_ repo)
|
||||
- ``VERSION`` to the release version - **UPDATE THIS LAST** as all workflows above typically depend on it to set the version correctly
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Bonsai - OpenBIM Blender Add-on
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
# This file is part of Bonsai.
|
||||
#
|
||||
# Bonsai is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Bonsai 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import test.bim.bootstrap
|
||||
import ifcopenshell.api.cost
|
||||
|
||||
import bonsai.core.tool
|
||||
import bonsai.tool as tool
|
||||
import test.bim.bootstrap
|
||||
from test.bim.bootstrap import NewFile
|
||||
|
||||
from bonsai.tool.cost import Cost as subject
|
||||
|
||||
class TestImplementsTool(NewFile):
|
||||
def test_run(self):
|
||||
assert isinstance(subject(), bonsai.core.tool.Cost)
|
||||
|
||||
class TestDisableEditingCostItemParent(NewFile):
|
||||
def test_avoid_recursion_error(newfile, monkeypatch):
|
||||
class DummyProps:
|
||||
def __init__(self):
|
||||
self.change_cost_item_parent = None
|
||||
self.active_cost_item_id = 5
|
||||
|
||||
props = DummyProps()
|
||||
monkeypatch.setattr(
|
||||
"bonsai.tool.Cost.get_cost_props",
|
||||
lambda: props
|
||||
)
|
||||
subject.disable_editing_cost_item_parent()
|
||||
assert props.active_cost_item_id == 0
|
||||
assert props.change_cost_item_parent is not False
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
SHELL := sh
|
||||
IS_STABLE:=FALSE
|
||||
PYTHON:=python3.11
|
||||
PIP:=pip3.11
|
||||
PYTHON:=python3
|
||||
PIP:=pip3
|
||||
VERSION:=$(shell cat ../../VERSION)
|
||||
VERSION_DATE:=$(shell date '+%y%m%d')
|
||||
SED:=sed -i
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
"Area": "get_net_side_area",
|
||||
"Height": "get_height",
|
||||
"Perimeter": "get_rectangular_perimeter",
|
||||
"Width": "get_length"
|
||||
"Width": "get_x"
|
||||
}
|
||||
},
|
||||
"IfcDuctFitting + IfcDuctFittingType": {
|
||||
|
||||
@@ -29,16 +29,7 @@ async function ensurePyodide() {
|
||||
const micropip = pyodide.pyimport("micropip");
|
||||
micropip.install("python-dateutil")
|
||||
|
||||
// Detect python minor version (3.12 vs 3.13) and pick a matching wheel.
|
||||
const pyVer = pyodide.runPython(`
|
||||
import sys
|
||||
f"{sys.version_info.major}.{sys.version_info.minor}"
|
||||
`);
|
||||
|
||||
const wheelUrl =
|
||||
pyVer === "3.13"
|
||||
? "https://ifcopenshell.github.io/wasm-wheels/ifcopenshell-0.8.3+34a1bc6-cp313-cp313-emscripten_4_0_9_wasm32.whl"
|
||||
: "https://ifcopenshell.github.io/wasm-wheels/ifcopenshell-0.8.2+d50e806-cp312-cp312-emscripten_3_1_58_wasm32.whl";
|
||||
const wheelUrl = "https://ifcopenshell.github.io/wasm-wheels/ifcopenshell-0.8.5-cp313-cp313-pyodide_2025_0_wasm32.whl";
|
||||
|
||||
await micropip.install(wheelUrl);
|
||||
|
||||
|
||||
@@ -52,6 +52,15 @@ taxonomy::ptr mapping::map_impl(const IfcSchema::IfcPointByDistanceExpression* i
|
||||
if (inst->OffsetVertical().has_value()) {
|
||||
auto offset_vertical = inst->OffsetVertical().get() * length_unit_;
|
||||
o += offset_vertical * z;
|
||||
|
||||
auto tmp1 = (z * offset_vertical).eval();
|
||||
auto tmp2 = (Eigen::Vector3d(0, 0, 1) * offset_vertical).eval();
|
||||
auto tmp3 = (tmp1 - tmp2).eval();
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "local z: " << z.x() << "," << z.y() << "," << z.z() << "; delta: " << tmp3.x() << "," << tmp3.y() << "," << tmp3.z();
|
||||
auto osss = oss.str();
|
||||
std::wcout << osss.c_str() << std::endl;
|
||||
}
|
||||
|
||||
if (inst->OffsetLongitudinal().has_value()) {
|
||||
|
||||
@@ -8,10 +8,10 @@ sessions.
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pip install ifcmcp
|
||||
pip install ifcopenshell-mcp
|
||||
```
|
||||
|
||||
Requires `ifcopenshell`, `ifcquery`, and `ifcedit`. The `mcp` package is an optional dependency needed to run the server; install it with `pip install ifcmcp[mcp]` or add `mcp` separately.
|
||||
Requires `ifcopenshell`, `ifcquery`, and `ifcedit`. The `mcp` package is an optional dependency needed to run the server; install it with `pip install ifcopenshell-mcp[mcp]` or add `mcp` separately.
|
||||
|
||||
## Running the server
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ VERSION_DATE:=$(shell date '+%y%m%d')
|
||||
PYVERSION:=py311
|
||||
PLATFORM:=linux64
|
||||
|
||||
PYTHON:=python3.11
|
||||
PIP:=pip3.11
|
||||
PYTHON:=python3
|
||||
PIP:=pip3
|
||||
SED:=sed -i
|
||||
VENV_ACTIVATE:=bin/activate
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_ins
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(file, history)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
if len(inverse.RelatedObjects) >= 2 or inverse.RelatingControl == cost_item:
|
||||
if len(inverse.RelatedObjects) >= 2:
|
||||
continue
|
||||
history = inverse.OwnerHistory
|
||||
file.remove(inverse)
|
||||
|
||||
@@ -42,7 +42,8 @@ WHITE = numpy.array((1.0, 1.0, 1.0))
|
||||
|
||||
DO_NOTHING = lambda *args: None
|
||||
|
||||
ARRANGE_POLYGON_SETTINGS = W.arrange_polygon_settings() if hasattr(W, 'arrange_polygon_settings') else None
|
||||
ARRANGE_POLYGON_SETTINGS = W.arrange_polygon_settings() if hasattr(W, "arrange_polygon_settings") else None
|
||||
|
||||
|
||||
@dataclass
|
||||
class draw_settings:
|
||||
|
||||
@@ -196,9 +196,12 @@ def get_cost_items_for_product(product: ifcopenshell.entity_instance) -> list[if
|
||||
:return: A list of IfcCostItem objects representing the cost items related to the product.
|
||||
"""
|
||||
cost_items = []
|
||||
for assignment in product.HasAssignments:
|
||||
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl.is_a("IfcCostItem"):
|
||||
cost_items.append(assignment.RelatingControl)
|
||||
for assignment in product.HasAssignments or []:
|
||||
if assignment.is_a("IfcRelAssignsToControl"):
|
||||
control = assignment.RelatingControl
|
||||
if control and control.is_a("IfcCostItem"):
|
||||
cost_items.append(control)
|
||||
|
||||
return cost_items
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# IfcOpenShell - IFC toolkit and geometry engine
|
||||
# Copyright (C) 2021 Dion Moult <dion@thinkmoult.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 pytest
|
||||
|
||||
import ifcopenshell.api.control
|
||||
import ifcopenshell.api.cost
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api.root
|
||||
|
||||
import ifcopenshell.util.cost as subject
|
||||
|
||||
class TestGetCostItemForProduct(test.bootstrap.IFC4):
|
||||
def test_run(self):
|
||||
model = self.file
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
cost_schedule = ifcopenshell.api.cost.add_cost_schedule(model)
|
||||
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=cost_schedule)
|
||||
ifcopenshell.api.control.assign_control(model, related_objects=[element], relating_control=item1)
|
||||
assert list(subject.get_cost_items_for_product(element)) == [item1]
|
||||
|
||||
def test_remove_cost_item(self):
|
||||
model = self.file
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
cost_schedule = ifcopenshell.api.cost.add_cost_schedule(model)
|
||||
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=cost_schedule)
|
||||
ifcopenshell.api.control.assign_control(model, related_objects=[element], relating_control=item1)
|
||||
ifcopenshell.api.cost.remove_cost_item(model, cost_item = item1)
|
||||
assert list(subject.get_cost_items_for_product(element)) == []
|
||||
|
||||
def test_no_assigned_cost_items(self):
|
||||
model = self.file
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
cost_schedule = ifcopenshell.api.cost.add_cost_schedule(model)
|
||||
item1 = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=cost_schedule)
|
||||
assert list(subject.get_cost_items_for_product(element)) == []
|
||||
|
||||
@@ -475,7 +475,7 @@ class IFC_PARSE_API IfcHierarchyHelper : public IfcParse::IfcFile {
|
||||
t->set_attribute_value(1, owner_hist);
|
||||
int relating_index = 4;
|
||||
int related_index = 5;
|
||||
if (T::Class().name() == "IfcRelContainedInSpatialStructure" || std::is_base_of<typename Schema::IfcRelDefines, T>::value) {
|
||||
if (T::Class().name() == "IfcRelContainedInSpatialStructure" || T::Class().name() == "IfcRelReferencedInSpatialStructure" || std::is_base_of<typename Schema::IfcRelDefines, T>::value) {
|
||||
// some classes have attributes reversed.
|
||||
std::swap(relating_index, related_index);
|
||||
}
|
||||
|
||||
@@ -1461,8 +1461,8 @@ double point_to_oriented_box_distance(const DPoint& p, const MergedBoxRecord& bo
|
||||
|
||||
std::map<Point_2, std::vector<Point_2>> snap_points_to_box_axes(
|
||||
const CenterLineGraphData& graph,
|
||||
const std::vector<MergedBoxRecord>& boxes)
|
||||
{
|
||||
const std::vector<MergedBoxRecord>& boxes,
|
||||
const K::FT& max_projection_distance) {
|
||||
std::vector<Point_2> snapped_points(graph.points.size());
|
||||
|
||||
for (size_t i = 0; i < graph.points.size(); ++i) {
|
||||
@@ -1521,7 +1521,10 @@ std::map<Point_2, std::vector<Point_2>> snap_points_to_box_axes(
|
||||
}
|
||||
return a.line_distance < b.line_distance;
|
||||
});
|
||||
snapped_points[i] = best.projection;
|
||||
|
||||
if ((snapped_points[i] - best.projection).squared_length() < (max_projection_distance * max_projection_distance)) {
|
||||
snapped_points[i] = best.projection;
|
||||
}
|
||||
}
|
||||
|
||||
std::map<Point_2, std::set<Point_2>> adjacency;
|
||||
@@ -1545,8 +1548,8 @@ std::map<Point_2, std::vector<Point_2>> snap_points_to_box_axes(
|
||||
Graph2D<K> join_segment_runs(
|
||||
DebugWriter& debug,
|
||||
const std::map<Point_2, std::vector<Point_2>>& line_graph,
|
||||
const std::map<Point_2, double>& midpoint_to_edge_length)
|
||||
{
|
||||
const std::map<Point_2, double>& midpoint_to_edge_length,
|
||||
const K::FT& max_projection_distance) {
|
||||
auto graph = make_center_line_graph_data(line_graph, midpoint_to_edge_length);
|
||||
auto runs = runs_from_graph(graph);
|
||||
runs.erase(std::remove_if(runs.begin(), runs.end(), [](const LineRun& run) {
|
||||
@@ -1577,7 +1580,7 @@ Graph2D<K> join_segment_runs(
|
||||
}
|
||||
debug.write_polygons(run_polygons, "merged_boxes");
|
||||
|
||||
auto snapped_graph = snap_points_to_box_axes(graph, boxes);
|
||||
auto snapped_graph = snap_points_to_box_axes(graph, boxes, max_projection_distance);
|
||||
return Graph2D<K>(snapped_graph);
|
||||
}
|
||||
|
||||
@@ -2069,6 +2072,83 @@ std::list<std::pair<Point_2, Point_2>> extend_end_vertices_based_on_input(
|
||||
return constructed_segments;
|
||||
}
|
||||
|
||||
std::list<std::pair<Point_2, Point_2>>
|
||||
extend_end_vertices_based_on_input_simple(
|
||||
const Graph2D<K>& G,
|
||||
const Polygon_list& outer_perimiter,
|
||||
const K::FT& max_projection_distance)
|
||||
{
|
||||
std::list<std::pair<Point_2, Point_2>> constructed_segments;
|
||||
|
||||
for (auto it = G.vertices_begin(); it != G.vertices_end(); ++it) {
|
||||
if (it->second.size() == 1) {
|
||||
auto& M = it->first;
|
||||
|
||||
for (auto& bnd : outer_perimiter) {
|
||||
// if point M is contained in bnd interior:
|
||||
// if (!bnd.has_on_unbounded_side(M)) {
|
||||
if (bnd.has_on_bounded_side(M)) {
|
||||
auto& incoming = *it->second.begin();
|
||||
// create ray incoming -> M
|
||||
CGAL::Ray_2<K> ray(incoming, M - incoming);
|
||||
|
||||
// intersect ray with boundary
|
||||
boost::optional<CGAL::Segment_2<K>> closest_segment;
|
||||
boost::optional<CGAL::Point_2<K>> closest_intersection_point;
|
||||
K::FT sq_distance_along_ray = std::numeric_limits<double>::infinity();
|
||||
for (auto jt = bnd.edges_begin(); jt != bnd.edges_end(); ++jt) {
|
||||
const auto& seg = *jt;
|
||||
auto x = CGAL::intersection(ray, seg);
|
||||
if (x) {
|
||||
if (auto* xp = variant_get<CGAL::Point_2<K>>(&*x)) {
|
||||
auto dist = ((*xp) - M).squared_length();
|
||||
if (dist < sq_distance_along_ray) {
|
||||
if (dist < (max_projection_distance * max_projection_distance)) {
|
||||
closest_segment = seg;
|
||||
closest_intersection_point = *xp;
|
||||
sq_distance_along_ray = dist;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_intersection_point) {
|
||||
constructed_segments.push_front({M, *closest_intersection_point});
|
||||
} else {
|
||||
|
||||
// Loop over boundary segments, and project point onto it, take the closest
|
||||
K::FT closest_distance = std::numeric_limits<double>::infinity();
|
||||
boost::optional<CGAL::Point_2<K>> closest_point;
|
||||
for (auto& poly : outer_perimiter) {
|
||||
for (auto jt = poly.edges_begin(); jt != poly.edges_end(); ++jt) {
|
||||
auto seg = *jt;
|
||||
auto Pp = seg.supporting_line().projection(M);
|
||||
if (seg.has_on(Pp)) {
|
||||
auto d = CGAL::squared_distance(Pp, M);
|
||||
if (d < (max_projection_distance * max_projection_distance)) {
|
||||
if (d < closest_distance) {
|
||||
closest_distance = d;
|
||||
closest_point = Pp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (closest_point) {
|
||||
constructed_segments.push_front({M, *closest_point});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return constructed_segments;
|
||||
}
|
||||
|
||||
void fuse_corridor_halves_with_input(Arrangement_2& arr, Graph2D<K>& G, SegmentLookup& segment_lookup, const Polygon_list& input_polygons, DebugWriter& debug_output) {
|
||||
std::set<Arrangement_2::Halfedge_handle> edges_to_remove;
|
||||
|
||||
@@ -2161,7 +2241,7 @@ class Segment_2_less {
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& right) {
|
||||
std::vector<K::FT> arrangement_cell_iou(DebugWriter& debug_output, Arrangement_2& left, Arrangement_2& right) {
|
||||
|
||||
using Walk_pl = CGAL::Arr_walk_along_line_point_location<Arrangement_2>;
|
||||
Walk_pl walk_pl(right);
|
||||
@@ -2170,6 +2250,9 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
|
||||
std::vector<K::FT> return_values;
|
||||
|
||||
K::FT max_iou_deviation = 1;
|
||||
std::array<Polygon_2, 2> max_deviation_poly_pair;
|
||||
|
||||
for (auto it = left.faces_begin(); it != left.faces_end(); ++it) {
|
||||
if (!it->is_unbounded()) {
|
||||
// convert arr facet to polygon with holes
|
||||
@@ -2178,6 +2261,9 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
for (auto hit = it->inner_ccbs_begin(); hit != it->inner_ccbs_end(); ++hit) {
|
||||
pwh.add_hole(circ_to_poly(*hit));
|
||||
}
|
||||
if (!pwh.outer_boundary().is_simple()) {
|
||||
throw std::runtime_error("Polygon with holes has a non-simple outer boundary");
|
||||
}
|
||||
|
||||
CGAL::Polygon_triangulation_decomposition_2<K> decompositor;
|
||||
std::vector<Polygon_2> temp;
|
||||
@@ -2218,9 +2304,22 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
}
|
||||
}
|
||||
|
||||
if (max_score == -std::numeric_limits<double>::infinity()) {
|
||||
// no more points to try
|
||||
return_values.push_back(0);
|
||||
break;
|
||||
}
|
||||
|
||||
visited_points.insert(best_point);
|
||||
|
||||
auto res = walk_pl.locate(best_point);
|
||||
if (auto* v = variant_get<Arrangement_2::Face_const_handle>(&res)) {
|
||||
if ((*v)->is_unbounded()) {
|
||||
// try next point
|
||||
continue;
|
||||
}
|
||||
if (visited_faces_on_right.count(*v) > 0) {
|
||||
// Maybe we should be more permissive, try some other points etc.
|
||||
return_values.push_back(0);
|
||||
} else {
|
||||
// convert arr facet to polygon with holes
|
||||
@@ -2229,6 +2328,9 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
for (auto hit = (*v)->inner_ccbs_begin(); hit != (*v)->inner_ccbs_end(); ++hit) {
|
||||
pwh_right.add_hole(circ_to_poly(*hit));
|
||||
}
|
||||
if (!pwh_right.outer_boundary().is_simple()) {
|
||||
throw std::runtime_error("Polygon with holes has a non-simple outer boundary");
|
||||
}
|
||||
|
||||
// compute intersection over union of pwh and the original polygon
|
||||
if (CGAL::do_intersect(pwh, pwh_right)) {
|
||||
@@ -2238,7 +2340,7 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
for (auto& r : result) {
|
||||
auto poly_area = r.outer_boundary().area();
|
||||
for (auto& h : r.holes()) {
|
||||
poly_area -= h.area();
|
||||
poly_area -= CGAL::abs(h.area());
|
||||
}
|
||||
intersection_area += poly_area;
|
||||
}
|
||||
@@ -2246,9 +2348,15 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
CGAL::join(pwh, pwh_right, poly12);
|
||||
typename K::FT union_area = poly12.outer_boundary().area();
|
||||
for (auto& h : poly12.holes()) {
|
||||
union_area -= h.area();
|
||||
union_area -= CGAL::abs(h.area());
|
||||
}
|
||||
return_values.push_back(intersection_area / union_area);
|
||||
|
||||
auto& v = return_values.back();
|
||||
if (v < max_iou_deviation) {
|
||||
max_iou_deviation = v;
|
||||
max_deviation_poly_pair = {pwh.outer_boundary(), pwh_right.outer_boundary()};
|
||||
}
|
||||
} else {
|
||||
return_values.push_back(0);
|
||||
}
|
||||
@@ -2263,6 +2371,11 @@ std::vector<K::FT> arrangement_cell_iou(Arrangement_2& left, Arrangement_2& righ
|
||||
}
|
||||
}
|
||||
|
||||
if (max_iou_deviation != 1) {
|
||||
debug_output.write_polygon(max_deviation_poly_pair[0], "max_iou_deviation_left");
|
||||
debug_output.write_polygon(max_deviation_poly_pair[1], "max_iou_deviation_right");
|
||||
}
|
||||
|
||||
return return_values;
|
||||
}
|
||||
|
||||
@@ -2935,6 +3048,18 @@ class timer {
|
||||
bool enabled_;
|
||||
};
|
||||
|
||||
size_t delete_same_facet_edge_pairs(Arrangement_2& arr) {
|
||||
size_t n_deleted = 0;
|
||||
for (auto it = arr.edges_begin(); it != arr.edges_end();) {
|
||||
decltype(it) current = it++;
|
||||
if (current->face() == current->twin()->face()) {
|
||||
arr.remove_edge(current);
|
||||
n_deleted++;
|
||||
}
|
||||
}
|
||||
return n_deleted;
|
||||
}
|
||||
|
||||
void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std::vector<Polygon_2>& input_polygons_, std::vector<Polygon_2>& output_polygons, double polygon_offset_distance = -1.) {
|
||||
static const double OVERLAP_RESOLUTION_DISTANCE = 1.e-1;
|
||||
// even larger amount of inset so that outer perimeter is safely within all input polygons even when overlap resolution is applied
|
||||
@@ -3142,8 +3267,10 @@ void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std
|
||||
t0 = timer.start("center line cleaning");
|
||||
|
||||
Graph2D<K> G;
|
||||
Graph2D<K> G_orig(line_graph);
|
||||
|
||||
if (settings.line_cleaning_algo == 0) {
|
||||
G = join_segment_runs(debug_output, line_graph, midpoint_to_edge_length);
|
||||
G = join_segment_runs(debug_output, line_graph, midpoint_to_edge_length, subdivision_length * 4);
|
||||
Arrangement_2 arr;
|
||||
G.to_arrangement(arr);
|
||||
Graph2D<K> G2;
|
||||
@@ -3181,7 +3308,65 @@ void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std
|
||||
|
||||
t0 = timer.start("topology");
|
||||
|
||||
auto segments = extend_end_vertices_based_on_input(G, midpoint_to_segment, segment_to_input_facet, outer_perimiter, segment_lookup, subdivision_length * 4);
|
||||
std::list<std::pair<Point_2, Point_2>> segments, segments1, segments2;
|
||||
|
||||
if (settings.line_cleaning_algo == 0) {
|
||||
segments1 = extend_end_vertices_based_on_input_simple(G, outer_perimiter, subdivision_length * 4);
|
||||
segments2 = extend_end_vertices_based_on_input_simple(G_orig, outer_perimiter, subdivision_length * 4);
|
||||
|
||||
Arrangement_2 arr_clean;
|
||||
G.to_arrangement(arr_clean);
|
||||
for (auto& pq : segments1) {
|
||||
if (pq.first == pq.second) {
|
||||
continue;
|
||||
}
|
||||
CGAL::insert(arr_clean, Segment_2(pq.first, pq.second));
|
||||
}
|
||||
|
||||
Arrangement_2 arr_orig;
|
||||
G_orig.to_arrangement(arr_orig);
|
||||
for (auto& pq : segments2) {
|
||||
if (pq.first == pq.second) {
|
||||
continue;
|
||||
}
|
||||
CGAL::insert(arr_orig, Segment_2(pq.first, pq.second));
|
||||
}
|
||||
|
||||
delete_same_facet_edge_pairs(arr_clean);
|
||||
delete_same_facet_edge_pairs(arr_orig);
|
||||
|
||||
for (auto& p : outer_perimiter) {
|
||||
for (auto it = p.edges_begin(); it != p.edges_end(); ++it) {
|
||||
auto source = it->source();
|
||||
auto target = it->target();
|
||||
if (source == target) {
|
||||
continue;
|
||||
}
|
||||
CGAL::insert(arr_orig, Segment_2(source, target));
|
||||
CGAL::insert(arr_clean, Segment_2(source, target));
|
||||
}
|
||||
}
|
||||
|
||||
auto ious = arrangement_cell_iou(debug_output, arr_clean, arr_orig);
|
||||
/*
|
||||
for (auto& iou : ious) {
|
||||
std::cout << " " << CGAL::to_double(iou - 1);
|
||||
}
|
||||
std::cout << std::endl;
|
||||
*/
|
||||
|
||||
auto it = std::min_element(ious.begin(), ious.end());
|
||||
|
||||
if (it != ious.end() && (*it < 0.5)) {
|
||||
std::cerr << "Significant difference between cleaned and original arrangement, using original for topology reconstruction: " << *it << std::endl;
|
||||
segments = segments2;
|
||||
G = G_orig;
|
||||
} else {
|
||||
segments = segments1;
|
||||
}
|
||||
} else {
|
||||
segments = extend_end_vertices_based_on_input(G, midpoint_to_segment, segment_to_input_facet, outer_perimiter, segment_lookup, subdivision_length * 4);
|
||||
}
|
||||
|
||||
// Now plot the edges on an arrangement in order to find planar cycles
|
||||
// and merge the corridor-halves with their neighbouring input polygon
|
||||
@@ -3256,7 +3441,7 @@ void arrange_cgal_polygons(svgfill::arrange_polygon_settings settings, const std
|
||||
double threshold;
|
||||
clean_noisy_paths(debug_output, arr, segment_lookup, threshold);
|
||||
remove_colinear_vertices(arr);
|
||||
clean_noisy_bounds(debug_output, arr, segment_lookup, threshold);
|
||||
// clean_noisy_bounds(debug_output, arr, segment_lookup, threshold);
|
||||
}
|
||||
|
||||
t0.stop();
|
||||
|
||||
Reference in New Issue
Block a user