ruby3mf 0.1.11 → 0.1.13
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/bin/cli.rb +7 -0
- data/lib/ruby3mf.rb +3 -2
- data/lib/ruby3mf/content_types.rb +1 -3
- data/lib/ruby3mf/document.rb +33 -2
- data/lib/ruby3mf/edge_list.rb +61 -0
- data/lib/ruby3mf/errors.yml +96 -0
- data/lib/ruby3mf/global_xml_validations.rb +36 -0
- data/lib/ruby3mf/log3mf.rb +14 -3
- data/lib/ruby3mf/mesh_analyzer.rb +78 -0
- data/lib/ruby3mf/model3mf.rb +15 -7
- data/lib/ruby3mf/relationships.rb +7 -5
- data/lib/ruby3mf/texture3mf.rb +0 -1
- data/lib/ruby3mf/thumbnail3mf.rb +6 -0
- data/lib/ruby3mf/version.rb +1 -1
- data/ruby3mf.gemspec +1 -8
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53cf0043b4bcebef905de98f2137ad6db1b36fc4
|
4
|
+
data.tar.gz: 3a970b15e198109bf6638f7206569e0b90bc51fc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 91a20f9e6da5bf9f89b582509764e0ef3cf0a320c798c0020fd5fc4fd6e9e1a931193005549c0777e30b19292fcb33b7b044fee6ff70ba6ec8b36fcd0411e7e7
|
7
|
+
data.tar.gz: a2203b11cb4f4ecdd9ec342ae73ae5fa2b59e46d58474671ff2123e7dd88c3438c85d7cfe54bfb3c59ac024f501dfd8ed6a818a43647dc02d9bb3866ca137095
|
data/bin/cli.rb
CHANGED
@@ -3,3 +3,10 @@
|
|
3
3
|
require_relative '../lib/ruby3mf'
|
4
4
|
|
5
5
|
doc = Document.read(ARGV.first)
|
6
|
+
|
7
|
+
errors = Log3mf.entries(:error, :fatal_error)
|
8
|
+
puts "Validating file: #{ARGV.first}"
|
9
|
+
errors.each do |ent|
|
10
|
+
h = { context: ent[0], severity: ent[1], message: ent[2] }
|
11
|
+
puts h
|
12
|
+
end
|
data/lib/ruby3mf.rb
CHANGED
@@ -6,14 +6,15 @@ require_relative "ruby3mf/model3mf"
|
|
6
6
|
require_relative "ruby3mf/relationships"
|
7
7
|
require_relative "ruby3mf/thumbnail3mf"
|
8
8
|
require_relative "ruby3mf/texture3mf"
|
9
|
+
require_relative "ruby3mf/global_xml_validations"
|
9
10
|
|
10
11
|
require 'zip'
|
11
12
|
require 'nokogiri'
|
12
13
|
require 'json'
|
13
14
|
require 'mimemagic'
|
14
|
-
|
15
|
+
require 'uri'
|
16
|
+
require 'yaml'
|
15
17
|
|
16
18
|
module Ruby3mf
|
17
|
-
|
18
19
|
# Your code goes here...
|
19
20
|
end
|
@@ -6,9 +6,7 @@ class ContentTypes
|
|
6
6
|
Log3mf.context "parse" do |l|
|
7
7
|
begin
|
8
8
|
|
9
|
-
doc =
|
10
|
-
config.strict.nonet.noblanks
|
11
|
-
end
|
9
|
+
doc = GlobalXMLValidations.validate_parse(zip_entry)
|
12
10
|
|
13
11
|
l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
|
14
12
|
l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
|
data/lib/ruby3mf/document.rb
CHANGED
@@ -28,7 +28,6 @@ class Document
|
|
28
28
|
def self.read(input_file)
|
29
29
|
|
30
30
|
m = new(input_file)
|
31
|
-
|
32
31
|
begin
|
33
32
|
Log3mf.context "zip" do |l|
|
34
33
|
begin
|
@@ -43,6 +42,25 @@ class Document
|
|
43
42
|
|
44
43
|
l.info "Zip file is valid"
|
45
44
|
|
45
|
+
# check for valid, absolute URI's for each path name
|
46
|
+
|
47
|
+
zip_file.each do |part|
|
48
|
+
l.context "part names /#{part.name}" do |l|
|
49
|
+
unless part.name.end_with? '[Content_Types].xml'
|
50
|
+
begin
|
51
|
+
u = URI part.name
|
52
|
+
rescue ArgumentError
|
53
|
+
l.error :err_uri_bad
|
54
|
+
next
|
55
|
+
end
|
56
|
+
|
57
|
+
u.path.split('/').each do |segment|
|
58
|
+
l.error :err_uri_hidden_file if (segment.start_with? '.') && !(segment.end_with? '.rels')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
46
64
|
l.context "content types" do |l|
|
47
65
|
# 1. Get Content Types
|
48
66
|
content_type_match = zip_file.glob('\[Content_Types\].xml').first
|
@@ -71,7 +89,19 @@ class Document
|
|
71
89
|
# 3. Validate all relationships
|
72
90
|
m.relationships.each do |rel|
|
73
91
|
l.context rel[:target] do |l|
|
92
|
+
|
93
|
+
begin
|
94
|
+
u = URI rel[:target]
|
95
|
+
rescue URI::InvalidURIError
|
96
|
+
l.error :err_uri_bad
|
97
|
+
next
|
98
|
+
end
|
99
|
+
|
100
|
+
l.error :err_uri_relative_path unless u.to_s.start_with? '/'
|
101
|
+
|
74
102
|
target = rel[:target].gsub(/^\//, "")
|
103
|
+
l.error :err_uri_empty_segment if target.end_with? '/' or target.include? '//'
|
104
|
+
l.error :err_uri_relative_path if target.include? '/../'
|
75
105
|
relationship_file = zip_file.glob(target).first
|
76
106
|
|
77
107
|
if relationship_file
|
@@ -92,6 +122,7 @@ class Document
|
|
92
122
|
end
|
93
123
|
end
|
94
124
|
end
|
125
|
+
|
95
126
|
return m
|
96
127
|
rescue Zip::Error
|
97
128
|
l.fatal_error 'File provided is not a valid ZIP archive', page: 9
|
@@ -123,7 +154,7 @@ class Document
|
|
123
154
|
end
|
124
155
|
end
|
125
156
|
|
126
|
-
File.open(output_file, "wb") {|f| f.write(buffer.string) }
|
157
|
+
File.open(output_file, "wb") { |f| f.write(buffer.string) }
|
127
158
|
|
128
159
|
end
|
129
160
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class EdgeList
|
2
|
+
|
3
|
+
def initialize()
|
4
|
+
@edges = { }
|
5
|
+
end
|
6
|
+
|
7
|
+
def add_edge(first_vertex, second_vertex)
|
8
|
+
if first_vertex < second_vertex
|
9
|
+
edge = "#{first_vertex}:#{second_vertex}"
|
10
|
+
else
|
11
|
+
edge = "#{second_vertex}:#{first_vertex}"
|
12
|
+
end
|
13
|
+
|
14
|
+
(pos_count, neg_count) = @edges[edge]
|
15
|
+
|
16
|
+
if pos_count == nil or neg_count == nil
|
17
|
+
pos_count = 0
|
18
|
+
neg_count = 0
|
19
|
+
end
|
20
|
+
|
21
|
+
pos_count += 1 if first_vertex < second_vertex
|
22
|
+
neg_count += 1 if second_vertex < first_vertex
|
23
|
+
|
24
|
+
@edges[edge] = [pos_count, neg_count]
|
25
|
+
end
|
26
|
+
|
27
|
+
def edge_count(first_vertex, second_vertex)
|
28
|
+
if first_vertex < second_vertex
|
29
|
+
edge = "#{first_vertex}:#{second_vertex}"
|
30
|
+
else
|
31
|
+
edge = "#{second_vertex}:#{first_vertex}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@edges[edge]
|
35
|
+
end
|
36
|
+
|
37
|
+
def print_list()
|
38
|
+
@edges.each do |key, value|
|
39
|
+
(pos, neg) = value
|
40
|
+
puts "#{pos} : #{neg}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_edges()
|
45
|
+
@edges.each do |key, value|
|
46
|
+
(pos, neg) = value
|
47
|
+
|
48
|
+
if (pos > 1 and neg == 0) or (pos == 0 and neg > 1)
|
49
|
+
return :bad_orientation
|
50
|
+
elsif pos + neg == 1
|
51
|
+
return :hole
|
52
|
+
elsif pos != 1 or neg != 1
|
53
|
+
return :nonmanifold
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
:ok
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,96 @@
|
|
1
|
+
resource_contentype_invalid:
|
2
|
+
msg: "resource in model has invalid contenttype %{bt}"
|
3
|
+
page: 10
|
4
|
+
err_uri_empty_segment:
|
5
|
+
msg: 'No segment of a 3MF part name path may be empty'
|
6
|
+
page: 13
|
7
|
+
err_uri_hidden_file:
|
8
|
+
msg: "Other than /_rels/.rels, no segment of a 3MF part name may start with the '.' character"
|
9
|
+
page: 13
|
10
|
+
err_uri_bad:
|
11
|
+
msg: 'Path names must be valid Open Package Convention URIs or IRIs'
|
12
|
+
page: 13
|
13
|
+
err_uri_relative_path:
|
14
|
+
msg: 'Part names must not include relative paths'
|
15
|
+
page: 13
|
16
|
+
model_resource_not_in_rels:
|
17
|
+
msg: "Missing required resource: %{mr} Resource referenced in model, but not in .rels relationship file"
|
18
|
+
page: 10
|
19
|
+
resource_3dmodel_orientation:
|
20
|
+
msg: "Bad triangle orientation"
|
21
|
+
page: 27
|
22
|
+
resource_3dmodel_hole:
|
23
|
+
msg: "Hole in model"
|
24
|
+
page: 27
|
25
|
+
resource_3dmodel_nonmanifold:
|
26
|
+
msg: "Non-manifold edge in 3dmodel"
|
27
|
+
page: 27
|
28
|
+
3d_model_invalid_xml:
|
29
|
+
msg: "Model file invalid XML. Exception Start tag expected, '<' not found"
|
30
|
+
page:
|
31
|
+
3d_payload_files:
|
32
|
+
msg: "Relationship Target file /3D/Texture/texture2.texture not found"
|
33
|
+
page: 11
|
34
|
+
content_types_invalid_xml:
|
35
|
+
msg: "[Content_Types].xml file is not valid XML. Start tag expected, '<' not found"
|
36
|
+
page: 15
|
37
|
+
dot_rels_file_has_invalid_xml:
|
38
|
+
msg: "Relationships (.rel) file is not a valid XML file: Start tag expected, '<' not found"
|
39
|
+
page: 4
|
40
|
+
dot_rels_file_missing_relationships_element:
|
41
|
+
msg: ".rels XML must have <Relationships> root element"
|
42
|
+
page: 4
|
43
|
+
dot_rels_file_no_relationship_element:
|
44
|
+
msg: "No relationship elements found"
|
45
|
+
page: 4
|
46
|
+
invalid_content_type:
|
47
|
+
msg: "[Content_Types].xml is missing required ContentType \"application/vnd.openxmlformats-package.relationships+xml\""
|
48
|
+
page: 10
|
49
|
+
invalid_startpart_type:
|
50
|
+
msg: "rels/.rels Relationship file has an invalid attribute type for the root 3D Model (StartPart). The required type is \"http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel\""
|
51
|
+
page: 10
|
52
|
+
invalid_thumbnail_colorspace:
|
53
|
+
msg: "CMYK JPEG images must not be used for the thumbnail"
|
54
|
+
page: 36
|
55
|
+
invalid_thumbnail_file_type:
|
56
|
+
msg: "Expected a png or jpeg thumbnail but the thumbnail was of type image/gif"
|
57
|
+
page: 36
|
58
|
+
invalid_texture_file_type:
|
59
|
+
msg: "Expected a png or jpeg texture but the texture was of type image/gif"
|
60
|
+
page: 16
|
61
|
+
missing_content_types:
|
62
|
+
msg: "Missing required file: [Content_Types].xml"
|
63
|
+
page: 4
|
64
|
+
missing_dot_rels_file:
|
65
|
+
msg: "Missing required file _rels/.rels"
|
66
|
+
page: 4
|
67
|
+
missing_rels_folder:
|
68
|
+
msg: "Missing required file _rels/.rels"
|
69
|
+
page: 4
|
70
|
+
multiple_relationships:
|
71
|
+
msg: "There MUST NOT be more than one relationship of a given relationship type from one part to a second part"
|
72
|
+
page: 10
|
73
|
+
no_3d_model:
|
74
|
+
msg: "Relationship Target file /3D/3dmodel.model not found"
|
75
|
+
page: 11
|
76
|
+
not_a_zip:
|
77
|
+
msg: "File provided is not a valid ZIP archive"
|
78
|
+
page: 9
|
79
|
+
zero_size_texture:
|
80
|
+
msg: "Texture file must be valid image file"
|
81
|
+
page: 16
|
82
|
+
resource_contentype_invalid:
|
83
|
+
msg: "resource in model has invalid contenttype image/ping"
|
84
|
+
page: 10
|
85
|
+
has_xml_space_attribute:
|
86
|
+
msg: "found an xml:space attribute when it is not allowed"
|
87
|
+
page: 16
|
88
|
+
wrong_encoding:
|
89
|
+
msg: "found XML content that was not UTF8 encoded"
|
90
|
+
page: 15
|
91
|
+
missing_object_pid:
|
92
|
+
msg: "object with triangle color override missing object level pid"
|
93
|
+
page: 26
|
94
|
+
missing_model_children:
|
95
|
+
msg: "model element must include resources and build as child elements"
|
96
|
+
page: 20
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class GlobalXMLValidations
|
2
|
+
|
3
|
+
def self.validate_parse(file)
|
4
|
+
doc = Nokogiri::XML(file.get_input_stream) do |config|
|
5
|
+
config.strict.nonet.noblanks
|
6
|
+
end
|
7
|
+
validate(file, doc)
|
8
|
+
doc
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.validate(file, document)
|
12
|
+
Log3mf.context "global xml validations" do |l|
|
13
|
+
l.error :has_xml_space_attribute if space_attribute_exists?(document)
|
14
|
+
l.error :wrong_encoding if xml_not_utf8_encoded?(document)
|
15
|
+
l.error :dtd_not_allowed if dtd_exists?(file)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.space_attribute_exists?(document)
|
20
|
+
!(document.xpath('//*[@xml:space]').empty?)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.xml_not_utf8_encoded?(document)
|
24
|
+
!(document.encoding == 'UTF-8' || document.encoding == 'utf-8')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.dtd_exists?(file)
|
28
|
+
found = file.get_input_stream.find { |line| line =~ /(!DOCTYPE\b)|(!ELEMENT\b)|(!ENTITY\b)|(!NOTATION\b)|(!ATTLIST\b)/ }
|
29
|
+
!found.nil?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'singleton'
|
2
|
+
require 'yaml'
|
2
3
|
|
3
4
|
# Example usage:
|
4
5
|
|
@@ -33,6 +34,8 @@ class Log3mf
|
|
33
34
|
@log_list = []
|
34
35
|
@context_stack = []
|
35
36
|
@ledger = []
|
37
|
+
errormap_path = File.join(File.dirname(__FILE__),"errors.yml")
|
38
|
+
@errormap = YAML.load_file(errormap_path)
|
36
39
|
end
|
37
40
|
|
38
41
|
def reset_log
|
@@ -68,11 +71,20 @@ class Log3mf
|
|
68
71
|
end
|
69
72
|
|
70
73
|
def log(severity, message, options={})
|
71
|
-
|
72
|
-
|
74
|
+
if message.is_a?(Symbol)
|
75
|
+
new_log(severity, message, options)
|
76
|
+
else
|
77
|
+
@log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
78
|
+
# puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
|
79
|
+
end
|
73
80
|
raise FatalError if severity == :fatal_error
|
74
81
|
end
|
75
82
|
|
83
|
+
def new_log(severity, message, options={})
|
84
|
+
error_info = @errormap.fetch(message.to_s)
|
85
|
+
@log_list << ["#{@context_stack.join("/")}", severity, error_info["msg"], page: error_info["page"]] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
86
|
+
end
|
87
|
+
|
76
88
|
def count_entries(*levels)
|
77
89
|
entries(*levels).count
|
78
90
|
end
|
@@ -120,4 +132,3 @@ class Log3mf
|
|
120
132
|
Log3mf.instance.to_json
|
121
133
|
end
|
122
134
|
end
|
123
|
-
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'edge_list'
|
2
|
+
|
3
|
+
|
4
|
+
def find_child(node, child_name)
|
5
|
+
node.children.each do |child|
|
6
|
+
if child.name == child_name
|
7
|
+
return child
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
class MeshAnalyzer
|
16
|
+
|
17
|
+
def self.validate_object(object)
|
18
|
+
Log3mf.context "validating geometry" do |l|
|
19
|
+
list = EdgeList.new
|
20
|
+
|
21
|
+
# if a triangle has a pid, then the object needs a pid
|
22
|
+
has_triangle_pid = false
|
23
|
+
|
24
|
+
mesh = find_child(object, "mesh")
|
25
|
+
if mesh
|
26
|
+
triangles = find_child(mesh, "triangles")
|
27
|
+
|
28
|
+
if triangles
|
29
|
+
triangles.children.each do |triangle|
|
30
|
+
v1 = triangle.attributes["v1"].to_s().to_i()
|
31
|
+
v2 = triangle.attributes["v2"].to_s().to_i()
|
32
|
+
v3 = triangle.attributes["v3"].to_s().to_i()
|
33
|
+
|
34
|
+
list.add_edge(v1, v2)
|
35
|
+
list.add_edge(v2, v3)
|
36
|
+
list.add_edge(v3, v1)
|
37
|
+
|
38
|
+
if not has_triangle_pid
|
39
|
+
has_triangle_pid = triangle.attributes["pid"] != nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
has_object_material = object.attributes["pid"] and object.attributes["pindex"]
|
44
|
+
if has_triangle_pid and not has_object_material
|
45
|
+
l.error :missing_object_pid
|
46
|
+
end
|
47
|
+
|
48
|
+
result = list.verify_edges()
|
49
|
+
if result == :bad_orientation
|
50
|
+
l.fatal_error :resource_3dmodel_orientation
|
51
|
+
elsif result == :hole
|
52
|
+
l.fatal_error :resource_3dmodel_hole
|
53
|
+
elsif result == :nonmanifold
|
54
|
+
l.fatal_error :resource_3dmodel_nonmanifold
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.validate(model_doc)
|
62
|
+
root = model_doc.root
|
63
|
+
node = root
|
64
|
+
|
65
|
+
if node.name == "model"
|
66
|
+
resources = find_child(node, "resources")
|
67
|
+
|
68
|
+
if resources
|
69
|
+
resources.children.each do |resource|
|
70
|
+
solid_model = resource.attributes["type"].to_s() == "model" or resource.attributes["type"].to_s() == "solidsupport"
|
71
|
+
if resource.name == "object" and solid_model
|
72
|
+
validate_object(resource)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/ruby3mf/model3mf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require_relative 'mesh_analyzer'
|
2
|
+
|
1
3
|
class Model3mf
|
2
4
|
|
3
5
|
def self.parse(document, zip_entry)
|
@@ -5,10 +7,7 @@ class Model3mf
|
|
5
7
|
|
6
8
|
Log3mf.context "parsing model" do |l|
|
7
9
|
begin
|
8
|
-
model_doc =
|
9
|
-
config.strict.nonet.noblanks
|
10
|
-
end
|
11
|
-
l.info "We Found a Model, and it's XML!"
|
10
|
+
model_doc = GlobalXMLValidations.validate_parse(zip_entry)
|
12
11
|
rescue Nokogiri::XML::SyntaxError => e
|
13
12
|
l.fatal_error "Model file invalid XML. Exception #{e}"
|
14
13
|
end
|
@@ -25,7 +24,7 @@ class Model3mf
|
|
25
24
|
l.info "All model required resources are defined in .rels relationship files."
|
26
25
|
else
|
27
26
|
missing_resources.each { |mr|
|
28
|
-
l.error
|
27
|
+
l.error :model_resource_not_in_rels, mr: mr
|
29
28
|
}
|
30
29
|
end
|
31
30
|
|
@@ -40,13 +39,22 @@ class Model3mf
|
|
40
39
|
l.info "All model resource contenttypes are valid"
|
41
40
|
else
|
42
41
|
bad_types.each { |bt|
|
43
|
-
|
44
|
-
l.error "resource in model has invalid contenttype #{bt}", page: 10
|
42
|
+
l.error :resource_contentype_invalid, bt: bt
|
45
43
|
}
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
47
|
end
|
48
|
+
|
49
|
+
l.context "verifying model structure" do |l|
|
50
|
+
root = model_doc.root
|
51
|
+
l.error :root_3dmodel_element_not_model if root.name != "model"
|
52
|
+
|
53
|
+
children = model_doc.root.children.map { |child| child.name }
|
54
|
+
l.error :missing_model_children unless children.include?("resources") && children.include?("build")
|
55
|
+
end
|
56
|
+
|
57
|
+
MeshAnalyzer.validate(model_doc)
|
50
58
|
end
|
51
59
|
model_doc
|
52
60
|
end
|
@@ -5,9 +5,7 @@ class Relationships
|
|
5
5
|
Log3mf.context "parsing relationships" do |l|
|
6
6
|
begin
|
7
7
|
# Parse Relationships XML
|
8
|
-
doc =
|
9
|
-
config.strict.nonet.noblanks
|
10
|
-
end
|
8
|
+
doc = GlobalXMLValidations.validate_parse(zip_entry)
|
11
9
|
|
12
10
|
# Verify <Relationships><Relationship/></Relationships>
|
13
11
|
root_element = doc.children[0]
|
@@ -16,6 +14,11 @@ class Relationships
|
|
16
14
|
if relationship_elements.size > 0
|
17
15
|
relationship_elements.each do |node|
|
18
16
|
if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
|
17
|
+
relationships.each do |previous_rel|
|
18
|
+
if previous_rel[:target] == node['Target'] && previous_rel[:type] == node['Type']
|
19
|
+
l.error :multiple_relationships
|
20
|
+
end
|
21
|
+
end
|
19
22
|
relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
|
20
23
|
l.info "adding relationship: #{relationships.last.inspect}"
|
21
24
|
else
|
@@ -30,8 +33,7 @@ class Relationships
|
|
30
33
|
start_part_type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
|
31
34
|
start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
|
32
35
|
if start_part_count != 1
|
33
|
-
l.error
|
34
|
-
The correct attribute type should be \"#{start_part_type}\"", page: 10
|
36
|
+
l.error :invalid_startpart_type
|
35
37
|
end
|
36
38
|
end
|
37
39
|
end
|
data/lib/ruby3mf/texture3mf.rb
CHANGED
data/lib/ruby3mf/thumbnail3mf.rb
CHANGED
@@ -1,12 +1,18 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
|
1
3
|
class Thumbnail3mf
|
2
4
|
|
3
5
|
def self.parse(doc, relationship_file)
|
4
6
|
img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
|
7
|
+
img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
|
8
|
+
|
5
9
|
Log3mf.context "Thumbnail3mf" do |l|
|
6
10
|
l.fatal_error "thumbnail file must be valid image file", page: 10 unless img_type
|
11
|
+
l.fatal_error "CMYK JPEG images must not be used for the thumbnail", page: 36 if img_colorspace.include? "CMYK"
|
7
12
|
l.debug "thumbnail is of type: #{img_type}"
|
8
13
|
l.error "Expected a png or jpeg thumbnail but the thumbnail was of type #{img_type}", page: 12 unless ['image/png', 'image/jpeg'].include? img_type.type
|
9
14
|
end
|
10
15
|
|
11
16
|
end
|
17
|
+
|
12
18
|
end
|
data/lib/ruby3mf/version.rb
CHANGED
data/ruby3mf.gemspec
CHANGED
@@ -14,14 +14,6 @@ Gem::Specification.new do |spec|
|
|
14
14
|
spec.homepage = "https://github.com/IPGPTP/ruby3mf"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
|
-
# Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
|
18
|
-
# delete this section to allow pushing this gem to any host.
|
19
|
-
# if spec.respond_to?(:metadata)
|
20
|
-
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
-
# else
|
22
|
-
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
23
|
-
# end
|
24
|
-
|
25
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
18
|
spec.bindir = "exe"
|
27
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -35,4 +27,5 @@ Gem::Specification.new do |spec|
|
|
35
27
|
spec.add_runtime_dependency 'rubyzip'
|
36
28
|
spec.add_runtime_dependency 'nokogiri', '~>1.6.8'
|
37
29
|
spec.add_runtime_dependency 'mimemagic'
|
30
|
+
spec.add_runtime_dependency 'mini_magick', '~> 4.6'
|
38
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby3mf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mike Whitmarsh, Jeff Porter, and William Hertling
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,6 +108,20 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: mini_magick
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '4.6'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '4.6'
|
111
125
|
description: Read, write and validate 3MF files with Ruby easily.
|
112
126
|
email:
|
113
127
|
- mwhit@hp.com
|
@@ -133,7 +147,11 @@ files:
|
|
133
147
|
- lib/ruby3mf.rb
|
134
148
|
- lib/ruby3mf/content_types.rb
|
135
149
|
- lib/ruby3mf/document.rb
|
150
|
+
- lib/ruby3mf/edge_list.rb
|
151
|
+
- lib/ruby3mf/errors.yml
|
152
|
+
- lib/ruby3mf/global_xml_validations.rb
|
136
153
|
- lib/ruby3mf/log3mf.rb
|
154
|
+
- lib/ruby3mf/mesh_analyzer.rb
|
137
155
|
- lib/ruby3mf/model3mf.rb
|
138
156
|
- lib/ruby3mf/relationships.rb
|
139
157
|
- lib/ruby3mf/texture3mf.rb
|