ruby3mf 0.1.13 → 0.1.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ruby3mf.rb +1 -0
- data/lib/ruby3mf/document.rb +23 -9
- data/lib/ruby3mf/errors.yml +32 -8
- data/lib/ruby3mf/interpolation.rb +43 -0
- data/lib/ruby3mf/log3mf.rb +6 -13
- data/lib/ruby3mf/mesh_analyzer.rb +6 -0
- data/lib/ruby3mf/model3mf.rb +56 -0
- data/lib/ruby3mf/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49c3ed54c8588a2979b63822dbab8f3e339b5207
|
4
|
+
data.tar.gz: 50e088646a7159cf293fb896ce6ce7a5284b1131
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9ecf6eaf61cbb478408212ef0741133b8c1d88d6891eaa6b330dc3102c752572da8b13f664f35067d5a167fbc25bc0333d07885a8dfb94ea29c6421a0f06e085
|
7
|
+
data.tar.gz: f220c8fc02162b03dbfbb2fa66b4e380f19e7a684ca0d59a5ab8cba6342333b00c35060802b9a2758f7109ff823792f62847e9b61ac6f606049ccd5c222db9fc
|
data/lib/ruby3mf.rb
CHANGED
data/lib/ruby3mf/document.rb
CHANGED
@@ -8,11 +8,18 @@ class Document
|
|
8
8
|
attr_accessor :objects
|
9
9
|
attr_accessor :zip_filename
|
10
10
|
|
11
|
+
# Relationship schemas
|
12
|
+
MODEL_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel'
|
13
|
+
THUMBNAIL_TYPE = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail'
|
14
|
+
TEXTURE_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture'
|
15
|
+
PRINT_TICKET_TYPE = 'http://schemas.microsoft.com/3dmanufacturing/2013/01/printticket'
|
16
|
+
|
11
17
|
# Relationship Type => Class validating relationship type
|
12
18
|
RELATIONSHIP_TYPES = {
|
13
|
-
|
14
|
-
|
15
|
-
|
19
|
+
MODEL_TYPE => {klass: 'Model3mf', collection: :models},
|
20
|
+
THUMBNAIL_TYPE => {klass: 'Thumbnail3mf', collection: :thumbnails},
|
21
|
+
TEXTURE_TYPE => {klass: 'Texture3mf', collection: :textures},
|
22
|
+
PRINT_TICKET_TYPE => {}
|
16
23
|
}
|
17
24
|
|
18
25
|
def initialize(zip_filename)
|
@@ -85,6 +92,11 @@ class Document
|
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
95
|
+
l.context "print tickets" do |l|
|
96
|
+
print_ticket_types = m.relationships.select { |rel| rel[:type] == PRINT_TICKET_TYPE }
|
97
|
+
l.error :multiple_print_tickets if print_ticket_types.size > 1
|
98
|
+
end
|
99
|
+
|
88
100
|
l.context "relationship elements" do |l|
|
89
101
|
# 3. Validate all relationships
|
90
102
|
m.relationships.each do |rel|
|
@@ -107,13 +119,15 @@ class Document
|
|
107
119
|
if relationship_file
|
108
120
|
relationship_type = RELATIONSHIP_TYPES[rel[:type]]
|
109
121
|
if relationship_type.nil?
|
110
|
-
l.
|
122
|
+
l.error :invalid_relationship_type, type: rel[:type]
|
111
123
|
else
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
124
|
+
unless relationship_type[:klass].nil?
|
125
|
+
m.send(relationship_type[:collection]) << {
|
126
|
+
rel_id: rel[:id],
|
127
|
+
target: rel[:target],
|
128
|
+
object: Object.const_get(relationship_type[:klass]).parse(m, relationship_file)
|
129
|
+
}
|
130
|
+
end
|
117
131
|
end
|
118
132
|
else
|
119
133
|
l.error "Relationship Target file #{rel[:target]} not found", page: 11
|
data/lib/ruby3mf/errors.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
resource_contentype_invalid:
|
2
|
-
msg: "
|
2
|
+
msg: "Resource in model has invalid contenttype %{bt}"
|
3
3
|
page: 10
|
4
4
|
err_uri_empty_segment:
|
5
5
|
msg: 'No segment of a 3MF part name path may be empty'
|
@@ -49,6 +49,9 @@ invalid_content_type:
|
|
49
49
|
invalid_startpart_type:
|
50
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
51
|
page: 10
|
52
|
+
invalid_relationship_type:
|
53
|
+
msg: "Invalid relationship type '%{type}' specified in .rels relationship file. Parts in the 3D payload MUST use one of the appropriate relationship types to establish that relationship between two parts in the payload."
|
54
|
+
page: 10
|
52
55
|
invalid_thumbnail_colorspace:
|
53
56
|
msg: "CMYK JPEG images must not be used for the thumbnail"
|
54
57
|
page: 36
|
@@ -79,18 +82,39 @@ not_a_zip:
|
|
79
82
|
zero_size_texture:
|
80
83
|
msg: "Texture file must be valid image file"
|
81
84
|
page: 16
|
82
|
-
resource_contentype_invalid:
|
83
|
-
msg: "resource in model has invalid contenttype image/ping"
|
84
|
-
page: 10
|
85
85
|
has_xml_space_attribute:
|
86
|
-
msg: "
|
86
|
+
msg: "xml:space attribute is not allowed"
|
87
87
|
page: 16
|
88
|
+
invalid_model_unit:
|
89
|
+
msg: "Invalid unit value of '%{unit}' specified in model file."
|
90
|
+
page: 17
|
88
91
|
wrong_encoding:
|
89
|
-
msg: "
|
92
|
+
msg: "XML content must be UTF8 encoded"
|
90
93
|
page: 15
|
91
94
|
missing_object_pid:
|
92
|
-
msg: "
|
95
|
+
msg: "Object with triangle color override missing object level pid"
|
93
96
|
page: 26
|
94
97
|
missing_model_children:
|
95
|
-
msg: "
|
98
|
+
msg: "Model element must include resources and build as child elements"
|
96
99
|
page: 20
|
100
|
+
object_with_components_and_pid:
|
101
|
+
msg: "object with components and pid or pindex"
|
102
|
+
page: 26
|
103
|
+
build_with_other_item:
|
104
|
+
msg: "build item with object of type other"
|
105
|
+
page: 27
|
106
|
+
resource_id_collision:
|
107
|
+
msg: "resources must be unique within the model"
|
108
|
+
page: 22
|
109
|
+
metadata_elements_with_same_name:
|
110
|
+
msg: "metadata elements must not share the same name"
|
111
|
+
page: 22
|
112
|
+
multiple_print_tickets:
|
113
|
+
msg: "Only one print ticket allowed for any given model"
|
114
|
+
page: 13
|
115
|
+
unknown_required_extension:
|
116
|
+
msg: "Required extension not supported: %{ext}"
|
117
|
+
page: 14
|
118
|
+
missing_extension_namespace_uri:
|
119
|
+
msg: "Required extension '%{ns}' MUST refer to namespace with URI"
|
120
|
+
page: 14
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Interpolation
|
2
|
+
|
3
|
+
INTERPOLATION_PATTERN = Regexp.union(
|
4
|
+
/%%/,
|
5
|
+
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
|
6
|
+
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
|
7
|
+
)
|
8
|
+
|
9
|
+
def interpolate(string, values = {})
|
10
|
+
if values.keys == 0
|
11
|
+
string
|
12
|
+
else
|
13
|
+
string.gsub(INTERPOLATION_PATTERN) do |match|
|
14
|
+
if match == '%%'
|
15
|
+
'%'
|
16
|
+
else
|
17
|
+
key = ($1 || $2 || match.tr("%{}", "")).to_sym
|
18
|
+
value = values[key]
|
19
|
+
value = value.call(values) if value.respond_to?(:call)
|
20
|
+
$3 ? sprintf("%#{$3}", value) : value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def symbolize_recursive(hash)
|
27
|
+
{}.tap do |h|
|
28
|
+
hash.each { |key, value| h[key.to_sym] = map_value(value) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def map_value(thing)
|
33
|
+
case thing
|
34
|
+
when Hash
|
35
|
+
symbolize_recursive(thing)
|
36
|
+
when Array
|
37
|
+
thing.map { |v| map_value(v) }
|
38
|
+
else
|
39
|
+
thing
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/ruby3mf/log3mf.rb
CHANGED
@@ -23,6 +23,7 @@ require 'yaml'
|
|
23
23
|
|
24
24
|
class Log3mf
|
25
25
|
include Singleton
|
26
|
+
include Interpolation
|
26
27
|
|
27
28
|
LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
|
28
29
|
|
@@ -63,28 +64,20 @@ class Log3mf
|
|
63
64
|
|
64
65
|
def method_missing(name, *args, &block)
|
65
66
|
if LOG_LEVELS.include? name.to_sym
|
66
|
-
#puts "***** #{name} called from #{caller[0]}"
|
67
67
|
log(name.to_sym, *args)
|
68
68
|
else
|
69
69
|
super
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
73
|
-
def log(severity, message, options={})
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
# puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
|
79
|
-
end
|
73
|
+
def log(severity, message, options = {})
|
74
|
+
error = @errormap.fetch(message.to_s) { {"msg" => message.to_s, "page" => nil } }
|
75
|
+
options[:page] = error["page"] unless options[:page]
|
76
|
+
message = interpolate(error["msg"], options)
|
77
|
+
@log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
|
80
78
|
raise FatalError if severity == :fatal_error
|
81
79
|
end
|
82
80
|
|
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
|
-
|
88
81
|
def count_entries(*levels)
|
89
82
|
entries(*levels).count
|
90
83
|
end
|
@@ -15,6 +15,12 @@ end
|
|
15
15
|
class MeshAnalyzer
|
16
16
|
|
17
17
|
def self.validate_object(object)
|
18
|
+
Log3mf.context "verifying object" do |l|
|
19
|
+
children = object.children.map { |child| child.name }
|
20
|
+
have_override = object.attributes["pid"] or object.attributes["pindex"]
|
21
|
+
l.error :object_with_components_and_pid if have_override && children.include?("components")
|
22
|
+
end
|
23
|
+
|
18
24
|
Log3mf.context "validating geometry" do |l|
|
19
25
|
list = EdgeList.new
|
20
26
|
|
data/lib/ruby3mf/model3mf.rb
CHANGED
@@ -2,6 +2,13 @@ require_relative 'mesh_analyzer'
|
|
2
2
|
|
3
3
|
class Model3mf
|
4
4
|
|
5
|
+
VALID_UNITS = [ 'micron', 'millimeter', 'centimeter', 'meter', 'inch', 'foot' ].freeze
|
6
|
+
VALID_EXTENSIONS = {
|
7
|
+
'http://schemas.microsoft.com/3dmanufacturing/slice/2015/07' => {},
|
8
|
+
'http://schemas.microsoft.com/3dmanufacturing/material/2015/02' => {},
|
9
|
+
'http://schemas.microsoft.com/3dmanufacturing/production/2015/06' => {},
|
10
|
+
}.freeze
|
11
|
+
|
5
12
|
def self.parse(document, zip_entry)
|
6
13
|
model_doc = nil
|
7
14
|
|
@@ -12,6 +19,24 @@ class Model3mf
|
|
12
19
|
l.fatal_error "Model file invalid XML. Exception #{e}"
|
13
20
|
end
|
14
21
|
|
22
|
+
l.context "verifying requiredextensions" do |l|
|
23
|
+
required_extensions = model_doc.css("//model")[0]["requiredextensions"]
|
24
|
+
if required_extensions
|
25
|
+
required_extensions.split(" ").each do |ns|
|
26
|
+
namespace_uri = model_doc.namespaces["xmlns:#{ns}"]
|
27
|
+
if namespace_uri
|
28
|
+
if VALID_EXTENSIONS.has_key? namespace_uri
|
29
|
+
l.info "Found a valid required extension: #{namespace_uri}"
|
30
|
+
else
|
31
|
+
l.error :unknown_required_extension, ext: namespace_uri
|
32
|
+
end
|
33
|
+
else
|
34
|
+
l.error :missing_extension_namespace_uri, ns: ns
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
15
40
|
l.context "verifying 3D payload required resources" do |l|
|
16
41
|
# results = model_doc.css("model resources m:texture2d")
|
17
42
|
required_resources = model_doc.css("//model//resources//*[path]").collect { |n| n["path"] }
|
@@ -46,14 +71,45 @@ class Model3mf
|
|
46
71
|
|
47
72
|
end
|
48
73
|
|
74
|
+
l.context "verifying resources" do |l|
|
75
|
+
resources = find_child(model_doc.root, "resources")
|
76
|
+
if resources
|
77
|
+
ids = resources.children.map { |child| child.attributes["id"].to_s() if child.attributes["id"] }
|
78
|
+
l.error :resource_id_collision if ids.uniq.size != ids.size
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
49
82
|
l.context "verifying model structure" do |l|
|
50
83
|
root = model_doc.root
|
51
84
|
l.error :root_3dmodel_element_not_model if root.name != "model"
|
52
85
|
|
86
|
+
l.error(:invalid_model_unit, unit: root.attr('unit')) unless VALID_UNITS.include?(root.attr('unit'))
|
87
|
+
|
53
88
|
children = model_doc.root.children.map { |child| child.name }
|
54
89
|
l.error :missing_model_children unless children.include?("resources") && children.include?("build")
|
55
90
|
end
|
56
91
|
|
92
|
+
l.context "verifying build items" do |l|
|
93
|
+
build = find_child(model_doc.root, "build")
|
94
|
+
if build
|
95
|
+
items = build.children.map { |child| child.attributes["objectid"].to_s() if child.name == "item" }
|
96
|
+
|
97
|
+
resources = find_child(model_doc.root, "resources")
|
98
|
+
resources.children.each do |resource|
|
99
|
+
if resource.name == "object"
|
100
|
+
object_id = resource.attributes["id"].to_s()
|
101
|
+
l.error :build_with_other_item if resource.attributes["type"].to_s() == "other" and items.include?(object_id)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
l.context "checking metadata" do |l|
|
108
|
+
metadata = model_doc.root.css("metadata")
|
109
|
+
metadata_names = metadata.map { |met| met['name'] }
|
110
|
+
l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
|
111
|
+
end
|
112
|
+
|
57
113
|
MeshAnalyzer.validate(model_doc)
|
58
114
|
end
|
59
115
|
model_doc
|
data/lib/ruby3mf/version.rb
CHANGED
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.15
|
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: 2017-01-
|
11
|
+
date: 2017-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -150,6 +150,7 @@ files:
|
|
150
150
|
- lib/ruby3mf/edge_list.rb
|
151
151
|
- lib/ruby3mf/errors.yml
|
152
152
|
- lib/ruby3mf/global_xml_validations.rb
|
153
|
+
- lib/ruby3mf/interpolation.rb
|
153
154
|
- lib/ruby3mf/log3mf.rb
|
154
155
|
- lib/ruby3mf/mesh_analyzer.rb
|
155
156
|
- lib/ruby3mf/model3mf.rb
|