class Model3mf

Constants

KNOWN_EXTENSIONS
MATERIAL_EXTENSION
PRODUCTION_EXTENSION
SLICE_EXTENSION
VALID_CORE_METADATA_NAMES

Public Class Methods

find_model_object(model_doc, id, type) click to toggle source
# File lib/ruby3mf/model3mf.rb, line 124
def self.find_model_object(model_doc, id, type)
  model_doc.css('model/resources/object').select do |item|
    item.attributes['id'].to_s == id && item.attributes['type'] && item.attributes['type'].value == type
  end
end
parse(document, zip_entry) click to toggle source
# File lib/ruby3mf/model3mf.rb, line 15
def self.parse(document, zip_entry)
  model_doc = nil

  Log3mf.context "parsing model" do |l|
    begin
      model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
    rescue Nokogiri::XML::SyntaxError => e
      l.fatal_error :model_invalid_xml, e: e
    end

    l.context "verifying supported extensions" do |l|
      model_doc.css("//model").first.namespaces.each do |prefix, uri|
        unless prefix == "xmlns"
          ext = KNOWN_EXTENSIONS[uri]
          if ext.nil? || !ext[:supported]
            l.warning :unsupported_extension, ext: (ext.nil? ? uri : ext[:name])
          end
        end
      end
    end

    l.context "verifying requiredextensions" do |l|
      model_doc.css("//model").map { |node| node.attributes["requiredextensions"] }.compact.each do |required_extension|
        required_extension.value.split(" ").each do |ns|
          namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
          l.error :missing_extension_namespace_uri, ns: ns unless namespace_uri
        end
      end
    end

    l.context "verifying 3D payload required resources" do |l|
      # results = model_doc.css("model resources m:texture2d")
      required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
      required_resources += model_doc.css("//model//resources//object[thumbnail]").collect { |n| n["thumbnail"] }

      # for each, ensure that they exist in m.relationships
      relationship_resources = []
      rel_file = "#{Pathname(zip_entry.name).dirname.to_s}/_rels/#{File.basename(zip_entry.name)}.rels"
      relationships = document.relationships[rel_file]
      unless (relationships.nil?)
        relationship_resources = relationships.map { |relationship| relationship[:target] }
      end

      missing_resources = (required_resources - relationship_resources)
      if missing_resources.empty?
        l.info "All model required resources are defined in .rels relationship files."
      else
        missing_resources.each { |mr|
          l.error :model_resource_not_in_rels, mr: mr
        }
      end

    end

    l.context 'verifying resources' do |l|
      resources = model_doc.root.css("resources")
      if resources
        ids = resources.children.map { |child| child.attributes['id'].to_s if child.attributes['id'] }
        l.error :resource_id_collision if ids.uniq.size != ids.size
        pids = resources.children.map { |child| child.attributes['pid'].to_s }
        missing_pids = pids.select { |pid| !pid.empty? and !ids.include? pid }
        missing_pids.each do |p|
          l.error :resource_pid_missing, pid: p
        end
      end
    end

    l.context "verifying build items" do |l|

      build_items = model_doc.css('build/item')
      object_ids = build_items.map { |x| x.attributes["objectid"].value }
      other_build_items = object_ids.map { |id| find_model_object(model_doc, id, 'other') }.flatten

      l.error :build_with_other_item if other_build_items.any?
    end

    l.context "checking metadata" do |l|
      metadata_names = model_doc.root.css("metadata").map { |met| met['name'] }
      l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?

      unless (metadata_names - VALID_CORE_METADATA_NAMES).empty?
        extra_names = metadata_names - VALID_CORE_METADATA_NAMES
        ns_names = extra_names.select { |n| n.include? ':' }

        l.error :invalid_metadata_under_defaultns unless (extra_names - ns_names).empty?

        unless ns_names.empty?
          prefixes = model_doc.root.namespace_definitions.map { |defs| defs.prefix }.reject { |pre| pre.nil? }
          l.error :invalid_metadata_name unless (ns_names.collect { |i| i.split(':').first } - prefixes).empty?
        end
      end
    end

    includes_material = model_doc.namespaces.values.include?(MATERIAL_EXTENSION)
    MeshAnalyzer.validate(model_doc, includes_material)

    l.context "verifying triangle normal" do |l|
      model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
        meshes = object.css('mesh')
        meshes.each do |mesh|
          processor = MeshNormalAnalyzer.new(mesh)
          l.error :inward_facing_normal if processor.found_inward_triangle
        end
      end
    end
  end
  model_doc
end