mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 01:41:57 +00:00
Merge branch 'v0.6.0' of https://github.com/IfcOpenShell/IfcOpenShell into v0.6.0
This commit is contained in:
@@ -1,8 +1,84 @@
|
||||
import math
|
||||
import isodate
|
||||
import datetime
|
||||
from dateutil import parser
|
||||
from ifcopenshell.api.sequence.data import Data
|
||||
|
||||
|
||||
def count_working_days(start, finish, calendar):
|
||||
result = 0
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
finish_date = datetime.date(finish.year, finish.month, finish.day)
|
||||
while current_date <= finish_date:
|
||||
if is_working_day(current_date, calendar):
|
||||
result += 1
|
||||
current_date += datetime.timedelta(days=1)
|
||||
return result
|
||||
|
||||
|
||||
def is_working_day(day, calendar):
|
||||
is_working_day = False
|
||||
for work_time_id in calendar["WorkingTimes"] or []:
|
||||
if is_work_time_applicable_to_day(Data.work_times[work_time_id], day):
|
||||
is_working_day = True
|
||||
break
|
||||
if not is_working_day:
|
||||
return is_working_day
|
||||
for work_time_id in calendar["ExceptionTimes"] or []:
|
||||
if is_work_time_applicable_to_day(Data.work_times[work_time_id], day):
|
||||
is_working_day = False
|
||||
break
|
||||
return is_working_day
|
||||
|
||||
|
||||
def is_work_time_applicable_to_day(work_time, day):
|
||||
if work_time["Start"] and work_time["Start"] > day:
|
||||
return False
|
||||
|
||||
if work_time["Finish"] and work_time["Finish"] < day:
|
||||
return False
|
||||
|
||||
if not work_time["RecurrencePattern"]:
|
||||
return True
|
||||
|
||||
recurrence = Data.recurrence_patterns[work_time["RecurrencePattern"]]
|
||||
|
||||
if recurrence["RecurrenceType"] == "DAILY":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return True
|
||||
if not work_time["Start"]:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence["RecurrenceType"] == "WEEKLY":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return (day.weekday() + 1) in recurrence["WeekdayComponent"]
|
||||
if not work_time["Start"]:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence["RecurrenceType"] == "MONTHLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return day.day in recurrence["DayComponent"]
|
||||
return False # TODO
|
||||
elif recurrence["RecurrenceType"] == "MONTHLY_BY_POSITION":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return (day.weekday() + 1) in recurrence["WeekdayComponent"] and math.floor(day.day / 7) + 1 == recurrence[
|
||||
"Position"
|
||||
]
|
||||
return False # TODO
|
||||
elif recurrence["RecurrenceType"] == "YEARLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return day.month in recurrence["MonthComponent"] and day.day in recurrence["DayComponent"]
|
||||
return False # TODO
|
||||
elif recurrence["RecurrenceType"] == "YEARLY_BY_POSITION":
|
||||
if not recurrence["Interval"] and not recurrence["Occurrences"]:
|
||||
return (
|
||||
day.month in recurrence["MonthComponent"]
|
||||
and (day.weekday() + 1) in recurrence["WeekdayComponent"]
|
||||
and math.floor(day.day / 7) + 1 == recurrence["Position"]
|
||||
)
|
||||
return False # TODO
|
||||
|
||||
|
||||
def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False, is_latest=False):
|
||||
task = Data.tasks[ifc_definition_id]
|
||||
if task["TaskTime"]:
|
||||
@@ -10,9 +86,32 @@ def derive_date(ifc_definition_id, attribute_name, date=None, is_earliest=False,
|
||||
if current_date:
|
||||
return current_date
|
||||
for subtask in task["RelatedObjects"]:
|
||||
current_date = derive_date(
|
||||
subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest
|
||||
)
|
||||
current_date = derive_date(subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest)
|
||||
if is_earliest:
|
||||
if current_date and (date is None or current_date < date):
|
||||
date = current_date
|
||||
if is_latest:
|
||||
if current_date and (date is None or current_date > date):
|
||||
date = current_date
|
||||
return date
|
||||
|
||||
|
||||
def derive_calendar(ifc_definition_id):
|
||||
task = Data.tasks[ifc_definition_id]
|
||||
if task["HasAssignmentsWorkCalendar"]:
|
||||
return Data.work_calendars[task["HasAssignmentsWorkCalendar"][0]]
|
||||
if task["Nests"]:
|
||||
return derive_calendar(task["Nests"][0])
|
||||
|
||||
|
||||
def derive_duration(ifc_definition_id, attribute_name):
|
||||
task = Data.tasks[ifc_definition_id]
|
||||
if task["TaskTime"]:
|
||||
current_date = Data.task_times[task["TaskTime"]][attribute_name]
|
||||
if current_date:
|
||||
return current_date
|
||||
for subtask in task["RelatedObjects"]:
|
||||
current_date = derive_date(subtask, attribute_name, date=date, is_earliest=is_earliest, is_latest=is_latest)
|
||||
if is_earliest:
|
||||
if current_date and (date is None or current_date < date):
|
||||
date = current_date
|
||||
@@ -31,6 +130,7 @@ def parse_datetime(value):
|
||||
except:
|
||||
return None
|
||||
|
||||
|
||||
def parse_duration(value):
|
||||
try:
|
||||
return isodate.parse_duration(value)
|
||||
|
||||
@@ -291,6 +291,14 @@ class LoadTaskProperties(bpy.types.Operator):
|
||||
item.is_successor = self.props.active_task_id in [
|
||||
Data.sequences[r]["RelatingProcess"] for r in task["IsSuccessorFrom"]
|
||||
]
|
||||
|
||||
calendar = helper.derive_calendar(item.ifc_definition_id)
|
||||
if task["HasAssignmentsWorkCalendar"]:
|
||||
item.calendar = calendar["Name"] or "Unnamed"
|
||||
else:
|
||||
item.calendar = ""
|
||||
item.derived_calendar = calendar["Name"] or "Unnamed" if calendar else ""
|
||||
|
||||
if task["TaskTime"]:
|
||||
task_time = Data.task_times[task["TaskTime"]]
|
||||
item.start = self.canonicalise_time(task_time["ScheduleStart"])
|
||||
@@ -303,12 +311,13 @@ class LoadTaskProperties(bpy.types.Operator):
|
||||
derived_finish = helper.derive_date(item.ifc_definition_id, "ScheduleFinish", is_latest=True)
|
||||
item.derived_start = self.canonicalise_time(derived_start) if derived_start else ""
|
||||
item.derived_finish = self.canonicalise_time(derived_finish) if derived_finish else ""
|
||||
if derived_start and derived_finish:
|
||||
derived_duration = ifcopenshell.util.date.timedelta2duration(derived_finish - derived_start)
|
||||
item.derived_duration = isodate.duration_isoformat(derived_duration)
|
||||
if derived_start and derived_finish and calendar:
|
||||
derived_duration = helper.count_working_days(derived_start, derived_finish, calendar)
|
||||
item.derived_duration = f"P{derived_duration}D"
|
||||
item.start = "-"
|
||||
item.finish = "-"
|
||||
item.duration = "-"
|
||||
|
||||
self.props.is_task_update_enabled = True
|
||||
return {"FINISHED"}
|
||||
|
||||
@@ -1065,6 +1074,10 @@ class EditTaskCalendar(bpy.types.Operator):
|
||||
},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if Data.tasks[self.task]["RelatedObjects"]:
|
||||
bpy.ops.bim.load_task_properties()
|
||||
else:
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -1085,6 +1098,10 @@ class RemoveTaskCalendar(bpy.types.Operator):
|
||||
},
|
||||
)
|
||||
Data.load(IfcStore.get_file())
|
||||
if Data.tasks[self.task]["RelatedObjects"]:
|
||||
bpy.ops.bim.load_task_properties()
|
||||
else:
|
||||
bpy.ops.bim.load_task_properties(task=self.task)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
|
||||
@@ -156,9 +156,11 @@ class Task(PropertyGroup):
|
||||
duration: StringProperty(name="Duration")
|
||||
start: StringProperty(name="Start", update=updateTaskTimeStart)
|
||||
finish: StringProperty(name="Finish", update=updateTaskTimeFinish)
|
||||
calendar: StringProperty(name="Calendar")
|
||||
derived_start: StringProperty(name="Derived Start")
|
||||
derived_finish: StringProperty(name="Derived Finish")
|
||||
derived_duration: StringProperty(name="Derived Duration")
|
||||
derived_calendar: StringProperty(name="Derived Calendar")
|
||||
is_predecessor: BoolProperty(name="Is Predecessor")
|
||||
is_successor: BoolProperty(name="Is Successor")
|
||||
|
||||
@@ -189,6 +191,7 @@ class BIMWorkScheduleProperties(PropertyGroup):
|
||||
task_attributes: CollectionProperty(name="Task Attributes", type=Attribute)
|
||||
should_show_visualisation_ui: BoolProperty(name="Should Show Visualisation UI", default=False)
|
||||
should_show_times: BoolProperty(name="Should Show Times", default=False)
|
||||
should_show_calendars: BoolProperty(name="Should Show Calendars", default=False)
|
||||
active_task_time_id: IntProperty(name="Active Task Id")
|
||||
task_time_attributes: CollectionProperty(name="Task Time Attributes", type=Attribute)
|
||||
contracted_tasks: StringProperty(name="Contracted Task Items", default="[]")
|
||||
|
||||
@@ -109,6 +109,7 @@ class BIM_PT_work_schedules(Panel):
|
||||
row.operator("bim.edit_work_schedule", text="", icon="CHECKMARK")
|
||||
elif self.props.editing_type == "TASKS":
|
||||
row.prop(self.props, "should_show_times", text="", icon="TIME")
|
||||
row.prop(self.props, "should_show_calendars", text="", icon="VIEW_ORTHO")
|
||||
row.prop(self.props, "should_show_visualisation_ui", text="", icon="CAMERA_STEREO")
|
||||
row.operator("bim.generate_gantt_chart", text="", icon="NLA").work_schedule = work_schedule_id
|
||||
row.operator("bim.add_summary_task", text="", icon="ADD").work_schedule = work_schedule_id
|
||||
@@ -336,6 +337,12 @@ class BIM_UL_tasks(UIList):
|
||||
else:
|
||||
row.prop(item, "duration", emboss=False, text="")
|
||||
|
||||
if props.should_show_calendars:
|
||||
if item.derived_calendar:
|
||||
row.label(text=item.derived_calendar + "*")
|
||||
else:
|
||||
row.label(text=item.calendar or "-")
|
||||
|
||||
if context.active_object:
|
||||
oprops = context.active_object.BIMObjectProperties
|
||||
row = layout.row(align=True)
|
||||
@@ -349,10 +356,6 @@ class BIM_UL_tasks(UIList):
|
||||
if props.active_task_id == item.ifc_definition_id:
|
||||
if props.editing_task_type == "TASKTIME":
|
||||
row.operator("bim.edit_task_time", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "CALENDAR":
|
||||
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "SEQUENCE":
|
||||
row.operator("bim.disable_editing_task", text="", icon="CHECKMARK")
|
||||
elif props.editing_task_type == "ATTRIBUTES":
|
||||
row.operator("bim.edit_task", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_task", text="", icon="CANCEL")
|
||||
|
||||
@@ -22,6 +22,7 @@ class Usecase:
|
||||
"RelatingPropertyDefinition": pset,
|
||||
}
|
||||
)
|
||||
return pset
|
||||
elif self.settings["product"].is_a("IfcTypeObject"):
|
||||
pset = self.file.create_entity(
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
@@ -29,8 +30,9 @@ class Usecase:
|
||||
has_property_sets = list(self.settings["product"].HasPropertySets or [])
|
||||
has_property_sets.append(pset)
|
||||
self.settings["product"].HasPropertySets = has_property_sets
|
||||
return pset
|
||||
elif self.settings["product"].is_a("IfcMaterialDefinition"):
|
||||
pset = self.file.create_entity(
|
||||
return self.file.create_entity(
|
||||
"IfcMaterialProperties",
|
||||
**{
|
||||
"Name": self.settings["Name"],
|
||||
|
||||
@@ -22,3 +22,4 @@ class Usecase:
|
||||
"RelatingPropertyDefinition": qto,
|
||||
}
|
||||
)
|
||||
return qto
|
||||
|
||||
@@ -133,6 +133,7 @@ class Data:
|
||||
data["TaskTime"] = data["TaskTime"].id()
|
||||
for rel in task.IsNestedBy:
|
||||
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcTask")]
|
||||
data["Nests"] = [r.RelatingObject.id() for r in task.Nests or []]
|
||||
[
|
||||
data["RelatingProducts"].append(r.RelatingProduct.id())
|
||||
for r in task.HasAssignments
|
||||
|
||||
+20
-16
@@ -1,5 +1,4 @@
|
||||
import datetime
|
||||
from datetime import timedelta
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
@@ -94,6 +93,7 @@ class P62Ifc:
|
||||
self.calendars[calendar_id] = {
|
||||
"Name": calendar.find("pr:Name", self.ns).text,
|
||||
"Type": calendar.find("pr:Type", self.ns).text,
|
||||
"HoursPerDay": calendar.find("pr:HoursPerDay", self.ns).text,
|
||||
"StandardWorkWeek": standard_work_week,
|
||||
"HolidayOrExceptions": exceptions,
|
||||
}
|
||||
@@ -122,7 +122,7 @@ class P62Ifc:
|
||||
"Identification": activity.find("pr:Id", self.ns).text,
|
||||
"StartDate": datetime.datetime.fromisoformat(activity.find("pr:StartDate", self.ns).text),
|
||||
"FinishDate": datetime.datetime.fromisoformat(activity.find("pr:FinishDate", self.ns).text),
|
||||
"PlannedDuration": datetime.timedelta(hours=float(activity.find("pr:PlannedDuration", self.ns).text)),
|
||||
"PlannedDuration": activity.find("pr:PlannedDuration", self.ns).text,
|
||||
"Status": activity.find("pr:Status", self.ns).text,
|
||||
"CalendarObjectId": activity.find("pr:CalendarObjectId", self.ns).text,
|
||||
"ifc": None,
|
||||
@@ -238,10 +238,9 @@ class P62Ifc:
|
||||
"sequence.edit_recurrence_pattern",
|
||||
self.file,
|
||||
recurrence_pattern=recurrence,
|
||||
attributes={"DayComponent": month_data["FullDay"], "MonthComponent": [month], "Occurrences": 1},
|
||||
attributes={"DayComponent": month_data["FullDay"], "MonthComponent": [month]},
|
||||
)
|
||||
|
||||
|
||||
def process_work_time_exceptions(self, year, month, month_data, calendar):
|
||||
for day in month_data["WorkTime"]:
|
||||
if day["ifc"]:
|
||||
@@ -280,7 +279,7 @@ class P62Ifc:
|
||||
"sequence.edit_recurrence_pattern",
|
||||
self.file,
|
||||
recurrence_pattern=recurrence,
|
||||
attributes={"DayComponent": day_component, "MonthComponent": [month], "Occurrences": 1},
|
||||
attributes={"DayComponent": day_component, "MonthComponent": [month]},
|
||||
)
|
||||
for work_time in day["WorkTimes"]:
|
||||
ifcopenshell.api.run(
|
||||
@@ -291,7 +290,6 @@ class P62Ifc:
|
||||
end_time=work_time["Finish"],
|
||||
)
|
||||
|
||||
|
||||
def create_tasks(self, work_schedule):
|
||||
for wbs in self.wbs.values():
|
||||
self.create_task_from_wbs(wbs, work_schedule)
|
||||
@@ -336,6 +334,7 @@ class P62Ifc:
|
||||
},
|
||||
)
|
||||
task_time = ifcopenshell.api.run("sequence.add_task_time", self.file, task=activity["ifc"])
|
||||
calendar = self.calendars[activity["CalendarObjectId"]]
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
@@ -344,18 +343,23 @@ class P62Ifc:
|
||||
"ScheduleStart": activity["StartDate"],
|
||||
"ScheduleFinish": activity["FinishDate"],
|
||||
"DurationType": "WORKTIME" if activity["PlannedDuration"] else None,
|
||||
"ScheduleDuration": activity["PlannedDuration"] if activity["PlannedDuration"] else None,
|
||||
"ScheduleDuration": datetime.timedelta(
|
||||
days=float(activity["PlannedDuration"]) / float(calendar["HoursPerDay"])
|
||||
)
|
||||
or None
|
||||
if activity["PlannedDuration"]
|
||||
else None,
|
||||
},
|
||||
)
|
||||
# Seem crashy
|
||||
ifcopenshell.api.run(
|
||||
"control.assign_control",
|
||||
self.file,
|
||||
**{
|
||||
"relating_control": calendar["ifc"],
|
||||
"related_object": activity["ifc"],
|
||||
},
|
||||
)
|
||||
if activity["CalendarObjectId"]:
|
||||
ifcopenshell.api.run(
|
||||
"control.assign_control",
|
||||
self.file,
|
||||
**{
|
||||
"relating_control": self.calendars[activity["CalendarObjectId"]]["ifc"],
|
||||
"related_object": activity["ifc"],
|
||||
},
|
||||
)
|
||||
|
||||
def create_rel_sequences(self):
|
||||
self.sequence_type_map = {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
unit = {"is_metric": "METERS" in self.args[0], "raw": self.args[0]}
|
||||
self.file_patched = ifcopenshell.api.run("project.create_file", version=self.file.schema)
|
||||
project = ifcopenshell.api.run("root.create_entity", self.file_patched, ifc_class="IfcProject")
|
||||
unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit})
|
||||
|
||||
# Is there a better way?
|
||||
for element in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
|
||||
element.Precision = 1E-8
|
||||
|
||||
# If we don't add openings first, they don't get converted
|
||||
for element in self.file.by_type("IfcOpeningElement"):
|
||||
self.file_patched.add(element)
|
||||
|
||||
for element in self.file:
|
||||
self.file_patched.add(element)
|
||||
|
||||
new_length = [u for u in unit_assignment.Units if u.UnitType == "LENGTHUNIT"][0]
|
||||
old_length = [
|
||||
u for u in self.file_patched.by_type("IfcProject")[1].UnitsInContext.Units if u.UnitType == "LENGTHUNIT"
|
||||
][0]
|
||||
|
||||
for inverse in self.file_patched.get_inverse(old_length):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, old_length, new_length)
|
||||
|
||||
self.file_patched.remove(old_length)
|
||||
self.file_patched.remove(project)
|
||||
@@ -0,0 +1,59 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
self.qto_template_cache = {}
|
||||
self.psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
|
||||
self.source_property_name = self.args[0]
|
||||
self.destination_quantity_name = self.args[1]
|
||||
|
||||
for product in self.file.by_type("IfcTypeProduct"):
|
||||
self.process_product(product, product.HasPropertySets or [])
|
||||
|
||||
for product in self.file.by_type("IfcProduct"):
|
||||
self.process_product(
|
||||
product,
|
||||
[r.RelatingPropertyDefinition for r in product.IsDefinedBy if r.is_a("IfcRelDefinesByProperties")],
|
||||
)
|
||||
|
||||
def process_product(self, product, definitions):
|
||||
value = None
|
||||
has_quantity = False
|
||||
qtos = {}
|
||||
for definition in definitions or []:
|
||||
if definition.is_a("IfcPropertySet"):
|
||||
for prop in definition.HasProperties:
|
||||
if prop.is_a("IfcPropertySingleValue") and prop.Name == self.source_property_name:
|
||||
value = prop.NominalValue.wrappedValue if prop.NominalValue else None
|
||||
elif definition.is_a("IfcElementQuantity"):
|
||||
qtos[definition.Name] = definition
|
||||
for quantity in definition.Quantities:
|
||||
if quantity.is_a("IfcPhysicalSimpleQuantity") and quantity.Name == self.destination_quantity_name:
|
||||
has_quantity = True
|
||||
|
||||
if value and not has_quantity:
|
||||
qto_name = self.get_qto_name(product.is_a())
|
||||
qto = qtos.get(qto_name, ifcopenshell.api.run("pset.add_qto", self.file, product=product, Name=qto_name))
|
||||
ifcopenshell.api.run(
|
||||
"pset.edit_qto", self.file, qto=qto, Properties={self.destination_quantity_name: value}
|
||||
)
|
||||
|
||||
def get_qto_name(self, ifc_class):
|
||||
for template in self.get_qto_templates(ifc_class):
|
||||
if self.destination_quantity_name in [t.Name for t in template.HasPropertyTemplates]:
|
||||
return template.Name
|
||||
|
||||
def get_qto_templates(self, ifc_class):
|
||||
if ifc_class not in self.qto_template_cache:
|
||||
self.qto_template_cache[ifc_class] = self.psetqto.get_applicable(ifc_class, qto_only=True)
|
||||
return self.qto_template_cache[ifc_class]
|
||||
Reference in New Issue
Block a user