ruby3mf 0.2.5 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +13 -13
- data/.rspec +2 -2
- data/bin/batch.rb +3 -1
- data/bin/cli.rb +12 -12
- data/bin/console +14 -14
- data/bin/folder_test.sh +13 -13
- data/bin/suite_test.sh +41 -41
- data/lib/ruby3mf.rb +26 -26
- data/lib/ruby3mf/3MFcoreSpec_1.1.xsd.template +188 -188
- data/lib/ruby3mf/content_types.rb +77 -73
- data/lib/ruby3mf/document.rb +242 -242
- data/lib/ruby3mf/edge_list.rb +60 -60
- data/lib/ruby3mf/errors.yml +188 -188
- data/lib/ruby3mf/log3mf.rb +135 -135
- data/lib/ruby3mf/mesh_analyzer.rb +80 -80
- data/lib/ruby3mf/mesh_normal_analyzer.rb +218 -218
- data/lib/ruby3mf/model3mf.rb +117 -118
- data/lib/ruby3mf/relationships.rb +50 -50
- data/lib/ruby3mf/schema_files.rb +26 -26
- data/lib/ruby3mf/texture3mf.rb +29 -29
- data/lib/ruby3mf/thumbnail3mf.rb +21 -21
- data/lib/ruby3mf/version.rb +3 -3
- data/lib/ruby3mf/xml.xsd +286 -286
- data/lib/ruby3mf/xml_val.rb +68 -74
- data/ruby3mf.gemspec +32 -32
- data/suite.011917.out +236 -236
- metadata +3 -9
- data/foo/2D/ffffa2c3-ba74-4bea-a4d0-167a4211134d.model +0 -18747
- data/foo/3D/3dmodel.model +0 -40
- data/foo/3D/_rels/3dmodel.model.rels +0 -4
- data/foo/Thumbnails/ffffa6c3-ba74-4bea-a4d0-167a4211134d.model +0 -0
- data/foo/[Content_Types].xml +0 -7
- data/foo/_rels/.rels +0 -5
data/lib/ruby3mf/model3mf.rb
CHANGED
@@ -1,118 +1,117 @@
|
|
1
|
-
class Model3mf
|
2
|
-
|
3
|
-
MATERIAL_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/material/2015/02'
|
4
|
-
SLICE_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07'
|
5
|
-
PRODUCTION_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
|
6
|
-
|
7
|
-
VALID_EXTENSIONS = {
|
8
|
-
MATERIAL_EXTENSION => {},
|
9
|
-
SLICE_EXTENSION => {},
|
10
|
-
PRODUCTION_EXTENSION => {}
|
11
|
-
}.freeze
|
12
|
-
|
13
|
-
VALID_CORE_METADATA_NAMES = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate'].freeze
|
14
|
-
|
15
|
-
def self.parse(document, zip_entry)
|
16
|
-
model_doc = nil
|
17
|
-
|
18
|
-
Log3mf.context "parsing model" do |l|
|
19
|
-
begin
|
20
|
-
model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
|
21
|
-
rescue Nokogiri::XML::SyntaxError => e
|
22
|
-
l.fatal_error :model_invalid_xml, e: e
|
23
|
-
end
|
24
|
-
|
25
|
-
l.context "verifying requiredextensions" do |l|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
namespace_uri
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
required_resources
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
relationships
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
missing_resources
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
missing_pids
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
meshes
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
1
|
+
class Model3mf
|
2
|
+
|
3
|
+
MATERIAL_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/material/2015/02'
|
4
|
+
SLICE_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07'
|
5
|
+
PRODUCTION_EXTENSION = 'http://schemas.microsoft.com/3dmanufacturing/production/2015/06'
|
6
|
+
|
7
|
+
VALID_EXTENSIONS = {
|
8
|
+
MATERIAL_EXTENSION => {},
|
9
|
+
SLICE_EXTENSION => {},
|
10
|
+
PRODUCTION_EXTENSION => {}
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
VALID_CORE_METADATA_NAMES = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate'].freeze
|
14
|
+
|
15
|
+
def self.parse(document, zip_entry)
|
16
|
+
model_doc = nil
|
17
|
+
|
18
|
+
Log3mf.context "parsing model" do |l|
|
19
|
+
begin
|
20
|
+
model_doc = XmlVal.validate_parse(zip_entry, SchemaFiles::SchemaTemplate)
|
21
|
+
rescue Nokogiri::XML::SyntaxError => e
|
22
|
+
l.fatal_error :model_invalid_xml, e: e
|
23
|
+
end
|
24
|
+
|
25
|
+
l.context "verifying requiredextensions" do |l|
|
26
|
+
model_doc.css("//model").map{|node| node.attributes["requiredextensions"]}.compact.each do |required_extension|
|
27
|
+
required_extension.value.split(" ").each do |ns|
|
28
|
+
namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
|
29
|
+
if namespace_uri
|
30
|
+
if VALID_EXTENSIONS.has_key? namespace_uri
|
31
|
+
l.info "Found a valid required extension: #{namespace_uri}"
|
32
|
+
else
|
33
|
+
l.error :unknown_required_extension, ext: namespace_uri
|
34
|
+
end
|
35
|
+
else
|
36
|
+
l.error :missing_extension_namespace_uri, ns: ns
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
l.context "verifying 3D payload required resources" do |l|
|
43
|
+
# results = model_doc.css("model resources m:texture2d")
|
44
|
+
required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
|
45
|
+
required_resources += model_doc.css("//model//resources//object[thumbnail]").collect { |n| n["thumbnail"] }
|
46
|
+
|
47
|
+
# for each, ensure that they exist in m.relationships
|
48
|
+
relationship_resources = []
|
49
|
+
rel_file = "#{Pathname(zip_entry.name).dirname.to_s}/_rels/#{File.basename(zip_entry.name)}.rels"
|
50
|
+
relationships = document.relationships[rel_file]
|
51
|
+
unless (relationships.nil?)
|
52
|
+
relationship_resources = relationships.map { |relationship| relationship[:target] }
|
53
|
+
end
|
54
|
+
|
55
|
+
missing_resources = (required_resources - relationship_resources)
|
56
|
+
if missing_resources.empty?
|
57
|
+
l.info "All model required resources are defined in .rels relationship files."
|
58
|
+
else
|
59
|
+
missing_resources.each { |mr|
|
60
|
+
l.error :model_resource_not_in_rels, mr: mr
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
l.context 'verifying resources' do |l|
|
67
|
+
resources = model_doc.root.css("resources")
|
68
|
+
if resources
|
69
|
+
ids = resources.children.map { |child| child.attributes['id'].to_s if child.attributes['id'] }
|
70
|
+
l.error :resource_id_collision if ids.uniq.size != ids.size
|
71
|
+
pids = resources.children.map { |child| child.attributes['pid'].to_s }
|
72
|
+
missing_pids = pids.select { |pid| !pid.empty? and !ids.include? pid }
|
73
|
+
missing_pids.each do |p|
|
74
|
+
l.error :resource_pid_missing, pid: p
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
l.context "verifying build items" do |l|
|
80
|
+
|
81
|
+
l.error :build_with_other_item if model_doc.css('build/item').map { |x| x.attributes["objectid"].value }.map{ |id| model_doc.search(".//xmlns:object[@id=$id][@type=$type]", nil, { :id => id, :type => 'other' } ) }.flatten.any?
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
l.context "checking metadata" do |l|
|
86
|
+
metadata_names = model_doc.root.css("metadata").map { |met| met['name'] }
|
87
|
+
l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
|
88
|
+
|
89
|
+
unless (metadata_names - VALID_CORE_METADATA_NAMES).empty?
|
90
|
+
extra_names = metadata_names - VALID_CORE_METADATA_NAMES
|
91
|
+
ns_names = extra_names.select { |n| n.include? ':' }
|
92
|
+
|
93
|
+
l.error :invalid_metadata_under_defaultns unless (extra_names - ns_names).empty?
|
94
|
+
|
95
|
+
unless ns_names.empty?
|
96
|
+
prefixes = model_doc.root.namespace_definitions.map { |defs| defs.prefix }.reject { |pre| pre.nil? }
|
97
|
+
l.error :invalid_metadata_name unless (ns_names.collect { |i| i.split(':').first } - prefixes).empty?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
includes_material = model_doc.namespaces.values.include?(MATERIAL_EXTENSION)
|
103
|
+
MeshAnalyzer.validate(model_doc, includes_material)
|
104
|
+
|
105
|
+
l.context "verifying triangle normal" do |l|
|
106
|
+
model_doc.css('model/resources/object').select { |object| ['model', 'solidsupport', ''].include?(object.attributes['type'].to_s) }.each do |object|
|
107
|
+
meshes = object.css('mesh')
|
108
|
+
meshes.each do |mesh|
|
109
|
+
processor = MeshNormalAnalyzer.new(mesh)
|
110
|
+
l.error :inward_facing_normal if processor.found_inward_triangle
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
model_doc
|
116
|
+
end
|
117
|
+
end
|
@@ -1,50 +1,50 @@
|
|
1
|
-
class Relationships
|
2
|
-
|
3
|
-
def self.parse(zip_entry)
|
4
|
-
relationships = []
|
5
|
-
Log3mf.context "parsing relationships" do |l|
|
6
|
-
begin
|
7
|
-
# Parse Relationships XML
|
8
|
-
doc = XmlVal.validate_parse(zip_entry)
|
9
|
-
|
10
|
-
# Verify <Relationships><Relationship/></Relationships>
|
11
|
-
root_element = doc.children[0]
|
12
|
-
if root_element.name == "Relationships"
|
13
|
-
relationship_elements = root_element.children
|
14
|
-
if relationship_elements.size > 0
|
15
|
-
relationship_elements.each do |node|
|
16
|
-
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
-
l.error :multiple_relationships if (relationships.select { |r| r[:target] == node['Target'] && r[:type] == node['Type'] }.size > 0)
|
18
|
-
l.error :non_unique_rel_id, :file => zip_entry.name, :id => node['Id'] if (relationships.select { |r| r[:id] == node['Id'] }.size > 0)
|
19
|
-
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
|
-
l.info "adding relationship: #{relationships.last.inspect}"
|
21
|
-
else
|
22
|
-
unless node.is_a? Nokogiri::XML::Text
|
23
|
-
l.info "found non-Relationship node: #{node.name}"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
if zip_entry.name=="_rels/.rels"
|
29
|
-
l.context "Verifying StartPart" do |l|
|
30
|
-
start_part_type = Document::MODEL_TYPE
|
31
|
-
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
|
-
if start_part_count != 1
|
33
|
-
l.error :invalid_startpart_type
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
else
|
38
|
-
l.error :dot_rels_file_no_relationship_element
|
39
|
-
end
|
40
|
-
else
|
41
|
-
l.error :dot_rels_file_missing_relationships_element
|
42
|
-
end
|
43
|
-
|
44
|
-
rescue Nokogiri::XML::SyntaxError => e
|
45
|
-
l.error :dot_rels_file_has_invalid_xml, e: "#{e.message}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
relationships
|
49
|
-
end
|
50
|
-
end
|
1
|
+
class Relationships
|
2
|
+
|
3
|
+
def self.parse(zip_entry)
|
4
|
+
relationships = []
|
5
|
+
Log3mf.context "parsing relationships" do |l|
|
6
|
+
begin
|
7
|
+
# Parse Relationships XML
|
8
|
+
doc = XmlVal.validate_parse(zip_entry)
|
9
|
+
|
10
|
+
# Verify <Relationships><Relationship/></Relationships>
|
11
|
+
root_element = doc.children[0]
|
12
|
+
if root_element.name == "Relationships"
|
13
|
+
relationship_elements = root_element.children
|
14
|
+
if relationship_elements.size > 0
|
15
|
+
relationship_elements.each do |node|
|
16
|
+
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
+
l.error :multiple_relationships if (relationships.select { |r| r[:target] == node['Target'] && r[:type] == node['Type'] }.size > 0)
|
18
|
+
l.error :non_unique_rel_id, :file => zip_entry.name, :id => node['Id'] if (relationships.select { |r| r[:id] == node['Id'] }.size > 0)
|
19
|
+
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
|
+
l.info "adding relationship: #{relationships.last.inspect}"
|
21
|
+
else
|
22
|
+
unless node.is_a? Nokogiri::XML::Text
|
23
|
+
l.info "found non-Relationship node: #{node.name}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if zip_entry.name=="_rels/.rels"
|
29
|
+
l.context "Verifying StartPart" do |l|
|
30
|
+
start_part_type = Document::MODEL_TYPE
|
31
|
+
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
|
+
if start_part_count != 1
|
33
|
+
l.error :invalid_startpart_type
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
else
|
38
|
+
l.error :dot_rels_file_no_relationship_element
|
39
|
+
end
|
40
|
+
else
|
41
|
+
l.error :dot_rels_file_missing_relationships_element
|
42
|
+
end
|
43
|
+
|
44
|
+
rescue Nokogiri::XML::SyntaxError => e
|
45
|
+
l.error :dot_rels_file_has_invalid_xml, e: "#{e.message}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
relationships
|
49
|
+
end
|
50
|
+
end
|
data/lib/ruby3mf/schema_files.rb
CHANGED
@@ -1,26 +1,26 @@
|
|
1
|
-
require 'erb'
|
2
|
-
|
3
|
-
class SchemaFiles
|
4
|
-
|
5
|
-
SchemaTemplate = File.join(File.dirname(__FILE__), "3MFcoreSpec_1.1.xsd.template")
|
6
|
-
SchemaLocation = File.join(File.dirname(__FILE__), "xml.xsd")
|
7
|
-
|
8
|
-
class << self
|
9
|
-
|
10
|
-
def open(file)
|
11
|
-
@@template ||= File.open(file, "r") do |file|
|
12
|
-
file.read
|
13
|
-
end
|
14
|
-
|
15
|
-
yield(SchemaFiles.render)
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
def render
|
20
|
-
@@xsd_content ||= ERB.new(@@template).result( binding )
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
|
26
|
-
end
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
class SchemaFiles
|
4
|
+
|
5
|
+
SchemaTemplate = File.join(File.dirname(__FILE__), "3MFcoreSpec_1.1.xsd.template")
|
6
|
+
SchemaLocation = File.join(File.dirname(__FILE__), "xml.xsd")
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def open(file)
|
11
|
+
@@template ||= File.open(file, "r") do |file|
|
12
|
+
file.read
|
13
|
+
end
|
14
|
+
|
15
|
+
yield(SchemaFiles.render)
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
def render
|
20
|
+
@@xsd_content ||= ERB.new(@@template).result( binding )
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
data/lib/ruby3mf/texture3mf.rb
CHANGED
@@ -1,29 +1,29 @@
|
|
1
|
-
class Texture3mf
|
2
|
-
attr_accessor :name
|
3
|
-
|
4
|
-
def initialize(document)
|
5
|
-
@doc = document
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.parse(document, relationship_file)
|
9
|
-
t = new(document)
|
10
|
-
t.name = relationship_file.name
|
11
|
-
stream = relationship_file.get_input_stream
|
12
|
-
img_type = MimeMagic.by_magic(stream)
|
13
|
-
Log3mf.context "Texture3mf" do |l|
|
14
|
-
l.fatal_error :zero_size_texture unless img_type
|
15
|
-
l.debug "texture is of type: #{img_type}"
|
16
|
-
l.error(:invalid_texture_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
17
|
-
end
|
18
|
-
t
|
19
|
-
end
|
20
|
-
|
21
|
-
def update(bytes)
|
22
|
-
@doc.objects[name]=bytes
|
23
|
-
end
|
24
|
-
|
25
|
-
def contents
|
26
|
-
@doc.objects[name] || @doc.contents_for(name)
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
1
|
+
class Texture3mf
|
2
|
+
attr_accessor :name
|
3
|
+
|
4
|
+
def initialize(document)
|
5
|
+
@doc = document
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.parse(document, relationship_file)
|
9
|
+
t = new(document)
|
10
|
+
t.name = relationship_file.name
|
11
|
+
stream = relationship_file.get_input_stream
|
12
|
+
img_type = MimeMagic.by_magic(stream)
|
13
|
+
Log3mf.context "Texture3mf" do |l|
|
14
|
+
l.fatal_error :zero_size_texture unless img_type
|
15
|
+
l.debug "texture is of type: #{img_type}"
|
16
|
+
l.error(:invalid_texture_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
|
17
|
+
end
|
18
|
+
t
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(bytes)
|
22
|
+
@doc.objects[name]=bytes
|
23
|
+
end
|
24
|
+
|
25
|
+
def contents
|
26
|
+
@doc.objects[name] || @doc.contents_for(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|