modules/bim/lib/open_project/bim/bcf_json/viewpoint_reader.rb
# frozen_string_literal: true
require "bigdecimal"
module OpenProject::Bim
module BcfJson
class ViewpointReader
ROOT_NODE ||= "VisualizationInfo"
attr_reader :uuid, :xml
def initialize(uuid, xml)
@uuid = uuid
@xml = xml
end
def result
viewpoint_hash
end
def to_json(*_args)
viewpoint_hash.to_json
end
private
##
# Retrieve the viewpoint hash without root node, if any.
def viewpoint_hash
@viewpoint_hash ||= begin
hash = FasterConverter.xml_to_hash(xml)
hash = hash[ROOT_NODE] if hash[ROOT_NODE]
# Perform destructive transformations
transformations.each do |method_name|
send(method_name, hash)
end
hash
end
end
def transformations
%i[
remove_keys
transform_keys
set_uuid
transform_perspective_camera
transform_orthogonal_camera
transform_lines
transform_clipping_planes
transform_bitmaps
transform_selections
transform_coloring
transform_visibility
]
end
def remove_keys(hash)
hash.delete "xmlns:xsi"
hash.delete "xmlns:xsd"
hash.delete "VisualizationInfo" unless hash["VisualizationInfo"]
end
def set_uuid(hash)
hash["guid"] = uuid
end
def transform_keys(hash)
# Underscore all keys, will make it easier to reuse more portions of the XML
hash.deep_transform_keys!(&:underscore)
end
##
# Transform perspective_camera into json float values
def transform_orthogonal_camera(hash)
transform_camera hash, "orthogonal_camera"
end
##
# Transform orthogonal_camera into json float values
def transform_perspective_camera(hash)
transform_camera hash, "perspective_camera"
end
def transform_camera(hash, key)
return unless hash[key]
hash[key].transform_values! do |v|
if v.is_a?(Hash)
v.transform_values! { |val| to_numeric(val) }
else
to_numeric(v)
end
end
end
def transform_lines(hash)
return unless hash["lines"]
hash["lines"] = [hash["lines"]["line"]].flatten(1).map! do |line|
line.deep_transform_values! { |val| to_numeric(val) }
end
end
def transform_clipping_planes(hash)
return unless hash["clipping_planes"]
hash["clipping_planes"] = [hash["clipping_planes"]["clipping_plane"]].flatten(1).map! do |plane|
plane.deep_transform_values! { |val| to_numeric(val) }
end
end
def transform_bitmaps(hash)
return unless hash["bitmaps"]
# Bitmaps can be multiple items within the root bitmaps node
# this is different from the other entries
# https://github.com/buildingSMART/BCF-XML/pull/44/files
bitmaps = Array.wrap(hash["bitmaps"])
hash["bitmaps"] = bitmaps.map! do |bitmap|
bitmap["bitmap_type"] = bitmap.delete("bitmap").downcase
bitmap["bitmap_data"] = bitmap.delete("reference")
bitmap["height"] = to_numeric(bitmap["height"])
%w[location normal up].each do |key|
next unless bitmap.key?(key)
# Transform all coordinates to floats
bitmap[key].transform_values! { |val| to_numeric(val) }
end
bitmap
end
end
##
# Move selections up the tree from the nested XML node
def transform_selections(hash)
return unless (selections = hash.dig("components", "selection", "component"))
# Ensure selections are an array
selections = Array.wrap(selections)
# Skip any components that have no guid
selections.select! { |item| item["ifc_guid"] }
hash["components"]["selection"] = selections
end
##
# Move coloring up the tree from the nested XML node
def transform_coloring(hash)
return unless (colors = hash.dig("components", "coloring", "color"))
# avoid Array(colors) since that deconstructs the array
colors = Array.wrap(colors)
hash["components"]["coloring"] = colors.map do |entry|
# Prepend hash for hex color
entry["color"] = "##{entry['color']}"
# Fix items name
entry["components"] = Array.wrap(entry.delete("component"))
entry
end
end
def transform_visibility(hash)
return unless (visibility = hash.dig("components", "visibility"))
visibility["default_visibility"] = visibility["default_visibility"] == "true"
# Hoist exceptions components up from the nested XML node
exceptions = visibility.dig("exceptions", "component")
visibility["exceptions"] = Array.wrap(exceptions) if exceptions
# Move view_setup_hints
view_setup_hints = hash.dig("components", "view_setup_hints")
visibility["view_setup_hints"] = view_setup_hints.transform_values { |val| val == "true" } if view_setup_hints
# Remove the old node
hash["components"].delete("view_setup_hints")
end
def to_numeric(anything)
num = BigDecimal(anything.to_s)
if num.frac == 0
num.to_i
else
num.to_f
end
end
end
end
end