Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow for passing IFC Schema attributes on API methods #3851

Open
wants to merge 23 commits into
base: v0.7.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
641f491
Make it possible to retrieve IFC schema attrs for ifcopenshell.api us…
cvillagrasa Oct 5, 2023
2cf1e98
Use IfcMaterialLayer attrs on material.add_layer
cvillagrasa Oct 5, 2023
a08fb05
Move attr_parsing logic from util.schema to util.schema.py
cvillagrasa Oct 9, 2023
abe3327
Perhaps better to pass schema attrs in snake case?
cvillagrasa Oct 9, 2023
7d0de76
Merge branch 'v0.7.0' into use_schema_args
cvillagrasa Nov 16, 2023
8ae3534
Add multischema API generation with AST
cvillagrasa Nov 16, 2023
19a2e62
AST tweaking
cvillagrasa Nov 17, 2023
c8b7fd9
Autogenerate schema-dependent file modules, instead of API Usecases
cvillagrasa Nov 18, 2023
8533558
Refactor considering dataclasses and __init__ args
cvillagrasa Nov 21, 2023
e91be60
Big bugfixing + some new code. Now the API multischema build step wor…
cvillagrasa Nov 23, 2023
db35aa9
Get rid of \xa0
cvillagrasa Nov 24, 2023
507a0cf
Static build step up and running.
cvillagrasa Nov 24, 2023
7251e69
Make the linter happy
cvillagrasa Nov 24, 2023
f5ad212
Quick integration of the build step with api.__init__ and file
cvillagrasa Nov 24, 2023
af689c5
Minor bugfix
cvillagrasa Nov 24, 2023
e028f0d
Add return annotation to material.add_layer
cvillagrasa Nov 24, 2023
3ae7cea
Remove logic from previous signature-based solution
cvillagrasa Nov 24, 2023
7af4e72
Quick temp sql fix I don't like
cvillagrasa Nov 24, 2023
dc709c1
More robust IO
cvillagrasa Nov 27, 2023
7c5d2cf
Fix bug when an alias wasn't present at all
cvillagrasa Nov 27, 2023
2d64788
Docstrings
cvillagrasa Nov 27, 2023
f826d17
Minor bug
cvillagrasa Nov 27, 2023
fd9a666
Initial testing
cvillagrasa Nov 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 24 additions & 5 deletions src/ifcopenshell-python/ifcopenshell/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.

"""High level user-oriented IFC authoring capabilities"""

import json
import os
import inspect
from pathlib import Path
from collections import defaultdict
from typing import DefaultDict
import numpy
import importlib
import ifcopenshell
Expand All @@ -29,7 +32,7 @@
post_listeners = {}


def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
def run(usecase_path, ifc_file=None, on_static_version=None, should_run_listeners=True, **settings):
if should_run_listeners:
for listener in pre_listeners.get(usecase_path, {}).values():
listener(usecase_path, ifc_file, settings)
Expand Down Expand Up @@ -58,9 +61,11 @@ def serialise_entity_instance(entity):
# except:
# print(usecase_path, vcs_settings)

importlib.import_module(f"ifcopenshell.api.{usecase_path}")
api_path = f"api_{on_static_version}" if on_static_version else "api"
importlib.import_module(f"ifcopenshell.{api_path}")
importlib.import_module(f"ifcopenshell.{api_path}.{usecase_path}")
module, usecase = usecase_path.split(".")
usecase_class = getattr(getattr(getattr(ifcopenshell.api, module), usecase), "Usecase")
usecase_class = getattr(getattr(getattr(getattr(ifcopenshell, api_path), module), usecase), "Usecase")

if ifc_file:
result = usecase_class(ifc_file, **settings).execute()
Expand Down Expand Up @@ -167,3 +172,17 @@ def extract_docs(module, usecase):
node_data["description"] = description.strip()
node_data["inputs"] = inputs
return node_data


def list_actions() -> DefaultDict[str, list[str]]:
actions_to_exclude: list[str] = ["__init__", "settings", "multischema"]
api_actions: DefaultDict[str, list[str]] = defaultdict(list)
ios_dir = Path(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))

for path in ios_dir.glob("*/*.py"):
if (action := path.stem) in actions_to_exclude:
continue
module = path.parent.name
api_actions[module].append(action)

return api_actions
120 changes: 62 additions & 58 deletions src/ifcopenshell-python/ifcopenshell/api/material/add_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,76 +16,80 @@
# 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 __future__ import annotations
from dataclasses import dataclass
import ifcopenshell
from ifcopenshell.util.schema import with_schema_attrs


