mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Improve time and date support (don't cast to datetime) and support all recurrence types and time periods in work schedules
This commit is contained in:
@@ -58,6 +58,7 @@ classes = (
|
||||
prop.BIMWorkScheduleProperties,
|
||||
prop.BIMTaskTreeProperties,
|
||||
prop.WorkCalendar,
|
||||
prop.RecurrenceComponent,
|
||||
prop.BIMWorkCalendarProperties,
|
||||
ui.BIM_PT_work_plans,
|
||||
ui.BIM_PT_work_schedules,
|
||||
|
||||
@@ -5,6 +5,7 @@ import time
|
||||
import pystache
|
||||
import webbrowser
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
from dateutil import parser
|
||||
from blenderbim.bim.ifc import IfcStore
|
||||
@@ -803,9 +804,9 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
|
||||
work_calendar: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkCalendarProperties
|
||||
while len(props.work_calendar_attributes) > 0:
|
||||
props.work_calendar_attributes.remove(0)
|
||||
self.props = context.scene.BIMWorkCalendarProperties
|
||||
while len(self.props.work_calendar_attributes) > 0:
|
||||
self.props.work_calendar_attributes.remove(0)
|
||||
|
||||
data = Data.work_calendars[self.work_calendar]
|
||||
|
||||
@@ -813,7 +814,7 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.work_calendar_attributes.add()
|
||||
new = self.props.work_calendar_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
@@ -824,8 +825,8 @@ class EnableEditingWorkCalendar(bpy.types.Operator):
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_work_calendar_id = self.work_calendar
|
||||
props.is_editing = "ATTRIBUTES"
|
||||
self.props.active_work_calendar_id = self.work_calendar
|
||||
self.props.is_editing = "ATTRIBUTES"
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -894,9 +895,9 @@ class EnableEditingWorkTime(bpy.types.Operator):
|
||||
work_time: bpy.props.IntProperty()
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkCalendarProperties
|
||||
while len(props.work_time_attributes) > 0:
|
||||
props.work_time_attributes.remove(0)
|
||||
self.props = context.scene.BIMWorkCalendarProperties
|
||||
while len(self.props.work_time_attributes) > 0:
|
||||
self.props.work_time_attributes.remove(0)
|
||||
|
||||
data = Data.work_times[self.work_time]
|
||||
|
||||
@@ -904,7 +905,7 @@ class EnableEditingWorkTime(bpy.types.Operator):
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(attribute)
|
||||
if data_type == "entity":
|
||||
continue
|
||||
new = props.work_time_attributes.add()
|
||||
new = self.props.work_time_attributes.add()
|
||||
new.name = attribute.name()
|
||||
new.is_null = data[attribute.name()] is None
|
||||
new.is_optional = attribute.optional()
|
||||
@@ -917,9 +918,51 @@ class EnableEditingWorkTime(bpy.types.Operator):
|
||||
new.enum_items = json.dumps(ifcopenshell.util.attribute.get_enum_items(attribute))
|
||||
if data[attribute.name()]:
|
||||
new.enum_value = data[attribute.name()]
|
||||
props.active_work_time_id = self.work_time
|
||||
|
||||
self.initialise_recurrence_components()
|
||||
self.load_recurrence_pattern_data(data)
|
||||
self.props.active_work_time_id = self.work_time
|
||||
return {"FINISHED"}
|
||||
|
||||
def initialise_recurrence_components(self):
|
||||
if len(self.props.day_components) == 0:
|
||||
for i in range(0, 31):
|
||||
new = self.props.day_components.add()
|
||||
new.name = str(i + 1)
|
||||
if len(self.props.weekday_components) == 0:
|
||||
for d in ["M", "T", "W", "T", "F", "S", "S"]:
|
||||
new = self.props.weekday_components.add()
|
||||
new.name = d
|
||||
if len(self.props.month_components) == 0:
|
||||
for m in ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]:
|
||||
new = self.props.month_components.add()
|
||||
new.name = m
|
||||
|
||||
def load_recurrence_pattern_data(self, work_time):
|
||||
self.props.position = 0
|
||||
self.props.interval = 0
|
||||
self.props.occurrences = 0
|
||||
self.props.start_time = ""
|
||||
self.props.end_time = ""
|
||||
for component in self.props.day_components:
|
||||
component.is_specified = False
|
||||
for component in self.props.weekday_components:
|
||||
component.is_specified = False
|
||||
for component in self.props.month_components:
|
||||
component.is_specified = False
|
||||
if not work_time["RecurrencePattern"]:
|
||||
return
|
||||
recurrence_pattern = Data.recurrence_patterns[work_time["RecurrencePattern"]]
|
||||
for attribute in ["Position", "Interval", "Occurrences"]:
|
||||
if recurrence_pattern[attribute]:
|
||||
setattr(self.props, attribute.lower(), recurrence_pattern[attribute])
|
||||
for component in recurrence_pattern["DayComponent"] or []:
|
||||
self.props.day_components[component - 1].is_specified = True
|
||||
for component in recurrence_pattern["WeekdayComponent"] or []:
|
||||
self.props.weekday_components[component - 1].is_specified = True
|
||||
for component in recurrence_pattern["MonthComponent"] or []:
|
||||
self.props.month_components[component - 1].is_specified = True
|
||||
|
||||
|
||||
class DisableEditingWorkTime(bpy.types.Operator):
|
||||
bl_idname = "bim.disable_editing_work_time"
|
||||
@@ -935,9 +978,9 @@ class EditWorkTime(bpy.types.Operator):
|
||||
bl_label = "Edit Work Time"
|
||||
|
||||
def execute(self, context):
|
||||
props = context.scene.BIMWorkCalendarProperties
|
||||
self.props = context.scene.BIMWorkCalendarProperties
|
||||
attributes = {}
|
||||
for attribute in props.work_time_attributes:
|
||||
for attribute in self.props.work_time_attributes:
|
||||
if attribute.is_null:
|
||||
attributes[attribute.name] = None
|
||||
else:
|
||||
@@ -949,12 +992,49 @@ class EditWorkTime(bpy.types.Operator):
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_work_time",
|
||||
self.file,
|
||||
**{"work_time": self.file.by_id(props.active_work_time_id), "attributes": attributes},
|
||||
**{"work_time": self.file.by_id(self.props.active_work_time_id), "attributes": attributes},
|
||||
)
|
||||
|
||||
work_time = Data.work_times[self.props.active_work_time_id]
|
||||
if work_time["RecurrencePattern"]:
|
||||
self.edit_recurrence_pattern(work_time["RecurrencePattern"])
|
||||
|
||||
Data.load(IfcStore.get_file())
|
||||
bpy.ops.bim.disable_editing_work_time()
|
||||
return {"FINISHED"}
|
||||
|
||||
def edit_recurrence_pattern(self, recurrence_pattern_id):
|
||||
recurrence_pattern = self.file.by_id(recurrence_pattern_id)
|
||||
attributes = {
|
||||
"Interval": self.props.interval if self.props.interval > 0 else None,
|
||||
"Occurrences": self.props.occurrences if self.props.occurrences > 0 else None,
|
||||
}
|
||||
applicable_data = {
|
||||
"DAILY": ["Interval", "Occurrences"],
|
||||
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
|
||||
"BY_DAY_COUNT": ["Interval", "Occurrences"],
|
||||
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
|
||||
}
|
||||
if "Position" in applicable_data[recurrence_pattern.RecurrenceType]:
|
||||
attributes["Position"] = self.props.position if self.props.position != 0 else None
|
||||
if "DayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
||||
attributes["DayComponent"] = [i + 1 for i, c in enumerate(self.props.day_components) if c.is_specified]
|
||||
if "WeekdayComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
||||
attributes["WeekdayComponent"] = [
|
||||
i + 1 for i, c in enumerate(self.props.weekday_components) if c.is_specified
|
||||
]
|
||||
if "MonthComponent" in applicable_data[recurrence_pattern.RecurrenceType]:
|
||||
attributes["MonthComponent"] = [i + 1 for i, c in enumerate(self.props.month_components) if c.is_specified]
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_recurrence_pattern",
|
||||
self.file,
|
||||
**{"recurrence_pattern": recurrence_pattern, "attributes": attributes},
|
||||
)
|
||||
|
||||
|
||||
class RemoveWorkTime(bpy.types.Operator):
|
||||
bl_idname = "bim.remove_work_time"
|
||||
@@ -1009,15 +1089,22 @@ class AddTimePeriod(bpy.types.Operator):
|
||||
def execute(self, context):
|
||||
self.props = context.scene.BIMWorkCalendarProperties
|
||||
self.file = IfcStore.get_file()
|
||||
try:
|
||||
start_time = parser.parse(self.props.start_time)
|
||||
end_time = parser.parse(self.props.end_time)
|
||||
except:
|
||||
return {"FINISHED"}
|
||||
ifcopenshell.api.run(
|
||||
"sequence.add_time_period",
|
||||
self.file,
|
||||
**{
|
||||
"recurrence_pattern": self.file.by_id(self.recurrence_pattern),
|
||||
"start_time": self.props.start_time,
|
||||
"end_time": self.props.start_time,
|
||||
"start_time": start_time,
|
||||
"end_time": end_time,
|
||||
},
|
||||
)
|
||||
self.props.start_time = ""
|
||||
self.props.end_time = ""
|
||||
Data.load(IfcStore.get_file())
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
@@ -168,6 +168,11 @@ class WorkCalendar(PropertyGroup):
|
||||
ifc_definition_id: IntProperty(name="IFC Definition ID")
|
||||
|
||||
|
||||
class RecurrenceComponent(PropertyGroup):
|
||||
name: StringProperty(name="Name")
|
||||
is_specified: BoolProperty(name="Is Specified")
|
||||
|
||||
|
||||
class BIMWorkCalendarProperties(PropertyGroup):
|
||||
work_calendar_attributes: CollectionProperty(name="Work Calendar Attributes", type=Attribute)
|
||||
work_time_attributes: CollectionProperty(name="Work Time Attributes", type=Attribute)
|
||||
@@ -175,15 +180,12 @@ class BIMWorkCalendarProperties(PropertyGroup):
|
||||
work_calendars: CollectionProperty(name="Work Calendar", type=WorkCalendar)
|
||||
active_work_calendar_id: IntProperty(name="Active Work Calendar Id")
|
||||
active_work_time_id: IntProperty(name="Active Work Time Id")
|
||||
weekday_component_monday: BoolProperty(name="M")
|
||||
weekday_component_tuesday: BoolProperty(name="T")
|
||||
weekday_component_wednesday: BoolProperty(name="W")
|
||||
weekday_component_thursday: BoolProperty(name="T")
|
||||
weekday_component_friday: BoolProperty(name="F")
|
||||
weekday_component_saturday: BoolProperty(name="S")
|
||||
weekday_component_sunday: BoolProperty(name="S")
|
||||
dummy_bool: BoolProperty(name="Active Work Calendar Id")
|
||||
dummy_int: IntProperty(name="Active Work Calendar Id")
|
||||
day_components: CollectionProperty(name="Day Components", type=RecurrenceComponent)
|
||||
weekday_components: CollectionProperty(name="Weekday Components", type=RecurrenceComponent)
|
||||
month_components: CollectionProperty(name="Month Components", type=RecurrenceComponent)
|
||||
position: IntProperty(name="Position")
|
||||
interval: IntProperty(name="Recurrence Interval")
|
||||
occurrences: IntProperty(name="Occurs N Times")
|
||||
recurrence_types: EnumProperty(items=[
|
||||
("DAILY", "Daily", "e.g. Every day"),
|
||||
("WEEKLY", "Weekly", "e.g. Every Friday"),
|
||||
|
||||
@@ -74,7 +74,6 @@ class BIM_PT_work_plans(Panel):
|
||||
op.work_schedule = int(self.props.work_schedules)
|
||||
|
||||
|
||||
|
||||
class BIM_PT_work_schedules(Panel):
|
||||
bl_label = "IFC Work Schedules"
|
||||
bl_idname = "BIM_PT_work_schedules"
|
||||
@@ -116,7 +115,9 @@ class BIM_PT_work_schedules(Panel):
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
|
||||
else:
|
||||
row.operator("bim.enable_editing_tasks", text="", icon="ACTION").work_schedule = work_schedule_id
|
||||
row.operator("bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL").work_schedule = work_schedule_id
|
||||
row.operator(
|
||||
"bim.enable_editing_work_schedule", text="", icon="GREASEPENCIL"
|
||||
).work_schedule = work_schedule_id
|
||||
row.operator("bim.remove_work_schedule", text="", icon="X").work_schedule = work_schedule_id
|
||||
|
||||
if self.props.active_work_schedule_id == work_schedule_id:
|
||||
@@ -311,7 +312,11 @@ class BIM_PT_work_calendars(Panel):
|
||||
|
||||
def draw_work_time_ui(self, work_time, time_type):
|
||||
row = self.layout.row(align=True)
|
||||
row.label(text=work_time["Name"] or "Unnamed", icon="MESH_GRID" if time_type == "WorkingTimes" else "LIGHTPROBE_GRID")
|
||||
row.label(
|
||||
text=work_time["Name"] or "Unnamed", icon="MESH_GRID" if time_type == "WorkingTimes" else "LIGHTPROBE_GRID"
|
||||
)
|
||||
if work_time["Start"] or work_time["Finish"]:
|
||||
row.label(text="{} - {}".format(work_time["Start"] or "*", work_time["Finish"] or "*"))
|
||||
if self.props.active_work_time_id == work_time["id"]:
|
||||
row.operator("bim.edit_work_time", text="", icon="CHECKMARK")
|
||||
row.operator("bim.disable_editing_work_time", text="", icon="CANCEL")
|
||||
@@ -366,23 +371,42 @@ class BIM_PT_work_calendars(Panel):
|
||||
op = row.operator("bim.remove_time_period", text="", icon="X")
|
||||
op.time_period = time_period_id
|
||||
|
||||
if recurrence_pattern["RecurrenceType"] == "DAILY":
|
||||
pass # No need to show any custom UI
|
||||
if recurrence_pattern["RecurrenceType"] == "WEEKLY":
|
||||
applicable_data = {
|
||||
"DAILY": ["Interval", "Occurrences"],
|
||||
"WEEKLY": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_DAY_OF_MONTH": ["DayComponent", "Interval", "Occurrences"],
|
||||
"MONTHLY_BY_POSITION": ["WeekdayComponent", "Position", "Interval", "Occurrences"],
|
||||
"BY_DAY_COUNT": ["Interval", "Occurrences"],
|
||||
"BY_WEEKDAY_COUNT": ["WeekdayComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_DAY_OF_MONTH": ["DayComponent", "MonthComponent", "Interval", "Occurrences"],
|
||||
"YEARLY_BY_POSITION": ["WeekdayComponent", "MonthComponent", "Position", "Interval", "Occurrences"],
|
||||
}
|
||||
|
||||
if "Position" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
row = box.row()
|
||||
row.prop(self.props, "position")
|
||||
|
||||
if "DayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
for i, component in enumerate(self.props.day_components):
|
||||
if i % 7 == 0:
|
||||
row = box.row(align=True)
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
if "WeekdayComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "weekday_component_monday", text="M")
|
||||
row.prop(self.props, "weekday_component_tuesday", text="T")
|
||||
row.prop(self.props, "weekday_component_wednesday", text="W")
|
||||
row.prop(self.props, "weekday_component_thursday", text="T")
|
||||
row.prop(self.props, "weekday_component_friday", text="F")
|
||||
row.prop(self.props, "weekday_component_saturday", text="S")
|
||||
row.prop(self.props, "weekday_component_sunday", text="S")
|
||||
for component in self.props.weekday_components:
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "dummy_int", text="Recurrence Interval")
|
||||
row = box.row(align=True)
|
||||
row.prop(self.props, "dummy_int", text="Occurs N Times")
|
||||
if "MonthComponent" in applicable_data[recurrence_pattern["RecurrenceType"]]:
|
||||
for i, component in enumerate(self.props.month_components):
|
||||
if i % 4 == 0:
|
||||
row = box.row(align=True)
|
||||
row.prop(component, "is_specified", text=component.name)
|
||||
|
||||
row = box.row()
|
||||
row.prop(self.props, "interval")
|
||||
row = box.row()
|
||||
row.prop(self.props, "occurrences")
|
||||
|
||||
def draw_editable_ui(self):
|
||||
for attribute in self.props.work_calendar_attributes:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import datetime
|
||||
from re import findall
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def duration2dict(duration):
|
||||
@@ -12,12 +12,16 @@ def duration2dict(duration):
|
||||
def ifc2datetime(element):
|
||||
if isinstance(element, str) and element[0] == "P": # IfcDuration
|
||||
return duration2dict(element)
|
||||
elif isinstance(element, str): # IfcDateTime, IfcDate
|
||||
return datetime.fromisoformat(element)
|
||||
elif isinstance(element, str) and element[2] == ":": # IfcTime
|
||||
return datetime.time.fromisoformat(element)
|
||||
elif isinstance(element, str) and ":" in element: # IfcDateTime
|
||||
return datetime.datetime.fromisoformat(element)
|
||||
elif isinstance(element, str): # IfcDate
|
||||
return datetime.date.fromisoformat(element)
|
||||
elif isinstance(element, int): # IfcTimeStamp
|
||||
return datetime.fromtimestamp(element)
|
||||
return datetime.datetime.fromtimestamp(element)
|
||||
elif element.is_a("IfcDateAndTime"):
|
||||
return datetime(
|
||||
return datetime.datetime(
|
||||
element.DateComponent.YearComponent,
|
||||
element.DateComponent.MonthComponent,
|
||||
element.DateComponent.DayComponent,
|
||||
@@ -27,7 +31,7 @@ def ifc2datetime(element):
|
||||
# TODO: implement TimeComponent timezone
|
||||
)
|
||||
elif element.is_a("IfcCalendarDate"):
|
||||
return datetime(
|
||||
return datetime.date(
|
||||
element.YearComponent,
|
||||
element.MonthComponent,
|
||||
element.DayComponent,
|
||||
@@ -36,15 +40,24 @@ def ifc2datetime(element):
|
||||
|
||||
def datetime2ifc(dt, ifc_type):
|
||||
if isinstance(dt, str):
|
||||
dt = datetime.fromisoformat(dt)
|
||||
dt = datetime.datetime.fromisoformat(dt)
|
||||
if ifc_type == "IfcTimeStamp":
|
||||
return int(dt.timestamp())
|
||||
elif ifc_type == "IfcDateTime":
|
||||
return dt.isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return datetime.datetime.combine(dt, datetime.datetime.min.time()).isoformat()
|
||||
elif ifc_type == "IfcDate":
|
||||
return dt.date().isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.date().isoformat()
|
||||
elif isinstance(dt, datetime.date):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcTime":
|
||||
return dt.time().isoformat()
|
||||
if isinstance(dt, datetime.datetime):
|
||||
return dt.time().isoformat()
|
||||
elif isinstance(dt, datetime.time):
|
||||
return dt.isoformat()
|
||||
elif ifc_type == "IfcCalendarDate":
|
||||
return {"DayComponent": dt.day, "MonthComponent": dt.month, "YearComponent": dt.year}
|
||||
elif ifc_type == "IfcLocalTime":
|
||||
|
||||
Reference in New Issue
Block a user