Compare commits

...

1 Commits

Author SHA1 Message Date
Richard Brice edfcdbb865 Playing with input panels for alignment 2025-05-15 10:19:19 -07:00
16 changed files with 844 additions and 5 deletions
@@ -16,21 +16,51 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a __init__.py file to load all of its classes. Every
# operation, property, and interface needs to be registered with the Blender
# system when the add-on loads. This is where it happens.
import bpy
from . import ui, prop, operator
# from . import ui, prop, operator
from . import operator
classes = (operator.ImportAlignmentCSV,)
# You'll need to provide a list of every one of your classes here. If you forget
# to specify your class, it won't load and you won't be able to use that
# operator, property, or interface.
classes = (
operator.ImportAlignmentCSV,
operator.BuildAlignment,
operator.SurveyPoint,
prop.BIMAlignmentBuilderProperties,
ui.BIM_PT_alignment,
)
def menu_func_import(self, context):
self.layout.operator(operator.ImportAlignmentCSV.bl_idname, text="Alignment (.csv)")
# When the add-on loads, this register function is called. This allows you to
# perform additional tasks during startup. If you need to store custom
# properties, this is where you tell Blender where they are going to be stored.
# You might see more advanced registrations happening in other modules.
def register():
# Properties are usually stored on bpy.types.Scene when they are something
# that affects everything in the project, or bpy.types.Object when they
# affect a single BIM element.
bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
bpy.types.Scene.BIMAlignmentBuilderProperties = bpy.props.PointerProperty(type=prop.BIMAlignmentBuilderProperties)
# When someone disables the add-on, we need to unload everything we loaded. This
# does the reverse of the register function.
def unregister():
del bpy.types.Scene.BIMAlignmentBuilderProperties
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
@@ -0,0 +1,80 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a data.py file to load data for its interface panels to
# display. When a panel needs to show information that doesn't come from a
# Blender property, it gets that data from one of these classes. This separation
# of "fetching data" and "displaying data" is a common concept that makes UI
# code much simpler, more efficient, and less error-prone to refresh data.
import bonsai.tool as tool
# All data must have a refresh function. The refresh function simply sets all
# data class to be not yet loaded. So the next time the interface is drawn to
# the user, it will force the data to be reloaded.
def refresh():
# When you define your own data classes, just add to this list!
AlignmentBuilderData.is_loaded = False
# This is a sample data class. It correlates to a single interface panel. Panels
# should not share data classes. This makes it easy to write your interface
# without having your code mixed in with other parts of the interface. As a
# convention, the class is named the same name as the panel.
class AlignmentBuilderData:
# All data classes must have two variables. One to store all the data it has
# loaded and another to store the load state.
data = {}
is_loaded = False
x = 0.0
y = 0.0
# Every data class must have a load function. This lets us load data in a
# predictable manner.
@classmethod
def load(cls):
# The load function always has two responsibilities: populate the data,
# and set is_loaded to true.
cls.data = {
"has_project": cls.has_project(),
"x" : cls.x(),
"y" : cls.y()
}
cls.is_loaded = True
@classmethod
def has_project(cls):
# Here, we check whether or not there is an active IFC project in our
# Blender session.
return bool(tool.Ifc.get())
@classmethod
def x(cls):
return cls.x
@classmethod
def y(cls):
return cls.y
@@ -18,6 +18,17 @@
# pyright: reportUnnecessaryTypeIgnoreComment=error
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has an operator.py file to define all of the buttons that a user
# can click on from the Blender interface. Blender calls these buttons
# "Operators", since they correlate to a single user operation.
import os
import ifcopenshell.api.alignment
@@ -28,7 +39,7 @@ import json
import time
import calendar
import isodate
import bonsai.core.sequence as core
import bonsai.core.alignment as core
import bonsai.tool as tool
import bonsai.bim.module.sequence.helper as helper
import ifcopenshell.api.spatial
@@ -112,3 +123,41 @@ class ImportAlignmentCSV(bpy.types.Operator, tool.Ifc.Operator, ImportHelper):
tool.Collector.assign(alignment_obj, should_clean_users_collection=False)
self.report({"INFO"}, "Imported in %s seconds" % (time.time() - start))
# Each button correlates to a class like the one below. In this case, we're
# creating a new button that will execute a hello world feature.
class BuildAlignment(bpy.types.Operator, tool.Ifc.Operator):
# Every operator has a unique ID. If you enable Python tooltips and hover
# over any button in the interface, you will see each button will run a
# function that uses this ID. For example, hovering over this button will
# show that the code it executes is "bpy.ops.bim.demonstrate_hello_world()"
bl_idname = "bim.build_alignment"
# In the interface, this button will have the text "Build Alignment".
bl_label = "Build Alignment"
# This code means that the user can undo or redo after pressing the button.
bl_options = {"REGISTER", "UNDO"}
# When hovering over the button, this helpful description will be shown.
bl_description = "Builds a dummy alignment"
# When the button is pressed, this _execute() function will run.
def _execute(self, context):
# Every operator should do one thing only: execute a core function. In
# order to execute a core function, the operator's responsibility is to
# pass in all of the inputs the core needs to do its job.
# A core function simply tells tools what to do, so a core function will
# always need at least one tool as an input.
core.build_alignment(tool.Alignment)
class SurveyPoint(bpy.types.Operator,tool.Ifc.Operator):
bl_idname = "bim.add_survey_point"
bl_label = "Add Survey Point"
bl_options = {"REGISTER","UNDO"}
bl_description = "Adds a survey point"
def _execute(self,context):
core.add_survey_point(tool.Alignment,x=bpy.context.scene.BIMAlignmentBuilderProperties.x,y=bpy.context.scene.BIMAlignmentBuilderProperties.y)
@@ -0,0 +1,64 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a prop.py file to define Blender properties. Any time you
# want an interface widget like an input field, dropdown, checkbox, or number
# slider, you need a Blender property to store that widget's data. If you want
# to store data that will affect the Blender interface, you also need a
# property. Properties are stored in the .blend file, so when your user closes
# their Blender session, and reopens it, things are how they left it.
import bpy
from bpy.types import PropertyGroup
# Properties have many different data types. We won't use all of them in this
# demo module, but this is a list for your reference.
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
# All properties must belong in a property group. Usually, you'd have a group
# named after your module.
class BIMAlignmentBuilderProperties(PropertyGroup):
# This first property is a string. This means that in the interface, it will
# represent a text input field. We can give it a name and a default value.
# The name will be the label shown next to the input field in the interface.
name: StringProperty(name="Name", default="New Project Name")
# Not all properties need to be shown using their equivalent input widget.
# In this case, we can store a message string, but we will never show it as
# an input text field in the ui.py.
message: StringProperty(name="Message")
show_hints: BoolProperty(name="Show Hints", default=False)
#webui_message: StringProperty(name="Web UI Message", default="Hello, Web UI!")
x: FloatProperty(name="x")
y: FloatProperty(name="y")
@@ -0,0 +1,140 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a ui.py file to define its interface. Interfaces describe how
# panels, buttons, labels, and input fields are laid out.
import bpy
from bonsai.bim.module.alignment.data import AlignmentBuilderData
# Every panel in the interface correlates to one of these classes. If you enable
# the "Developer Extras" option in Blender, then you can actually right click on
# any panel in Blender, and click "Edit Source". This will bring you to a ui.py
# file like this one where you can see this code. Pretty neat!
class BIM_PT_alignment(bpy.types.Panel):
# Every panel has a title.
bl_label = "Alignment Demo"
# Every panel must have an ID. It must be unique. For example, you may want
# to later reference the panel (such as if you want to create a nested
# subpanel).
bl_idname = "BIM_PT_alignment"
# This tells the panel to appear in the properties section (in the bottom
# right by default) of the Blender interface.
bl_space_type = "VIEW_3D" #"PROPERTIES"
bl_region_type = "UI" #"WINDOW"
bl_category = "Survey"
# This tells the panel to appear in the "scene" tab of the properties panel.
#bl_context = "scene"
# Every panel has a draw function. This draws all the layout of the panel,
# including all of its labels, buttons, and so on. Note that if the panel is
# hidden, this draw function will not be called.
def draw(self, context):
# Before drawing any content, the panel must load any dynamic variables
# that it will show. Most panels have dynamic content, which display
# changing data from your BIM model. You must load that data first. The
# two lines are always the same, "if not loaded, then load".
if not AlignmentBuilderData.is_loaded:
# Each panel should have its own data class that it loads data from.
# Note that we only load data if it hasn't already been loaded.
# This is because interface panels are drawn continuously. This
# draw() function will be called every time you scroll or move your
# mouse over it. Loading data is slow, so we only refresh data when
# we have to.
AlignmentBuilderData.load()
# Interface panels often show properties. For convenience, define where
# the properties are stored for the module.
self.props = context.scene.BIMAlignmentBuilderProperties
# This defines a new "row" in our layout. When a new row is defined, the
# things on that row, like buttons, labels, and input fields, show on a
# new line.
row = self.layout.row()
# This is the simplest interface element - a label placed in our row.
# If you enable the "Icon Viewer" add-on, you can view a list of
# Blender's built-in icons to choose from in the Blender text editor's
# side panel.
row.label(text="This is a demo panel", icon="INFO")
# Our interface can contain simple logic. For example, if we don't have
# an IFC project, show an error message and don't draw anything else.
# The ui.py should always have very simple logic. Details like how to
# determine whether we have a project is delegated to the data loader.
if not AlignmentBuilderData.data["has_project"]:
row = self.layout.row()
row.label(text="Load or create an IFC project first", icon="ERROR")
return
row = self.layout.row()
# When you want to show a button in the interface, you have to specify
# an operator. Operators are defined in operator.py. You reference an
# operator using its unique ID.
row.operator("bim.build_alignment")
# Blender properties can be used to affect what your panel shows. Notice
# how the logic is very simple. Only use simple boolean predicates and
# loops in your layout. No complex logic should ever been seen in the
# interface code.
#if self.props.message:
# row = self.layout.row()
# row.label(text=self.props.message)
#row = self.layout.row(align=True)
#row.label(text="Build Dummy Alignment")
# Sometimes you need data that doesn't come from Blender properties,
# like from your IFC model. In this case, just get some data from your
# data loader. For those familiar with how templating languages work,
# this is exactly the same.
#row.label(text=AlignmentData.data["project_name"])
#row = self.layout.row()
# We've seen how to show text labels and buttons using operators. What
# about text input fields, number sliders, dropdowns, checkboxes, and
# scrollable lists? The way Blender works is that you simply show a
# Blender property in your interface. The data type of that property
# determines which UI widget is shown. A text property will show a text
# field. A number property will show a number slider. An enum property
# will show a drop down. And so on. In this case, our name property is a
# text data type, so expect to see a text input field show up here.
#row.prop(self.props, "name")
#row = self.layout.row()
# Here's another button, referencing another operator.
#row.operator("bim.demonstrate_rename_project")
row = self.layout.row()
row.label(text="Survey Point")
row.prop(self.props,"x")
row.prop(self.props,"y")
row = self.layout.row()
row.operator("bim.add_survey_point")
#if self.props.show_hints:
# row = self.layout.row()
# row.label(text="Name cannot be blank!")
+57
View File
@@ -0,0 +1,57 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a core.py file to define all of its core functions. A core
# function describes what happens when the user wants to do something like
# pressing a button.
# Think of a core function as a short poem of pseudocode that describes what
# happens in different usecases. A core should be no more than 50 lines of code,
# even in the most complex of features. A core simply delegates tasks to tools
# in a sequence that describes the flow of logic in a feature - in other words,
# it tells tools to do different things. You will notice that the core doesn't
# have any code that deals with Blender or IFC directly - these are all little
# details that are hidden away in tools. The core is not interested in these
# details, the core is only concerned with the big picture.
# Imagine, no matter how complex a software can be, every feature can be
# described in regular sentences in under 50 lines. That is the purpose of the
# core.
# Here's the simplest possible core function. It does one thing only. Remember:
# core functions delegate tasks to tools, so all core functions need at least
# one tool.
def build_alignment(alignment):
# We're telling the demo tool to set a message. We aren't interested how the
# tool works, that's a detail. We aren't interested in the interface, like
# where the message is shown. You can name these functions whatever you feel
# best describes what's going on, like if you had to describe the feature to
# someone else.
alignment.build()
def add_survey_point(alignment,x,y):
alignment.add_survey_point(x,y)
+4
View File
@@ -74,6 +74,10 @@ class Aggregate:
def get_container(cls, element): pass
def get_relating_object(cls, related_element): pass
@interface
class Alignment:
def build_alignment(cls): pass
def add_survey_point(cls,x,y): pass
@interface
class Bcf:
+1
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from bonsai.tool.aggregate import Aggregate
from bonsai.tool.alignment import Alignment
from bonsai.tool.bcf import Bcf
from bonsai.tool.blender import Blender
from bonsai.tool.boundary import Boundary
+120
View File
@@ -0,0 +1,120 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Bonsai is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
# ############################################################################ #
# Hey there! Welcome to the Bonsai code. Please feel free to reach
# out if you have any questions or need further guidance. Happy hacking!
# ############################################################################ #
# Every module has a tool file which implements all the functions that the core
# needs. Whereas the core is simply high level code, the tool file has the
# concrete implementations, dealing with exactly how things interact with
# Blender's property systems, IFC's data structures, the filesystem, geometry
# processing, and more.
import ifcopenshell.settings
import bpy
import bonsai.core.tool
import bonsai.tool as tool
import ifcopenshell.api
import ifcopenshell.api.alignment
import ifcopenshell
import ifcopenshell.api.cogo
# There is always one class in each tool file, which implements the interface
# defined by `core/tool.py`.
class Alignment(bonsai.core.tool.Alignment):
@classmethod
def add_survey_point(cls,x,y):
model = tool.Ifc.get()
point = model.createIfcCartesianPoint((x,y))
annotation = ifcopenshell.api.cogo.add_survey_point(model,point)
m = tool.Loader.create_point_cloud_mesh(annotation.Representation.Representations[0])
tool.Ifc.link(annotation.Representation.Representations[0],m)
# create a new Blender object
annotation_obj = bpy.data.objects.new(tool.Loader.get_name(annotation), m)
# link the blender object to with the IFC element
tool.Geometry.link(annotation, annotation_obj)
# assign the object to the blender collections
tool.Collector.assign(annotation_obj, should_clean_users_collection=False)
@classmethod
def build(cls):
coordinates = [(0.0,0.0),(100.0,0.0),(1000.,200.)]
radii = [(100.)]
vpoints = [(0.0,0.0),(100.0,0.0),(200.0,150.0)]
lengths = [(50.)]
model = tool.Ifc.get()
# create an IfcAlignment with Name="Dummy"
alignment = ifcopenshell.api.alignment.create_alignment_by_pi_method(model,"Dummy",coordinates,radii,vpoints,lengths)
ifcopenshell.api.alignment.create_geometric_representation(model, alignment)
ifcopenshell.api.alignment.add_stationing_to_alignment(model, alignment=alignment, start_station=0.0)
# IFC 4.1.5.1 alignments cannot be contained in spatial structures, but can be referenced into them
sites = model.by_type("IfcSite")
for site in sites:
ifcopenshell.api.spatial.reference_structure(model, products=[alignment], relating_structure=site)
# process the generated IfcReferent for the alignment
for rel in alignment.IsNestedBy:
for referent in rel.RelatedObjects:
if referent.is_a("IfcReferent"):
referent_obj = bpy.data.objects.new(tool.Loader.get_name(referent), None)
tool.Geometry.link(referent, referent_obj)
tool.Collector.assign(referent_obj, should_clean_users_collection=False)
# an alignment can be an aggregation of multiple child alignments (ie. multiple verticals for a single horizontal)
# get all the alignment curves
curves = []
for rel in alignment.IsDecomposedBy:
for agg in rel.RelatedObjects:
if agg.is_a("IfcAlignment"):
curves.append(ifcopenshell.api.alignment.get_curve(agg)) # 3D curve
# if there aren't any curves from aggregation, then there is only a single vertical or no vertical
if len(curves) == 0:
curves.append(ifcopenshell.api.alignment.get_curve(alignment))
settings = ifcopenshell.geom.settings()
for curve in curves:
shape = ifcopenshell.geom.create_shape(settings, curve)
# create a new Blender mesh
mesh_name = tool.Loader.get_mesh_name_from_shape(shape)
mesh = bpy.data.meshes.new(mesh_name)
m = tool.Loader.convert_geometry_to_mesh(shape, mesh)
# create a new Blender object
alignment_obj = bpy.data.objects.new(tool.Loader.get_name(alignment), m)
# link the blender object to with the alignment element
tool.Geometry.link(alignment, alignment_obj)
# assign the object to the blender collections
tool.Collector.assign(alignment_obj, should_clean_users_collection=False)
@@ -0,0 +1,31 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
"""
Coordinate Geometry (cogo) functions primarily for survey points and control monument for layout, parcels, etc.
"""
from .add_survey_point import add_survey_point
from .assign_survey_point import assign_survey_point
from .edit_survey_point import edit_survey_point
__all__ = [
"add_survey_point",
"assign_survey_point",
"edit_survey_point",
]
@@ -0,0 +1,44 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from ifcopenshell import entity_instance
import typing
def add_survey_point(file: ifcopenshell.file, survey_point: entity_instance) -> entity_instance:
"""
Adds a single survey point to the model based on IFC Concept Template 4.1.7.1.2.5.
Survey points are located relative to IfcRepresentationContext.WorldCoordinateSystem
:param survey_point: The survey point
:return: an IfcAnnotation entity
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
"""
context = ifcopenshell.util.representation.get_context(file,"Model","Annotation","MODEL_VIEW")
shape_representation = file.createIfcShapeRepresentation(ContextOfItems=context,RepresentationIdentifier='Annotation',RepresentationType='Point',Items=[survey_point])
representation = file.createIfcProductDefinitionShape(Representations=[shape_representation])
annotation = file.createIfcAnnotation(ifcopenshell.guid.new(),ObjectPlacement=context.WorldCoordinateSystem,Representation=representation,PredefinedType="SURVEY")
site = file.by_type("IfcSite")[0]
ifcopenshell.api.spatial.assign_container(file,relating_structure=site,products=[annotation])
return annotation
@@ -0,0 +1,38 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from ifcopenshell import entity_instance
import typing
def assign_survey_point(annotation: entity_instance, survey_point: entity_instance):
"""
Assigns a coordinate point to a survey point annotation
:param annotaton: The survey point annotation
:param survey_point: The survey point
:return: None
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
ifcopenshell.api.cogo.assign_surve_point(annotation,file.createIfcCartesianPoint(4000.0,3500.0,100.0))
"""
annotation.Representation.Representations[0].Items = [survey_point]
@@ -0,0 +1,40 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from ifcopenshell import entity_instance
import typing
def edit_survey_point(annotation: entity_instance, x:float,y:float,z:float=0.0):
"""
Edits the location of a previously defined survey point
:param survey_point: The survey point
:return: None
Example:
.. code:: python
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint(4000.0,3500.0)))
ifcopenshell.api.cogo.edit_surve_point(annotation,3500.0,2000.0)
"""
if annotation.Representation.Representations[0].Items[0].Dim == 2:
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y))
else:
annotation.Representation.Representations[0].Items[0].Coordinates = ((x,y,z))
@@ -0,0 +1,45 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_add_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
file,
context_type="Model",
context_identifier="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
@@ -0,0 +1,48 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_assign_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
file,
context_type="Model",
context_identifier="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
ifcopenshell.api.cogo.assign_survey_point(annotation,file.createIfcCartesianPoint((20.0,30.0,40.0)))
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((20.0,30.0,40.0))
@@ -0,0 +1,48 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2025 Thomas Krijnen <thomas@aecgeeks.com>
#
# This file is part of IfcOpenShell.
#
# IfcOpenShell is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# IfcOpenShell is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import pytest
import ifcopenshell.api.alignment
import ifcopenshell.api.context
import ifcopenshell.api.cogo
def test_edit_survey_point():
file = ifcopenshell.file(schema="IFC4X3_ADD2")
project = file.createIfcProject(Name="Test")
site = file.createIfcSite(GlobalId=ifcopenshell.guid.new(),Name="MySite")
ifcopenshell.api.aggregate.assign_object(file,relating_object=project,products=[site])
geometric_representation_context = ifcopenshell.api.context.add_context(file, context_type="Model")
axis_model_representation_subcontext = ifcopenshell.api.context.add_context(
file,
context_type="Model",
context_identifier="Annotation",
target_view="MODEL_VIEW",
parent=geometric_representation_context,
)
annotation = ifcopenshell.api.cogo.add_survey_point(file,file.createIfcCartesianPoint((50.0,10.0)))
assert annotation
assert annotation.PredefinedType == "SURVEY"
assert annotation.Representation.Representations[0].RepresentationIdentifier == "Annotation"
assert annotation.Representation.Representations[0].RepresentationType == "Point"
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((50.0,10.0))
ifcopenshell.api.cogo.edit_survey_point(annotation,20.0,30.0)
assert annotation.Representation.Representations[0].Items[0].Coordinates == pytest.approx((20.0,30.0))