@with_schema_attrs(ifc_class="IfcMaterialLayer", defaults={"LayerThickness": 1.})
@dataclass
class Usecase:
def __init__(self, file, layer_set=None, material=None):
"""Adds a new layer to a layer set
"""Adds a new layer to a layer set

A layer represents a portion of material within a layered build up,
defined by a thickness. Typical layered construction includes walls and
slabs, where a wall might include a layer of finish, a layer of
structure, a layer of insulation, and so on. It is recommended to define
layered construction this way where it is unnecessary to define the
exact geometry of how the wall or slab will be built, and it will
instead be determined on site by a trade.
A layer represents a portion of material within a layered build up,
defined by a thickness. Typical layered construction includes walls and
slabs, where a wall might include a layer of finish, a layer of
structure, a layer of insulation, and so on. It is recommended to define
layered construction this way where it is unnecessary to define the
exact geometry of how the wall or slab will be built, and it will
instead be determined on site by a trade.

Layers are defined in a particular order and thickness, so that it is
clear which layer comes next.
Layers are defined in a particular order and thickness, so that it is
clear which layer comes next.

:param layer_set: The IfcMaterialLayerSet that the layer is part of. The
layer set represents a group of layers. See
ifcopenshell.api.material.add_material_set for more information on
how to add a layer set.
:type layer_set: ifcopenshell.entity_instance.entity_instance
:param material: The IfcMaterial that the layer is made out of.
:type material: ifcopenshell.entity_instance.entity_instance
:return: The newly created IfcMaterialLayer
:rtype: ifcopenshell.entity_instance.entity_instance
:param layer_set: The IfcMaterialLayerSet that the layer is part of. The
layer set represents a group of layers. See
ifcopenshell.api.material.add_material_set for more information on
how to add a layer set.
:type layer_set: ifcopenshell.entity_instance.entity_instance
:param material: The IfcMaterial that the layer is made out of.
:type material: ifcopenshell.entity_instance.entity_instance
:return: The newly created IfcMaterialLayer
:rtype: ifcopenshell.entity_instance.entity_instance

Example:
Example:

.. code:: python
.. code:: python

# Let's imagine we have a wall type that has two layers of
# gypsum with steel studs inside. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType", name="WAL01")
# Let's imagine we have a wall type that has two layers of
# gypsum with steel studs inside. Notice we are assigning to
# the type only, as all occurrences of that type will automatically
# inherit the material.
wall_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWallType", name="WAL01")

# First, let's create a material set. This will later be assigned
# to our wall type element.
material_set = ifcopenshell.api.run("material.add_material_set", model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")
# First, let's create a material set. This will later be assigned
# to our wall type element.
material_set = ifcopenshell.api.run("material.add_material_set", model,
name="GYP-ST-GYP", set_type="IfcMaterialLayerSet")

# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
gypsum = ifcopenshell.api.run("material.add_material", model, name="PB01", category="gypsum")
steel = ifcopenshell.api.run("material.add_material", model, name="ST01", category="steel")
# Let's create a few materials, it's important to also give them
# categories. This makes it easy for model recipients to do things
# like "show me everything made out of aluminium / concrete / steel
# / glass / etc". The IFC specification states a list of categories
# you can use.
gypsum = ifcopenshell.api.run("material.add_material", model, name="PB01", category="gypsum")
steel = ifcopenshell.api.run("material.add_material", model, name="ST01", category="steel")

# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=gypsum)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=steel)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 92})
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=gypsum)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})
# Now let's use those materials as three layers in our set, such
# that the steel studs are sandwiched by the gypsum. Let's imagine
# we're setting the layer thickness in millimeters.
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=gypsum)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=steel)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 92})
layer = ifcopenshell.api.run("material.add_layer", model, layer_set=material_set, material=gypsum)
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})

# Great! Let's assign our material set to our wall type.
ifcopenshell.api.run("material.assign_material", model, product=wall_type, material=material_set)
"""
self.file = file
self.settings = {"layer_set": layer_set, "material": material}
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.run("material.assign_material", model, product=wall_type, material=material_set)
"""
file: ifcopenshell.file
layer_set: ifcopenshell.entity_instance

def execute(self):
layers = list(self.settings["layer_set"].MaterialLayers or [])
layer = self.file.create_entity(
"IfcMaterialLayer", **{"Material": self.settings["material"], "LayerThickness": 1.0}
)
def execute(self) -> ifcopenshell.entity_instance:
layers = list(self.layer_set.MaterialLayers or [])
layer = self.file.create_entity("IfcMaterialLayer", **self.schema_attrs())
layers.append(layer)
self.settings["layer_set"].MaterialLayers = layers
self.layer_set.MaterialLayers = layers
return layer