mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
@@ -98,9 +98,10 @@ def draw_attribute(
|
|||||||
if attribute.is_uri:
|
if attribute.is_uri:
|
||||||
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
op = layout.operator("bim.select_uri_attribute", text="", icon="FILE_FOLDER")
|
||||||
op.data_path = attribute.path_from_id("string_value")
|
op.data_path = attribute.path_from_id("string_value")
|
||||||
elif attribute.special_type == "DATE":
|
elif attribute.special_type in ("DATE", "DATETIME"):
|
||||||
op = layout.operator("bim.datepicker", text="", icon="TIME")
|
op = layout.operator("bim.datepicker", text="", icon="TIME")
|
||||||
op.target_prop = attribute.path_from_id("string_value")
|
op.target_prop = attribute.path_from_id("string_value")
|
||||||
|
op.include_time = attribute.special_type == "DATETIME"
|
||||||
|
|
||||||
if attribute.is_optional:
|
if attribute.is_optional:
|
||||||
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
layout.prop(attribute, "is_null", icon="RADIOBUT_OFF" if attribute.is_null else "RADIOBUT_ON", text="")
|
||||||
@@ -170,6 +171,8 @@ def import_attribute(
|
|||||||
new.is_uri = True
|
new.is_uri = True
|
||||||
elif attribute.type_of_attribute()._is("IfcDate"):
|
elif attribute.type_of_attribute()._is("IfcDate"):
|
||||||
new.special_type = "DATE"
|
new.special_type = "DATE"
|
||||||
|
elif attribute.type_of_attribute()._is("IfcDateTime"):
|
||||||
|
new.special_type = "DATETIME"
|
||||||
elif data_type == "boolean":
|
elif data_type == "boolean":
|
||||||
new.bool_value = False if new.is_null else bool(data[attribute.name()])
|
new.bool_value = False if new.is_null else bool(data[attribute.name()])
|
||||||
elif data_type == "integer":
|
elif data_type == "integer":
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ def parse_duration(value):
|
|||||||
|
|
||||||
|
|
||||||
def canonicalise_time(time: Union[datetime, None]) -> str:
|
def canonicalise_time(time: Union[datetime, None]) -> str:
|
||||||
|
"""Actualy canonicalises datetime as just a date, time is not included."""
|
||||||
if not time:
|
if not time:
|
||||||
return "-"
|
return "-"
|
||||||
return time.strftime("%d/%m/%y")
|
return time.strftime("%d/%m/%y")
|
||||||
|
|||||||
@@ -1178,27 +1178,45 @@ class Bonsai_DatePicker(bpy.types.Operator):
|
|||||||
bl_label = "Date Picker"
|
bl_label = "Date Picker"
|
||||||
bl_idname = "bim.datepicker"
|
bl_idname = "bim.datepicker"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
display_date: bpy.props.StringProperty(name="Display Date")
|
|
||||||
target_prop: bpy.props.StringProperty(name="Target date prop to set")
|
target_prop: bpy.props.StringProperty(name="Target date prop to set")
|
||||||
|
# TODO: base it on property type.
|
||||||
|
include_time: bpy.props.BoolProperty(name="Include Time", default=True)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
target_prop: str
|
||||||
|
include_time: bool
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
selected_date = context.scene.DatePickerProperties.selected_date
|
||||||
try:
|
try:
|
||||||
value = parser.parse(context.scene.DatePickerProperties.selected_date, dayfirst=True, fuzzy=True)
|
# Just to make sure the date is valid.
|
||||||
self.set_scene_prop(self.target_prop, helper.canonicalise_time(value))
|
tool.Sequence.parse_isodate_datetime(selected_date, self.include_time)
|
||||||
except:
|
self.set_scene_prop(self.target_prop, selected_date)
|
||||||
pass
|
return {"FINISHED"}
|
||||||
return {"FINISHED"}
|
except Exception as e:
|
||||||
|
self.report({"ERROR"}, f"Provided date is invalid: '{selected_date}'. Exception: {str(e)}.")
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
current_date = parser.parse(context.scene.DatePickerProperties.display_date, dayfirst=True, fuzzy=True)
|
props = context.scene.DatePickerProperties
|
||||||
current_month = (current_date.year, current_date.month)
|
display_date = tool.Sequence.parse_isodate_datetime(props.display_date, False)
|
||||||
|
current_month = (display_date.year, display_date.month)
|
||||||
lines = calendar.monthcalendar(*current_month)
|
lines = calendar.monthcalendar(*current_month)
|
||||||
month_title, week_titles = calendar.month(*current_month).splitlines()[:2]
|
month_title, week_titles = calendar.month(*current_month).splitlines()[:2]
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.prop(context.scene.DatePickerProperties, "selected_date")
|
row.prop(props, "selected_date", text="Date")
|
||||||
|
|
||||||
|
# Time.
|
||||||
|
if self.include_time:
|
||||||
|
row = layout.row()
|
||||||
|
row.label(text="Time:")
|
||||||
|
row.prop(props, "selected_hour", text="H")
|
||||||
|
row.prop(props, "selected_min", text="M")
|
||||||
|
row.prop(props, "selected_sec", text="S")
|
||||||
|
|
||||||
|
# Month.
|
||||||
split = layout.split()
|
split = layout.split()
|
||||||
col = split.row()
|
col = split.row()
|
||||||
op = col.operator("bim.redraw_datepicker", icon="TRIA_LEFT", text="")
|
op = col.operator("bim.redraw_datepicker", icon="TRIA_LEFT", text="")
|
||||||
@@ -1210,12 +1228,14 @@ class Bonsai_DatePicker(bpy.types.Operator):
|
|||||||
op = col.operator("bim.redraw_datepicker", icon="TRIA_RIGHT", text="")
|
op = col.operator("bim.redraw_datepicker", icon="TRIA_RIGHT", text="")
|
||||||
op.action = "next"
|
op.action = "next"
|
||||||
|
|
||||||
|
# Day of week.
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
for title in week_titles.split():
|
for title in week_titles.split():
|
||||||
col = row.column(align=True)
|
col = row.column(align=True)
|
||||||
col.alignment = "CENTER"
|
col.alignment = "CENTER"
|
||||||
col.label(text=title.strip())
|
col.label(text=title.strip())
|
||||||
|
|
||||||
|
# Days calendar.
|
||||||
for line in lines:
|
for line in lines:
|
||||||
row = layout.row(align=True)
|
row = layout.row(align=True)
|
||||||
for i in line:
|
for i in line:
|
||||||
@@ -1223,15 +1243,31 @@ class Bonsai_DatePicker(bpy.types.Operator):
|
|||||||
if i == 0:
|
if i == 0:
|
||||||
col.label(text=" ")
|
col.label(text=" ")
|
||||||
else:
|
else:
|
||||||
|
selected_date = datetime(year=display_date.year, month=display_date.month, day=i)
|
||||||
op = col.operator("bim.datepicker_setdate", text="{:2d}".format(i))
|
op = col.operator("bim.datepicker_setdate", text="{:2d}".format(i))
|
||||||
selected_date = "{}/{}/{}".format(i, current_date.month, current_date.year)
|
if self.include_time:
|
||||||
selected_date = parser.parse(selected_date, dayfirst=True, fuzzy=True)
|
selected_date = selected_date.replace(
|
||||||
op.selected_date = helper.canonicalise_time(selected_date)
|
hour=props.selected_hour, minute=props.selected_min, second=props.selected_sec
|
||||||
|
)
|
||||||
|
op.selected_date = tool.Sequence.isodate_datetime(selected_date, self.include_time)
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
self.display_date = self.get_scene_prop(self.target_prop) or helper.canonicalise_time(datetime.now())
|
props = context.scene.DatePickerProperties
|
||||||
context.scene.DatePickerProperties.display_date = self.display_date
|
current_date_str = self.get_scene_prop(self.target_prop)
|
||||||
context.scene.DatePickerProperties.selected_date = self.display_date
|
if current_date_str:
|
||||||
|
current_date = tool.Sequence.parse_isodate_datetime(current_date_str, self.include_time)
|
||||||
|
else:
|
||||||
|
current_date = datetime.now()
|
||||||
|
# Seconds of the moment when datepicker opened will probably only annoy users.
|
||||||
|
current_date = current_date.replace(second=0)
|
||||||
|
|
||||||
|
if self.include_time:
|
||||||
|
props["selected_hour"] = current_date.hour
|
||||||
|
props["selected_min"] = current_date.minute
|
||||||
|
props["selected_sec"] = current_date.second
|
||||||
|
|
||||||
|
props.display_date = tool.Sequence.isodate_datetime(current_date.replace(day=1), False)
|
||||||
|
props.selected_date = tool.Sequence.isodate_datetime(current_date, self.include_time)
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
def get_scene_prop(self, prop_path: str) -> str:
|
def get_scene_prop(self, prop_path: str) -> str:
|
||||||
@@ -1261,15 +1297,15 @@ class Bonsai_RedrawDatePicker(bpy.types.Operator):
|
|||||||
action: bpy.props.StringProperty()
|
action: bpy.props.StringProperty()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
current_date = parser.parse(context.scene.DatePickerProperties.display_date, dayfirst=True, fuzzy=True)
|
props = context.scene.DatePickerProperties
|
||||||
|
current_date = tool.Sequence.parse_isodate_datetime(props.display_date, False)
|
||||||
|
|
||||||
if self.action == "previous":
|
if self.action == "previous":
|
||||||
date_to_set = current_date - relativedelta.relativedelta(months=1)
|
date_to_set = current_date - relativedelta.relativedelta(months=1)
|
||||||
elif self.action == "next":
|
else: # "next".
|
||||||
date_to_set = current_date + relativedelta.relativedelta(months=1)
|
date_to_set = current_date + relativedelta.relativedelta(months=1)
|
||||||
|
|
||||||
context.scene.DatePickerProperties.display_date = helper.canonicalise_time(date_to_set)
|
props.display_date = tool.Sequence.isodate_datetime(date_to_set, False)
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -520,9 +520,23 @@ class BIMWorkCalendarProperties(PropertyGroup):
|
|||||||
end_time: StringProperty(name="End Time")
|
end_time: StringProperty(name="End Time")
|
||||||
|
|
||||||
|
|
||||||
|
def update_selected_date(self: "DatePickerProperties", context: bpy.types.Context) -> None:
|
||||||
|
# `include_time` is `True`, otherwise time props are not displayed in UI.
|
||||||
|
include_time = True
|
||||||
|
selected_date = tool.Sequence.parse_isodate_datetime(self.selected_date, include_time)
|
||||||
|
selected_date = selected_date.replace(hour=self.selected_hour, minute=self.selected_min, second=self.selected_sec)
|
||||||
|
self.selected_date = tool.Sequence.isodate_datetime(selected_date, include_time)
|
||||||
|
|
||||||
|
|
||||||
class DatePickerProperties(PropertyGroup):
|
class DatePickerProperties(PropertyGroup):
|
||||||
display_date: StringProperty(name="Display Date")
|
display_date: StringProperty(
|
||||||
|
name="Display Date",
|
||||||
|
description="Needed to keep track of what month is currently opened in date picker without affecting the currently selected date.",
|
||||||
|
)
|
||||||
selected_date: StringProperty(name="Selected Date")
|
selected_date: StringProperty(name="Selected Date")
|
||||||
|
selected_hour: IntProperty(min=0, max=23, update=update_selected_date)
|
||||||
|
selected_min: IntProperty(min=0, max=59, update=update_selected_date)
|
||||||
|
selected_sec: IntProperty(min=0, max=59, update=update_selected_date)
|
||||||
|
|
||||||
|
|
||||||
class BIMDateTextProperties(PropertyGroup):
|
class BIMDateTextProperties(PropertyGroup):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import bpy
|
import bpy
|
||||||
@@ -24,7 +25,9 @@ import base64
|
|||||||
import pystache
|
import pystache
|
||||||
import mathutils
|
import mathutils
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
import isodate
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
|
import ifcopenshell.ifcopenshell_wrapper as W
|
||||||
import ifcopenshell.util.sequence
|
import ifcopenshell.util.sequence
|
||||||
import ifcopenshell.util.date
|
import ifcopenshell.util.date
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
@@ -34,7 +37,11 @@ import bonsai.tool as tool
|
|||||||
import bonsai.bim.helper
|
import bonsai.bim.helper
|
||||||
from dateutil import parser
|
from dateutil import parser
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Any, Union, Literal
|
from datetime import time as datetime_time
|
||||||
|
from typing import Optional, Any, Union, Literal, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
import bonsai.bim.prop
|
||||||
|
|
||||||
|
|
||||||
class Sequence(bonsai.core.tool.Sequence):
|
class Sequence(bonsai.core.tool.Sequence):
|
||||||
@@ -344,19 +351,23 @@ class Sequence(bonsai.core.tool.Sequence):
|
|||||||
def load_task_time_attributes(cls, task_time: ifcopenshell.entity_instance) -> None:
|
def load_task_time_attributes(cls, task_time: ifcopenshell.entity_instance) -> None:
|
||||||
import bonsai.bim.module.sequence.helper as helper
|
import bonsai.bim.module.sequence.helper as helper
|
||||||
|
|
||||||
def callback(name, prop, data):
|
def callback(
|
||||||
|
name: str, prop: Union[bonsai.bim.prop.Attribute, None], data: dict[str, Any]
|
||||||
|
) -> Union[bool, None]:
|
||||||
if prop and prop.data_type == "string":
|
if prop and prop.data_type == "string":
|
||||||
duration_props = bpy.context.scene.BIMWorkScheduleProperties.durations_attributes.add()
|
# TODO: Check actual attribute type instead of providing attribute names.
|
||||||
duration_props.name = name
|
if name in ("ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"):
|
||||||
if prop.is_null:
|
duration_props = bpy.context.scene.BIMWorkScheduleProperties.durations_attributes.add()
|
||||||
for key in duration_props.keys():
|
duration_props.name = name
|
||||||
if key != "name":
|
if prop.is_null:
|
||||||
setattr(duration_props, key, 0)
|
for key in duration_props.keys():
|
||||||
return True
|
if key != "name":
|
||||||
if name in ["ScheduleDuration", "ActualDuration", "FreeFloat", "TotalFloat"] and data[name]:
|
setattr(duration_props, key, 0)
|
||||||
for key, value in helper.parse_duration_as_blender_props(data[name]).items():
|
return True
|
||||||
duration_props[key] = value
|
if data[name]:
|
||||||
return True
|
for key, value in helper.parse_duration_as_blender_props(data[name]).items():
|
||||||
|
duration_props[key] = value
|
||||||
|
return True
|
||||||
if isinstance(data[name], datetime):
|
if isinstance(data[name], datetime):
|
||||||
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
prop.string_value = "" if prop.is_null else data[name].isoformat()
|
||||||
return True
|
return True
|
||||||
@@ -1713,3 +1724,16 @@ class Sequence(bonsai.core.tool.Sequence):
|
|||||||
def get_animation_color_scheme(cls):
|
def get_animation_color_scheme(cls):
|
||||||
if len(bpy.context.scene.BIMAnimationProperties.saved_color_schemes) > 0:
|
if len(bpy.context.scene.BIMAnimationProperties.saved_color_schemes) > 0:
|
||||||
return tool.Ifc.get().by_id(int(bpy.context.scene.BIMAnimationProperties.saved_color_schemes))
|
return tool.Ifc.get().by_id(int(bpy.context.scene.BIMAnimationProperties.saved_color_schemes))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse_isodate_datetime(cls, datetime_str: str, include_time: bool) -> datetime:
|
||||||
|
if include_time:
|
||||||
|
return isodate.parse_datetime(datetime_str)
|
||||||
|
date = isodate.parse_date(datetime_str)
|
||||||
|
return datetime.combine(date, datetime_time())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def isodate_datetime(cls, datetime_: datetime, include_time: bool) -> str:
|
||||||
|
if include_time:
|
||||||
|
return isodate.datetime_isoformat(datetime_)
|
||||||
|
return isodate.date_isoformat(datetime_)
|
||||||
|
|||||||
Reference in New Issue
Block a user