mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-07 08:21:42 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 07b53f38b4 | |||
| e80fd56d3f | |||
| 0f0ced593b | |||
| 05bd7df68f | |||
| ee4681dc4c | |||
| f679c63a18 | |||
| e6cc0e7813 | |||
| cf2acfc649 | |||
| 60ebb99fda | |||
| ab96add772 | |||
| d8de623086 | |||
| a3f2e061fb | |||
| f51a4673db | |||
| 75b8d4f218 | |||
| b8136d4762 | |||
| ecde429d36 | |||
| 1771b34449 | |||
| 41469acbc8 | |||
| 547b22199f | |||
| 94c15213f6 | |||
| fca258fb07 | |||
| bcfad8d96d | |||
| aa5f5120e0 | |||
| 81986bcbfb | |||
| 03de69814a | |||
| 4dc6a0f2bc | |||
| 47f058341b | |||
| 845a13ba83 | |||
| 530841967e | |||
| e95da857d6 | |||
| d587d1ac11 | |||
| 6117417b89 | |||
| 0398584c69 | |||
| 0c69f85d5e |
@@ -53,7 +53,7 @@ jobs:
|
||||
python ../nix/cache_dependencies.py unpack
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: mac-${{ matrix.arch }}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
python ../IfcOpenShell/nix/cache_dependencies.py unpack
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
python3 ../nix/cache_dependencies.py unpack
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
python3 ../nix/cache_dependencies.py unpack
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}-rockylinux9
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: win-${{ matrix.arch }}
|
||||
# Windows ccache needs ~1GB
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
-
|
||||
name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
|
||||
-
|
||||
name: Build ifcopenshell
|
||||
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
libhdf5-dev libcgal-dev libeigen3-dev
|
||||
|
||||
- name: ccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.20
|
||||
uses: hendrikmuhs/ccache-action@v1.2.21
|
||||
with:
|
||||
key: ubuntu-22.04-${{ runner.arch }}
|
||||
|
||||
|
||||
+5
-3
@@ -14,6 +14,7 @@
|
||||
/src/ifcmax/out/
|
||||
/src/ifcwrap/out/
|
||||
/src/qtviewer/out/
|
||||
/src/ifctester/webapp/public/pyodide/
|
||||
|
||||
/win/BuildDepsCache*.txt
|
||||
|
||||
@@ -85,10 +86,11 @@ src/bonsai/bonsai/translations.py
|
||||
# bonsai external dependencies (cloned for just ty checks)
|
||||
src/bonsai/external_dependencies/
|
||||
|
||||
# bonsai test temp files
|
||||
# bonsai test temp/cache files
|
||||
src/bonsai/test/files/temp
|
||||
src/bonsai/test/files/basic.ifc.cache.blend
|
||||
src/bonsai/test/files/basic.ifc.cache.sqlite
|
||||
src/bonsai/test/files/*.cache.blend
|
||||
src/bonsai/test/files/*.cache.json
|
||||
src/bonsai/test/files/*.cache.sqlite
|
||||
|
||||
# bonsai data
|
||||
src/bonsai/bonsai/bim/data/build/
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ name = "IfcOpenShell"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"black==26.3.1",
|
||||
"ruff==0.15.6",
|
||||
"ruff==0.15.7",
|
||||
"poethepoet",
|
||||
"gersemi==0.26.1",
|
||||
]
|
||||
|
||||
@@ -45,16 +45,19 @@ from bonsai.bim.module.nest.decorator import NestDecorator
|
||||
|
||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
||||
global_subscription_owner = object()
|
||||
# Separate owner for per-object msgbus subscriptions (name, active_material_index).
|
||||
# Using a dedicated owner allows clearing all per-object subscriptions at once
|
||||
# during undo/redo without affecting other global subscriptions.
|
||||
object_subscription_owner = object()
|
||||
|
||||
|
||||
def name_callback(obj: Union[bpy.types.Object, bpy.types.Material], data: str) -> None:
|
||||
try:
|
||||
obj.name
|
||||
except:
|
||||
# The object is invalid but somehow still has a callback. Clear all
|
||||
# msgbus subscriptions to prevent useless further triggers.
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
return # In case the object RNA is gone during an undo / redo operation
|
||||
# The object is invalid but somehow still has a callback.
|
||||
# This can occur during undo/redo when the Python wrapper is stale.
|
||||
return
|
||||
# Blender names are up to 63 UTF-8 bytes
|
||||
if len(bytes(obj.name, "utf-8")) >= 63:
|
||||
return
|
||||
@@ -189,7 +192,7 @@ def subscribe_to(obj: bpy.types.ID, data_path: str, callback: Callable[[bpy.type
|
||||
return
|
||||
bpy.msgbus.subscribe_rna(
|
||||
key=subscribe_to,
|
||||
owner=obj,
|
||||
owner=object_subscription_owner,
|
||||
args=(
|
||||
obj,
|
||||
data_path,
|
||||
|
||||
@@ -316,11 +316,8 @@ class IfcStore:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if "guid" in data:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
obj = IfcStore.get_object_by_name(data["obj"])
|
||||
if obj is None:
|
||||
# obj was just created during this step and didn't existed before.
|
||||
return
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
# Note: msgbus subscriptions are cleared globally during
|
||||
# rebuild_element_maps which runs after every undo/redo.
|
||||
|
||||
@staticmethod
|
||||
def commit_link_element(data: OperationData) -> None:
|
||||
@@ -367,10 +364,8 @@ class IfcStore:
|
||||
del IfcStore.id_map[data["id"]]
|
||||
if "guid" in data:
|
||||
del IfcStore.guid_map[data["guid"]]
|
||||
obj = IfcStore.get_object_by_name(data["obj"])
|
||||
# obj might be removed after unlink.
|
||||
if not obj:
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
# Note: msgbus subscriptions are cleared globally during
|
||||
# rebuild_element_maps which runs after every undo/redo.
|
||||
|
||||
@staticmethod
|
||||
def unlink_element(
|
||||
|
||||
@@ -64,8 +64,8 @@ class MaterialCreator:
|
||||
mesh: Union[OBJECT_DATA_TYPE, None],
|
||||
shape_has_openings: bool,
|
||||
) -> None:
|
||||
if ((rep := getattr(element, "Representation", ...) is not ...) and not rep) or (
|
||||
(rep := getattr(element, "RepresentationMaps", ...) is not ...) and not rep
|
||||
if (((rep := getattr(element, "Representation", ...)) is not ... and not rep) or
|
||||
((rep := getattr(element, "RepresentationMaps", ...)) is not ... and not rep)
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
@@ -1426,6 +1426,7 @@ class CreateDrawing(bpy.types.Operator):
|
||||
"/Pset_.*Common/.Status",
|
||||
"EPset_Status.Status",
|
||||
"EPset_Status.UserDefinedStatus",
|
||||
"Material.Name",
|
||||
]
|
||||
|
||||
group = root.find("{http://www.w3.org/2000/svg}g")
|
||||
|
||||
@@ -2289,7 +2289,7 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
|
||||
elif obj in pprops.clipping_planes_objs:
|
||||
self.report({"ERROR"}, "Clipping planes cannot be edited")
|
||||
elif element:
|
||||
if not obj.data:
|
||||
if not obj.data or obj.type not in ("MESH", "CURVE"):
|
||||
self.report({"INFO"}, "No geometry to edit")
|
||||
elif tool.Geometry.is_locked(element):
|
||||
self.report({"ERROR"}, lock_error_message(obj.name))
|
||||
|
||||
@@ -540,6 +540,16 @@ class AddBoolean(Operator, tool.Ifc.Operator):
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(tool.Ifc.get(), first_item, second_items, props.operator)
|
||||
|
||||
rep_obj = tool.Geometry.get_geometry_props().representation_obj
|
||||
if booleans:
|
||||
# Users typically select two top-level items and expect the
|
||||
# operand to be absorbed into the boolean, not remain as a
|
||||
# standalone item alongside it.
|
||||
representation = tool.Geometry.get_active_representation(rep_obj)
|
||||
representation = ifcopenshell.util.representation.resolve_representation(representation)
|
||||
second_items_set = set(second_items)
|
||||
new_items = [i for i in representation.Items if i not in second_items_set]
|
||||
if new_items:
|
||||
representation.Items = new_items
|
||||
rep_element = tool.Ifc.get_entity(rep_obj)
|
||||
tool.Model.mark_manual_booleans(rep_element, booleans)
|
||||
tool.Geometry.reload_representation(rep_obj)
|
||||
|
||||
@@ -51,21 +51,11 @@ def add_instance_flooring_covering_from_cursor(
|
||||
if isinstance(space_polygon, str):
|
||||
return
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True)
|
||||
name = "Covering"
|
||||
mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm)
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
|
||||
obj = spatial.create_object("Covering")
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
spatial.assign_type_to_obj(obj)
|
||||
|
||||
spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points)
|
||||
body = spatial.get_body_representation(obj)
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
spatial.set_covering_representation_from_polygon(obj, space_polygon, polygon_is_si=True)
|
||||
|
||||
|
||||
def add_instance_ceiling_covering_from_cursor(
|
||||
@@ -95,21 +85,11 @@ def add_instance_ceiling_covering_from_cursor(
|
||||
if isinstance(space_polygon, str):
|
||||
return
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True)
|
||||
name = "Covering"
|
||||
mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm)
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
|
||||
obj = spatial.create_object("Covering")
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.translate_obj_to_z_location(obj, z + ceiling_height)
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
spatial.assign_type_to_obj(obj)
|
||||
|
||||
spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points)
|
||||
body = spatial.get_body_representation(obj)
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
spatial.set_covering_representation_from_polygon(obj, space_polygon, polygon_is_si=True)
|
||||
|
||||
|
||||
def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spatial]) -> None:
|
||||
@@ -127,19 +107,7 @@ def regen_selected_covering_object(root: type[tool.Root], spatial: type[tool.Spa
|
||||
if isinstance(space_polygon, str):
|
||||
return
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(space_polygon, h=0, polygon_is_si=True)
|
||||
|
||||
name = "Aux"
|
||||
mesh = spatial.get_named_mesh_from_bmesh(name=name, bmesh=bm)
|
||||
mesh = spatial.get_transformed_mesh_from_local_to_global(mesh)
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
|
||||
spatial.assign_swept_area_outer_curve_from_2d_vertices(active_obj, vertices=points)
|
||||
body = spatial.get_body_representation(active_obj)
|
||||
spatial.regen_obj_representation(active_obj, body)
|
||||
spatial.set_covering_representation_from_polygon(active_obj, space_polygon, polygon_is_si=True)
|
||||
|
||||
|
||||
# TODO CHECK IF IT IS POSSIBLE TO CREATE ONLY ONE CORE FUNCTION FOR _FROM_WALLS
|
||||
@@ -151,22 +119,13 @@ def add_instance_flooring_coverings_from_walls(root: type[tool.Root], spatial: t
|
||||
union = spatial.get_union_shape_from_selected_objects()
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
|
||||
bm = spatial.get_bmesh_from_polygon(poly, h=0, polygon_is_si=False)
|
||||
|
||||
name = "Covering" + str(i)
|
||||
obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
|
||||
|
||||
spatial.set_obj_origin_to_bboxcenter(obj)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
|
||||
spatial.assign_type_to_obj(obj)
|
||||
|
||||
spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points)
|
||||
body = spatial.get_body_representation(obj)
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
spatial.set_covering_representation_from_polygon(obj, poly, polygon_is_si=False)
|
||||
|
||||
|
||||
def add_instance_ceiling_coverings_from_walls(
|
||||
@@ -179,22 +138,13 @@ def add_instance_ceiling_coverings_from_walls(
|
||||
union = spatial.get_union_shape_from_selected_objects()
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
|
||||
bm = spatial.get_bmesh_from_polygon(poly, h=0, polygon_is_si=False)
|
||||
|
||||
name = "Covering" + str(i)
|
||||
obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
|
||||
|
||||
spatial.set_obj_origin_to_bboxcenter(obj)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
|
||||
points = spatial.get_2d_vertices_from_obj(obj)
|
||||
points = spatial.get_scaled_2d_vertices(points)
|
||||
|
||||
spatial.assign_type_to_obj(obj)
|
||||
|
||||
spatial.assign_swept_area_outer_curve_from_2d_vertices(obj, vertices=points)
|
||||
body = spatial.get_body_representation(obj)
|
||||
spatial.regen_obj_representation(obj, body)
|
||||
spatial.set_covering_representation_from_polygon(obj, poly, polygon_is_si=False)
|
||||
|
||||
|
||||
class NoDefaultContainer(Exception):
|
||||
|
||||
@@ -219,13 +219,8 @@ def generate_space(
|
||||
else:
|
||||
assert space_polygon
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(space_polygon, h=h, polygon_is_si=True)
|
||||
|
||||
mesh = spatial.get_named_mesh_from_bmesh(name="Space", bmesh=bm)
|
||||
|
||||
if element and element.is_a("IfcSpace"):
|
||||
mesh = spatial.get_transformed_mesh_from_local_to_global(mesh)
|
||||
spatial.edit_active_space_obj_from_mesh(mesh)
|
||||
spatial.set_space_representation_from_polygon(active_obj, element, space_polygon, h, polygon_is_si=True)
|
||||
spatial.translate_obj_to_z_location(active_obj, z)
|
||||
else:
|
||||
if relating_type:
|
||||
@@ -233,12 +228,13 @@ def generate_space(
|
||||
else:
|
||||
name = "Space"
|
||||
|
||||
obj = spatial.get_named_obj_from_mesh(name, mesh)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_cursor_position_and_zero_elevation(obj)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
spatial.set_space_representation_from_polygon(obj, element, space_polygon, h, polygon_is_si=True)
|
||||
|
||||
if relating_type:
|
||||
spatial.assign_relating_type_to_element(ifc, type, element, relating_type)
|
||||
@@ -257,16 +253,16 @@ def generate_spaces_from_walls(
|
||||
for i, linear_ring in enumerate(union.interiors):
|
||||
poly = spatial.get_buffered_poly_from_linear_ring(linear_ring)
|
||||
|
||||
bm = spatial.get_bmesh_from_polygon(poly, h, polygon_is_si=False)
|
||||
|
||||
name = "Space" + str(i)
|
||||
|
||||
obj = spatial.get_named_obj_from_bmesh(name, bmesh=bm)
|
||||
|
||||
spatial.set_obj_origin_to_bboxcenter_and_zero_elevation(obj)
|
||||
obj = spatial.create_object(name)
|
||||
spatial.set_obj_origin_to_polygon_center(obj, poly, polygon_is_si=False)
|
||||
spatial.translate_obj_to_z_location(obj, z)
|
||||
spatial.assign_ifcspace_class_to_obj(obj)
|
||||
|
||||
element = ifc.get_entity(obj)
|
||||
spatial.set_space_representation_from_polygon(obj, element, poly, h, polygon_is_si=False)
|
||||
|
||||
|
||||
def toggle_space_visibility(ifc: type[tool.Ifc], spatial: type[tool.Spatial]) -> None:
|
||||
model = ifc.get()
|
||||
|
||||
@@ -999,14 +999,12 @@ class Spatial:
|
||||
def get_purged_inner_holes_poly(cls, union_geom, min_area): pass
|
||||
def get_poly_valid_interior_list(cls, poly, min_area, interiors_list): pass
|
||||
def get_buffered_poly_from_linear_ring(cls, linear_ring): pass
|
||||
def get_bmesh_from_polygon(cls, poly, h, polygon_is_si=False): pass
|
||||
def get_named_obj_from_bmesh(cls, name, bmesh): pass
|
||||
def get_named_obj_from_mesh(cls, name, mesh): pass
|
||||
def get_named_mesh_from_bmesh(cls, name, bmesh): pass
|
||||
def get_transformed_mesh_from_local_to_global(cls, mesh): pass
|
||||
def edit_active_space_obj_from_mesh(cls, mesh): pass
|
||||
def set_obj_origin_to_bboxcenter(cls, obj): pass
|
||||
def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj): pass
|
||||
def get_2d_vertices_from_polygon(cls, poly, obj, polygon_is_si=True): pass
|
||||
def set_extrusion_representation_from_polygon(cls, obj, element, poly, depth_ifc, polygon_is_si=True): pass
|
||||
def set_space_representation_from_polygon(cls, obj, element, poly, h, polygon_is_si=True): pass
|
||||
def set_covering_representation_from_polygon(cls, obj, poly, polygon_is_si=True): pass
|
||||
def create_object(cls, name): pass
|
||||
def set_obj_origin_to_polygon_center(cls, obj, poly, polygon_is_si=True): pass
|
||||
def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj): pass
|
||||
def get_selected_objects(cls): pass
|
||||
def get_active_obj(cls): pass
|
||||
@@ -1014,14 +1012,9 @@ class Spatial:
|
||||
def get_active_obj_height(cls): pass
|
||||
def get_relating_type_id(cls): pass
|
||||
def translate_obj_to_z_location(cls, obj, z): pass
|
||||
def get_2d_vertices_from_obj(cls, obj): pass
|
||||
def get_scaled_2d_vertices(cls, points): pass
|
||||
def assign_swept_area_outer_curve_from_2d_vertices(cls, obj, vertices): pass
|
||||
def get_body_representation(cls, obj): pass
|
||||
def assign_ifcspace_class_to_obj(cls, obj): pass
|
||||
def assign_type_to_obj(cls, obj): pass
|
||||
def assign_relating_type_to_element(cls, ifc, type, element, relating_type): pass
|
||||
def regen_obj_representation(cls, obj, body): pass
|
||||
def toggle_spaces_visibility_wired_and_textured(cls, spaces): pass
|
||||
def toggle_hide_spaces(cls, spaces): pass
|
||||
def set_default_container(cls, container): pass
|
||||
|
||||
@@ -49,21 +49,40 @@ class Aggregate(bonsai.core.tool.Aggregate):
|
||||
related_object = tool.Ifc.get_entity(related_obj)
|
||||
if not relating_object or not related_object:
|
||||
return False
|
||||
if relating_object == related_object:
|
||||
return False
|
||||
|
||||
is_compatible_class = False
|
||||
if (relating_object.is_a("IfcElement") or relating_object.is_a("IfcElementType")) and related_object.is_a(
|
||||
"IfcElement"
|
||||
):
|
||||
return True
|
||||
if tool.Ifc.get_schema() == "IFC2X3":
|
||||
is_compatible_class = True
|
||||
elif tool.Ifc.get_schema() == "IFC2X3":
|
||||
if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"):
|
||||
return True
|
||||
if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialStructureElement"):
|
||||
return True
|
||||
is_compatible_class = True
|
||||
elif relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialStructureElement"):
|
||||
is_compatible_class = True
|
||||
else:
|
||||
if relating_object.is_a("IfcSpatialElement") and related_object.is_a("IfcSpatialElement"):
|
||||
return True
|
||||
if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialElement"):
|
||||
return True
|
||||
return False
|
||||
is_compatible_class = True
|
||||
elif relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialElement"):
|
||||
is_compatible_class = True
|
||||
|
||||
if not is_compatible_class:
|
||||
return False
|
||||
|
||||
# Prevent cyclic references: walk up the full hierarchy from the
|
||||
# proposed parent and reject if we encounter the proposed child.
|
||||
ancestor = ifcopenshell.util.element.get_parent(relating_object)
|
||||
seen = {relating_object}
|
||||
while ancestor:
|
||||
if ancestor == related_object:
|
||||
return False
|
||||
if ancestor in seen:
|
||||
break
|
||||
seen.add(ancestor)
|
||||
ancestor = ifcopenshell.util.element.get_parent(ancestor)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def has_physical_body_representation(cls, element: ifcopenshell.entity_instance) -> bool:
|
||||
|
||||
@@ -857,6 +857,8 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
def edit_text_literals(cls, obj: bpy.types.Object, literal_attributes: dict) -> None:
|
||||
if not literal_attributes:
|
||||
return
|
||||
assert (element := tool.Ifc.get_entity(obj))
|
||||
assert (rep := cls.get_annotation_representation(element))
|
||||
to_remove = [i for i in rep.Items if i.is_a("IfcTextLiteral")]
|
||||
|
||||
@@ -1407,8 +1407,6 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
:param representation_item: item to remove.
|
||||
:param element: item's element. Is used to unmark manual booleans.
|
||||
"""
|
||||
# NOTE: we assume it's not the last representation item
|
||||
# otherwise we probably would need to remove representation too
|
||||
# NOTE: a lot of shared code with `geometry.remove_representation`
|
||||
ifc_file = tool.Ifc.get()
|
||||
shape_aspects: list[ifcopenshell.entity_instance] = []
|
||||
@@ -1467,7 +1465,10 @@ class Geometry(bonsai.core.tool.Geometry):
|
||||
cls.remove_representation_items_from_shape_aspect([representation_item], shape_aspect)
|
||||
|
||||
if representation:
|
||||
representation.Items = tuple(set(representation.Items) - {representation_item})
|
||||
new_items = tuple(set(representation.Items) - {representation_item})
|
||||
if not new_items:
|
||||
return
|
||||
representation.Items = new_items
|
||||
also_consider = list(consider_inverses)
|
||||
ifcopenshell.util.element.remove_deep2(ifc_file, representation_item, also_consider=also_consider)
|
||||
|
||||
|
||||
@@ -197,12 +197,16 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
if not cls.get():
|
||||
return
|
||||
|
||||
# Clear all per-object msgbus subscriptions at once using the dedicated
|
||||
# owner. After undo/redo, per-object Python wrappers have new
|
||||
# identities so clearing by individual obj would miss stale
|
||||
# subscriptions registered with the old wrappers.
|
||||
bpy.msgbus.clear_by_owner(bonsai.bim.handler.object_subscription_owner)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.library:
|
||||
continue
|
||||
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
|
||||
element = cls.get_entity(obj)
|
||||
if not element:
|
||||
continue
|
||||
@@ -217,8 +221,6 @@ class Ifc(bonsai.core.tool.Ifc):
|
||||
if obj.library:
|
||||
continue
|
||||
|
||||
bpy.msgbus.clear_by_owner(obj)
|
||||
|
||||
style = cls.get_entity(obj)
|
||||
if not style:
|
||||
continue
|
||||
|
||||
@@ -45,9 +45,23 @@ class Nest(bonsai.core.tool.Nest):
|
||||
related_object = tool.Ifc.get_entity(related_obj)
|
||||
if not relating_object or not related_object:
|
||||
return False
|
||||
if relating_object.is_a("IfcElement") and related_object.is_a("IfcElement"):
|
||||
return True
|
||||
return False
|
||||
if relating_object == related_object:
|
||||
return False
|
||||
is_compatible_class = relating_object.is_a("IfcElement") and related_object.is_a("IfcElement")
|
||||
if not is_compatible_class:
|
||||
return False
|
||||
# Prevent cyclic references: walk up the full hierarchy from the
|
||||
# proposed parent and reject if we encounter the proposed child.
|
||||
ancestor = ifcopenshell.util.element.get_parent(relating_object)
|
||||
seen = {relating_object}
|
||||
while ancestor:
|
||||
if ancestor == related_object:
|
||||
return False
|
||||
if ancestor in seen:
|
||||
break
|
||||
seen.add(ancestor)
|
||||
ancestor = ifcopenshell.util.element.get_parent(ancestor)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def disable_editing(cls, obj: bpy.types.Object) -> None:
|
||||
|
||||
+124
-151
@@ -21,13 +21,13 @@ from __future__ import annotations
|
||||
import json
|
||||
from collections import defaultdict
|
||||
from collections.abc import Generator, Iterable
|
||||
from math import pi
|
||||
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
||||
|
||||
import bmesh
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.attribute
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.util.classification
|
||||
@@ -991,114 +991,107 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
return poly
|
||||
|
||||
@classmethod
|
||||
def get_bmesh_from_polygon(cls, poly: Polygon, h: float, polygon_is_si: bool = False) -> bmesh.types.BMesh:
|
||||
"""
|
||||
:param h: Height, in meters.
|
||||
:param polygon_is_si: Should be True if `poly` is defined in meters.
|
||||
"""
|
||||
mat = Matrix()
|
||||
bm = bmesh.new()
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
mat_invert = mat.inverted()
|
||||
si_conversion = 1.0 if polygon_is_si else ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
new_verts = [
|
||||
# bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion)) for v in poly.exterior.coords[0:-1]
|
||||
bm.verts.new(mat_invert @ (Vector([v[0], v[1], 0]) * si_conversion))
|
||||
for v in shapely.get_exterior_ring(poly).coords[0:-1]
|
||||
]
|
||||
[bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
|
||||
bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0]))
|
||||
|
||||
bm.verts.index_update()
|
||||
bm.edges.index_update()
|
||||
|
||||
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=1e-5)
|
||||
bmesh.ops.triangle_fill(bm, edges=bm.edges)
|
||||
bmesh.ops.dissolve_limit(bm, angle_limit=pi / 180 * 5, verts=bm.verts, edges=bm.edges)
|
||||
|
||||
if h != 0:
|
||||
extrusion = bmesh.ops.extrude_face_region(bm, geom=bm.faces)
|
||||
extruded_verts = [g for g in extrusion["geom"] if isinstance(g, bmesh.types.BMVert)]
|
||||
bmesh.ops.translate(bm, vec=[0.0, 0.0, h], verts=extruded_verts)
|
||||
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||
|
||||
return bm
|
||||
|
||||
@classmethod
|
||||
def get_named_obj_from_bmesh(cls, name: str, bmesh: bmesh.types.BMesh) -> bpy.types.Object:
|
||||
mesh = cls.get_named_mesh_from_bmesh(name, bmesh)
|
||||
obj = cls.get_named_obj_from_mesh(name, mesh)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def get_named_obj_from_mesh(cls, name: str, mesh: bpy.types.Mesh) -> bpy.types.Object:
|
||||
def create_object(cls, name: str) -> bpy.types.Object:
|
||||
mesh = bpy.data.meshes.new(name=name)
|
||||
obj = bpy.data.objects.new(name, mesh)
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def get_named_mesh_from_bmesh(cls, name: str, bmesh: bmesh.types.BMesh) -> bpy.types.Mesh:
|
||||
mesh = bpy.data.meshes.new(name=name)
|
||||
bmesh.to_mesh(mesh)
|
||||
bmesh.free()
|
||||
return mesh
|
||||
def set_obj_origin_to_polygon_center(
|
||||
cls, obj: bpy.types.Object, poly: Polygon, polygon_is_si: bool = True
|
||||
) -> None:
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
centroid = poly.centroid
|
||||
if polygon_is_si:
|
||||
obj.location = Vector((centroid.x, centroid.y, 0))
|
||||
else:
|
||||
obj.location = Vector((centroid.x * unit_scale, centroid.y * unit_scale, 0))
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_transformed_mesh_from_local_to_global(cls, mesh: bpy.types.Mesh) -> bpy.types.Mesh:
|
||||
active_obj = cls.get_active_obj()
|
||||
mat = active_obj.matrix_world
|
||||
mesh.transform(mat.inverted())
|
||||
mesh.update()
|
||||
return mesh
|
||||
def get_2d_vertices_from_polygon(
|
||||
cls,
|
||||
poly: Polygon,
|
||||
obj: bpy.types.Object,
|
||||
polygon_is_si: bool = True,
|
||||
) -> list[list[float]]:
|
||||
"""Convert a world-space shapely polygon to 2D vertices in obj's local space, in IFC file units.
|
||||
|
||||
:param poly: The polygon in world space.
|
||||
:param obj: The Blender object whose local space is used.
|
||||
:param polygon_is_si: True if polygon coords are in SI, False if in IFC file units.
|
||||
:return: List of [x, y] coordinates (not closed).
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
||||
bpy.context.view_layer.update()
|
||||
mat_inv = obj.matrix_world.inverted()
|
||||
coords_2d = []
|
||||
for v in shapely.get_exterior_ring(poly).coords[:-1]:
|
||||
world_si = Vector((v[0], v[1], 0))
|
||||
if not polygon_is_si:
|
||||
world_si = world_si * unit_scale
|
||||
local_si = mat_inv @ world_si
|
||||
coords_2d.append([local_si.x / unit_scale, local_si.y / unit_scale])
|
||||
return coords_2d
|
||||
|
||||
@classmethod
|
||||
def edit_active_space_obj_from_mesh(cls, mesh: bpy.types.Mesh) -> None:
|
||||
active_obj = bpy.context.active_object
|
||||
old_mesh = active_obj.data
|
||||
old_mesh_name = old_mesh.name
|
||||
assert active_obj and isinstance(old_mesh, bpy.types.Mesh)
|
||||
tool.Geometry.get_mesh_props(mesh).ifc_definition_id = tool.Geometry.get_mesh_props(old_mesh).ifc_definition_id
|
||||
tool.Geometry.change_object_data(active_obj, mesh, is_global=True)
|
||||
tool.Ifc.edit(active_obj)
|
||||
tool.Blender.remove_data_block(old_mesh)
|
||||
# Rename after old mesh is removed to avoid .001 suffix.
|
||||
mesh.name = old_mesh_name
|
||||
def set_extrusion_representation_from_polygon(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
element: ifcopenshell.entity_instance,
|
||||
poly: Polygon,
|
||||
depth_ifc: float,
|
||||
polygon_is_si: bool = True,
|
||||
) -> None:
|
||||
"""Create or replace the IFC body representation from a polygon extrusion.
|
||||
|
||||
:param obj: The Blender object.
|
||||
:param element: The IFC product entity.
|
||||
:param poly: The polygon in world space.
|
||||
:param depth_ifc: The extrusion depth in IFC file units.
|
||||
:param polygon_is_si: True if polygon coords are in SI, False if in IFC file units.
|
||||
"""
|
||||
ifc_file = tool.Ifc.get()
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(ifc_file)
|
||||
|
||||
coords_2d = cls.get_2d_vertices_from_polygon(poly, obj, polygon_is_si)
|
||||
|
||||
curve = builder.polyline(coords_2d, closed=True)
|
||||
item = builder.extrude(curve, magnitude=depth_ifc)
|
||||
|
||||
old_body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if old_body:
|
||||
context = old_body.ContextOfItems
|
||||
ifcopenshell.api.geometry.unassign_representation(ifc_file, product=element, representation=old_body)
|
||||
ifcopenshell.api.geometry.remove_representation(ifc_file, representation=old_body)
|
||||
else:
|
||||
context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
new_body = builder.get_representation(context, item)
|
||||
ifcopenshell.api.geometry.assign_representation(ifc_file, product=element, representation=new_body)
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=new_body,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_bboxcenter(cls, obj: bpy.types.Object) -> None:
|
||||
mat = obj.matrix_world
|
||||
inverted = mat.inverted()
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
def set_space_representation_from_polygon(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
element: ifcopenshell.entity_instance,
|
||||
poly: Polygon,
|
||||
h: float,
|
||||
polygon_is_si: bool = True,
|
||||
) -> None:
|
||||
"""Create or replace the IFC body representation of a space from a polygon.
|
||||
|
||||
oldLoc = obj.location
|
||||
newLoc = global_bbox_center
|
||||
diff = newLoc - oldLoc
|
||||
for vert in obj.data.vertices:
|
||||
aux_vector = mat @ vert.co
|
||||
aux_vector = aux_vector - diff
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_bboxcenter_and_zero_elevation(cls, obj: bpy.types.Object) -> None:
|
||||
mat = obj.matrix_world
|
||||
inverted = mat.inverted()
|
||||
local_bbox_center = 0.125 * sum((Vector(b) for b in obj.bound_box), Vector())
|
||||
global_bbox_center = mat @ local_bbox_center
|
||||
global_obj_origin = global_bbox_center
|
||||
global_obj_origin.z = 0
|
||||
|
||||
oldLoc = obj.location
|
||||
newLoc = global_obj_origin
|
||||
diff = newLoc - oldLoc
|
||||
for vert in obj.data.vertices:
|
||||
aux_vector = mat @ vert.co
|
||||
aux_vector = aux_vector - diff
|
||||
vert.co = inverted @ aux_vector
|
||||
obj.location = newLoc
|
||||
:param h: The height in SI (meters).
|
||||
"""
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
cls.set_extrusion_representation_from_polygon(obj, element, poly, h / unit_scale, polygon_is_si)
|
||||
|
||||
@classmethod
|
||||
def set_obj_origin_to_cursor_position_and_zero_elevation(cls, obj: bpy.types.Object) -> None:
|
||||
@@ -1145,65 +1138,53 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
if z != 0:
|
||||
obj.location = obj.location + Vector((0, 0, z))
|
||||
|
||||
@classmethod
|
||||
def get_2d_vertices_from_obj(cls, obj: bpy.types.Object) -> list[tuple]:
|
||||
points = []
|
||||
vectors = [v.co for v in obj.data.vertices.values()]
|
||||
for vector in vectors:
|
||||
points.append(vector.xy)
|
||||
|
||||
points.append(vectors[0].xy)
|
||||
return points
|
||||
|
||||
@classmethod
|
||||
def get_scaled_2d_vertices(cls, points: list[Vector]) -> list[tuple[float, float]]:
|
||||
model = tool.Ifc.get()
|
||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(model)
|
||||
_points = []
|
||||
for p in points:
|
||||
_p = list(p)
|
||||
_p[0] /= unit_scale
|
||||
_p[1] /= unit_scale
|
||||
_points.append(_p)
|
||||
return _points
|
||||
|
||||
@classmethod
|
||||
def assign_swept_area_outer_curve_from_2d_vertices(cls, obj: bpy.types.Object, vertices: list[Vector]) -> None:
|
||||
body = cls.get_body_representation(obj)
|
||||
model = tool.Ifc.get()
|
||||
extrusion = tool.Model.get_extrusion(body)
|
||||
area = extrusion.SweptArea
|
||||
old_area = area.OuterCurve
|
||||
|
||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(model)
|
||||
outer_curve = builder.polyline(vertices, closed=True)
|
||||
|
||||
area.OuterCurve = outer_curve
|
||||
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_area)
|
||||
|
||||
@classmethod
|
||||
def get_body_representation(cls, obj: bpy.types.Object) -> Union[ifcopenshell.entity_instance, None]:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
return ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
|
||||
@classmethod
|
||||
def assign_ifcspace_class_to_obj(cls, obj: bpy.types.Object) -> None:
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcSpace")
|
||||
bonsai.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class="IfcSpace",
|
||||
should_add_representation=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def assign_type_to_obj(cls, obj: bpy.types.Object) -> None:
|
||||
# TODO this code looks in the wrong spot and suspicious
|
||||
props = tool.Model.get_model_props()
|
||||
ifc_file = tool.Ifc.get()
|
||||
relating_type_id = props.relating_type_id
|
||||
relating_type = tool.Ifc.get().by_id(int(relating_type_id))
|
||||
relating_type = ifc_file.by_id(int(relating_type_id))
|
||||
ifc_class = relating_type.is_a()
|
||||
instance_class = ifcopenshell.util.type.get_applicable_entities(ifc_class, ifc_file.schema)[0]
|
||||
bpy.ops.bim.assign_class(obj=obj.name, ifc_class=instance_class)
|
||||
bonsai.core.root.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=instance_class,
|
||||
should_add_representation=False,
|
||||
)
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
assert element
|
||||
ifcopenshell.api.type.assign_type(ifc_file, related_objects=[element], relating_type=relating_type)
|
||||
|
||||
@classmethod
|
||||
def set_covering_representation_from_polygon(
|
||||
cls,
|
||||
obj: bpy.types.Object,
|
||||
poly: Polygon,
|
||||
polygon_is_si: bool = True,
|
||||
) -> None:
|
||||
"""Create the covering body representation from a polygon, extruded by the type's material layer thickness."""
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
relating_type = ifcopenshell.util.element.get_type(element)
|
||||
material = ifcopenshell.util.element.get_material(relating_type, should_skip_usage=True)
|
||||
depth = 0.0
|
||||
if material and material.is_a("IfcMaterialLayerSet"):
|
||||
depth = sum(layer.LayerThickness for layer in material.MaterialLayers)
|
||||
cls.set_extrusion_representation_from_polygon(obj, element, poly, depth, polygon_is_si)
|
||||
|
||||
@classmethod
|
||||
def assign_relating_type_to_element(
|
||||
cls,
|
||||
@@ -1214,14 +1195,6 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
) -> None:
|
||||
bonsai.core.type.assign_type(ifc, tool.Model, type, element=element, type=relating_type)
|
||||
|
||||
@classmethod
|
||||
def regen_obj_representation(cls, obj: bpy.types.Object, body: ifcopenshell.entity_instance) -> None:
|
||||
bonsai.core.geometry.switch_representation(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
obj=obj,
|
||||
representation=body,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_space_visibility(cls, is_visible: bool) -> None:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Feature: Covering
|
||||
Covers covering tool.
|
||||
|
||||
Scenario: Execute generate flooring coverings from walls
|
||||
Scenario: Add flooring from walls
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
@@ -24,9 +24,9 @@ Scenario: Execute generate flooring coverings from walls
|
||||
And the cursor is at "0,2.0,0"
|
||||
And I set "scene.BIMModelProperties.length" to "1.9"
|
||||
And I press "bim.add_occurrence"
|
||||
# add_instance_flooring_coverings_from_walls is expecting FLOORING predefined type.
|
||||
# Set COV30 predefined type to FLOORING.
|
||||
And the object "IfcCoveringType/COV30" is selected
|
||||
And I look at the "Attributes" panel
|
||||
And I look at the "Object Attributes" panel
|
||||
And I click "Edit"
|
||||
And I set the "PredefinedType" property to "FLOORING"
|
||||
And I click "Save Attributes"
|
||||
@@ -42,3 +42,108 @@ Scenario: Execute generate flooring coverings from walls
|
||||
Then the object "IfcCovering/Covering0" exists
|
||||
And the object "IfcCovering/Covering0" is at "1.8,1.05,0.0"
|
||||
And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03"
|
||||
|
||||
Scenario: Add ceiling from walls
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_occurrence"
|
||||
And the object "IfcWall/Wall" is selected
|
||||
And I press "bim.change_layer_length(length=3.6)"
|
||||
And the cursor is at "3.6,0.1,3"
|
||||
And I set "scene.BIMModelProperties.length" to "2.0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "3.5,2.1,3"
|
||||
And I set "scene.BIMModelProperties.length" to "3.5"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "0,2.0,0"
|
||||
And I set "scene.BIMModelProperties.length" to "1.9"
|
||||
And I press "bim.add_occurrence"
|
||||
# Set COV30 predefined type to CEILING.
|
||||
And the object "IfcCoveringType/COV30" is selected
|
||||
And I look at the "Object Attributes" panel
|
||||
And I click "Edit"
|
||||
And I set the "PredefinedType" property to "CEILING"
|
||||
And I click "Save Attributes"
|
||||
# Run the operator with ceiling height = 2.7 (default).
|
||||
When the object "IfcWall/Wall" is selected
|
||||
And additionally the object "IfcWall/Wall.001" is selected
|
||||
And additionally the object "IfcWall/Wall.002" is selected
|
||||
And additionally the object "IfcWall/Wall.003" is selected
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_instance_ceiling_coverings_from_walls"
|
||||
Then the object "IfcCovering/Covering0" exists
|
||||
And the object "IfcCovering/Covering0" is at "1.8,1.05,2.7"
|
||||
And the object "IfcCovering/Covering0" dimensions are "3.4,1.9,0.03"
|
||||
|
||||
Scenario: Add flooring from cursor
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "1.1,0,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the object "IfcWall/Wall.001" is selected
|
||||
And I press "bim.hotkey(hotkey='S_R')"
|
||||
And the cursor is at "0,.9,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "-1,0,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the object "IfcWall/Wall.003" is selected
|
||||
And I press "bim.hotkey(hotkey='S_R')"
|
||||
And the object "IfcWall/Wall.003" is moved to "0,0,0"
|
||||
# Set COV30 predefined type to FLOORING.
|
||||
And the object "IfcCoveringType/COV30" is selected
|
||||
And I look at the "Object Attributes" panel
|
||||
And I click "Edit"
|
||||
And I set the "PredefinedType" property to "FLOORING"
|
||||
And I click "Save Attributes"
|
||||
# Generate covering from cursor inside the room.
|
||||
When the cursor is at "0.5,0.5,0"
|
||||
And I deselect all objects
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_instance_flooring_covering_from_cursor"
|
||||
Then the object "IfcCovering/Covering" exists
|
||||
And the object "IfcCovering/Covering" dimensions are "1,0.8,0.03"
|
||||
|
||||
Scenario: Add ceiling from cursor
|
||||
Given an empty IFC project
|
||||
And I load the demo construction library
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcWallType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcWallType') if e.Name == 'WAL100'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "1.1,0,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the object "IfcWall/Wall.001" is selected
|
||||
And I press "bim.hotkey(hotkey='S_R')"
|
||||
And the cursor is at "0,.9,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the cursor is at "-1,0,0"
|
||||
And I press "bim.add_occurrence"
|
||||
And the object "IfcWall/Wall.003" is selected
|
||||
And I press "bim.hotkey(hotkey='S_R')"
|
||||
And the object "IfcWall/Wall.003" is moved to "0,0,0"
|
||||
# Set COV30 predefined type to CEILING.
|
||||
And the object "IfcCoveringType/COV30" is selected
|
||||
And I look at the "Object Attributes" panel
|
||||
And I click "Edit"
|
||||
And I set the "PredefinedType" property to "CEILING"
|
||||
And I click "Save Attributes"
|
||||
# Generate covering from cursor inside the room.
|
||||
When the cursor is at "0.5,0.5,0"
|
||||
And I deselect all objects
|
||||
And I set "scene.BIMModelProperties.ifc_class" to "IfcCoveringType"
|
||||
And the variable "element_type" is "[e for e in {ifc}.by_type('IfcCoveringType') if e.Name == 'COV30'][0].id()"
|
||||
And I set "scene.BIMModelProperties.relating_type_id" to "{element_type}"
|
||||
And I press "bim.add_instance_ceiling_covering_from_cursor"
|
||||
Then the object "IfcCovering/Covering" exists
|
||||
And the object "IfcCovering/Covering" dimensions are "1,0.8,0.03"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.context
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.root
|
||||
@@ -99,6 +100,42 @@ class TestCanAggregate(NewFile):
|
||||
subelement_obj = bpy.data.objects.new("Object", None)
|
||||
assert subject.can_aggregate(element_obj, subelement_obj) is False
|
||||
|
||||
def test_element_cannot_aggregate_to_itself(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
element = ifc.createIfcElementAssembly()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_aggregate(element_obj, element_obj) is False
|
||||
|
||||
def test_cyclic_aggregation_is_prevented(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
assembly_a = ifc.createIfcElementAssembly()
|
||||
assembly_a_obj = bpy.data.objects.new("AssemblyA", None)
|
||||
tool.Ifc.link(assembly_a, assembly_a_obj)
|
||||
beam = ifc.createIfcBeam()
|
||||
beam_obj = bpy.data.objects.new("Beam", None)
|
||||
tool.Ifc.link(beam, beam_obj)
|
||||
ifcopenshell.api.aggregate.assign_object(ifc, products=[beam], relating_object=assembly_a)
|
||||
assert subject.can_aggregate(beam_obj, assembly_a_obj) is False
|
||||
|
||||
def test_deep_cyclic_aggregation_is_prevented(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
assembly_a = ifc.createIfcElementAssembly()
|
||||
assembly_a_obj = bpy.data.objects.new("AssemblyA", None)
|
||||
tool.Ifc.link(assembly_a, assembly_a_obj)
|
||||
assembly_b = ifc.createIfcElementAssembly()
|
||||
assembly_b_obj = bpy.data.objects.new("AssemblyB", None)
|
||||
tool.Ifc.link(assembly_b, assembly_b_obj)
|
||||
beam = ifc.createIfcBeam()
|
||||
beam_obj = bpy.data.objects.new("Beam", None)
|
||||
tool.Ifc.link(beam, beam_obj)
|
||||
ifcopenshell.api.aggregate.assign_object(ifc, products=[assembly_b], relating_object=assembly_a)
|
||||
ifcopenshell.api.aggregate.assign_object(ifc, products=[beam], relating_object=assembly_b)
|
||||
assert subject.can_aggregate(beam_obj, assembly_a_obj) is False
|
||||
|
||||
|
||||
class TestHasPhysicalBodyRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import bpy
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.spatial
|
||||
|
||||
import bonsai.core.tool
|
||||
@@ -51,6 +52,42 @@ class TestCanNest(NewFile):
|
||||
subelement_obj = bpy.data.objects.new("Object", None)
|
||||
assert subject.can_nest(element_obj, subelement_obj) is False
|
||||
|
||||
def test_element_cannot_nest_to_itself(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
element = ifc.createIfcWall()
|
||||
element_obj = bpy.data.objects.new("Object", None)
|
||||
tool.Ifc.link(element, element_obj)
|
||||
assert subject.can_nest(element_obj, element_obj) is False
|
||||
|
||||
def test_cyclic_nesting_is_prevented(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
wall_a = ifc.createIfcWall()
|
||||
wall_a_obj = bpy.data.objects.new("WallA", None)
|
||||
tool.Ifc.link(wall_a, wall_a_obj)
|
||||
wall_b = ifc.createIfcWall()
|
||||
wall_b_obj = bpy.data.objects.new("WallB", None)
|
||||
tool.Ifc.link(wall_b, wall_b_obj)
|
||||
ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_b], relating_object=wall_a)
|
||||
assert subject.can_nest(wall_b_obj, wall_a_obj) is False
|
||||
|
||||
def test_deep_cyclic_nesting_is_prevented(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
wall_a = ifc.createIfcWall()
|
||||
wall_a_obj = bpy.data.objects.new("WallA", None)
|
||||
tool.Ifc.link(wall_a, wall_a_obj)
|
||||
wall_b = ifc.createIfcWall()
|
||||
wall_b_obj = bpy.data.objects.new("WallB", None)
|
||||
tool.Ifc.link(wall_b, wall_b_obj)
|
||||
wall_c = ifc.createIfcWall()
|
||||
wall_c_obj = bpy.data.objects.new("WallC", None)
|
||||
tool.Ifc.link(wall_c, wall_c_obj)
|
||||
ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_b], relating_object=wall_a)
|
||||
ifcopenshell.api.nest.assign_object(ifc, related_objects=[wall_c], relating_object=wall_b)
|
||||
assert subject.can_nest(wall_c_obj, wall_a_obj) is False
|
||||
|
||||
|
||||
class TestDisableEditing(NewFile):
|
||||
def test_run(self):
|
||||
|
||||
@@ -28,6 +28,7 @@ dist:
|
||||
mkdir -p dist
|
||||
cp -r $(PACKAGE_NAME) build/
|
||||
cp pyproject.toml build/
|
||||
if [ -f README.md ]; then cp README.md build/; fi
|
||||
ifeq ($(IS_STABLE), TRUE)
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' build/pyproject.toml
|
||||
ifdef IS_MODULE
|
||||
|
||||
@@ -51,8 +51,10 @@ def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instanc
|
||||
new = context.ParentContext
|
||||
for inverse in file.get_inverse(context):
|
||||
if inverse.is_a("IfcCoordinateOperation"):
|
||||
# Trick to make sure the coordinate operation is not referenced
|
||||
# by a context so we can delete it safely
|
||||
inverse.SourceCRS = inverse.TargetCRS
|
||||
ifcopenshell.util.element.remove_deep(file, inverse)
|
||||
ifcopenshell.util.element.remove_deep2(file, inverse)
|
||||
else:
|
||||
ifcopenshell.util.element.replace_attribute(inverse, context, new)
|
||||
file.remove(context)
|
||||
|
||||
@@ -59,6 +59,6 @@ def edit_cost_value(
|
||||
value["ValueComponent"],
|
||||
)
|
||||
value = file.create_entity("IfcMeasureWithUnit", value_component, value["UnitComponent"])
|
||||
if old_unit_basis and file.get_total_inverses(old_unit_basis) == 0:
|
||||
ifcopenshell.util.element.remove_deep(file, old_unit_basis)
|
||||
if old_unit_basis:
|
||||
ifcopenshell.util.element.remove_deep2(file, old_unit_basis)
|
||||
setattr(cost_value, name, value)
|
||||
|
||||
@@ -22,11 +22,13 @@ import ifcopenshell.util.element
|
||||
|
||||
|
||||
def remove_feature(file: ifcopenshell.file, feature: ifcopenshell.entity_instance) -> None:
|
||||
"""Remove a feature
|
||||
"""Permanently delete a feature element and its void or projection relationship.
|
||||
|
||||
Fillings are retained as orphans. Featured elements remain. Features
|
||||
cannot exist by themselves, so not only is the relationship removed, the
|
||||
feature is also removed.
|
||||
The feature entity (e.g. IfcOpeningElement) is removed from the model
|
||||
along with its IfcRelVoidsElement or IfcRelProjectsElement relationship.
|
||||
The host element (wall, slab, etc.) is unaffected. Any fillings (windows,
|
||||
doors) that occupied the opening become orphaned and must be separately
|
||||
deleted via root.remove_product.
|
||||
|
||||
:param feature: The IfcFeatureElement to remove.
|
||||
|
||||
|
||||
@@ -31,17 +31,6 @@ def add_boolean(
|
||||
) -> list[ifcopenshell.entity_instance]:
|
||||
"""Adds a boolean operation to two or more representation items
|
||||
|
||||
If an IfcBooleanOperand is part of the top level items in an
|
||||
IfcShapeRepresentation, it will be removed from that level whilst being
|
||||
added to the IfcBooleanResult. This is because it is generally intuitive
|
||||
that an item is either participating in a boolean operation, or being an
|
||||
item in its own right, but not both.
|
||||
|
||||
However, if an IfcBooleanOperand is part of another boolean operation
|
||||
already, it will not be removed from the existing operation. A new
|
||||
operation will be created, and therefore it will participate in two
|
||||
operations.
|
||||
|
||||
This function protects against recursive booleans.
|
||||
|
||||
After a boolean operation is made, since the items of
|
||||
@@ -101,9 +90,6 @@ def add_boolean(
|
||||
|
||||
booleans = []
|
||||
for second_item in second_items:
|
||||
for inverse in file.get_inverse(second_item):
|
||||
if inverse.is_a("IfcShapeRepresentation"):
|
||||
inverse.Items = list(set(inverse.Items) - {second_item})
|
||||
if first.is_a("IfcTesselatedFaceSet"):
|
||||
first.Closed = True # For now, trust the user to do the right thing.
|
||||
if second_item.is_a("IfcTesselatedFaceSet"):
|
||||
|
||||
@@ -47,7 +47,9 @@ def add_wall_representation(
|
||||
:param thickness: The thickness of the wall in meters.
|
||||
:param x_angle: The slope angle along the wall's X-axis, in radians.
|
||||
:param clippings: List of clipping definitions. Clippings can be `Clipping` objects
|
||||
or dictionaries of arguments for `Clipping.parse`.
|
||||
or dictionaries of arguments for `Clipping.parse`. Each clipping has a
|
||||
``normal`` that points toward the removed material (the discarded side),
|
||||
not toward the kept material; see :func:`clip_solid` for details.
|
||||
:param booleans: List of any existing IfcBooleanResults.
|
||||
:return: IfcShapeRepresentation.
|
||||
"""
|
||||
|
||||
@@ -52,10 +52,11 @@ def edit_object_placement(
|
||||
:param is_si: If True, the matrix is given in SI units. If false, in
|
||||
project units.
|
||||
:param should_transform_children: A child element is a nested element,
|
||||
opening, filling, etc. If true, child elements will move along with the
|
||||
parent. If false, child elements will stay where they are. Because most
|
||||
placements in IFC are relative, this means that if a child moves, we
|
||||
actually don't change their placement.
|
||||
opening, filling, etc. If True, child elements move along with the
|
||||
parent; pass True when moving an assembly (roof, furniture group, etc.)
|
||||
and you want all children to follow. If False (default), child elements
|
||||
keep their current world positions; their local placements are rewritten
|
||||
to compensate for the parent move.
|
||||
:return: The new or updated IfcLocalPlacement entity
|
||||
"""
|
||||
usecase = Usecase()
|
||||
|
||||
@@ -69,6 +69,12 @@ def regenerate_wall_representation(
|
||||
additional extrusions are generated for each connection that boolean
|
||||
difference the base extrusion.
|
||||
|
||||
Clippings applied via :func:`geometry.clip_solid` or
|
||||
:func:`geometry.clip_solid_bounded` are preserved only if the ``element``
|
||||
parameter was passed when creating them, which registers the result in the
|
||||
``BBIM_Boolean`` property set. Clippings created without that parameter
|
||||
are silently discarded during regeneration.
|
||||
|
||||
This will also update the axis line representation (e.g. trim the axis line
|
||||
to any connections).
|
||||
|
||||
|
||||
@@ -83,6 +83,7 @@ def validate_type(
|
||||
|
||||
if remaining_items:
|
||||
ifcopenshell.api.geometry.add_boolean(file, preferred_item, remaining_items, "UNION")
|
||||
representation.Items = [i for i in representation.Items if i not in remaining_items]
|
||||
|
||||
representation.RepresentationType = ifcopenshell.util.representation.guess_type(representation.Items)
|
||||
if representation.RepresentationType == "CSG":
|
||||
|
||||
@@ -42,7 +42,5 @@ def remove_grid_axis(file: ifcopenshell.file, axis: ifcopenshell.entity_instance
|
||||
ifcopenshell.api.grid.remove_grid_axis(model, axis=axis_2)
|
||||
"""
|
||||
axis_curve = axis.AxisCurve
|
||||
if file.get_total_inverses(axis_curve) == 1:
|
||||
ifcopenshell.util.element.remove_deep(file, axis_curve)
|
||||
file.remove(axis_curve)
|
||||
file.remove(axis)
|
||||
ifcopenshell.util.element.remove_deep2(file, axis_curve)
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
from typing import Union
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.owner
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.element
|
||||
|
||||
@@ -137,7 +139,10 @@ def assign_object(
|
||||
if not objects_to_change:
|
||||
return is_nested_by
|
||||
|
||||
# NOTE: An object can both be nested and assigned to a container or an aggregate.
|
||||
# Can be either only nested, aggregated, or contained at the same time.
|
||||
possibly_contained = [o for o in objects_without_nests if hasattr(o, "ContainedInStructure")]
|
||||
ifcopenshell.api.spatial.unassign_container(file, products=possibly_contained)
|
||||
ifcopenshell.api.aggregate.unassign_object(file, products=objects_without_nests)
|
||||
|
||||
# unassign elements from previous nests
|
||||
for nests in previous_nests_rels:
|
||||
|
||||
@@ -22,9 +22,9 @@ import ifcopenshell.util.element
|
||||
def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.entity_instance) -> None:
|
||||
"""Removes a property template
|
||||
|
||||
Note that a property set template should always have at least one
|
||||
property template to be valid, so take care when removing property
|
||||
templates.
|
||||
Note that a property set template should always have at least one property
|
||||
template to be valid. So a property set template will not be removed if it
|
||||
is the only template ina a property ste template.
|
||||
|
||||
:param prop_template: The IfcSimplePropertyTemplate to remove.
|
||||
:return: None
|
||||
@@ -43,10 +43,8 @@ def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.en
|
||||
ifcopenshell.api.pset_template.remove_prop_template(model, prop_template=prop2)
|
||||
"""
|
||||
for inverse in file.get_inverse(prop_template):
|
||||
if len(inverse.HasPropertyTemplates) == 1:
|
||||
inverse.HasPropertyTemplates = []
|
||||
else:
|
||||
if len(inverse.HasPropertyTemplates) > 1:
|
||||
has_property_templates = list(inverse.HasPropertyTemplates)
|
||||
has_property_templates.remove(prop_template)
|
||||
inverse.HasPropertyTemplates = has_property_templates
|
||||
ifcopenshell.util.element.remove_deep(file, prop_template)
|
||||
ifcopenshell.util.element.remove_deep2(file, prop_template)
|
||||
|
||||
@@ -38,4 +38,4 @@ def remove_pset_template(file: ifcopenshell.file, pset_template: ifcopenshell.en
|
||||
# Let's remove the template.
|
||||
ifcopenshell.api.pset_template.remove_pset_template(model, pset_template=template)
|
||||
"""
|
||||
ifcopenshell.util.element.remove_deep(file, pset_template)
|
||||
ifcopenshell.util.element.remove_deep2(file, pset_template)
|
||||
|
||||
@@ -79,5 +79,5 @@ def add_resource_quantity(
|
||||
old_quantity = resource.BaseQuantity
|
||||
resource.BaseQuantity = quantity
|
||||
if old_quantity:
|
||||
ifcopenshell.util.element.remove_deep(file, old_quantity)
|
||||
ifcopenshell.util.element.remove_deep2(file, old_quantity)
|
||||
return quantity
|
||||
|
||||
@@ -47,4 +47,4 @@ def remove_resource_quantity(file: ifcopenshell.file, resource: ifcopenshell.ent
|
||||
old_quantity = resource.BaseQuantity
|
||||
resource.BaseQuantity = None
|
||||
if old_quantity:
|
||||
ifcopenshell.util.element.remove_deep(file, old_quantity)
|
||||
ifcopenshell.util.element.remove_deep2(file, old_quantity)
|
||||
|
||||
@@ -26,7 +26,7 @@ def assign_process(
|
||||
relating_process: ifcopenshell.entity_instance,
|
||||
related_object: ifcopenshell.entity_instance,
|
||||
) -> ifcopenshell.entity_instance:
|
||||
"""Assigns an object to be related to a process, typically a construction task
|
||||
"""Assigns an object as an input, control, or resource of a process
|
||||
|
||||
Processes work using the ICOM (Input, Controls, Outputs, Mechanisms)
|
||||
paradigm in IFC. This process model is commonly used in modeling
|
||||
@@ -63,6 +63,17 @@ def assign_process(
|
||||
|
||||
For resources, any construction resource may be assigned to a task.
|
||||
|
||||
.. warning::
|
||||
|
||||
This function creates an **Input** relationship
|
||||
(``IfcRelAssignsToProcess``), meaning the product is *consumed* or
|
||||
*operated on* by the task — the typical case is demolition or
|
||||
maintenance.
|
||||
|
||||
If the task *constructs or installs* a product (e.g. erecting a wall
|
||||
or fitting a window), use :func:`assign_product` instead, which
|
||||
creates an **Output** relationship (``IfcRelAssignsToProduct``).
|
||||
|
||||
:param relating_process: The IfcProcess (typically IfcTask) that the
|
||||
input, control, or resource is related to.
|
||||
:param related_object: The IfcProduct (for input), IfcCostItem (for
|
||||
@@ -77,7 +88,7 @@ def assign_process(
|
||||
# need to be part of a work schedule.
|
||||
schedule = ifcopenshell.api.sequence.add_work_schedule(model, name="Construction Schedule A")
|
||||
|
||||
# Let's create a construction task. Note that the predefined type is
|
||||
# Let's create a demolition task. Note that the predefined type is
|
||||
# important to distinguish types of tasks.
|
||||
task = ifcopenshell.api.sequence.add_task(model,
|
||||
work_schedule=schedule, name="Demolish existing", identification="A", predefined_type="DEMOLITION")
|
||||
@@ -85,8 +96,12 @@ def assign_process(
|
||||
# Let's say we have a wall somewhere.
|
||||
wall = ifcopenshell.api.root.create_entity(model, ifc_class="IfcWall")
|
||||
|
||||
# Let's demolish that wall!
|
||||
# The wall is an INPUT to the demolition task (it will be consumed).
|
||||
ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=wall)
|
||||
|
||||
# For a construction task that BUILDS a wall, use assign_product instead:
|
||||
# build_task = ifcopenshell.api.sequence.add_task(model, ..., predefined_type="CONSTRUCTION")
|
||||
# ifcopenshell.api.sequence.assign_product(model, relating_product=wall, related_object=build_task)
|
||||
"""
|
||||
if related_object.HasAssignments:
|
||||
for assignment in related_object.HasAssignments:
|
||||
|
||||
@@ -47,4 +47,5 @@ def remove_unit(file: ifcopenshell.file, unit: ifcopenshell.entity_instance) ->
|
||||
unit_assignment.Units = units
|
||||
else:
|
||||
file.remove(unit_assignment)
|
||||
ifcopenshell.util.element.remove_deep(file, unit)
|
||||
# TODO handle other possible unit inverses
|
||||
ifcopenshell.util.element.remove_deep2(file, unit)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# 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 ifcopenshell.api.cost
|
||||
import ifcopenshell.api.unit
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestEditCostValue(test.bootstrap.IFC4):
|
||||
def test_editing_applied_value(self):
|
||||
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
|
||||
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
|
||||
ifcopenshell.api.cost.edit_cost_value(self.file, cost_value=value, attributes={"AppliedValue": 42.0})
|
||||
assert value.AppliedValue.wrappedValue == 42.0
|
||||
|
||||
def test_editing_unit_basis_removes_old_deeply(self):
|
||||
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
|
||||
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
|
||||
unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.cost.edit_cost_value(
|
||||
self.file,
|
||||
cost_value=value,
|
||||
attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}},
|
||||
)
|
||||
old_basis = value.UnitBasis
|
||||
assert old_basis is not None
|
||||
old_basis_id = old_basis.id()
|
||||
# Now change to a new unit basis — the old one should be deeply removed.
|
||||
ifcopenshell.api.cost.edit_cost_value(
|
||||
self.file,
|
||||
cost_value=value,
|
||||
attributes={"UnitBasis": {"ValueComponent": 2.0, "UnitComponent": unit}},
|
||||
)
|
||||
assert value.UnitBasis is not None
|
||||
assert value.UnitBasis.id() != old_basis_id
|
||||
|
||||
def test_clearing_unit_basis(self):
|
||||
schedule = ifcopenshell.api.cost.add_cost_schedule(self.file)
|
||||
item = ifcopenshell.api.cost.add_cost_item(self.file, cost_schedule=schedule)
|
||||
value = ifcopenshell.api.cost.add_cost_value(self.file, parent=item)
|
||||
unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT")
|
||||
ifcopenshell.api.cost.edit_cost_value(
|
||||
self.file,
|
||||
cost_value=value,
|
||||
attributes={"UnitBasis": {"ValueComponent": 1.0, "UnitComponent": unit}},
|
||||
)
|
||||
assert value.UnitBasis is not None
|
||||
ifcopenshell.api.cost.edit_cost_value(
|
||||
self.file, cost_value=value, attributes={"UnitBasis": None}
|
||||
)
|
||||
assert value.UnitBasis is None
|
||||
|
||||
|
||||
class TestEditCostValueIFC4X3(test.bootstrap.IFC4X3, TestEditCostValue):
|
||||
pass
|
||||
@@ -42,7 +42,7 @@ class TestAddBoolean(test.bootstrap.IFC4):
|
||||
assert boolean.FirstOperand == first
|
||||
assert boolean.SecondOperand == second
|
||||
assert boolean.Operator == "DIFFERENCE"
|
||||
assert set(rep.Items) == {boolean}
|
||||
assert set(rep.Items) == {boolean, second}
|
||||
|
||||
def test_adding_multiple_booleans_from_three_top_level_items(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
@@ -58,13 +58,14 @@ class TestAddBoolean(test.bootstrap.IFC4):
|
||||
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1, second2])
|
||||
assert len(booleans) == 2
|
||||
assert len(rep.Items) == 1
|
||||
assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult")
|
||||
assert rep.Items[0].SecondOperand == second2
|
||||
assert rep.Items[0].Operator == "DIFFERENCE"
|
||||
assert rep.Items[0].FirstOperand.FirstOperand == first
|
||||
assert rep.Items[0].FirstOperand.SecondOperand == second1
|
||||
assert rep.Items[0].FirstOperand.Operator == "DIFFERENCE"
|
||||
final_boolean = booleans[-1]
|
||||
assert final_boolean.FirstOperand.is_a("IfcBooleanResult")
|
||||
assert final_boolean.SecondOperand == second2
|
||||
assert final_boolean.Operator == "DIFFERENCE"
|
||||
assert final_boolean.FirstOperand.FirstOperand == first
|
||||
assert final_boolean.FirstOperand.SecondOperand == second1
|
||||
assert final_boolean.FirstOperand.Operator == "DIFFERENCE"
|
||||
assert set(rep.Items) == {final_boolean, second1, second2}
|
||||
|
||||
def test_adding_a_boolean_to_an_existing_operand_from_a_top_level_item(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
@@ -78,14 +79,16 @@ class TestAddBoolean(test.bootstrap.IFC4):
|
||||
second2 = builder.block()
|
||||
rep = builder.get_representation(body, [first, second1])
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
|
||||
# second1 stays in Items, add second2 as well
|
||||
rep.Items = list(rep.Items) + [second2]
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second2])
|
||||
assert len(booleans) == 1
|
||||
assert len(rep.Items) == 1
|
||||
assert rep.Items[0].FirstOperand.is_a("IfcBooleanResult")
|
||||
assert rep.Items[0].SecondOperand == second2
|
||||
assert rep.Items[0].FirstOperand.FirstOperand == first
|
||||
assert rep.Items[0].FirstOperand.SecondOperand == second1
|
||||
final_boolean = booleans[0]
|
||||
assert final_boolean.FirstOperand.is_a("IfcBooleanResult")
|
||||
assert final_boolean.SecondOperand == second2
|
||||
assert final_boolean.FirstOperand.FirstOperand == first
|
||||
assert final_boolean.FirstOperand.SecondOperand == second1
|
||||
assert set(rep.Items) == {final_boolean, second1, second2}
|
||||
|
||||
def test_adding_a_boolean_to_an_existing_operand_from_another_operand(self):
|
||||
ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject")
|
||||
@@ -104,7 +107,7 @@ class TestAddBoolean(test.bootstrap.IFC4):
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first1, [second2])
|
||||
|
||||
assert len(booleans) == 1
|
||||
assert len(rep.Items) == 2
|
||||
assert len(rep.Items) == 4
|
||||
|
||||
assert self.file.get_total_inverses(first1) == 1
|
||||
result = next(iter(self.file.get_inverse(first1)))
|
||||
@@ -132,14 +135,15 @@ class TestAddBoolean(test.bootstrap.IFC4):
|
||||
rep = builder.get_representation(body, [first, second])
|
||||
ifcopenshell.api.geometry.add_boolean(self.file, first, [second])
|
||||
ifcopenshell.api.geometry.add_boolean(self.file, first, [second])
|
||||
assert len(rep.Items) == 1
|
||||
assert rep.Items[0].FirstOperand == first
|
||||
assert rep.Items[0].SecondOperand == second
|
||||
assert set(rep.Items) == {self.file.by_type("IfcBooleanResult")[0], second}
|
||||
boolean = self.file.by_type("IfcBooleanResult")[0]
|
||||
assert boolean.FirstOperand == first
|
||||
assert boolean.SecondOperand == second
|
||||
ifcopenshell.api.geometry.add_boolean(self.file, second, [second])
|
||||
ifcopenshell.api.geometry.add_boolean(self.file, second, [first])
|
||||
assert len(rep.Items) == 1
|
||||
assert rep.Items[0].FirstOperand == first
|
||||
assert rep.Items[0].SecondOperand == second
|
||||
assert set(rep.Items) == {boolean, second}
|
||||
assert boolean.FirstOperand == first
|
||||
assert boolean.SecondOperand == second
|
||||
assert len(self.file.by_type("IfcBooleanResult")) == 1
|
||||
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ class TestValidateType(test.bootstrap.IFC4):
|
||||
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
|
||||
assert len(booleans) == 1
|
||||
assert len(rep.Items) == 3
|
||||
assert len(rep.Items) == 4
|
||||
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is True
|
||||
assert len(rep.Items) == 1
|
||||
assert rep.RepresentationType == "CSG"
|
||||
@@ -96,9 +96,9 @@ class TestValidateType(test.bootstrap.IFC4):
|
||||
|
||||
booleans = ifcopenshell.api.geometry.add_boolean(self.file, first, [second1])
|
||||
assert len(booleans) == 1
|
||||
assert len(rep.Items) == 2
|
||||
assert len(rep.Items) == 3 # boolean replaced first, but second1 stays in Items
|
||||
assert ifcopenshell.api.geometry.validate_type(self.file, rep) is False
|
||||
assert len(rep.Items) == 2
|
||||
assert len(rep.Items) == 2 # validate_type unioned second1 into the boolean
|
||||
assert rep.RepresentationType is None
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# 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 ifcopenshell.api.grid
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestRemoveGridAxis(test.bootstrap.IFC4):
|
||||
def test_removing_an_axis_removes_its_curve(self):
|
||||
grid = self.file.createIfcGrid()
|
||||
axis = ifcopenshell.api.grid.create_grid_axis(
|
||||
self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid
|
||||
)
|
||||
axis.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))])
|
||||
axis2 = ifcopenshell.api.grid.create_grid_axis(
|
||||
self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid
|
||||
)
|
||||
axis2.AxisCurve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((1.0, 0.0, 0.0))])
|
||||
ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2)
|
||||
assert grid.UAxes == (axis,)
|
||||
assert len(self.file.by_type("IfcGridAxis")) == 1
|
||||
# The curve should be removed since it was only used by the removed axis.
|
||||
assert len(self.file.by_type("IfcPolyline")) == 1
|
||||
|
||||
def test_removing_an_axis_preserves_shared_curve(self):
|
||||
grid = self.file.createIfcGrid()
|
||||
shared_curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))])
|
||||
axis = ifcopenshell.api.grid.create_grid_axis(
|
||||
self.file, axis_tag="A", same_sense=True, uvw_axes="UAxes", grid=grid
|
||||
)
|
||||
axis.AxisCurve = shared_curve
|
||||
axis2 = ifcopenshell.api.grid.create_grid_axis(
|
||||
self.file, axis_tag="B", same_sense=True, uvw_axes="UAxes", grid=grid
|
||||
)
|
||||
axis2.AxisCurve = shared_curve
|
||||
ifcopenshell.api.grid.remove_grid_axis(self.file, axis=axis2)
|
||||
assert grid.UAxes == (axis,)
|
||||
# The shared curve should be preserved since it's still used by axis.
|
||||
assert shared_curve in self.file
|
||||
assert axis.AxisCurve == shared_curve
|
||||
|
||||
|
||||
class TestRemoveGridAxisIFC2X3(test.bootstrap.IFC2X3, TestRemoveGridAxis):
|
||||
pass
|
||||
@@ -18,8 +18,10 @@
|
||||
|
||||
import pytest
|
||||
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.nest
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.util.element
|
||||
import test.bootstrap
|
||||
|
||||
@@ -82,6 +84,24 @@ class TestAssignObject(test.bootstrap.IFC4):
|
||||
ifcopenshell.api.nest.assign_object(self.file, related_objects=subelements[2:3], relating_object=element2)
|
||||
assert rel.RelatedObjects == tuple(subelements[:2] + subelements[3:])
|
||||
|
||||
def test_nesting_removes_spatial_containment(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
storey = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcBuildingStorey")
|
||||
ifcopenshell.api.spatial.assign_container(self.file, products=[subelement], relating_structure=storey)
|
||||
assert ifcopenshell.util.element.get_container(subelement) == storey
|
||||
ifcopenshell.api.nest.assign_object(self.file, related_objects=[subelement], relating_object=element)
|
||||
assert ifcopenshell.util.element.get_container(subelement) is None
|
||||
|
||||
def test_nesting_removes_aggregate(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
subelement = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
assembly = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcElementAssembly")
|
||||
ifcopenshell.api.aggregate.assign_object(self.file, products=[subelement], relating_object=assembly)
|
||||
assert ifcopenshell.util.element.get_aggregate(subelement) == assembly
|
||||
ifcopenshell.api.nest.assign_object(self.file, related_objects=[subelement], relating_object=element)
|
||||
assert ifcopenshell.util.element.get_aggregate(subelement) is None
|
||||
|
||||
|
||||
class TestAssignObjectIFC2X3(test.bootstrap.IFC2X3, TestAssignObject):
|
||||
pass
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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 ifcopenshell.api.pset_template
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestRemovePropTemplate(test.bootstrap.IFC4):
|
||||
def test_removing_a_prop_template(self):
|
||||
template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors")
|
||||
prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template)
|
||||
prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template)
|
||||
ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop2)
|
||||
assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1
|
||||
assert template.HasPropertyTemplates == (prop1,)
|
||||
|
||||
def test_not_removing_the_last_prop_template(self):
|
||||
template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors")
|
||||
prop = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template)
|
||||
ifcopenshell.api.pset_template.remove_prop_template(self.file, prop_template=prop)
|
||||
# The last prop template should not be removed to keep the pset template valid.
|
||||
assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 1
|
||||
assert template.HasPropertyTemplates == (prop,)
|
||||
@@ -0,0 +1,35 @@
|
||||
# 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 ifcopenshell.api.pset_template
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestRemovePsetTemplate(test.bootstrap.IFC4):
|
||||
def test_removing_a_pset_template(self):
|
||||
template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors")
|
||||
ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template)
|
||||
assert len(self.file.by_type("IfcPropertySetTemplate")) == 0
|
||||
|
||||
def test_removing_a_pset_template_with_property_templates(self):
|
||||
template = ifcopenshell.api.pset_template.add_pset_template(self.file, name="ABC_RiskFactors")
|
||||
prop1 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template)
|
||||
prop2 = ifcopenshell.api.pset_template.add_prop_template(self.file, pset_template=template)
|
||||
ifcopenshell.api.pset_template.remove_pset_template(self.file, pset_template=template)
|
||||
assert len(self.file.by_type("IfcPropertySetTemplate")) == 0
|
||||
assert len(self.file.by_type("IfcSimplePropertyTemplate")) == 0
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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 ifcopenshell.api.resource
|
||||
import test.bootstrap
|
||||
|
||||
|
||||
class TestRemoveResourceQuantity(test.bootstrap.IFC4):
|
||||
def test_removing_a_resource_quantity(self):
|
||||
self.file.create_entity("IfcProject")
|
||||
resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource")
|
||||
ifcopenshell.api.resource.add_resource_quantity(
|
||||
self.file, resource=resource, ifc_class="IfcQuantityTime"
|
||||
)
|
||||
assert resource.BaseQuantity is not None
|
||||
ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource)
|
||||
assert resource.BaseQuantity is None
|
||||
assert len(self.file.by_type("IfcPhysicalSimpleQuantity")) == 0
|
||||
|
||||
def test_removing_a_resource_quantity_when_none_exists(self):
|
||||
self.file.create_entity("IfcProject")
|
||||
resource = ifcopenshell.api.resource.add_resource(self.file, ifc_class="IfcLaborResource")
|
||||
# Should not raise.
|
||||
ifcopenshell.api.resource.remove_resource_quantity(self.file, resource=resource)
|
||||
assert resource.BaseQuantity is None
|
||||
|
||||
|
||||
class TestRemoveResourceQuantityIFC2X3(test.bootstrap.IFC2X3, TestRemoveResourceQuantity):
|
||||
pass
|
||||
+63
-10
@@ -25,9 +25,19 @@ WEBAPP_BUILD_DIR := $(WEBAPP_DIR)/dist
|
||||
PYODIDE_DIR := $(WEBAPP_DIR)/public/pyodide
|
||||
PYODIDE_VERSION := 0.28.0
|
||||
PYODIDE_URL := https://github.com/pyodide/pyodide/releases/download/$(PYODIDE_VERSION)/pyodide-$(PYODIDE_VERSION).tar.bz2
|
||||
WORKER_BIN_DIR := $(WEBAPP_DIR)/public/worker/bin
|
||||
IFCOPENSHELL_WASM_WHEEL := ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl
|
||||
IFCOPENSHELL_WASM_WHEEL_URL := https://s3.amazonaws.com/ifcopenshell-builds/ifcopenshell-0.8.5%2Ba51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl
|
||||
IFCOPENSHELL_WASM_WHEEL_PATH := $(WORKER_BIN_DIR)/$(IFCOPENSHELL_WASM_WHEEL)
|
||||
WEBAPP_GENERATED_DIR := $(WEBAPP_DIR)/public/worker/generated
|
||||
WEBAPP_IFCTESTER_MANIFEST := $(WEBAPP_GENERATED_DIR)/ifctester.json
|
||||
WEBAPP_IFCTESTER_BUILD_DIR := build-webapp-wheel
|
||||
PACKAGE_WEBAPP_DIR := $(PACKAGE_NAME)/webapp
|
||||
PACKAGE_WEBAPP_WWW_DIR := $(PACKAGE_WEBAPP_DIR)/www
|
||||
|
||||
.PHONY: webapp-dev
|
||||
webapp-dev:
|
||||
webapp-dev: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||
cd $(WEBAPP_DIR) && npm install
|
||||
cd $(WEBAPP_DIR) && npm run dev
|
||||
|
||||
.PHONY: pyodide-download
|
||||
@@ -62,8 +72,43 @@ pyodide-download:
|
||||
echo "Pyodide $(PYODIDE_VERSION) prepared in $(PYODIDE_DIR)"; \
|
||||
fi
|
||||
|
||||
.PHONY: ifcopenshell-wasm-download
|
||||
ifcopenshell-wasm-download:
|
||||
@if [ -f "$(IFCOPENSHELL_WASM_WHEEL_PATH)" ]; then \
|
||||
echo "IfcOpenShell wasm wheel already exists at $(IFCOPENSHELL_WASM_WHEEL_PATH), skipping download"; \
|
||||
else \
|
||||
echo "Downloading IfcOpenShell wasm wheel..."; \
|
||||
mkdir -p $(WORKER_BIN_DIR); \
|
||||
rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl; \
|
||||
curl -fL -o "$(IFCOPENSHELL_WASM_WHEEL_PATH)" "$(IFCOPENSHELL_WASM_WHEEL_URL)"; \
|
||||
echo "IfcOpenShell wasm wheel prepared at $(IFCOPENSHELL_WASM_WHEEL_PATH)"; \
|
||||
fi
|
||||
|
||||
.PHONY: webapp-stage-ifctester-wheel
|
||||
webapp-stage-ifctester-wheel:
|
||||
rm -rf $(WEBAPP_GENERATED_DIR)
|
||||
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||
mkdir -p $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||
cp -r $(PACKAGE_NAME) $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/webapp
|
||||
cp pyproject.toml $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||
cp README.md $(WEBAPP_IFCTESTER_BUILD_DIR)/
|
||||
ifeq ($(IS_STABLE), TRUE)
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py
|
||||
else
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)a$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/pyproject.toml
|
||||
$(SED) 's/version = "0.0.0"/version = "$(VERSION)-alpha$(VERSION_DATE)"/' $(WEBAPP_IFCTESTER_BUILD_DIR)/$(PACKAGE_NAME)/__init__.py
|
||||
endif
|
||||
cd $(WEBAPP_IFCTESTER_BUILD_DIR) && $(PYTHON) -m venv env --system-site-packages && . env/$(VENV_ACTIVATE) && python -m pip install build && python -m build --wheel --no-isolation
|
||||
mkdir -p $(WEBAPP_GENERATED_DIR)
|
||||
wheel=$$(basename $(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$(PACKAGE_NAME)-*.whl); \
|
||||
cp "$(WEBAPP_IFCTESTER_BUILD_DIR)/dist/$$wheel" "$(WEBAPP_GENERATED_DIR)/$$wheel"; \
|
||||
printf '{\n "wheel_url": "/worker/generated/%s"\n}\n' "$$wheel" > "$(WEBAPP_IFCTESTER_MANIFEST)"
|
||||
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||
|
||||
.PHONY: webapp-build
|
||||
webapp-build: pyodide-download
|
||||
webapp-build: pyodide-download ifcopenshell-wasm-download webapp-stage-ifctester-wheel
|
||||
cd $(WEBAPP_DIR) && npm install
|
||||
cd $(WEBAPP_DIR) && npm run build
|
||||
|
||||
@@ -76,22 +121,30 @@ clean:
|
||||
rm -rf $(WEBAPP_BUILD_DIR)
|
||||
rm -rf $(WEBAPP_DIR)/node_modules
|
||||
rm -rf $(PYODIDE_DIR)
|
||||
rm -f $(WORKER_BIN_DIR)/ifcopenshell-*.whl
|
||||
rm -rf $(WEBAPP_GENERATED_DIR)
|
||||
rm -rf $(WEBAPP_IFCTESTER_BUILD_DIR)
|
||||
rm -rf $(PACKAGE_NAME)/webapp
|
||||
rm -rf dist
|
||||
|
||||
.PHONY: dist
|
||||
dist: webapp-prepare
|
||||
.PHONY: python-dist
|
||||
python-dist:
|
||||
rm -rf dist
|
||||
# For some reason OS is not initalized when we call common.mk dist, which matters on Windows.
|
||||
# So we pass it explicitly.
|
||||
$(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS)
|
||||
$(MAKE) -f ../common.mk dist PACKAGE_NAME=$(PACKAGE_NAME) OS=$(OS) IS_STABLE=$(IS_STABLE)
|
||||
|
||||
.PHONY: dist
|
||||
dist: webapp-prepare
|
||||
$(MAKE) python-dist OS=$(OS) IS_STABLE=$(IS_STABLE)
|
||||
|
||||
.PHONY: webapp-prepare
|
||||
webapp-prepare: webapp-build
|
||||
rm -rf $(PACKAGE_NAME)/webapp/www/*
|
||||
mkdir -p $(PACKAGE_NAME)/webapp/www
|
||||
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_NAME)/webapp/www/
|
||||
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_NAME)/webapp/__init__.py
|
||||
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_NAME)/webapp/serve.py
|
||||
rm -rf $(PACKAGE_WEBAPP_WWW_DIR)
|
||||
mkdir -p $(PACKAGE_WEBAPP_WWW_DIR)
|
||||
cp -r $(WEBAPP_BUILD_DIR)/* $(PACKAGE_WEBAPP_WWW_DIR)/
|
||||
cp $(WEBAPP_DIR)/__init__.py $(PACKAGE_WEBAPP_DIR)/__init__.py
|
||||
cp $(WEBAPP_DIR)/serve.py $(PACKAGE_WEBAPP_DIR)/serve.py
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
||||
@@ -11,6 +11,8 @@ node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
public/worker/bin/ifcopenshell-*.whl
|
||||
public/worker/generated
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
@@ -24,4 +26,4 @@ dist-ssr
|
||||
*.sw?
|
||||
|
||||
.claude
|
||||
experiment/*
|
||||
experiment/*
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"files": {
|
||||
"ignore": [
|
||||
"dist",
|
||||
"node_modules",
|
||||
"public/pyodide",
|
||||
"**/*.svelte"
|
||||
]
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"include": [
|
||||
"src/modules/wasm/worker/**"
|
||||
],
|
||||
"linter": {
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noExplicitAny": "off"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
/**
|
||||
* svelte-preprocess cannot figure out whether you have
|
||||
* a value or a type, so tell TypeScript to enforce using
|
||||
* `import type` instead of `import` for Types.
|
||||
*/
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
/**
|
||||
* To have warnings / errors of the Svelte compiler at the
|
||||
* correct position, enable source maps by default.
|
||||
*/
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
/**
|
||||
* Typecheck JS in `.svelte` and `.js` files by default.
|
||||
* Disable this if you'd like to use dynamic types.
|
||||
*/
|
||||
"checkJs": false,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$src": ["./src"],
|
||||
"$src/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Use global.d.ts instead of compilerOptions.types
|
||||
* to avoid limiting type declarations.
|
||||
*/
|
||||
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
|
||||
}
|
||||
Generated
+258
@@ -16,6 +16,7 @@
|
||||
"svelte-spa-router": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
@@ -25,11 +26,13 @@
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.4.1"
|
||||
}
|
||||
},
|
||||
@@ -47,6 +50,170 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
|
||||
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "1.9.4",
|
||||
"@biomejs/cli-darwin-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64": "1.9.4",
|
||||
"@biomejs/cli-linux-arm64-musl": "1.9.4",
|
||||
"@biomejs/cli-linux-x64": "1.9.4",
|
||||
"@biomejs/cli-linux-x64-musl": "1.9.4",
|
||||
"@biomejs/cli-win32-arm64": "1.9.4",
|
||||
"@biomejs/cli-win32-x64": "1.9.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
|
||||
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
|
||||
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "1.9.4",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
|
||||
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@bufbuild/protobuf": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.5.1.tgz",
|
||||
@@ -1468,6 +1635,22 @@
|
||||
"dev": true,
|
||||
"license": "MIT/X11"
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
|
||||
@@ -2126,6 +2309,16 @@
|
||||
"svelte": "^5.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
@@ -2200,6 +2393,20 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/regexparam": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/regexparam/-/regexparam-2.0.2.tgz",
|
||||
@@ -2281,6 +2488,19 @@
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sade": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mri": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/sass-embedded": {
|
||||
"version": "1.89.0",
|
||||
"resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.89.0.tgz",
|
||||
@@ -2783,6 +3003,30 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-check": {
|
||||
"version": "4.3.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.3.5.tgz",
|
||||
"integrity": "sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.25",
|
||||
"chokidar": "^4.0.1",
|
||||
"fdir": "^6.2.0",
|
||||
"picocolors": "^1.0.0",
|
||||
"sade": "^1.7.4"
|
||||
},
|
||||
"bin": {
|
||||
"svelte-check": "bin/svelte-check"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||
"typescript": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svelte-sonner": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/svelte-sonner/-/svelte-sonner-1.0.5.tgz",
|
||||
@@ -2983,6 +3227,20 @@
|
||||
"url": "https://github.com/sponsors/Wombosvideo"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
|
||||
@@ -6,11 +6,15 @@
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"check": "tsc -p tsconfig.json --noEmit && svelte-check && biome lint .",
|
||||
"lint": "biome lint .",
|
||||
"lint:fix": "biome lint --write .",
|
||||
"preview": "vite preview",
|
||||
"deploy": "npm run build && npx wrangler pages deploy dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@internationalized/date": "^3.8.1",
|
||||
"@biomejs/biome": "^1.9.4",
|
||||
"@lucide/svelte": "^0.515.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.3",
|
||||
"@tailwindcss/vite": "^4.0.0",
|
||||
@@ -19,10 +23,12 @@
|
||||
"mode-watcher": "^1.1.0",
|
||||
"sass-embedded": "^1.89.0",
|
||||
"svelte": "^5.53.6",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^1.0.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"tailwind-variants": "^1.0.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"typescript": "^5.8.3",
|
||||
"tw-animate-css": "^1.3.2",
|
||||
"vite": "^6.4.1"
|
||||
},
|
||||
|
||||
BIN
Binary file not shown.
@@ -22,9 +22,6 @@ import os
|
||||
import sys
|
||||
|
||||
bonsai_lib_path = os.environ.get("BONSAI_LIB_PATH")
|
||||
print(os.environ)
|
||||
print(bonsai_lib_path)
|
||||
bonsai_version = os.environ.get("BONSAI_VERSION")
|
||||
|
||||
if bonsai_lib_path:
|
||||
sys.path.insert(0, bonsai_lib_path)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import Router from 'svelte-spa-router';
|
||||
import routes from './routes';
|
||||
</script>
|
||||
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module "*.svelte" {
|
||||
import type { SvelteComponent } from "svelte";
|
||||
export default class Component extends SvelteComponent {}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Menubar from "$lib/components/ui/menubar";
|
||||
import * as Dialog from "$lib/components/ui/dialog";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import * as API from "$src/modules/api/api.svelte.js";
|
||||
import { error, success, info } from "$src/modules/utils/toast.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import * as API from "$src/modules/api/api.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
|
||||
let { isOpen = false } = $props();
|
||||
let { isOpen = false } : { isOpen?: boolean } = $props();
|
||||
|
||||
function openForum() {
|
||||
window.open('https://community.osarch.org', '_blank');
|
||||
@@ -23,8 +23,9 @@
|
||||
try {
|
||||
await IDS.openDocument();
|
||||
} catch (err) {
|
||||
if (err.message !== 'File selection cancelled') {
|
||||
error('Error opening file: ' + err.message);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
if (message !== 'File selection cancelled') {
|
||||
error(`Error opening file: ${message}`);
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
@@ -50,7 +51,7 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -142,4 +143,4 @@
|
||||
</Dialog.Close>
|
||||
</Dialog.Footer>
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
</Dialog.Root>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { onMount } from "svelte";
|
||||
import { Module } from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import { Module } from "$src/modules/api/ids.svelte";
|
||||
</script>
|
||||
|
||||
<div class="app-ribbon">
|
||||
@@ -20,4 +19,4 @@
|
||||
<span>Error</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as Tooltip from "$lib/components/ui/tooltip";
|
||||
import { IFCModels, loadIfc, unloadIfc, auditIfc, openIfc, createAuditReport, clearIdsAuditReports, runAudit } from "$src/modules/api/api.svelte.js";
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte.js";
|
||||
import { IFCModels, openIfc, unloadIfc, runAudit } from "$src/modules/api/api.svelte";
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import { error, success } from "$src/modules/utils/toast.svelte";
|
||||
import { ChevronRightIcon, LinkIcon, XIcon } from "@lucide/svelte";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte.js";
|
||||
import { Bonsai, connect, disconnect, runAudit as runBonsaiAudit } from "$src/modules/api/bonsai.svelte";
|
||||
import { onMount } from 'svelte';
|
||||
import type { AuditReport } from "$src/types/report";
|
||||
|
||||
let isAuditing = $state(false);
|
||||
let activeTab = $state('home');
|
||||
let activeTab = $state<"home" | "bonsai">('home');
|
||||
// biome-ignore lint/style/useConst: Svelte state uses assignment for updates.
|
||||
let isMinimized = $state(false);
|
||||
|
||||
const handleLoadModel = async () => {
|
||||
@@ -16,15 +18,17 @@
|
||||
await openIfc();
|
||||
success('IFC model loaded successfully');
|
||||
} catch (err) {
|
||||
error(`Failed to load IFC model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to load IFC model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUnloadModel = async (modelId) => {
|
||||
const handleUnloadModel = async (modelId: string) => {
|
||||
try {
|
||||
await unloadIfc(modelId);
|
||||
} catch (err) {
|
||||
error(`Failed to unload model: ${err.message}`);
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
error(`Failed to unload model: ${message}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,14 +39,14 @@
|
||||
success('Audit completed successfully');
|
||||
} catch (err) {
|
||||
console.error("Audit failed: ", err);
|
||||
error(`Audit failed: check console for details`);
|
||||
error("Audit failed: check console for details");
|
||||
} finally {
|
||||
isAuditing = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleViewAuditReport = (auditId) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId);
|
||||
const handleViewAuditReport = (auditId: string) => {
|
||||
const auditReport = IFCModels.audits.find(audit => audit.id === auditId) as AuditReport | undefined;
|
||||
if (!auditReport) return;
|
||||
|
||||
// Switch to the IDS document that was used for this audit
|
||||
@@ -58,7 +62,7 @@
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes) => {
|
||||
const formatFileSize = (bytes: number) => {
|
||||
const units = ['B', 'KB', 'MB', 'GB'];
|
||||
let size = bytes;
|
||||
let unitIndex = 0;
|
||||
@@ -91,7 +95,7 @@
|
||||
<div class="buttons">
|
||||
<Tooltip.Provider>
|
||||
{#if isMinimized}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn expand-btn" onclick={() => isMinimized = false} aria-label="Expand Toolbar">
|
||||
<ChevronRightIcon size={24} />
|
||||
@@ -102,7 +106,7 @@
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
{/if}
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'home' ? 'active' : ''}" onclick={() => activeTab = 'home'} aria-label="Home">
|
||||
<svg class="w-6 h-6" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
|
||||
@@ -114,7 +118,7 @@
|
||||
<p>Home</p>
|
||||
</Tooltip.Content>
|
||||
</Tooltip.Root>
|
||||
<Tooltip.Root disableHoverableContent="true">
|
||||
<Tooltip.Root disableHoverableContent>
|
||||
<Tooltip.Trigger>
|
||||
<button class="tb-btn {activeTab === 'bonsai' ? 'active' : ''}" onclick={() => activeTab = 'bonsai'} aria-label="Bonsai Integration">
|
||||
<svg style="height: 20px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32mm" height="32mm" version="1.1" viewBox="0 0 32 32" xml:space="preserve">
|
||||
@@ -692,4 +696,4 @@
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import * as DropdownMenu from "$lib/components/ui/dropdown-menu";
|
||||
|
||||
let { addFacet } = $props();
|
||||
type FacetType = "entity" | "attribute" | "classification" | "partOf" | "property" | "material";
|
||||
|
||||
const { addFacet } : { addFacet: (facetType: FacetType) => void } = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
@@ -63,4 +65,4 @@
|
||||
Part Of
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</DropdownMenu.Root>
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<script>
|
||||
import * as IDS from "$src/modules/api/ids.svelte.js";
|
||||
<script lang="ts">
|
||||
import * as IDS from "$src/modules/api/ids.svelte";
|
||||
import type { IdsDocument } from "$src/types/ids";
|
||||
|
||||
function switchDocument(docId) {
|
||||
function switchDocument(docId: string) {
|
||||
IDS.Module.activeDocument = docId;
|
||||
}
|
||||
|
||||
function closeDocument(docId) {
|
||||
function closeDocument(docId: string) {
|
||||
IDS.deleteDocument(docId);
|
||||
}
|
||||
|
||||
const handleActivation = (event: KeyboardEvent, action: () => void) => {
|
||||
if (event.currentTarget !== event.target) return;
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
action();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="ids-tabs">
|
||||
@@ -15,10 +24,13 @@
|
||||
<div
|
||||
class="ids-tab"
|
||||
class:active={IDS.Module.activeDocument === docId}
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onclick={() => switchDocument(docId)}
|
||||
aria-label={doc.info.title || "Untitled"}
|
||||
onkeydown={(event) => handleActivation(event, () => switchDocument(docId))}
|
||||
aria-label={(doc as IdsDocument).info.title || "Untitled"}
|
||||
>
|
||||
<span class="tab-title">{doc.info.title || "Untitled"}</span>
|
||||
<span class="tab-title">{(doc as IdsDocument).info.title || "Untitled"}</span>
|
||||
<button
|
||||
class="tab-close"
|
||||
onclick={(e) => { e.stopPropagation(); closeDocument(docId); }}
|
||||
@@ -31,4 +43,4 @@
|
||||
</div>
|
||||
{/each}
|
||||
<div class="filler-tab"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"wasm": {
|
||||
"wheel_url": "/worker/bin/ifcopenshell-0.8.3+bb329af-cp313-cp313-emscripten_4_0_9_wasm32.whl",
|
||||
"wheel_url": "/worker/bin/ifcopenshell-0.8.5+a51b2c5-cp313-cp313-pyodide_2025_0_wasm32.whl",
|
||||
"odfpy_url": "/worker/bin/odfpy-1.4.2-py2.py3-none-any.whl",
|
||||
"api_py_url": "/worker/api.py",
|
||||
"pyodide_url": "https://cdn.jsdelivr.net/pyodide/v0.28.0/full/pyodide.js"
|
||||
"api_py_url": "/worker/api.py"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,7 +576,8 @@ html, body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
label {
|
||||
label,
|
||||
.form-label {
|
||||
margin-bottom: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import XIcon from "@lucide/svelte/icons/x";
|
||||
import * as Dialog from "./index.js";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import * as Dialog from "./index";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
portalProps?: Record<string, unknown>;
|
||||
children?: Snippet;
|
||||
showCloseButton?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +20,7 @@
|
||||
children,
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<Dialog.Portal {...portalProps}>
|
||||
@@ -35,4 +44,4 @@
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</DialogPrimitive.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Description
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-description"
|
||||
class={cn("text-muted-foreground text-sm", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -15,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Overlay
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Title
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dialog-title"
|
||||
class={cn("text-lg leading-none", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Dialog as DialogPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
<DialogPrimitive.Trigger bind:ref data-slot="dialog-trigger" {...restProps} />
|
||||
|
||||
+12
-4
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
checked = $bindable(false),
|
||||
@@ -10,7 +18,7 @@
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
|
||||
+11
-4
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
sideOffset?: number;
|
||||
portalProps?: Record<string, unknown>;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
sideOffset = 4,
|
||||
portalProps,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Portal {...portalProps}>
|
||||
@@ -22,4 +29,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
<DropdownMenuPrimitive.Group bind:ref data-slot="dropdown-menu-group" {...restProps} />
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,12 +1,20 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +25,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+8
-3
@@ -1,11 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
value?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
value = $bindable(),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioGroup
|
||||
@@ -13,4 +18,4 @@
|
||||
bind:value
|
||||
data-slot="dropdown-menu-radio-group"
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,14 +1,22 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import CircleIcon from "@lucide/svelte/icons/circle";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
value: string;
|
||||
children?: Snippet<[ { checked: boolean } ]>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
@@ -28,4 +36,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.({ checked })}
|
||||
{/snippet}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Separator
|
||||
@@ -14,4 +19,4 @@
|
||||
data-slot="dropdown-menu-separator"
|
||||
class={cn("bg-border -mx-1 my-1 h-px", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+11
-4
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<span
|
||||
@@ -16,4 +23,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</span>
|
||||
</span>
|
||||
|
||||
+9
-4
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
@@ -17,4 +22,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
+12
-4
@@ -1,7 +1,15 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -9,7 +17,7 @@
|
||||
inset,
|
||||
children,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
@@ -24,4 +32,4 @@
|
||||
>
|
||||
{@render children?.()}
|
||||
<ChevronRightIcon class="ml-auto size-4" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
|
||||
+7
-3
@@ -1,7 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
|
||||
|
||||
let { ref = $bindable(null), ...restProps } = $props();
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let { ref = $bindable(null), ...restProps } : Props = $props();
|
||||
</script>
|
||||
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
<DropdownMenuPrimitive.Trigger bind:ref data-slot="dropdown-menu-trigger" {...restProps} />
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import CheckIcon from "@lucide/svelte/icons/check";
|
||||
import MinusIcon from "@lucide/svelte/icons/minus";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
checked?: boolean;
|
||||
indeterminate?: boolean;
|
||||
children?: Snippet;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
@@ -10,7 +18,7 @@
|
||||
indeterminate = $bindable(false),
|
||||
children: childrenProp,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.CheckboxItem
|
||||
@@ -34,4 +42,4 @@
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
{/snippet}
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
</MenubarPrimitive.CheckboxItem>
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Align = "start" | "center" | "end";
|
||||
type Side = "top" | "bottom" | "left" | "right";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
sideOffset?: number;
|
||||
alignOffset?: number;
|
||||
align?: Align;
|
||||
side?: Side;
|
||||
portalProps?: Record<string, unknown>;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -11,7 +24,7 @@
|
||||
side = "bottom",
|
||||
portalProps,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Portal {...portalProps}>
|
||||
@@ -28,4 +41,4 @@
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
</MenubarPrimitive.Portal>
|
||||
</MenubarPrimitive.Portal>
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.GroupHeading
|
||||
@@ -15,4 +20,4 @@
|
||||
data-inset={inset}
|
||||
class={cn("px-2 py-1.5 text-sm font-medium data-[inset]:pl-8", className)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
<MenubarPrimitive.Group bind:ref data-slot="menubar-group" {...restProps} />
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { Menubar as MenubarPrimitive } from "bits-ui";
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { cn } from "$lib/utils";
|
||||
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
class?: string;
|
||||
inset?: boolean;
|
||||
variant?: string;
|
||||
} & Record<string, unknown>;
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
@@ -8,7 +15,7 @@
|
||||
inset = undefined,
|
||||
variant = "default",
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<MenubarPrimitive.Item
|
||||
@@ -21,4 +28,4 @@
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
/>
|
||||
/>
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import type { Snippet } from "svelte";
|
||||
type Props = {
|
||||
ref?: HTMLElement | null;
|
||||
inset?: boolean;
|
||||
children?: Snippet;
|
||||
class?: string;
|
||||
} & Record<string, unknown>;
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
inset,
|
||||
children,
|
||||
class: className,
|
||||
...restProps
|
||||
} = $props();
|
||||
} : Props = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
@@ -17,4 +24,4 @@
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user